[tbc] Copy-and-patch JIT execution mode (tbc.mode=jit)#378
Open
PhilippGrulich wants to merge 11 commits into
Open
[tbc] Copy-and-patch JIT execution mode (tbc.mode=jit)#378PhilippGrulich wants to merge 11 commits into
PhilippGrulich wants to merge 11 commits into
Conversation
Preparation for the copy-and-patch JIT execution mode: - TBCInstr.hpp: freestanding Instr/kNoReg/packedOffset* definitions (no <string>/<vector>), includable by the future stencil TU. - TBCVm.hpp/.cpp: VMContext, pushFrame, doExtCall, doIndirectCall hoisted out of TBCInterpreter.cpp's anonymous namespace so stitched code's plain-ABI helpers can share the exact frame logic. VMContext's hot members (sp/stackEnd/prog) now come first: their offsets become part of the stencil ABI. Adds a pendingException slot for the JIT's exception bridging. No behavior change; all 349 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
Implements a copy-and-patch JIT (Xu & Kjolstad 2021; the CPython 3.13+ technique) layered on the tbc bytecode. Opcode handlers are compiled offline into machine-code stencils with holes (relocations against magic _JIT_* symbols); at compile time the stitcher copies one stencil per instruction into an executable span and patches operands, branch targets, call-site records, and helper addresses — eliminating the interpreter dispatch entirely. Control falls directly from one instruction's code to the next (trailing continue-jumps are elided on fall-through, and x86-64 movabs+jmp pairs relax to direct jmp rel32). - jit/TBCStencilSource.cpp: stencil TU (all ~332 opcodes via the shared X-macro lists + @RET_VALUE/@RET_VOID/@EPILOGUE/@unwind synthetics), freestanding, preserve_none + musttail continuation ABI. - tools/stencils/build_stencils.py: strict extractor (clang -c -> llvm-readobj JSON -> .inc); unknown relocations/symbols/sections fail at generation time, never at runtime. - jit/generated/stencils_{x86_64,aarch64}_elf.inc: checked-in tables, regenerated on demand (tbc-regen-stencils target, clang >= 19). - jit/TBCStitcher.cpp: two-pass layout+patch; asmjit JitAllocator/ VirtMem own W^X and icache flushing; aarch64 movz/movk + branch26 patch kinds with in-span range thunks for helper calls. - jit/TBCStencilHelpers.cpp: plain-ABI helpers (frame push, external/ indirect calls via dyncall) that catch C++ exceptions into VMContext::pendingException — exceptions never unwind through stitched frames (no unwind info); the entry shim rethrows. - Execution: tbc.mode = interp (default) | jit (strict) | auto (degrade); stitched programs enter through jit::invokeJit, sharing the interpreter's frame layout, VM stack, and trampolines. Internal calls, external calls, allocas, superinstructions, and immediates all execute stitched. Stencil tables resolve by opcode NAME, so a stale generated table degrades to the interpreter instead of mis-executing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
… white-box tests - ExecutionTest.hpp / fuzz Harness.hpp: add a "tbc-jit" pseudo-backend (engine.backend=tbc + tbc.mode=jit), gated on runtime availability, so the entire execution/val/tracing suite and the differential fuzzer run on stitched copy-and-patch code (862 dynamic sections in the execution-tests binary alone). - TBCJitModeTest.cpp: pins jit against a de-optimized switch-dispatch interpreter reference on identical bytecode across all lowering-option combinations (each emits a different stencil population), plus internal-call, void-call, and external-call kernels, and the auto-degrades/jit-is-strict mode contract. - TBCJitWhiteBoxTest.cpp: hand-authored TBCPrograms exercising machinery user kernels cannot reach — the helper exception bridge (VM stack overflow during a stitched CALL surfaces as the interpreter's RuntimeException via pendingException + @unwind + shim rethrow) and the missing-stencil bail-out (stale-table degradation path). - Harness.hpp: NAUTILUS_FUZZ_BACKENDS=<comma-list> triage filter to bisect findings to specific differential peers. Note: exceptions thrown by invoke() targets terminate in dyncall's assembly (no unwind info) on every tbc mode including the interpreter — pre-existing engine behavior, unchanged by the JIT. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
exec_tbc_<kernel>_{interp,jit} A/B on identical bytecode, plus two new
call-heavy kernels the existing loop kernels cannot cover: an internal
Nautilus-to-Nautilus CALL loop (frame-push helper cost) and an external
invoke() loop (dyncall bridge cost).
Measured on this branch (Release, clang-20, x86-64):
fibonacci 2.79 ms -> 0.90 ms (3.1x)
sum 784 ms -> 182 ms (4.3x)
internal 4.75 ms -> 2.50 ms (1.9x)
external 4.63 ms -> 3.34 ms (1.4x)
add (entry-dominated): parity, as expected (same pushFrame work).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
…emulated GOT - Mach-O arm64 support: Apple codegen references every external symbol through the GOT (ARM64_RELOC_GOT_LOAD_PAGE21/PAGEOFF12 only), so the extractor gains a built-in Mach-O relocatable parser (llvm-readobj has no JSON for Mach-O) and the stitcher emulates the GOT with per-instruction 8-byte value slots inside the stitched span, pointing the adrp/ldr pairs at them. Trailing adrp/ldr/br continue sequences are detected for fall-through elision. stencils_arm64_macho.inc is generated and checked in; macOS execution is validated by CI. - aarch64 ELF path validated locally: full execution-tests binary cross-compiled (clang-20 --target=aarch64-linux-gnu) and run under qemu-aarch64 — 11397 assertions pass, including every tbc-jit pseudo-backend section (movz/movk patching, b imm26 branches, helper range thunks). - Guard the x86-64 relaxation peephole per-arch (caught by the cross build's -Werror). - tools/stencils/README.md + tbc README section documenting the JIT. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
GCC-degrade-path run caught the strict-mode throw surfacing at registerFunction rather than at the first call; wrap the whole sequence in the assertion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
- tbc README: add JIT-specific follow-ups (tiering measurement, perf-map/ GDB-JIT emission, macOS execution validation, CI stencil freshness check, native-ABI entry stencils, Windows posture) and link the typed call-thunk follow-up to the external-call benchmark finding. - docs/options.md: document the tbc options (tbc.mode and the previously undocumented interpreter/lowering toggles); refresh the engine.backend value list. - Root README: mention the JIT alongside the bytecode interpreter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
Contributor
There was a problem hiding this comment.
Tracing Benchmark
Details
| Benchmark suite | Current: b63ee21 | Previous: f40ce48 | Ratio |
|---|---|---|---|
tiered_twotier_addOne |
3.8205 ms (± 212.131) |
3.44247 ms (± 155.265) |
1.11 |
tiered_singletier_addOne |
3.69826 ms (± 146.859) |
3.3618 ms (± 130.456) |
1.10 |
single_compile_mlir_addOne |
3.68895 ms (± 171.112) |
3.43613 ms (± 177.952) |
1.07 |
single_compile_cpp_addOne |
21.4132 ms (± 655.556) |
26.4631 ms (± 711.56) |
0.81 |
single_compile_bc_addOne |
62.0028 us (± 13.602) |
98.0348 us (± 27.4974) |
0.63 |
tiered_twotier_sumLoop |
5.6845 ms (± 233.364) |
5.61799 ms (± 179.52) |
1.01 |
tiered_singletier_sumLoop |
6.02305 ms (± 118.079) |
5.76543 ms (± 418.806) |
1.04 |
single_compile_mlir_sumLoop |
6.05941 ms (± 135.079) |
5.767 ms (± 406.338) |
1.05 |
single_compile_cpp_sumLoop |
22.9822 ms (± 384.867) |
27.258 ms (± 795.55) |
0.84 |
single_compile_bc_sumLoop |
123.309 us (± 12.4626) |
132.325 us (± 17.4704) |
0.93 |
exec_bc_addOne |
34.8698 ns (± 5.20299) |
40.833 ns (± 5.46623) |
0.85 |
exec_mlir_addOne |
416.843 ns (± 16.7902) |
312.181 ns (± 13.3101) |
1.34 |
exec_cpp_addOne |
3.66189 ns (± 0.581281) |
3.96003 ns (± 0.49718) |
0.92 |
exec_interpreted_addOne |
37.0594 ns (± 4.77785) |
39.6466 ns (± 2.09221) |
0.93 |
ssa_add |
196.362 ns (± 19.2174) |
178.726 ns (± 10.343) |
1.10 |
ssa_ifThenElse |
445.018 ns (± 31.0425) |
451.646 ns (± 33.9467) |
0.99 |
ssa_deeplyNestedIfElse |
1.26811 us (± 78.5025) |
1.14266 us (± 62.247) |
1.11 |
ssa_loop |
478.4 ns (± 41.6017) |
473.159 ns (± 27.2745) |
1.01 |
ssa_ifInsideLoop |
1008.0199999999999 ns (± 65194.99999999999) |
890.244 ns (± 76.3035) |
1.13 |
ssa_loopDirectCall |
486.5 ns (± 44.7628) |
510.363 ns (± 88.0778) |
0.95 |
ssa_pointerLoop |
578.48 ns (± 36.7268) |
569.753 ns (± 27.8016) |
1.02 |
ssa_staticLoop |
440.096 ns (± 25.0542) |
422.875 ns (± 46.3699) |
1.04 |
ssa_fibonacci |
510.713 ns (± 71.1305) |
503.066 ns (± 51.0695) |
1.02 |
ssa_gcd |
448.499 ns (± 39.6278) |
454.45 ns (± 52.9235) |
0.99 |
ir_add |
856.868 ns (± 65.7648) |
743.704 ns (± 43.0261) |
1.15 |
ir_ifThenElse |
1.75685 us (± 119.143) |
1.56324 us (± 129.394) |
1.12 |
ir_deeplyNestedIfElse |
3.86259 us (± 279.741) |
3.33141 us (± 266.713) |
1.16 |
ir_loop |
1.82134 us (± 129.524) |
1.61495 us (± 125.262) |
1.13 |
ir_ifInsideLoop |
3.92963 us (± 1.15744) |
2.77107 us (± 242.983) |
1.42 |
ir_loopDirectCall |
2.07018 us (± 222.915) |
1.81245 us (± 144.862) |
1.14 |
ir_pointerLoop |
2.2606 us (± 177.704) |
2.01834 us (± 171.55) |
1.12 |
ir_staticLoop |
1.58698 us (± 121.872) |
1.41826 us (± 91.8721) |
1.12 |
ir_fibonacci |
1.92535 us (± 119.587) |
1.72537 us (± 118.025) |
1.12 |
ir_gcd |
1.68506 us (± 167.378) |
1.50566 us (± 228.854) |
1.12 |
ir_nestedIf10 |
8.96743 us (± 261.879) |
7.84665 us (± 845.15) |
1.14 |
ir_nestedIf100 |
100.269 us (± 3.36443) |
88.6726 us (± 6.54239) |
1.13 |
ir_chainedIf10 |
13.9143 us (± 794.806) |
11.9919 us (± 1.48366) |
1.16 |
ir_chainedIf100 |
181.377 us (± 4.4192) |
168.051 us (± 8.52439) |
1.08 |
exec_mlir_add |
12.6648 ns (± 4.08591) |
11.0862 ns (± 1.14504) |
1.14 |
exec_mlir_fibonacci |
29.0916 us (± 2.5367) |
15.6243 us (± 2.32465) |
1.86 |
exec_mlir_sum |
697.937 us (± 29.1324) |
606.846 us (± 40.7026) |
1.15 |
exec_cpp_add |
5.36992 ns (± 0.95018) |
4.77289 ns (± 0.792908) |
1.13 |
exec_cpp_fibonacci |
77.3181 us (± 5.30606) |
48.5936 us (± 4.5575) |
1.59 |
exec_cpp_sum |
16.5244 ms (± 72.6445) |
11.4598 ms (± 71.0041) |
1.44 |
exec_bc_add |
41.0893 ns (± 5.82849) |
53.5135 ns (± 1.24967) |
0.77 |
exec_bc_fibonacci |
327.726 us (± 6.52208) |
432.118 us (± 13.2637) |
0.76 |
exec_bc_sum |
71.3435 ms (± 761.015) |
95.4766 ms (± 189.461) |
0.75 |
exec_tbc_add |
28.8603 ns (± 2.36363) |
27.8693 ns (± 3.76267) |
1.04 |
exec_tbc_fibonacci |
141.376 us (± 4.37266) |
144.856 us (± 9.65742) |
0.98 |
exec_tbc_sum |
43.0128 ms (± 94.977) |
36.7237 ms (± 71.1662) |
1.17 |
exec_asmjit_add |
2.94321 ns (± 0.204662) |
3.55782 ns (± 0.729931) |
0.83 |
exec_asmjit_fibonacci |
13.7842 us (± 1.56906) |
14.3868 us (± 1.69882) |
0.96 |
exec_asmjit_sum |
3.75612 ms (± 13.3783) |
2.80271 ms (± 21.1561) |
1.34 |
exec_bc_add_passesOff |
40.6364 ns (± 3.70372) |
55.7061 ns (± 11.6292) |
0.73 |
exec_bc_add_passesOn |
40.106 ns (± 0.844161) |
55.1404 ns (± 7.17841) |
0.73 |
exec_bc_fibonacci_passesOff |
327.737 us (± 5.64689) |
430.671 us (± 10.1648) |
0.76 |
exec_bc_fibonacci_passesOn |
309.919 us (± 8.39899) |
390.329 us (± 13.4303) |
0.79 |
exec_bc_sum_passesOff |
71.3267 ms (± 692.546) |
95.4237 ms (± 432.425) |
0.75 |
exec_bc_sum_passesOn |
63.1463 ms (± 488.347) |
79.7055 ms (± 287.679) |
0.79 |
exec_tbc_add_passesOff |
28.2451 ns (± 1.0143) |
27.3588 ns (± 4.46378) |
1.03 |
exec_tbc_add_passesOn |
28.9539 ns (± 2.55106) |
26.6915 ns (± 3.04041) |
1.08 |
exec_tbc_fibonacci_passesOff |
141.297 us (± 2.83359) |
145.24 us (± 11.0975) |
0.97 |
exec_tbc_fibonacci_passesOn |
141.621 us (± 4.07885) |
143.12 us (± 7.02762) |
0.99 |
exec_tbc_sum_passesOff |
43.0259 ms (± 95.9382) |
36.7523 ms (± 125.509) |
1.17 |
exec_tbc_sum_passesOn |
39.408 ms (± 228.933) |
33.9784 ms (± 68.6602) |
1.16 |
exec_asmjit_add_passesOff |
2.93482 ns (± 0.170829) |
3.53975 ns (± 0.500097) |
0.83 |
exec_asmjit_add_passesOn |
2.94935 ns (± 0.229106) |
3.6568 ns (± 0.895016) |
0.81 |
exec_asmjit_fibonacci_passesOff |
14.1095 us (± 2.65127) |
14.5441 us (± 2.24492) |
0.97 |
exec_asmjit_fibonacci_passesOn |
14.2122 us (± 1.9883) |
14.3295 us (± 1.43398) |
0.99 |
exec_asmjit_sum_passesOff |
3.75622 ms (± 38.3388) |
2.79484 ms (± 24.0295) |
1.34 |
exec_asmjit_sum_passesOn |
3.74913 ms (± 23.432) |
2.80573 ms (± 35.3542) |
1.34 |
exec_bc_add_noRegAlloc |
40.2715 ns (± 1.70514) |
54.7084 ns (± 6.0781) |
0.74 |
exec_bc_add_regAlloc |
40.0686 ns (± 1.57065) |
54.7144 ns (± 6.20036) |
0.73 |
exec_bc_fibonacci_noRegAlloc |
330.236 us (± 11.2736) |
433.338 us (± 14.1354) |
0.76 |
exec_bc_fibonacci_regAlloc |
329.948 us (± 10.0893) |
433.108 us (± 13.4909) |
0.76 |
exec_bc_sum_noRegAlloc |
72.9027 ms (± 136.592) |
95.5085 ms (± 227.227) |
0.76 |
exec_bc_sum_regAlloc |
71.9919 ms (± 1.57105) |
95.4802 ms (± 174.487) |
0.75 |
exec_bc_add_call |
40.7607 ns (± 3.87051) |
55.0564 ns (± 6.92496) |
0.74 |
exec_bc_add_switch |
39.2882 ns (± 2.90946) |
41.3202 ns (± 4.98038) |
0.95 |
exec_bc_add_threaded |
36.7715 ns (± 2.77091) |
38.9284 ns (± 4.11859) |
0.94 |
exec_bc_fibonacci_call |
328.172 us (± 7.25286) |
432.62 us (± 13.9514) |
0.76 |
exec_bc_fibonacci_switch |
316.139 us (± 9.04894) |
332.087 us (± 10.4208) |
0.95 |
exec_bc_fibonacci_threaded |
392.622 us (± 10.3678) |
436.857 us (± 12.4801) |
0.90 |
exec_bc_sum_call |
71.7598 ms (± 1.3946) |
95.4494 ms (± 235.29) |
0.75 |
exec_bc_sum_switch |
69.8101 ms (± 1.80618) |
69.4439 ms (± 2.71317) |
1.01 |
exec_bc_sum_threaded |
83.8376 ms (± 1.103) |
86.6076 ms (± 717.139) |
0.97 |
exec_bc_add_threaded_noReuse |
37.0194 ns (± 2.81709) |
39.3206 ns (± 4.75011) |
0.94 |
exec_bc_add_threaded_reuse |
30.5146 ns (± 3.02793) |
35.2867 ns (± 5.26744) |
0.86 |
exec_bc_fibonacci_threaded_noReuse |
391.923 us (± 8.23418) |
435.479 us (± 10.6901) |
0.90 |
exec_bc_fibonacci_threaded_reuse |
392.174 us (± 8.10203) |
435.818 us (± 10.6278) |
0.90 |
exec_bc_sum_threaded_noReuse |
83.7314 ms (± 867.906) |
86.535 ms (± 741.907) |
0.97 |
exec_bc_sum_threaded_reuse |
83.4497 ms (± 49.3247) |
86.4911 ms (± 724.446) |
0.96 |
exec_bc_add_threaded_noSuperinstr |
36.9831 ns (± 3.22363) |
39.9908 ns (± 5.56132) |
0.92 |
exec_bc_add_threaded_superinstr |
36.7344 ns (± 2.94987) |
39.5102 ns (± 4.32363) |
0.93 |
e2e_tiered_bc_to_mlir |
3774.04 us (± 107020) |
3424.57 us (± 104297) |
1.10 |
e2e_single_mlir |
6.09479 ms (± 136.575) |
6.10746 ms (± 277.545) |
1.00 |
trace_add |
2.55756 us (± 254.897) |
2.32997 us (± 284.458) |
1.10 |
completing_trace_add |
2.58275 us (± 259.784) |
2.32121 us (± 238.704) |
1.11 |
trace_ifThenElse |
9.3109 us (± 1.30405) |
8.84805 us (± 1.46534) |
1.05 |
completing_trace_ifThenElse |
5.13112 us (± 408.034) |
4.51046 us (± 495.027) |
1.14 |
trace_deeplyNestedIfElse |
26.3231 us (± 4.21665) |
26.1408 us (± 3.22932) |
1.01 |
completing_trace_deeplyNestedIfElse |
13.8746 us (± 919.879) |
12.6294 us (± 1.79333) |
1.10 |
trace_loop |
9.04553 us (± 1.09741) |
8.56061 us (± 1.2196) |
1.06 |
completing_trace_loop |
5.22235 us (± 446.877) |
4.66355 us (± 478.542) |
1.12 |
trace_ifInsideLoop |
17.2518 us (± 2.07131) |
16.8386 us (± 3.29449) |
1.02 |
completing_trace_ifInsideLoop |
9.50418 us (± 844.796) |
8.37888 us (± 1.24115) |
1.13 |
trace_loopDirectCall |
9.02837 us (± 678.95) |
8.83234 us (± 1.32496) |
1.02 |
completing_trace_loopDirectCall |
5.26347 us (± 650.794) |
4.73988 us (± 531.606) |
1.11 |
trace_pointerLoop |
14.7084 us (± 1.13774) |
14.0468 us (± 2.23189) |
1.05 |
completing_trace_pointerLoop |
11.4385 us (± 1.56331) |
10.034 us (± 1.62767) |
1.14 |
trace_staticLoop |
7.65236 us (± 373.723) |
7.26519 us (± 739.036) |
1.05 |
completing_trace_staticLoop |
7.63234 us (± 406.122) |
7.34913 us (± 1.20138) |
1.04 |
trace_fibonacci |
10.4937 us (± 1.17907) |
10.0727 us (± 1.82544) |
1.04 |
completing_trace_fibonacci |
6.81912 us (± 957.25) |
6.11759 us (± 1.03048) |
1.11 |
trace_gcd |
8.09877 us (± 591.071) |
7.90518 us (± 1.21079) |
1.02 |
completing_trace_gcd |
4.32146 us (± 460.485) |
3.92041 us (± 451.136) |
1.10 |
trace_nestedIf10 |
40.8317 us (± 5.75133) |
38.5832 us (± 7.57042) |
1.06 |
completing_trace_nestedIf10 |
40.8805 us (± 5.2254) |
38.1096 us (± 6.35015) |
1.07 |
trace_nestedIf100 |
1.33405 ms (± 26.5775) |
1.36503 ms (± 44.0102) |
0.98 |
completing_trace_nestedIf100 |
1.41404 ms (± 26.7053) |
1.37896 ms (± 53.9371) |
1.03 |
trace_chainedIf10 |
96.9094 us (± 8.93641) |
99.3859 us (± 11.5858) |
0.98 |
completing_trace_chainedIf10 |
53.0828 us (± 7.07023) |
48.8038 us (± 8.12306) |
1.09 |
trace_chainedIf100 |
4.09924 ms (± 43.1165) |
4.45164 ms (± 51.5101) |
0.92 |
completing_trace_chainedIf100 |
2.45686 ms (± 27.711) |
2.27347 ms (± 48.137) |
1.08 |
comp_mlir_add |
6.12595 ms (± 176.348) |
6.00104 ms (± 325.341) |
1.02 |
comp_mlir_ifThenElse |
6.84481 ms (± 186.631) |
7.00804 ms (± 905.718) |
0.98 |
comp_mlir_deeplyNestedIfElse |
5.73258 ms (± 161.777) |
5.52347 ms (± 227.831) |
1.04 |
comp_mlir_loop |
7.7892 ms (± 176.341) |
8.29743 ms (± 330.108) |
0.94 |
comp_mlir_ifInsideLoop |
27.7481 ms (± 325.727) |
30.5552 ms (± 1.07569) |
0.91 |
comp_mlir_loopDirectCall |
12.0394 ms (± 259.069) |
12.8359 ms (± 474.872) |
0.94 |
comp_mlir_pointerLoop |
26.9279 ms (± 274.733) |
29.4418 ms (± 351.512) |
0.91 |
comp_mlir_staticLoop |
5.70189 ms (± 133.519) |
5.19965 ms (± 204.884) |
1.10 |
comp_mlir_fibonacci |
10.7648 ms (± 228.445) |
11.6453 ms (± 355.292) |
0.92 |
comp_mlir_gcd |
9.7842 ms (± 206.827) |
10.4805 ms (± 390.024) |
0.93 |
comp_mlir_nestedIf10 |
10.8599 ms (± 225.366) |
11.1312 ms (± 381.003) |
0.98 |
comp_mlir_nestedIf100 |
25.3402 ms (± 814.661) |
26.4105 ms (± 355.242) |
0.96 |
comp_mlir_chainedIf10 |
11.2132 ms (± 207.488) |
10.6172 ms (± 692.589) |
1.06 |
comp_mlir_chainedIf100 |
59.6641 ms (± 454.582) |
22.0431 ms (± 401.566) |
2.71 |
comp_cpp_add |
21.6056 ms (± 480.638) |
||
comp_cpp_ifThenElse |
21.6895 ms (± 480.162) |
||
comp_cpp_deeplyNestedIfElse |
22.7124 ms (± 484.4) |
||
comp_cpp_loop |
21.9059 ms (± 475.639) |
||
comp_cpp_ifInsideLoop |
22.698 ms (± 465.421) |
||
comp_cpp_loopDirectCall |
22.1783 ms (± 485.687) |
||
comp_cpp_pointerLoop |
22.3976 ms (± 550.389) |
||
comp_cpp_staticLoop |
21.7478 ms (± 455.558) |
||
comp_cpp_fibonacci |
21.9083 ms (± 441.769) |
||
comp_cpp_gcd |
22.0478 ms (± 475.297) |
||
comp_cpp_nestedIf10 |
24.8946 ms (± 541.327) |
||
comp_cpp_nestedIf100 |
57.4819 ms (± 502.818) |
||
comp_cpp_chainedIf10 |
27.1457 ms (± 495.484) |
||
comp_cpp_chainedIf100 |
87.0887 ms (± 704.97) |
||
comp_bc_add |
11.3999 us (± 2.29364) |
||
comp_bc_ifThenElse |
14.8491 us (± 3.1196) |
||
comp_bc_deeplyNestedIfElse |
20.7133 us (± 2.58874) |
||
comp_bc_loop |
14.886 us (± 2.71112) |
||
comp_bc_ifInsideLoop |
18.6447 us (± 3.64045) |
||
comp_bc_loopDirectCall |
15.1323 us (± 2.62169) |
||
comp_bc_pointerLoop |
16.3093 us (± 2.76895) |
||
comp_bc_staticLoop |
14.1993 us (± 3.20244) |
||
comp_bc_fibonacci |
15.2635 us (± 2.66626) |
||
comp_bc_gcd |
13.8447 us (± 2.35424) |
||
comp_bc_nestedIf10 |
39.7305 us (± 6.56122) |
||
comp_bc_nestedIf100 |
283.474 us (± 7.43136) |
||
comp_bc_chainedIf10 |
53.737 us (± 7.41373) |
||
comp_bc_chainedIf100 |
452.304 us (± 9.88686) |
||
comp_tbc_add |
2.01524 us (± 146.936) |
||
comp_tbc_ifThenElse |
4.4709 us (± 432.877) |
||
comp_tbc_deeplyNestedIfElse |
9.98292 us (± 721.449) |
||
comp_tbc_loop |
4.43595 us (± 354.649) |
||
comp_tbc_ifInsideLoop |
7.70818 us (± 567.481) |
||
comp_tbc_loopDirectCall |
4.70621 us (± 467.749) |
||
comp_tbc_pointerLoop |
5.96421 us (± 355.977) |
||
comp_tbc_staticLoop |
5.34118 us (± 365.234) |
||
comp_tbc_fibonacci |
5.04983 us (± 650.575) |
||
comp_tbc_gcd |
3.81973 us (± 269.709) |
||
comp_tbc_nestedIf10 |
30.1303 us (± 2.19224) |
||
comp_tbc_nestedIf100 |
291.911 us (± 6.48745) |
||
comp_tbc_chainedIf10 |
38.0531 us (± 4.02679) |
||
comp_tbc_chainedIf100 |
407.358 us (± 7.43445) |
||
comp_asmjit_add |
17.2893 us (± 3.90377) |
||
comp_asmjit_ifThenElse |
26.3066 us (± 6.59685) |
||
comp_asmjit_deeplyNestedIfElse |
35.9225 us (± 5.71542) |
||
comp_asmjit_loop |
26.4154 us (± 6.82451) |
||
comp_asmjit_ifInsideLoop |
34.0817 us (± 6.87674) |
||
comp_asmjit_loopDirectCall |
29.1259 us (± 6.929) |
||
comp_asmjit_pointerLoop |
31.6221 us (± 6.68343) |
||
comp_asmjit_staticLoop |
24.4511 us (± 7.10012) |
||
comp_asmjit_fibonacci |
29.6823 us (± 8.96493) |
||
comp_asmjit_gcd |
26.9043 us (± 6.30915) |
||
comp_asmjit_nestedIf10 |
61.0246 us (± 7.21237) |
||
comp_asmjit_nestedIf100 |
494.929 us (± 18.2761) |
||
comp_asmjit_chainedIf10 |
78.1102 us (± 9.87836) |
||
comp_asmjit_chainedIf100 |
632.547 us (± 16.7297) |
This comment was automatically generated by workflow using github-action-benchmark.
The benchmark runner used Ubuntu 24.04's default clang (18), which
cannot execute copy-and-patch stitched code (preserve_none needs
Clang 19+), so the new exec_tbc_*_{interp,jit} A/B benchmarks were
silently skipped and never published to the dashboard. Install
clang-21 from the LLVM apt repo, mirroring pr.yml's install step.
Note: the compiler bump shifts all published benchmark baselines; the
first run after merge establishes new reference numbers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
…ode-interpreter-kggqsw
main added BAND_b/BOR_b/BXOR_b (02eb413, val<bool> fuzz domains) after the tables were generated; the merged enum therefore contained opcodes with no stencil. The name-keyed resolution degraded exactly as designed (module-level interpreter bail-out with a precise reason — the opcodes were inserted mid-enum, so an order-indexed table would have silently mis-executed every subsequent opcode), and the strict tbc-jit fuzz peer turned it into a loud CI failure. Regenerating adds the three stencils on every target (339 entries). The CI fuzz replay gate now passes locally: 2000/2000 generated programs agree across all backends including tbc-jit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a copy-and-patch JIT (Xu & Kjølstad 2021; the technique behind CPython 3.13+'s JIT) as an opt-in execution mode of the
tbcbackend. The interpreter's remaining per-instruction cost — the dispatch indirect branch, theInstrload, and the operand-field decode — disappears entirely: at build time every opcode handler is compiled offline into a machine-code stencil with holes (relocations against magic_JIT_*symbols), and at compile time a stitcher copies one stencil per instruction into an executable span and patches operands, branch targets, call-site records, and helper addresses directly into the code. Control falls (or directly jumps) from one instruction's code to the next.Everything else — bytecode, lowering, register allocation, superinstruction fusion, immediate folding, frames, VM stack, trampolines, dyncall externals — is shared 1:1 with the interpreter, which doubles as a differential oracle on identical bytecode.
Measured (Release, x86-64, vs the tail-call interpreter on identical bytecode)
add)pushFrame)(
exec_tbc_*_{interp,jit}inExecutionBenchmark.cpp; tbc interp is itself ~3–4× overbc.)How it works
jit/TBCStencilSource.cpp): one freestandingpreserve_nonefunction per opcode, bodies mirroringTBCHandlers.hpp1:1 and generated from the same X-macro lists inTBCOpcodes.hpp; each ends in[[clang::musttail]]through_JIT_CONTINUE/_JIT_TARGET. Synthetic stencils@RET_VALUE/@RET_VOID/@EPILOGUE/@UNWINDhandle returns and program entry/exit.tools/stencils/build_stencils.py): compiles the TU per target with a pinned flag set (-mcmodel=large⇒ every symbol reference is a patchable absolute materialization), harvests bytes + relocations via llvm-readobj JSON (ELF) or a built-in Mach-O parser, and emits the checked-in tables injit/generated/. Strict: any unexpected relocation kind, symbol, or section is a hard error at generation time. Normal builds (incl. GCC) never need the toolchain; regenerate withcmake --build build --target tbc-regen-stencils(clang ≥ 19).jit/TBCStitcher.cpp): two-pass layout + patch into a single span per program (asmjitJitAllocator/VirtMemown W^X, MAP_JIT, icache flush). Operand register fields are patched pre-scaled as frame byte offsets (zero decode cost), 16-bit immediates as sign-extended 64-bit constants. Peepholes: trailing continue-jumps are elided on fall-through; x86-64movabs+jmp *%regpairs relax to directjmp rel32.R_X86_64_64); aarch64 ELF uses movz/movk chains + directbimm26 (+ in-span range thunks for helperbls); arm64 Mach-O GOT-loads every symbol, so the stitcher emulates the GOT with per-instruction value slots inside the span (the CPython trick).CALLpushes the same VM frame via a plain-ABI helper and tail-jumps to the callee's stitched entry;RETpops inline and tail-jumps to the native continuation in the frame header;CALL_EXT/CALL_INDreusedoExtCall(dyncall) via helpers. Reentrancy and the trampoline pool work unchanged (invoke()dispatches per program).VMContext::pendingException, exit through@UNWIND, and the entry shim rethrows. (Exceptions thrown byinvoke()targets already terminate in dyncall's assembly on every mode, interpreter included — pre-existing, unchanged.)tbc.mode = interp(default — JIT is opt-in) |jit(strict: throws when the build can't execute stitched code, so tests never silently run the wrong engine) |auto(best effort). Availability = generated table for the target + Clang 19+ host (preserve_noneentry call) + no ASan. Statistics reporttbc.mode,tbc.jit.codeSize.bytes, and skip reasons.Preparatory refactor (first commit)
TBCInstr.hppsplit out ofTBCCode.hpp(freestanding, includable by the stencil TU) and the VM core (VMContext,pushFrame,doExtCall,doIndirectCall) hoisted fromTBCInterpreter.cpp's anonymous namespace intoTBCVm.hpp/.cppso the JIT helpers share the exact frame logic. No behavior change.Testing
tbc-jitpseudo-backend inExecutionTest.hppruns the entire execution/val/tracing suite on stitched code (862 dynamic sections in execution-tests alone); also added as a differential-fuzzer peer.TBCJitModeTest.cpp: jit vs de-optimized switch-interpreter reference on identical bytecode × all lowering-option combinations (each combination emits a different stencil population), internal/void/external call kernels, and the mode contract.TBCJitWhiteBoxTest.cpp: hand-authoredTBCPrograms covering the exception bridge (VM-stack overflow inside a stitchedCALL) and the missing-stencil bail-out.tbc-jitsections.mainwith the JIT excluded — see notes below.)Notes for reviewers
preserve_noneuntil LLVM 19), so it validates that the Mach-O stencil table compiles and degrades cleanly but executes the interpreter. The Mach-O GOT-emulation path runs only under a non-Apple clang ≥ 19 on macOS; treat it as best-effort until exercised.ubuntu-24.04-armclang-21 job runs the whole suite on stitched aarch64 code (mirrors the local qemu validation).nautilus-fuzz-replay's smoke corpus fails during tracing ("no Return operation was recorded") on pristinemainin a clang-20/MLIR-off environment; alsoNAUTILUS_FUZZ_BACKENDS=bc-only runs hit "Invalid trace … constant loop" on trivial generated programs. Both are backend-independent tracing edges, untouched by this PR..incfiles are large but mechanical; reviewbuild_stencils.py+TBCStencilSource.cpp+TBCStitcher.cppinstead. A CI freshness check (regenerate +git diff --exit-code) is a possible follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_01NB22eBwKooQMBuwK4jMH2k
Generated by Claude Code