From 2eba63966bdc3d9cfdf41ee085f35cbb6d529bbc Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Wed, 7 Oct 2026 19:10:51 -0700 Subject: Towrds getting RA spill counts comparable Ignore JIGs. Get test suites more conical, so reg alloc spill counts are comparable --- chapter22/README.md | 77 +++++------------- .../main/java/com/seaofnodes/simple/IterPeeps.java | 19 ++++- .../com/seaofnodes/simple/node/CallEndNode.java | 93 ++++++++++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 2 + .../com/seaofnodes/simple/Chapter14FloatTest.java | 2 + .../java/com/seaofnodes/simple/Chapter14Test.java | 2 + .../java/com/seaofnodes/simple/Chapter15Test.java | 2 + .../java/com/seaofnodes/simple/Chapter16Test.java | 2 + .../java/com/seaofnodes/simple/Chapter17bTest.java | 2 + .../java/com/seaofnodes/simple/Chapter18Test.java | 3 +- .../java/com/seaofnodes/simple/Chapter19Test.java | 2 + .../java/com/seaofnodes/simple/Chapter20Test.java | 1 + .../test/java/com/seaofnodes/simple/progs/jig.smp | 4 +- 13 files changed, 151 insertions(+), 60 deletions(-) (limited to 'chapter22') diff --git a/chapter22/README.md b/chapter22/README.md index 34fb4d20..1f839de5 100644 --- a/chapter22/README.md +++ b/chapter22/README.md @@ -190,64 +190,29 @@ returned values and memory as well as successful execution. ## RegAlloc improvements: better color bias -Chapter 21 introduced conservative copy coalescing. Here we improve the register -preferences used when related live ranges cannot coalesce. A copy chain can take +Chapter 21 introduced conservative copy coalescing. Here we improve the register +preferences used when related live ranges cannot coalesce. A copy chain can take an already assigned register even at its terminal definition; a loop Phi prefers its backedge's register, avoiding a move on each iteration when possible. When no live range is trivially colorable, cheap values make better spill -candidates. We favor a cloneable definition with distant or multiple uses over +candidates. We favor a cloneable definition with distant or multiple uses over one already adjacent to its only use, then callee-save values whose long spans -are cheap to split. Register order breaks ties between callee saves. These are -small preferences, not a general cost model. Grouping popular values by register -class is left for Chapter 23, cold loop splits for 24, and area/cost ranking for 25. - -Chapter 21's adjacent-copy forwarding also carries forward: after coloring, -a sole use in the next instruction can read the source register when its -operand mask permits it and no two-address tie is broken. The table below -includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return -ABI checks added since the earlier audit (26 entries instead of 24). - -Run `make spill-stats` in this directory. Each row below uses **this chapter's -compiler**, default worklist seed 123, and the same source/target combinations -as the earlier cohort. The Windows run combines x86 SystemV/Win64 and RISC-V/ARM -SystemV. Diagnostic graphs and mask regressions do not contribute to the totals. - -| Program cohort | Compilations | Retained moves | Loop-weighted moves | -|---|---:|---:|---:| -| Chapter 20 | 39 | 324 | 443 | -| Chapter 21 | 52 | 432 | 964 | -| Chapter 22 | 26 | 67 | 67 | -| **Total** | **117** | **823** | **1,474** | - -`_spills` counts retained SplitNodes, including register moves; `_spillScaled` -weights each by `8^loopDepth`. These are compiler estimates, not measured runtime -memory traffic. The reporter also prints individual allocations and CPU/ABI sums. -Chapter 21's original `int age` person example is preserved as `person21`; this -chapter's `i32 age` version and revised infinite-loop example belong to cohort 22. - -Before adjacent-copy forwarding, a controlled comparison of the same compiler and correctness fixes with -Chapter 21's color preferences and spill ordering produce 885 moves and 1,543 -weighted moves. This chapter saves **47 weighted moves (3.0%)**. RISC-V BrainFuck -improves from 42 to 28 and ARM from 34 to 28 in each of its two cohorts. Some -MergeSort cases and RISC-V Sieve each cost one more move. Summing the whole suite -shows whether those local tradeoffs pay off. - -The original Chapter 22 snapshot, which mixed several later heuristics, produced -854 moves and 1,736 weighted moves on these same 115 compilations. The corrected, -staged version saves 240 weighted moves (13.8%). Neither comparison should be -confused with comparing entire compiler chapters: lowering also changes. For -example, the frozen Chapter 20 String input has no explicit return, and Chapter -22's default-return handling eliminated that workload in the earlier audit. -With lazy memory, some target compilations retain callable bodies; the current -table includes their moves. - -The full suite and statistics runner pass. Routine backend comparisons use a -fixed optimizer seed: -shuffling optimizer worklists should normalize to essentially the same graph, -so repeating allocation on those graphs adds little coverage. Use seed variation -when investigating optimizer normalization or worklist-order failures. - -The [backport record](../docs/chapter-backports.md) documents the resolved -String fall-through and returned-function retention/relocation failures, -including their reproductions and validation history. +are cheap to split. Register order breaks ties between callee saves. These +are small preferences, not a general cost model. Grouping popular values by +register class is left for Chapter 23, cold loop splits for 24, and area/cost +ranking for 25. + +Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each +cell is the sum of `_spillScaled`: retained split moves, including register +copies, weighted by loopDepth. Lower is better. **# tests counts +compilations** (program/CPU/ABI cases), including zero-spill cases, rather than +JUnit methods. The count and membership of each row stay fixed across columns. + + +| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | +|---|---:|---:|---:|---:| +| Ch 20 | 39 | 360 | 576 | 430 | +| Ch 21 | 52 | | 1,072 | 960 | +| Ch 22 | 26 | | | 63 | + diff --git a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java index ded3da7e..7c2f1b0c 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -46,7 +46,12 @@ public class IterPeeps { private final WorkList _work; - public IterPeeps( long seed ) { _work = new WorkList<>(seed); } + private final WorkList _workInline; + + public IterPeeps( long seed ) { + _work = new WorkList<>(seed); + _workInline = new WorkList<>(seed); + } @SuppressWarnings("unchecked") public N add( N n ) { return (N)_work.push(n); } @@ -57,12 +62,24 @@ public class IterPeeps { * Iterate peepholes to a fixed point */ public void iterate( CodeGen code ) { + while( true ) { + iteratePeeps(code); + CallEndNode cend; + while( (cend=_workInline.pop())!=null ) + if( cend.inlineSmall() ) break; + if( cend==null ) return; + } + } + + // Only try growing inlines once the shrinking peepholes reach a fixed point. + private void iteratePeeps( CodeGen code ) { assert progressOnList(code); int cnt=0; Node n; while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; + if( n instanceof CallEndNode cend ) _workInline.push(cend); cnt++; // Useful for debugging, searching which peephole broke things var obs = CodeGen.CODE._obs; if( obs != null ) obs.before(n); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 264a506e..94f5ee49 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -103,6 +103,99 @@ public class CallEndNode extends CFGNode implements MultiNode { return null; } + // Clone only tiny, straight-line boilerplate, after ordinary peeps settle. + // Larger bodies still use the existing single-caller, non-cloning inline. + public boolean inlineSmall() { + if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false; + Node fptr = call.fptr(); + if( !(fptr instanceof ConstantNode) || + !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) + { addDep(fptr); return false; } + if( call.err()!=null ) return false; + ReturnNode ret = (ReturnNode)in(1); + FunNode fun = ret.fun(); + if( fun._folding ) return false; + int len = smallBody(fun); + if( len==0 ) return false; + // All callers consume the three normal return projections. + for( Node use : _outputs ) + if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) || + (use instanceof CProjNode cp && cp._idx==0)) ) return false; + + // Substitute control and arguments directly; no new function identity, + // parameter Phis, or return address is needed for this straight line. + Node[] copy = new Node[len]; + for( int i=0; i1 ) { + Node use = out(0)==null ? out(1) : out(0); + int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx; + CodeGen.CODE.addAll(use._outputs); + use.subsume(result[idx]); + } + unkill(); + for( Node n : result ) n.unkill(); + CodeGen.CODE.invalidateIDepthCaches(); + return true; + } + + private static Node mapped(Node n, Node[] copy) { + for( int i=0; iTINYBODY.length ) return 0; + for( Node use : n._outputs ) { + if( use==null ) continue; + int j=0; + while( j +val _fib = {int n -> int temp=0; int f1=1; int f2=1; @@ -13,4 +13,4 @@ val fib = {int n -> return f2; }; -fib(10); +_fib(10); -- cgit v1.2.3