From 81b654d76444d747fdf1d053760e507b50f26e18 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 5 Oct 2026 15:38:06 -0700 Subject: Small peep to drop simple store-updates out of loops --- .../java/com/seaofnodes/simple/node/LoadNode.java | 1 + .../com/seaofnodes/simple/node/MemPhiNode.java | 5 +++ .../java/com/seaofnodes/simple/node/StoreNode.java | 40 ++++++++++++++++++++++ .../java/com/seaofnodes/simple/Chapter11Test.java | 5 +-- .../java/com/seaofnodes/simple/Chapter19Test.java | 14 ++++++-- .../java/com/seaofnodes/simple/Chapter21Test.java | 4 +-- .../com/seaofnodes/simple/MemoryEncodingTest.java | 6 ++-- 7 files changed, 65 insertions(+), 10 deletions(-) (limited to 'chapter21') diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java index f1a9cb07..3084b4f7 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -51,6 +51,7 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { + if( ptr()._type==Type.TOP ) return Type.TOP; // Dead pointer, not a type error. Type t = MemMergeNode.contents(mem(),_alias,this); // Update declared forward ref to the actual. if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java index e5a29329..1349cadb 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -36,6 +36,11 @@ public class MemPhiNode extends PhiNode { setDef(i,CodeGen.CODE.add(mem.alias(_alias))); return this; } + if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() && + in(2) instanceof StoreNode st && st.mem()==this ) { + Node sink = st.sink(this); + if( sink!=null ) return sink; + } return super.idealize(); } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java index d56f1699..a5b09663 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -94,6 +94,46 @@ public class StoreNode extends MemOpNode { return null; } + // Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)). + // With no observers of the backedge store, only the last value matters. + // The entry load preserves the field even when the loop takes zero trips. + Node sink(MemPhiNode phi) { + phi.addDepForwards(this); + if( nOuts()!=1 ) return null; + phi.addDep(ptr()); + // Start with a fixed field of an allocation dominating the loop. + // In particular, do not sink stores through a loop-varying address. + if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) || + !(ptr()._type instanceof TypeMemPtr tmp) || tmp._obj.isAry() || + !(off() instanceof ConstantNode) || err()!=null ) return null; + LoopNode loop = (LoopNode)phi.region(); + // Bulk splitting identifies precise slices by Region/alias. Finish it + // before replacing this memory point with a Store. + for( Node use : loop._outputs ) + if( use instanceof BulkMemPhiNode ) { + phi.addDepForwards(use); + return null; + } + phi.addDep(obj); + CFGNode ctrl = loop.entry(); + while( ctrl!=null && ctrl!=obj.cfg0() ) { + phi.addDep(ctrl); + ctrl=ctrl.idom(phi); + } + if( ctrl==null ) return null; + + Node mem = phi.in(1); + Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); + Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole()); + Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); + // Break the memory cycle, killing the old store. Keep the Phi alive + // even if that store was its last use, until our caller replaces it. + phi.keep(); + phi.setDef(2,mem); + phi.unkeep(); + return sink; + } + // Check that "mem" has no uses except "this" private boolean checkOnlyUse(Node mem) { if( mem.nOuts()==1 ) return true; diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter11Test.java index a38e93c1..439333ca 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -36,7 +36,8 @@ public class Chapter11Test { var stop = code._stop; int[] phis = {0}; stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; + // Store sinking can replace field memory Phis with value Phis. + if (n instanceof PhiNode) phis[0]++; if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) assertTrue(a=3); + assertTrue(phis[0]>=4); // Three fields and the loop counter. assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java index c230e615..0a92dc20 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -384,13 +384,21 @@ return sq(arg) + sq(3); private static java.util.ArrayList memoryPhis(Node stop) { var phis = new java.util.ArrayList(); - stop.walk(n -> { + // Count only definitions reachable from Stop, as instruction selection does. + // A dead Phi cycle can still appear through the reverse use edges. + var seen = new java.util.BitSet(); + var work = new java.util.ArrayList(); + work.add(stop); + for( int i=0; i { -- cgit v1.2.3