Files
browser/orderfile/tools/gen_order.py
T
Karl Seguin fe3a491d22 ci: regenerate orderifle
remove auto orderfile generation, too much hassle.

Manually regenerate the two orderfiles. Change it to use newlines so the diff
is better.
2026-09-02 09:44:25 +08:00

77 lines
3.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""usage: gen_order.py <hot.text> <hot.rodata> <out.ld> [--v8 <libc_v8.a> <v8.txt>] <obj-or-archive>...
Builds a symbol -> (file, section) map from the objects and emits an INSERT
linker script that places the hot sections in .text.hot / .rodata.hot ahead of
.text / .rodata. Patterns are scoped to their object file (`*api.o(...)`):
LLD tests every input section against every unscoped pattern, which turns a
26k-pattern script into an 80s link; scoped, it is a few seconds.
With --v8, symbols defined in that archive are written to <v8.txt> for
mark_hot_sections.zig instead, and the script matches them with one
`.text.hot.*` / `.rodata.hot.*` glob (V8's sections are all named `.text`).
"""
import sys, subprocess, collections, re, os
hot_text, hot_rodata, out = sys.argv[1:4]
objs = sys.argv[4:]
v8_archive = v8_out = None
if objs and objs[0] == "--v8":
v8_archive, v8_out = os.path.basename(objs[1]), objs[2]
objs = [objs[1]] + objs[3:]
loc_of = collections.defaultdict(set) # symbol -> {(archive, file, section)}
for o in objs:
p = subprocess.run(["objdump", "-t", o], capture_output=True, text=True).stdout
fname = archive = os.path.basename(o)
for line in p.splitlines():
m = re.match(r"^(.+?):\s+file format elf", line)
if m:
fname = os.path.basename(m.group(1))
continue
# value flags section<TAB>size name -- the section can contain spaces (zig)
if "\t" not in line: continue
left, right = line.split("\t", 1)
parts = right.split(None, 1)
if len(parts) != 2: continue
name = parts[1].strip()
for vis in (".hidden ", ".protected ", ".internal "):
if name.startswith(vis): name = name[len(vis):]
fl = left[17:24]
if "d" in fl: continue # section symbol
sec = left[24:].strip()
if sec.startswith((".text", ".rodata")):
loc_of[name].add((archive, fname, sec))
def esc(s):
return re.sub(r'([*?\[\]\\])', r'\\\1', s)
stats = collections.Counter()
v8_syms = []
def emit(hotfile, prefix):
by_file = collections.OrderedDict(); seen = set()
for name in open(hotfile).read().split("\n"):
if not name: continue
locs = loc_of.get(name)
if not locs: stats[prefix + " nomap"] += 1; continue
for archive, fname, sec in sorted(locs):
if not sec.startswith(prefix): continue
if archive == v8_archive:
# The embedded builtins blob is left cold (see
# mark_hot_sections.zig); don't emit a pattern for it.
if name.startswith("Builtins_"): stats[prefix + " v8-blob-skip"] += 1; continue
v8_syms.append(name); stats[prefix + " v8"] += 1; continue
if sec == prefix or sec.endswith("."): stats[prefix + " generic"] += 1; continue
if '"' in sec or '"' in fname: stats[prefix + " quote-skip"] += 1; continue
if (fname, sec) in seen: continue
seen.add((fname, sec)); by_file.setdefault(fname, []).append(sec)
stats[prefix + " sections"] = len(seen); stats[prefix + " files"] = len(by_file)
# LLD unquotes section names but not the file pattern, so that one stays bare.
lines = [f' *{esc(f)}(\n' + "\n".join(f' "{esc(s)}"' for s in secs) + "\n )" for f, secs in by_file.items()]
if v8_archive: lines.insert(0, f' *("{prefix}.hot.*")')
return lines
t = emit(hot_text, ".text"); r = emit(hot_rodata, ".rodata")
with open(out, "w") as f:
f.write("/* Generated by orderfile/tools/gen_order.py, see orderfile/README.md. */\n")
f.write("SECTIONS {\n .text.hot : {\n" + "\n".join(t) + "\n }\n} INSERT BEFORE .text;\n")
f.write("SECTIONS {\n .rodata.hot : {\n" + "\n".join(r) + "\n }\n} INSERT BEFORE .rodata;\n")
if v8_out:
with open(v8_out, "w") as f: f.write("\n".join(dict.fromkeys(v8_syms)) + "\n")
for k, v in sorted(stats.items()): print(f"{k}: {v}")