From f01ee0522e63b0266314b4e88e524ea2da95cf0d Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Wed, 30 Sep 2026 09:06:50 -0700 Subject: ch 20 forward port memory --- chapter20/README.md | 31 ++- .../main/java/com/seaofnodes/simple/Parser.java | 105 +++++---- .../simple/codegen/GlobalCodeMotion.java | 54 ++--- .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 240 +++++++++++++++++++++ .../java/com/seaofnodes/simple/node/LoadNode.java | 68 ++++-- .../com/seaofnodes/simple/node/MemMergeNode.java | 190 ++++++---------- .../java/com/seaofnodes/simple/node/MemOpNode.java | 33 +++ .../com/seaofnodes/simple/node/MemPhiNode.java | 44 ++++ .../java/com/seaofnodes/simple/node/NewNode.java | 58 ++--- .../main/java/com/seaofnodes/simple/node/Node.java | 14 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 102 ++++++--- .../com/seaofnodes/simple/node/RegionNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 12 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 75 ++++--- .../java/com/seaofnodes/simple/node/StartNode.java | 11 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 28 +-- .../seaofnodes/simple/node/cpus/arm/MemOpARM.java | 2 + .../seaofnodes/simple/node/cpus/arm/NewARM.java | 6 +- .../seaofnodes/simple/node/cpus/arm/StoreARM.java | 2 +- .../com/seaofnodes/simple/node/cpus/arm/arm.java | 2 + .../simple/node/cpus/riscv/MemOpRISC.java | 2 + .../seaofnodes/simple/node/cpus/riscv/NewRISC.java | 6 +- .../simple/node/cpus/riscv/StoreRISC.java | 2 +- .../seaofnodes/simple/node/cpus/riscv/riscv.java | 2 + .../simple/node/cpus/x86_64_v2/MemOpX86.java | 4 +- .../simple/node/cpus/x86_64_v2/NewX86.java | 6 +- .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 7 +- .../simple/print/SimpleGraphAdapter.java | 2 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 119 ++++++++++ .../java/com/seaofnodes/simple/Chapter10bTest.java | 60 ++++++ .../java/com/seaofnodes/simple/Chapter16Test.java | 23 ++ .../java/com/seaofnodes/simple/Chapter18Test.java | 66 ++++++ .../java/com/seaofnodes/simple/Chapter19Test.java | 67 ++++++ .../java/com/seaofnodes/simple/Chapter20Test.java | 25 ++- .../src/test/java/com/seaofnodes/simple/Eval2.java | 6 +- .../com/seaofnodes/simple/evaluator/Evaluator.java | 12 +- 36 files changed, 1081 insertions(+), 407 deletions(-) create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java create mode 100644 chapter20/src/test/java/com/seaofnodes/simple/Chapter10bTest.java (limited to 'chapter20') diff --git a/chapter20/README.md b/chapter20/README.md index e463831a..271653c6 100644 --- a/chapter20/README.md +++ b/chapter20/README.md @@ -152,6 +152,11 @@ represent mutable bitsets with the allocator routinely masking off bits when accumulating a set of constraints. To help keep these uses apart, the `RegMask` class includes mutable and immutable variants. +#### Memory + +Memory edges exist to enforce ordering, but do not get any registers. +The allocator basically ignores them. + ### Callee/Caller Save Registers @@ -505,21 +510,41 @@ checks, not additional program benchmarks. | Program-test cohort | Allocator chapter | Compilations | Split moves | Loop-weighted moves | |---|---:|---:|---:|---:| -| Chapter 20 | 20 | 39 | 238 | 357 | +| Chapter 20 | 20 | 39 | 234 | 360 | `_spills` counts surviving split moves, including register-to-register copies; it does not count only stack stores. `_spillScaled` weights each move by `8^loopDepth`, a rough cost estimate rather than measured execution frequency. -The weighted subtotals are 154 for x86-64, 101 for RISC-V, and 102 for ARM. +The weighted subtotals are 161 for x86-64, 100 for RISC-V, and 99 for ARM. ### Comments on the measurements Before the correctness fixes, this cohort had 235 moves / 354 weighted moves. -The final corrected baseline costs three more of each. The final narrow x86 +The corrected baseline before lazy memory cost three more of each. The final narrow x86 store-mask backport accounts for two of those moves: an integer byte/short store cannot consume an XMM register. This is a correctness cost, not a quality gain. The fixed-neighbor color-bias correction changes no counts in this cohort. +With lazy memory and selected-read ordering, the same 39 compilations have +234 moves / 360 weighted moves, versus 238 / 357 before the port. Register +legality passes on every target; allocator heuristics are unchanged. The changed +programs are: + +| Program | Target | Moves before / after | Weighted before / after | +|---|---|---:|---:| +| Array access (`testAlloc2`) | x86-64 | 1 / 2 | 1 / 2 | +| Array prefix sum (`testArray1`) | x86-64 | 5 / 6 | 5 / 13 | +| String hash | x86-64 | 7 / 5 | 21 / 19 | +| String hash | RISC-V | 4 / 3 | 4 / 3 | +| String hash | ARM | 5 / 3 | 5 / 3 | +| Merge sort | ARM | 37 / 36 | 37 / 36 | + +The prefix sum's extra loop move follows from scheduling selected reads before +clobbering writes. A diagnostic build omitting that ordering returns to 5 / 5; +it is not a valid alternative schedule. These are changed frontend and scheduling +graphs, not a comparison of allocator heuristics. The new memory-allocation +regressions check legality but do not add entries to this measured cohort. + Later chapters will add one quality technique at a time and end with this kind of table: Chapter 21 will measure both the Chapter 20 and Chapter 21 program cohorts using its allocator, and Chapter 25 will have rows for cohorts 20-25. diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java index af047850..16018fba 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java @@ -138,7 +138,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(_code._main,loc(),"arg"); @@ -178,7 +178,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode((Lexer)null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -190,11 +190,8 @@ public class Parser { // external calls. _scope.push(ScopeNode.Kind.Function); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i