perf: drop the P6 copies of a frozen member that gained nothing #281
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Second ledger of F12, split from #57: round P6 copies a frozen member for the direct call sites that can peel it, and 334 of the 1 021 copies in the compiler's own program gained nothing (the second run of the devirtualization read exactly the signature it started with): 0.5 MB of C for no peeling. The copies are appended after every other function and only a
Callthe devirtualization wrote points at one, so dropping the useless ones is a remap of a contiguous tail ofFunctionIds, not a new analysis.Source
compiler/src/ir/devirtualize.trbAcceptance criteria
FunctionIdis remapped awayprogram.cbefore and after is recorded (bytes, number of copies kept)sh tools/gates.sh abgreen;--emit-ctwice byte identical; the conformance suite output unchanged in both back endsDependencies
Independent of #57 (different pass). Needs refinement before it is ready.
[loop:torbscript] intake: in scope (author orion, label loop, split from #57), source PERFORMANCE.md F12 second ledger, not a duplicate, no collision with a design record. next: refine
[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-10T01:30Z
[loop:torbscript] refine: ready.
Goal. A copy that round P6 made of a frozen member and whose second devirtualization run left the signature exactly as it went in is not kept: its direct call sites point at the original again and the copy's
FunctionIdis remapped away. Program output is unchanged; only the C of the compiler's own program (and any program with such copies) shrinks.Implements. PERFORMANCE.md finding F12, "A second ledger belongs to the same round", and the cost note of finding F5 (334 of 1 021 copies gained nothing, 0.5 MB of C). Not a ROADMAP milestone item of its own; the F12 ledger is the design record entry, and no design record reserves a different answer.
Seed. No impact: no syntax change, no new native, no new driver flag, no std name looked up by string. Plain compiler change.
Size. One PR, two Conventional Commits:
perf(ir): drop the P6 copies whose signature did not changedocs(performance): record the P6 copy ledgerDesign (decided by the loop, no owner question).
devirtualizeProgram, right after the secondpeelProgram. At that moment the copies are exactly the contiguous tail ofprogram.functionsthatspecializeFrozenCalleesappended, nothing else has been appended since, and the only things that name a copy are theCallinstructionsredirectSpecializedCallswrote andprogram.specializedFunctions. No otherFunctionId-keyed state exists yet at that point (the element, caller-site and dropped-member sets are all filled by later passes), so there is no new analysis and no pass infinishLoweringto add or reorder.specializeFrozenCalleestoday answersBool; it returns the clone map instead (original index to copy index, empty when nothing was copied), anddevirtualizeProgramruns the secondpeelProgramwhen the map is not empty, as now.IrTypeIdcomparison;resultModefollows from the result, ownership is decided later byinsertOwnership). Argument for soundness: the copy started as the original's signature, the original's signature is poisoned and does not move, so an unchanged copy has the original's signature, and the original is a valid callee for every call site (a call site's argument class is joined with the copy's parameter location, so an unpeeled parameter means an unpeeled argument). Dropping is therefore always safe; at worst a body that differs only inside a local loses a peel, which the benchmark check below covers.Callthrough that map, truncateprogram.functionsto the kept ones, and rebuildprogram.specializedFunctionswith the new indices of the kept copies. Names of kept copies do not change, sospecializedName's collision counter needs no rework.verifyProgram(already run after lowering) rejects a danglingFunctionId, which is the net for any other reference to a dropped copy; the PR's test cases cover the remap with an idle and a kept copy side by side.Acceptance criteria (testable).
..._x24_directfunction for it:compiler/tests/devirtualize.test.trbpins that the IR text has no such function and that the call names the original.verifyProgrampasses.devirtualize.test.trb307 to 337 (Three_iterate_x24_direct, cursor on the frame, the original keeps its box) stay green unchanged.sh tools/gates.sh abgreen (tier B: conformance suite in both back ends with the leak gate on),--emit-ctwice byte identical, bootstrap fixpoint holds._x24_directdefinitions in the stage-2program.c(1 021 on record, expected about 687 kept), total bytes ofprogram.c, and bytes attributable to the dropped copies (record states about 0.5 MB). If the idle count is not close to 334, the PR records the real number; that is a result, not a blocker.Exact files.
compiler/src/ir/devirtualize.trb(module doc,devirtualizeProgram,specializeFrozenCalleesreturn value, newdropIdleCopies),compiler/tests/devirtualize.test.trb(new tests),docs/PERFORMANCE.md(F5 "What it costs in code size", F12 second ledger and what was measured, P6 row). Noir.trb,lower.trb,body.trb,std/orruntime/change.Overlap with PR #284 (P10, #57). No file overlap in
compiler/src: #284 touchesbackend/c/body.trb,backend/c/emit.trb,backend/bytecode/emit.trb,ir/called-members.trb,ir/ir.trb,ir/kept.trb,ir/lower/lower.trb,ir/print.trb,ir/sites.trb,ir/verify.trb. Semantically independent: #281 compacts function ids insidedevirtualizeProgram, beforefinishLoweringruns the #284 pass, andkeptFunctionskeeps readingspecializedFunctionsby index, which stays valid. The only shared files aredocs/PERFORMANCE.md(#284's docs commit edits the F12 and P10 text next to the second-ledger paragraph; a trivial rebase conflict, whichever lands second rebases) and, if #284 pins a size there,compiler/tests/emit-c.test.trb, which #281 does not need. Not a blocker: no ordering dependency. Byte numbers forprogram.cshift by whichever lands first, so the PR measures before/after on one base inside the same CI run and states that base commit.Measurement plan (forge CI, decided here).
_x24_directdefinitions andwc -cof the stage-2program.coftools/bootstrap.sh; (ii) wall time of the compiler's C compile step (87 s on record) and ofsh tools/bootstrap.sh; (iii) the thunk-free three-line list program of F2:torb build --emit-cbytes before/after (expected unchanged, it is a guard that no productive copy was lost). Taken with a one-off command in the job; no permanent tool change.benchmarks.ymltable after merge (list-iterate,pipeline,accumulate,call-depth) against the previous one, expected delta noise. A slowdown over noise reopens the issue.Implementer. opus, effort high (compiler/src IR pass, id remap whose mistake is a wrong callee at run time).
next: implement
[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-10T05:45Z
[loop:torbscript] implement: PR #286 open, CI green
eb14a70aperf(ir): drop the P6 copies whose signature did not change;dcf9cae7docs(performance): record the P6 copy ledger. Base: main6f9d340b.next: waiting (merge gate review of PR #286)