diff options
| author | Cliff Click <cliffc@acm.org> | 2026-09-30 16:41:07 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-09-30 16:41:07 -0700 |
| commit | a996996a1ad776089a4cb9cbf7744f5f4e8dde6d (patch) | |
| tree | b210c9175d96fd8e908a47660c044729788eb0e5 /chapter23 | |
| parent | f01ee0522e63b0266314b4e88e524ea2da95cf0d (diff) | |
| download | Simple-a996996a1ad776089a4cb9cbf7744f5f4e8dde6d.tar.gz Simple-a996996a1ad776089a4cb9cbf7744f5f4e8dde6d.zip | |
Forward port mem stuff to ch21
Also a minor reg-alloc improvement for BrainFuck, ported up to ch25
Diffstat (limited to 'chapter23')
7 files changed, 61 insertions, 17 deletions
diff --git a/chapter23/README.md b/chapter23/README.md index 4a1118b5..c8961647 100644 --- a/chapter23/README.md +++ b/chapter23/README.md @@ -481,18 +481,24 @@ the fallback, including splitting an existing copy when all uses have the same loop depth. Cold-first loop splitting is left for Chapter 24; area/cost spill ranking remains for Chapter 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. 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. All rows below use **this chapter's compiler**, seed 123, and frozen source/target combinations from each cohort. The Windows run combines x86 SystemV/Win64 and RISC-V/ARM SystemV. Diagnostic machine graphs are checked separately and do not contribute to the counts. -| Program cohort | Compilations | Split/move count | Loop-weighted count | +| Program cohort | Compilations | Retained moves | Loop-weighted moves | |---|---:|---:|---:| -| Chapter 20 | 39 | 332 | 458 | -| Chapter 21 | 52 | 450 | 989 | -| Chapter 22 | 24 | 67 | 67 | -| Chapter 23 | 30 | 84 | 231 | -| **Total** | **145** | **933** | **1,745** | +| Chapter 20 | 39 | 326 | 445 | +| Chapter 21 | 52 | 439 | 971 | +| Chapter 22 | 26 | 67 | 67 | +| Chapter 23 | 30 | 81 | 228 | +| **Total** | **147** | **913** | **1,711** | `_spills` counts retained SplitNodes, including register moves; `_spillScaled` weights them by `8^loopDepth`. These estimate compiler-generated moves, not @@ -500,7 +506,7 @@ runtime memory traffic. The reporter prints individual compilations and sums by CPU/ABI, and still reports failure when a spill expectation or execution check fails. -With grouping disabled and everything else held fixed, this suite produces +In the earlier audit, before adjacent-copy forwarding, disabling grouping produced **the same 933 moves / 1,745 weighted moves**. The original grouping code also has those totals. This suite therefore shows no spill improvement from grouping; the reduced machine-graph regression exercises its compatibility and call rules, @@ -508,7 +514,7 @@ including a null-mask crash in the old implementation. A plausible allocator heuristic needs measurements on workloads that reach it before claiming a win. For the older cohorts alone, Chapter 22's compiler produced 838 moves / 1,496 -weighted moves; this compiler produces 849 / 1,514. That comparison includes +weighted moves; this compiler then produced 849 / 1,514. That comparison includes changes outside allocation. In particular, Chapter 20's frozen String input survives optimization here and costs 16 weighted moves; Chapter 22 eliminated it at this seed. It would be misleading to attribute that difference to grouping. diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index 11c9409d..793732b4 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -591,8 +591,8 @@ public class RegAlloc { if( defreg != usereg && splitBypass(bb,j,n,defreg) ) usereg = lrg(n.in(1))._reg; - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -605,6 +605,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 044ad4ed..d0c48688 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -115,7 +115,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - testTarget(src,"x86_64_v2", "SystemV",48,null); + testTarget(src,"x86_64_v2", "SystemV",40,null); testTarget(src,"riscv" , "SystemV",17,null); testTarget(src,"arm" , "SystemV",18,null); } @@ -162,7 +162,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",9,"return .[];"); + testTarget(src,"x86_64_v2", "SystemV",7,"return .[];"); testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); } @@ -207,7 +207,7 @@ hashCode(s); """; testTarget(src,"x86_64_v2", "SystemV",9,null); testTarget(src,"riscv" , "SystemV",4,null); - testTarget(src,"arm" , "SystemV",3,null); + testTarget(src,"arm" , "SystemV",1,null); } @Test diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java index 2d0493e5..bab7a86a 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -21,7 +21,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,48,null); + Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); } @@ -49,7 +49,7 @@ return sqrt(arg) + sqrt(arg+2); String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash21.smp")); Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter21Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 6438801b..14f9fdb8 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -196,6 +196,7 @@ public class Chapter21Test { @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } @Test public void testNarrowStores() throws IOException { for( String type : new String[]{"i8","u8","i16","u16"} ) { @@ -339,7 +340,7 @@ public class Chapter21Test { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); testCPU(src,"x86_64_v2", "SystemV", 9,null); testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); } @Test public void testStringExport() throws IOException { diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java index cd8c43cf..7f4b86d5 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java @@ -11,7 +11,7 @@ public class Chapter23AllocTest { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); Chapter23Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter23Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter23Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null); } @Test diff --git a/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index f0830f1b..5b61d3b3 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -219,6 +219,31 @@ public class RegAllocTestSupport { assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + public static void coalescing() { // Merge, incompatible masks, interference, capacity, and adjacency remapping. for( int kind=0; kind<5; kind++ ) { |
