#!/usr/bin/env python3 """usage: gen_order.py [--v8 ] ... 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 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 sectionsize 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}")