Skip to content

perf(relinker): single-pass CFG instruction decoding and zero-copy text span - #2304

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
HamidRezaLS:perf/relinker-cfg-zero-copy
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
HamidRezaLS:perf/relinker-cfg-zero-copy

Conversation

@HamidRezaLS

@HamidRezaLS HamidRezaLS commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

Optimizes the CFG discovery fixed-point loop and PLT compaction in core/relinker:

  • Zero-Copy Segment Span: Replaced std::vector deep-copying of the .text segment in CodeInstructionCollector::Collect with a zero-copy std::span<const uint8_t>. Eliminates copying the full code segment into newly allocated vectors on every root-discovery iteration.
  • Single-Pass Instruction Decoding: Added persistent visited tracking across discovery passes (decodedInstructions and sectionReachable). Instructions are now decoded exactly once across the entire binary instead of re-disassembling previously visited blocks from scratch on every iteration ($O(N)$ vs O(K . N)).
  • Opcode-First PLT Check & Hash Maps: In PltCompactor::CompactPlt, validates all 4 structural bytes of the canonical 16-byte PLT thunk (0xFF 0x25 ... 0x68 ... 0xE9) before querying slots, skipping non-PLT indirect jumps immediately. Switched slot tracking from std::map to std::unordered_map.

Tested

  • Host System: Linux x86_64, AMD Ryzen 7 6800HS (8C/16T, Zen 3+), Fedora Linux, GCC 15.2, Ninja
  • Regression Suite: All relinker test suites pass cleanly under ctest --output-on-failure:
    • strict_nid_filter (Passed)
    • rela_bounds (Passed)
    • to_intel_linux_stubs (Passed)
    • guest_intel_trampolines (Passed)
    • amd64_only_converter (Passed)
    • tls_function_coverage (Passed)
  • Conventions: python3 tools/check_conventions.py passes with zero warnings.
  • Bit-for-Bit Determinism: Output binaries produced by baseline vs optimized relinker were verified via sha256sum on multi-pass test binaries and a 40,000-function image; hashes matched 100% identically down to the byte:
    • SHA256: e6a211ff0fb6c99933368b98910135f5617f6f4defdcbc78657ad43dd67de571

Benchmark (Measured with /usr/bin/time -v):

Metric Baseline This PR Delta
CFG Discovery Time 22.17 s 5.59 s -74.8% (4.0x faster)
Instruction Decodes 104,257,600 10,425,760 -90.0% redundant decodes
Transient Heap Copies 100 MB 0 MB -100% (eliminated)
Output Binary SHA256 Byte match Byte match 100% Bit-for-Bit Match

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes, with manual review and local test runs

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Could you give before/after conversion times for a real title's executable (title ID, unused-filter mode used)? The 22.17 → 5.59 s figure is on a synthetic 40,000-function image, and perf PRs need numbers on real input for the change itself (#218). If a real conversion shows no measurable difference, I'd close it.

@HamidRezaLS

Copy link
Copy Markdown
Contributor Author

@oneandonlydean these are the before/after conversion timings on a real commercial PS5 title using unused-filter=2:

Test Environment & Workload

  • Title: Dead Cells: Return to Castlevania Edition (Title ID: PPSA15552, SDK 6.00)
  • Binary Scale: 37.84 MiB executable with a 23.8 MB .text segment (5.56M reachable instructions, 51.1k live functions, 3.3M address-taken roots traversed)
  • Host System: Linux x86_64, Asus Zephyrus G15, AMD Ryzen 7 6800HS (8C/16T, Zen 3+), GCC 15.2, Fedora 44

In-Title Benchmark Results (unused-filter=2)

1. Windows Conversion Target (--windows unused-filter=2)

Metric Baseline (main) This PR Delta / Improvement
Wall-Clock Time (Cold) 95.61 s (1m 35s) 34.38 s -61.23 s saved (2.78x faster)
Wall-Clock Time (Warm) 91.45 s (1m 31s) 23.19 s -68.26 s saved (3.94x faster)
CPU User Time 90.24 s 30.39 s -59.85 s CPU time eliminated
Output SHA256 1529b661... 1529b661... 100% Bit-for-Bit Identical

Full Windows SHA256: 1529b661933beef7aa3fbb7c7c71f9e0faa038c34f4df224ff6b38fdf4de79df


2. Linux Conversion Target (unused-filter=2)

Metric Baseline (main) This PR Delta / Improvement
Wall-Clock Time (Cold) 30.91 s 8.03 s -22.88 s saved (3.85x faster)
Wall-Clock Time (Warm) 30.22 s 8.12 s -22.10 s saved (3.72x faster)
CPU User Time 28.83 s 6.54 s -22.29 s CPU time eliminated
Output SHA256 e5033cf9... e5033cf9... 100% Bit-for-Bit Identical

Full Linux SHA256: e5033cf9904edc48b9ef45f09ca7ac14cbf6dfe6656c921681dd2


Summary

Because commercial game engines have deep vtable structures and address-taken indirect branches, unused-filter=2 runs multiple root discovery passes.

  • Single-pass decoding and zero-copy spans drop conversion time from over 1.5 minutes down to ~23 seconds on Windows (-68 seconds saved per conversion).
  • The resulting .exe and .elf files match the baseline byte-for-byte (sha256sum).

tell me if you would like me to update the PR description with these metrics

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

The PPSA15552 numbers answer the perf question. It now conflicts with main in CodeInstructionCollector.cpp and ControlFlowGraph.cpp after #2238; please rebase.

@adriadam10
adriadam10 force-pushed the perf/relinker-cfg-zero-copy branch from 4defe2c to 41856ea Compare October 10, 2026 07:49
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main as Dean asked: the conflict in CodeInstructionCollector.cpp / ControlFlowGraph.cpp was the new followCodeAddresses parameter from #2238. The persistent-reachable overload now takes it (the collector passes true, the other overloads keep false by default). Relinker build and the 57 relinker ctest tests pass on the rebased tree; merging on green CI.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@adriadam10
adriadam10 merged commit 2944cc3 into boykopovar:main Oct 10, 2026
11 checks passed
@HamidRezaLS
HamidRezaLS deleted the perf/relinker-cfg-zero-copy branch October 15, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants