REXX Language implementation
Status: completed Apple ARM64 investigation and R5b closeout; profile-20 is the provisional product default; Intel Linux validation remains open
Date: 2026-08-09
This report records the complete rxvm instruction-handler refactoring and
code-generation investigation, including the unsuccessful intermediate forms.
It explains why apparently harmless C source changes produced large and
sometimes opposite performance movements under Apple Clang and GCC, what was
required to recover an equivalent all-inline control, and which source shapes
gave the best observed optimisation for each tested compiler.
The report covers both concrete interpreter engines:
rxtvm, the direct-threaded computed-goto engine; andrxbvm, the portable numeric-opcode switch engine.rxvm is a compiler-selected product alias, not a third engine. No result here
changes the public RXAS/RXBIN format, plugin ABI, language semantics, canonical
RXBIN image or product VM selection. The later R5b decision changes only the
internal default handler-placement panel from literal all-inline to profile-20.
The original profile-selected slowdown was not proportional to the number of
handlers called out of line. A single reachable but never-executed outlined
handler could materially slow Apple Clang. The primary cause was the C-level
address escape created by a pointer-rich facade over run() locals. It changed
register allocation, stack homes, alias analysis and, for rxtvm, the handling
of the function-local label table. The effect existed even when the outlined
edge was never taken.
Clang and GCC then required different repairs:
run() retains scalar locals,
takes a value snapshot only on the cold outlined edge, and sends all outlined
public instructions through one cold trampoline.rxbvm but regressed rxtvm.There was no evidence that helper sub-inlining caused the regression. Across the 176 selected public inline handlers, neither compiler changed a successful helper-inlining decision. Converting the inner value-manipulation helpers to macros is therefore not justified by this investigation and could remove useful compiler discretion while enlarging the owner again.
The final experimental panel places 176 public handlers and the two existing private fused handlers inline: 178 of 590 non-reserved public-plus-private definitions, or 30.17%. Excluding noisy Base64, profile-30 versus the rebuilt all-inline control is:
| Compiler | rxtvm |
rxbvm |
|---|---|---|
| Apple Clang 21 | -0.341% | +0.201% |
| Homebrew GCC 16.1 | +1.379% | +6.422% |
Positive values mean higher normalized throughput. The Clang result is effectively neutral; the GCC result is favorable. The panel remains an experimental measurement shape rather than a selected default.
The R2 refactor moved 651 semantic handler definitions into five coherent internal include files:
PRIVATE_R1_RELINK and PRIVATE_R2_COPYATTR1.Each handler is written once behind RXVM_HANDLER or
RXVM_PRIVATE_HANDLER. rxvmhandlerpolicy.h independently chooses whether a
definition expands directly inside rxvm_run_owned_core() or is emitted as a
force-noinline callable function. INTERRUPT is an owner pseudo-label, not an
RXAS instruction handler.
Three reproducible policies provide the experimental boundaries:
| Policy | Meaning |
|---|---|
all-inline |
Every handler body expands inside run() |
all-outline |
Every handler is callable; minimum owner and maximum call boundary |
profile-30 |
Frozen hot panel inline, remaining handlers callable |
The two dispatch engines consume the same handler bodies but present very
different control-flow structures to a C compiler. rxtvm requires labels to
remain owned by one stable noinline/noclone function and dispatches through
process-local label addresses. rxbvm dispatches numeric opcodes through a C
switch. Both execute a process-owned execution image while the canonical RXBIN
image remains immutable and authoritative for serialization, reflection,
source coordinates and debug identity.
There are three distinct controls and they must not be conflated:
6a65b9c685b3776da211bcd209af14fcf23be445.fd54b616764ef880270f4bce9dd202b476bf559c with every handler inline.Semantic equivalence alone was insufficient. The all-inline design contract also required:
The final R3 all-inline owner extents are byte-for-byte the R2 extents. Excluding Base64, R3 all-inline versus R2 all-inline is:
| Compiler | rxtvm |
rxbvm |
|---|---|---|
| Apple Clang 21 | -0.220% | -0.280% |
| Homebrew GCC 16.1 | -0.127% | +0.520% |
This is the required equivalent control. The first R3 formal attempt failed this contract even though it was functionally correct.
The retained runs used an Apple M5 with ten logical CPUs, a 128 KiB L1
instruction cache and a 6 MiB L2 cache, on AC power with low-power mode off and
no thermal warning. Products were ordinary profiling-off Release builds using
-O3 -DNDEBUG.
The compiler identities were:
arm64-apple-darwin25.5.0; and/opt/homebrew/opt/gcc/bin/gcc-16.On macOS, /usr/bin/gcc is Apple Clang and is not independent GCC evidence.
The GCC products used CREXX_ENABLE_TLS=OFF because the NETWORK backend uses
Clang blocks. This is a build limitation of the experiment, not a TLS policy
recommendation.
The final matrix covered Sieve, Permute, Bounce, Richards, Base64, Towers and RexxCPS; both compilers; both engines; and R2 all-inline, rebuilt R3 all-inline and R3 profile-30. Each of the 84 cells had two warmups and twelve recorded serial rounds. All 1,176 executions passed their exact output checks and all 1,008 recorded samples were retained. Source, manifest and product hashes were unchanged before and after measurement.
Base64 was retained, never discarded, but its known noise is why both all-seven and without-Base64 aggregates are reported. Code size is evidence about compiler shape, not proof of the active instruction-cache working set.
The first production-shaped change replaced the monolithic list of instruction
bodies with one macro definition per handler, grouped into five files. The
selected callable ABI used a struct containing pointers to most mutable
run() locals. Inline expansion used the same body against direct owner
locals; outlined expansion used that pointer facade and returned an owner
continuation result.
This achieved the maintenance objective and passed the complete correctness boundary. It also supplied all-inline and all-outline controls. Static shape under Clang was:
| Shape | Engine | run() bytes |
Product text bytes | Outlined symbols |
|---|---|---|---|---|
| Untouched | rxtvm |
535,556 | 831,532 | 0 |
| Untouched | rxbvm |
530,528 | 827,180 | 0 |
| R2 all-inline | rxtvm |
532,512 | 828,488 | 0 |
| R2 all-inline | rxbvm |
531,868 | 828,520 | 0 |
| R2 profile-30 | rxtvm |
200,160 | 888,228 | 475 |
| R2 profile-30 | rxbvm |
200,584 | 881,236 | 475 |
| R2 all-outline | rxtvm |
31,824 | 913,248 | 651 |
| R2 all-outline | rxbvm |
32,268 | 902,588 | 651 |
The frozen public heat panel put 176 of 588 non-reserved public handlers inline
and covered 99.9999969% of the measured public dynamic instruction count.
Nevertheless, profile-30 lost 9.35% on rxtvm and 12.08% on rxbvm versus
all-inline. All-outline lost 40.02% and 34.24% respectively.
The initial interpretation that six workloads executed no outlined handler was later found incomplete. Profiling attributed private fused execution to the first public opcode in its canonical sequence, hiding hot private handler calls.
Lesson: dynamic opcode coverage is necessary for placement, but is not a model of C compiler behaviour. A frequency-perfect panel can still change the entire hot owner’s register allocation and dispatch layout.
Before changing the ABI again, normalized all-inline, profile-30 and all-outline expansions were compared. The ledger passed 368 checks:
All governed profiles recorded zero actual interrupt selection, entry and transition events. Compiler optimisation records showed no changed successful helper-inlining decision across the selected handlers under either compiler.
This rejected two attractive but incorrect explanations: broken instruction search/expansion and lost helper sub-inlining.
Real GCC builds showed immediately that reduced owner size was not inherently
adverse. Relative to all-inline, the original R2 profile-30 shape was +8.999%
on GCC rxbvm (+7.387% without Base64). GCC rxtvm was mixed: +1.238% with
Base64 but -2.239% without it, ranging from +18.834% on Sieve to -25.819% on
Bounce. GCC all-outline remained adverse at -5.225% for rxbvm and -23.539%
for rxtvm across all seven.
GCC’s owners were much larger than Clang’s:
| GCC shape at this stage | rxtvm bytes |
rxbvm bytes |
|---|---|---|
| All-inline | 1,493,900 | 1,478,368 |
| Profile-30 | 537,568 | 539,648 |
| All-outline | 104,512 | 110,304 |
The different speed direction despite much larger code proved that run()
extent and L1I capacity cannot be used as a standalone selection rule.
The diagnostic series held hot handler bodies constant and altered only the reachable callable structure. The percentages below are elapsed-time changes over all-inline for Sieve, Permute and Bounce; positive is slower in this table.
| Diagnostic shape | Observed range across engines/workloads | Finding |
|---|---|---|
| Keep all 651 unused wrappers, no reachable call | -1.6% to +0.6% | Wrapper definitions and cold call-graph population alone are neutral |
| Reach continuation funnel, but make no handler call | -1.8% to +1.2% | The continuation switch alone is neutral |
| One never-executed outlined public site | +9.6% to +35.9% | One reachable facade escape is enough |
| Eight never-executed outlined sites | +13.1% to +36.3% | Loss is not proportional to site count |
| Forty-nine reserved outlined sites | +15.1% to +47.4% | Coldness does not protect the owner from compile-time alias effects |
| Make threaded label map static | +13.3% to +37.9% | Label-map escape is secondary, not the shared root cause |
| Remove interrupt poll, semantic-invalid ceiling | +2.2% to +72.1% | Poll affects code shape, but removal damages threaded execution and is invalid |
Optimized IR confirmed the common cause. Once an outlined call was reachable, the pointer facade retained 23 or 27 address-taking assignments in the GCC owners and corresponding allocas/member pointers under Clang. Even a call that never ran forced the compiler to respect possible mutation through those pointers.
For Clang rxtvm, the same escape caused the 650-entry local label table to be
copied into the owner frame. The frame grew from approximately 2,192 bytes to
6,832 bytes and gained a Darwin stack-probe call. rxbvm has no label-address
table, so this amplified the threaded loss but did not explain the shared
slowdown.
The next form replaced pointers to owner locals with scalar values copied only when control actually reached an outlined edge. Mutable fields were committed back after the handler returned. This removed hot-path address escape.
A facade-only snapshot recovered most owner size but retained workload losses. The decisive Clang change also removed outlined public identities from the hot owner and mapped them to one shared cold entry. Only there did the interpreter:
noinline,cold trampoline; andFor direct threading, an outlined instruction cell contains a label address, so the cold entry obtains the public opcode from the immutable canonical image. For switch dispatch, the process-owned execution image already contains the numeric opcode. Private outlined identities retain dedicated labels because they have no canonical public opcode of their own.
This brought Sieve and Permute to roughly 2% of all-inline in the bounded Clang pilot. A more complex family of narrowed per-handler ABIs was therefore not justified.
Bounce remained unexpectedly slow. Native samples showed execution in
PRIVATE_R1_RELINK and in the cold trampoline when private handlers were
outlined. The public semantic profiler had attributed that dispatch to
UNLINK, the first canonical opcode, and had therefore classified the actual
private callable identity incorrectly.
Both existing private fused handlers were made explicit inline members of the profile panel. The resulting three-workload Clang pilot was within about 1.3% of all-inline on both engines. This changed the denominator from 176/588 public handlers to 178/590 non-reserved public-plus-private definitions, or 30.17%.
Lesson: semantic profiling and placement profiling are different views. Semantic counts should retain canonical public identity, while placement analysis must also expose the process-private dispatch identity that consumes cycles.
The first formal candidate made two changes that looked reasonable in source:
This verdict was rejected for two independent reasons.
First, the rebuilt all-inline control was no longer equivalent. Without
Base64, rxtvm lost 5.09% under Clang and 5.66% under GCC versus R2. The
source deletion changed compiler heuristics and the direct-threaded layout even
though the removed facade had generated no surviving handler calls.
Second, the universal shared-cold form produced opposite GCC outcomes versus
that rebuilt control: rxtvm lost 5.33% while rxbvm gained 13.67% without
Base64. A common source abstraction was not a common optimisation strategy.
The rejected run is retained because it demonstrates two essential review rules: validate the baseline before interpreting a candidate, and never infer cross-compiler equivalence from source-level neatness.
The exact R2 all-inline pointer-facade source shape was restored behind a compile-time choice. In all-inline builds it is allowed to optimize away just as before. A three-hot-workload pilot returned all four rebuilt controls to within about 1.3% of R2, and the final seven-workload run confirmed both exact owner extents and performance equivalence.
This was not a request to preserve every historical line forever. It was proof that, for this unusually large function, compiler heuristics are part of the measured interface. Any future cleanup must establish a new equivalent control before it can be called neutral.
The final form selects the internal callable lowering by compiler and panel:
The preprocessor test for real GCC must exclude Clang explicitly because Clang
defines __GNUC__ for compatibility. _MSC_VER is also kept separate.
The two private fused handlers are inline in profile-30 for both compilers.
This design is implemented by RXVM_HANDLER_USE_POINTER_FACADE, not by a
public ABI or serialized instruction change.
Clang aggressively centralizes the interpreter’s normal direct-threaded dispatch. The all-inline and profile owners contain only about six to eight indirect branches in total, with normal computed-goto handlers converging on a central dispatch branch. The classic benefit of one indirect branch site per handler is therefore absent.
Clang also scalar-replaces the facade completely when no outlined call is
reachable. As soon as one such call exists, possible mutation through the
facade prevents the same scalar/register representation. In rxtvm, the label
array then escapes into a large stack object. Clang therefore benefits from a
late value snapshot and one unmistakably cold call edge.
Final Clang profile-30 owner extents are 205,548 bytes for rxtvm and 205,444
bytes for rxbvm, versus 532,512 and 531,868 bytes all-inline. The reduction
does not place the whole owner inside the 128 KiB L1I, and no claim is made that
whole-function extent equals resident hot footprint.
GCC preserves a distributed computed-goto shape: the comparison found about
1,520 indirect branches in all-inline rxtvm and 247 in profile-30. Its
rxbvm switch becomes a direct conditional decision tree rather than Clang’s
central indirect switch dispatch. Reducing handler population can improve that
tree significantly.
GCC materializes the approximately 5,200-byte threaded label table in every examined threaded shape, so the facade does not introduce the same binary elimination-to-stack transition observed under Clang. Its register allocation, branch prediction and outlining tradeoffs consequently differ.
For non-inline panels, GCC’s per-identity pointer-facade form is faster than the
shared cold form for rxtvm. Final GCC profile-30 owner extents are 547,808
bytes for rxtvm and 549,632 bytes for rxbvm, compared with 1,493,900 and
1,478,368 bytes all-inline.
GCC emits 409 visible handler symbols in the final products versus Clang’s 474, despite the same policy. That is optimizer folding/elimination, not a different handler selection.
Both compilers showed that cold code can influence hot code at compile time. Reachability, address-taking, alias sets, function-local labels and CFG predecessors matter even when runtime profiles show zero traversals.
Both compilers also rejected helper-sub-inlining as the cause: GCC reported 478 normalized helper decisions across the selected public handlers with zero all-inline/profile differences; Clang showed no changed successful decision. The large owner remains difficult code, but the evidence does not support replacing value helpers with macros merely to force more expansion.
| Build | Engine | run() bytes |
Text bytes | File bytes | Visible handler symbols |
|---|---|---|---|---|---|
| Clang R2/R3 all-inline | rxtvm |
532,512 | 828,488 | 1,020,632 | 0 |
| Clang R2/R3 all-inline | rxbvm |
531,868 | 828,520 | 1,020,808 | 0 |
| Clang R3 profile-30 | rxtvm |
205,548 | 896,184 | 1,109,592 | 474 |
| Clang R3 profile-30 | rxbvm |
205,444 | 892,572 | 1,109,528 | 474 |
| GCC R2/R3 all-inline | rxtvm |
1,493,900 | 1,864,152 | 2,138,184 | 0 |
| GCC R2/R3 all-inline | rxbvm |
1,478,368 | 1,848,632 | 2,121,672 | 0 |
| GCC R3 profile-30 | rxtvm |
547,808 | 1,434,616 | 1,779,960 | 409 |
| GCC R3 profile-30 | rxbvm |
549,632 | 1,429,112 | 1,763,432 | 409 |
Outlined functions reduce the owner but can increase total Clang text because the callable bodies remain elsewhere. Owner bytes, total text, file size, branch layout and runtime throughput must therefore remain separate measures.
| Compiler | Engine | All seven | Without Base64 |
|---|---|---|---|
| Clang | rxtvm |
-0.541% | -0.341% |
| Clang | rxbvm |
+0.274% | +0.201% |
| GCC | rxtvm |
+3.841% | +1.379% |
| GCC | rxbvm |
+8.053% | +6.422% |
For final profile-30, rxtvm is 0.294% faster than rxbvm under Clang without
Base64, effectively a tie. Under GCC it is 14.615% faster. In the all-inline
control the corresponding advantages are 0.839% and 20.315%.
These comparisons do not make GCC globally faster than Clang and do not select a product engine. They compare the two engines within each compiler build.
“Maximum” here means the best source/compiler result demonstrated on this Apple ARM64 host, not a universal compiler theorem. The following rules are the current engineering contract.
run() locals on a hot path merely to support a
cold callable handler.noinline,cold trampoline.__clang__, __GNUC__ and _MSC_VER deliberately; do not treat
__GNUC__ alone as proof of GCC.run() is not automatically faster, and fitting a whole symbol into
a nominal cache size is not a sufficient model of its active footprint.Any further handler, dispatch-tail, helper or interrupt-path change should use this order:
rxtvm and rxbvm with Apple Clang and real GCC; label any TLS
or platform difference.The retained final Clang and GCC profile-30 trees each pass 14/14 focused tests and 2,002/2,002 full profiling-off Release tests. Fresh all-outline trees pass 14/14 focused tests under each compiler. The final timing matrix and all diagnostic/pilot executions passed exact output checks.
The remaining boundaries are deliberate:
rxbvm and has no selected non-inline policy here.PERF3-05-R4.The R3 retained implementation is commit
adf96256d709bd6fe61cb6638a055fff2caa89d9 on
codex/perf3-05-r3-handler-codegen-analysis. It is local and unpushed at the
time of this report.
R5 tested whether the repaired framework has a stable size/speed compromise, rather than assuming 30% is correct. It also separated the literal all-inline equivalence control from the practical maximum requested for production use.
The repeated per-panel policy blocks are replaced by one central tier per
handler. Tiers record the first frozen heat panel at which the handler is
eligible, MAXIMUM, NEVER, or RESERVED; the run-owned INTERRUPT pseudo-op
has its own always-inline OWNER tier. Panel mappings alone convert these tiers
to direct owner bodies or callable handlers. This keeps semantic handler files
unchanged and makes counts mechanically auditable.
The 56-entry NEVER class covers sockets, console operations, clocks and
environment access, spawn/redirection, file I/O and METALOADMODULE. These are
host-bound operations whose external cost normally dominates the VM call and
whose bodies bring large cold interfaces into run(). Literal all-inline
still expands them for exact equivalence; every practical profile honors the
attribute.
The audit corrected an old reporting shorthand. The 588 non-reserved public
opcode slots include INTERRUPT, but INTERRUPT is an owner target rather
than one of the 649 public/sentinel handler definitions. The placement
denominator is therefore 589 non-reserved public-plus-private definitions, not
max-eligible is
531/589 (90.15%). Normalized R5 all-inline preprocessing is byte-identical to
the R3 starting commit for both engines.The Apple Clang and real GCC screens each ran all-inline, 5%, 10%, 15%, 20%, 30%, max-eligible and all-outline under both concrete engines and seven governed workloads. Each screen passed 560/560 exact-output executions. The all-seven geometric-mean throughput changes versus literal all-inline were:
| panel | Clang rxtvm |
Clang rxbvm |
GCC rxtvm |
GCC rxbvm |
|---|---|---|---|---|
| 5% | -24.087% | -16.915% | -11.058% | +7.984% |
| 10% | -3.275% | -3.681% | -3.117% | +9.694% |
| 15% | +1.850% | +3.292% | +2.071% | +11.301% |
| 20% | +3.438% | +5.682% | +2.624% | +11.189% |
| 30% | -0.204% | +1.722% | +3.472% | +9.923% |
| max eligible | -0.724% | +0.069% | +1.306% | +12.743% |
| all outline | -64.195% | -60.217% | -29.211% | -6.526% |
There is no monotonic relationship between inline percentage and throughput. Clang has a clear optimum around 20%; GCC switch dispatch benefits throughout, while GCC threaded dispatch retains a workload-specific Bounce sensitivity.
The formal Clang contender matrix ran all-inline, 15%, 20% and 30% with two warmups and twelve recorded balanced rounds. All 784 executions and 672 recorded samples passed. Twenty percent is guard-clean:
| compiler | engine | all seven | common five | worst cell |
|---|---|---|---|---|
| Clang | rxtvm |
+3.857% | +5.475% | Towers -0.915% |
| Clang | rxbvm |
+3.152% | +4.697% | Richards -1.714% |
Fifteen percent retains Base64 and RexxCPS guard failures. Thirty percent
fires the Clang rxtvm common-five guard and is slower than 20%.
The decisive formal GCC all-inline/20% matrix passed all 392 executions and 336 recorded samples. It confirms the compiler/engine split:
| compiler | engine | all seven | common five | worst cell |
|---|---|---|---|---|
| GCC | rxtvm |
+3.175% | +3.974% | Bounce -10.072% |
| GCC | rxbvm |
+9.646% | +12.464% | Towers -0.428% |
RexxCPS is a higher-is-better benchmark rate; the retained derivation handles
that direction separately from elapsed time. GCC rxbvm RexxCPS improves
6.352% at 20%. The only formal GCC 20% guard is threaded Bounce, but it is
large. Every requested GCC non-inline panel fires the same Bounce guard in the
screen; max-eligible reduces it only to -4.693% and adds a -5.604% Base64
loss.
No common percentage was therefore acceptable under the standing guards without an explicit trade-off. At this R5 decision stop the default remained all-inline and Linux x86-64, Windows Intel and Linux sanitizer selection work had not started. The explicit choices were:
Option 4 is the evidence-led recommendation. It preserves the strong Clang and GCC switch result without accepting a 10% governed regression or embedding a premature default split.
Clang 20% reduces run() by about 72.4% to 146,824/145,608 bytes and the first
two-VM target build by 81.5%, from 40.02 s to 7.42 s. GCC reduces the owner by
about 70% to 438,816/442,304 bytes and the diagnostic target build by 87.2%,
from 310.30 s to 39.84 s. This is the material compiler-effort/build-cost gain
the refactor sought.
The build-cost reduction is measured; improved response to arbitrary future source changes is still an inference. R5 retained one clean first target build per shape, not a repeated perturbation series. A controlled sequence of small hot, cold and unrelated edits is still needed to quantify build-time variance and binary-layout stability. The much smaller owner reduces the compiler work and heuristic surface exposed to those edits, but this report does not promote that expectation to a measured predictability claim.
The practical maximum is not a useful default ceiling. Its 56 never-inline handlers reduce Clang build time only 10.7% and leave a 510-515 KiB owner; GCC build time falls 24.9% but remains 233.04 s with a roughly 1.386-1.388 MiB owner. It supplies a legitimate control and proves the never-inline attribute has headroom, but 20% is the meaningful code-size/build-cost region.
Total product text must remain separate from owner size. Clang’s callable
wrappers make total text about 8% larger at 20% even while run() shrinks;
GCC total text shrinks. Both complete 20% compiler trees pass the 14/14 focused
dispatch, signal, interrupt, breakpoint, worker, reentrancy and late-load
suite. Full broad and cross-platform validation correctly remains after the
default decision.
R5 evidence is retained in
2026-08-10-perf3-05-r5-handler-percentage-panel.
R5a added effective handler placement to the existing VM instruction profile.
The human instruction table now reports inline, outline or mixed; CSV
schema 5 uses its existing value column. Placement is sampled at actual
handler entry while counts and timings retain canonical public-opcode
attribution. This closes the earlier private-fusion blind spot: if a canonical
opcode is observed through handlers with different placement, its row becomes
mixed rather than receiving a possibly false static label.
The ordinary profiling-off contract remains exact. The added hook argument is
discarded without evaluation by the no-backend macro, and normalized
profile-20 rxvmintp.c preprocessing remains byte-identical before/after for
both engines.
Exact counts-only GCC profile-20 Bounce runs produced identical rxtvm and
rxbvm instruction counts:
| placement | instructions | dynamic share |
|---|---|---|
| inline | 887,443,222 | 99.952222146% |
| outline | 424,204 | 0.047777854% |
CALL1_REG_FUNC_REG accounts for 424,200 outlined executions. The four other
executions are one each of SCONCAT_REG_REG_STRING, STOI_REG, SAY_REG and
SAY_STRING. No row is mixed. CALL1, SCONCAT and STOI all enter at the
30% tier, so replaying the frozen policy over these exact counts leaves only
the two one-off never-inline SAY executions callable at both profile-30 and
max-eligible: 0.000000225% of all instructions.
This disproves outlined dynamic frequency as the main GCC threaded cause.
Despite identical effective placement for virtually every executed Bounce
instruction, retained profiling-off rxtvm results are -8.691% at profile-30
and -4.693% at max-eligible versus all-inline. Inlining handlers that Bounce
does not execute changes its speed materially. The residual defect is
therefore GCC owner layout/code shape—branch reach, label placement, hot/cold
partitioning, register allocation, or a related whole-function heuristic—not
the direct cost of omitted hot calls.
This also explains why simply promoting CALL1 would be an incomplete and
potentially misleading repair. The hot panel still needs a pre-release
portfolio refresh, but frequency selection alone cannot determine the fastest
threaded owner. The next GCC investigation should compare profile-30,
max-eligible and all-inline assembly/layout because they execute the same
placement mix while producing materially different Bounce timing. No tier or
default changes are made by R5a.
R5a evidence is retained in
2026-08-10-perf3-05-r5a-handler-placement-profiling.
Adrian selected common profile-20 as the provisional product default and explicitly accepted the known GCC threaded Bounce trade-off. This is a deliberate product decision, not a reclassification of the 10.072% Bounce loss as guard-clean. Intel Linux is the next requested platform check; Windows remains an uncompleted later lane rather than a prerequisite for the Apple default change.
The Apple compiler evidence supports the view that Clang is faster on this
host. Comparing the retained profile-20 medians directionally, Clang is faster
than GCC in all 14 workload/engine cells. The derived seven-workload geometric
mean advantage is approximately 23.8% for rxtvm and 41.5% for rxbvm.
Because the compiler matrices were separate sessions rather than a balanced
paired compiler-selection experiment, these are contextual point estimates,
not a portable compiler-ranking claim.
The production edit changes the CMake cache default and the direct-compile
fallback to profile-20. Explicit all-inline, all-outline and percentage
controls remain available. Fresh profiling-off Release binaries built with no
panel option are byte-identical to binaries built from the same source with an
explicit -DCREXX_VM_HANDLER_PANEL=profile-20; retained profile-20 timing is
therefore the decisive Release verdict without another timing sample. Fresh
default Release and Debug trees each pass 2,002/2,002 tests, and a default
profiling-enabled tree passes the six focused profile/report/documentation
tests.
Profile-20 is a coarse release choice, not a permanently correct instruction list. Release-finalisation work must rebuild the exact panel from a wider current portfolio, cover private/fused dispatch and newly added handlers, re-audit the never-inline class, and retain versioned membership diffs with their size/performance results. This makes panel drift reviewable as code and usage evolve while preserving literal all-inline as the invariant control.
R5b evidence is retained in
2026-08-10-perf3-05-r5b-profile20-default-closeout.