summaryrefslogtreecommitdiff
path: root/chapter15/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-29 16:16:13 -0700
committerCliff Click <cliffc@acm.org>2026-09-29 16:16:13 -0700
commit684c23f1c5257886a219d540ac4ec0e574b7e49b (patch)
treec0b590dfa40967b9cbd316f6fe68162e7912dc60 /chapter15/src
parentb475b072b38db7f46406ed6128fffe3e15a6e7ac (diff)
downloadSimple-684c23f1c5257886a219d540ac4ec0e574b7e49b.tar.gz
Simple-684c23f1c5257886a219d540ac4ec0e574b7e49b.zip
Bring forward the lazy mem alias
Integrate with ch15 NewNode creating aliases
Diffstat (limited to 'chapter15/src')
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java28
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/Parser.java66
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java237
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java92
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java88
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java7
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java42
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java41
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/Node.java35
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java91
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java2
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java29
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java4
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java41
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java18
-rw-r--r--chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java5
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java14
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java10
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java26
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java16
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java10
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java135
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java64
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java6
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java68
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java15
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java33
31 files changed, 964 insertions, 267 deletions
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
index 27307d32..d17ec3b6 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -148,12 +148,8 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof LoadNode ld )
- for( Node memuse : ld.mem()._outputs )
- if( late[memuse._nid]==null &&
- // Load-use directly defines memory
- (memuse._type instanceof TypeMem ||
- // Load-use indirectly defines memory
- (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) )
+ for( Node memuse : ld.antiDeps() )
+ if( late[memuse._nid]==null )
continue outer;
// All uses done, schedule
@@ -165,9 +161,7 @@ public abstract class GlobalCodeMotion {
for( Node def : n._inputs ) {
if( def==null ) continue;
if( late[def._nid]==null ) work.push(def);
- for( Node out : def._outputs )
- if( out instanceof LoadNode ld && late[ld._nid]==null )
- work.push(ld);
+ wakeLoads(def,late,work,new BitSet());
}
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
@@ -224,13 +218,24 @@ public abstract class GlobalCodeMotion {
best instanceof IfNode;
}
+ // An allocation may consume a load's memory through a partial aggregate.
+ private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) {
+ if( visit.get(def._nid) ) return;
+ visit.set(def._nid);
+ for( Node out : def._outputs )
+ if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld);
+ if( def instanceof MemMergeNode )
+ for( int i=1; i<def.nIns(); i++ )
+ if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
+ }
+
private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
- for( Node mem : load.mem()._outputs ) {
+ for( Node mem : load.antiDeps() ) {
switch( mem ) {
case StoreNode st:
assert late[st._nid]!=null;
@@ -247,9 +252,6 @@ public abstract class GlobalCodeMotion {
if( phi.in(i)==load.mem() )
lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti);
break;
- case LoadNode ld: break; // Loads do not cause anti-deps on other loads
- case ReturnNode ret: break; // Load must already be ahead of Return
- case NeverNode never: break;
default: throw Utils.TODO();
}
}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java
index 965bd146..1fc6f312 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java
@@ -128,7 +128,7 @@ public class Parser {
_lexer = new Lexer(source);
_scope = new ScopeNode();
_continueScope = _breakScope = null;
- START = new StartNode(new Type[]{ Type.CONTROL, arg });
+ START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg });
STOP = new StopNode(source);
ZERO = con(0).keep();
XCTRL= new XCtrlNode().peephole().keep();
@@ -156,9 +156,8 @@ public class Parser {
// Enter a new scope for the initial control and arguments
_scope.push();
_scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole());
- _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 1, ScopeNode.ARG0).peephole());
-
- // Parse whole program
+ _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole());
+ _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole());
parseBlock();
if( ctrl()._type==Type.CONTROL )
@@ -250,7 +249,6 @@ public class Parser {
// Build and install the TypeStruct
TypeStruct ts = TypeStruct.make(typeName, fields.toArray(new Field[fields.size()]));
TYPES.put(typeName, TypeMemPtr.make(ts));
- START.addMemProj(ts, _scope); // Insert memory edges
return parseStatement();
}
@@ -536,7 +534,6 @@ public class Parser {
TypeStruct ts = TypeStruct.makeAry(TypeInteger.BOT,ALIAS++,t,ALIAS++);
TypeMemPtr tary = TypeMemPtr.make(ts);
TYPES.put(tname,tary);
- START.addMemProj(ts, _scope); // Insert memory alias edges
return tary;
}
@@ -727,16 +724,22 @@ public class Parser {
* Return a NewNode with pre-zeroed memory
*/
private Node newStruct(TypeStruct obj, Node size) {
- Field[] fs = obj._fields;
- Node[] ns = new Node[2+fs.length];
- ns[0] = ctrl();
- ns[1] = size;
- for( int i = 0; i < fs.length; i++ )
- ns[i+2] = memAlias(fs[i]._alias);
- Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole();
- for( int i = 0; i < fs.length; i++ )
- memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole());
- return new ProjNode(nnn,1,obj._name).peephole();
+ Node prior = mem().keep();
+ // No default: only the aliases initialized by this allocation.
+ MemMergeNode input = new MemMergeNode(null);
+ for( Field f : obj._fields )
+ input.alias(f._alias,prior);
+ Node nnn = new NewNode(TypeMemPtr.make(obj),ctrl(),input.peephole(),size).peephole().keep();
+ Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep();
+ Node out = new ProjNode(nnn,1,"$mem").peephole().keep();
+ MemMergeNode after = new MemMergeNode(prior);
+ for( Field f : obj._fields )
+ after.alias(f._alias,out);
+ mem(after.peephole());
+ out.unkeep();
+ nnn.unkeep();
+ prior.unkeep();
+ return ptr.unkeep();
}
private Node newArray(TypeStruct ary, Node len) {
@@ -745,17 +748,22 @@ public class Parser {
Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole();
Node ptr = newStruct(ary,size);
int alias = ary._fields[0]._alias; // Length alias
- memAlias(alias,new StoreNode("#",alias,TypeInteger.BOT,memAlias(alias),ptr,con( ary.offset(0) ), len.unkeep(), true ).peephole());
+ store("#",alias,TypeInteger.BOT,ptr,con(ary.offset(0)),len.unkeep(),true,null);
return ptr;
}
- // We set up memory aliases by inserting special vars in the scope these
- // variables are prefixed by $ so they cannot be referenced in Simple code.
- // Using vars has the benefit that all the existing machinery of scoping
- // and phis work as expected
- private Node memAlias(int alias ) { return _scope.lookup(memName(alias) ); }
- private Node memAlias(int alias, Node st) { return _scope.update(memName(alias), st); }
- public static String memName(int alias) { return ("$"+alias).intern(); }
+ // Memory is one hidden SSA variable, including across branches and loops.
+ private Node mem() { return _scope.lookup("$mem"); }
+ private Node mem(Node n) { return _scope.update("$mem",n); }
+
+ private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
+ Node prior = mem().keep();
+ Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init);
+ st.setDef(0,ctrl);
+ st = st.peephole();
+ mem(new MemMergeNode(prior,alias,st).peephole());
+ prior.unkeep();
+ }
/**
* Parse postfix expression. For now this is just a field
@@ -808,17 +816,13 @@ public class Parser {
Node val = parseExpression();
// Auto-truncate when storing to narrow fields
val = zsMask(val,f._type).keep();
- Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr.unkeep(), off.unkeep(), val, false);
- // Arrays include control, as a proxy for a safety range check.
- // Structs don't need this; they only need a NPE check which is
- // done via the type system.
- if( base.isAry() ) st.setDef(0,ctrl());
- memAlias(f._alias, st.peephole());
+ // Array control stands in for the future bounds check.
+ store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null);
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
}
- Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr.unkeep(), off);
+ Node load = new LoadNode(name, f._alias, f._type.glb(), mem(), expr.unkeep(), off);
// Arrays include control, as a proxy for a safety range check
// Structs don't need this; they only need a NPE check which is
// done via the type system.
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..84f97189
--- /dev/null
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,237 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import java.util.BitSet;
+
+/**
+ * A Phi for bulk memory. It covers all aliases except {@link #_aliases};
+ * those aliases must be represented by parallel precise MemPhis at the same
+ * program slice.
+ */
+public class BulkMemPhiNode extends PhiNode {
+
+ private static final BitSet EMPTY = new BitSet();
+
+ // Excluded Aliases; empty means NO exclusions.
+ // Each split clones the exclusions; published sets are never mutated.
+ public final BitSet _aliases;
+
+ public BulkMemPhiNode(String label, Node... inputs) {
+ this(label,EMPTY,inputs);
+ }
+
+ private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
+ super(label,TypeMem.BOT,inputs);
+ _aliases = aliases;
+ }
+
+ public boolean isSplit(int alias) { return _aliases.get(alias); }
+
+ @Override public String label() { return "BulkPhi"+_aliases; }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ return t instanceof TypeMem mem ? mem :
+ t==Type.BOTTOM ? TypeMem.BOT :
+ TypeMem.TOP;
+ }
+
+ @Override
+ public Node idealize() {
+ Node progress = super.idealize();
+ if( progress != null ) return progress;
+ if( ((RegionNode)region()).inProgress() || region().nIns()<=1 )
+ return null; // Input is in-progress
+
+ // "Peek through" a MemMerge that covers this alias set on its default
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) {
+ setDef(i,IterPeeps.add(mmm.in(1)));
+ return this;
+ }
+
+ // If any input or output uses or defines a specific alias we cover,
+ // make a private MemPhi for it, and add it to the excluded list.
+ for( int i=1; i<nIns(); i++ ) {
+ int alias = inputAlias(in(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ for( int i=0; i<_outputs.size(); i++ ) {
+ int alias = outputAlias(_outputs.get(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ return null;
+ }
+
+ static public boolean checkMem(Node n, int alias) {
+ for( Node m : n._inputs )
+ // Bulk memory supplies this alias
+ if( m instanceof BulkMemPhiNode bulk ) {
+ if( bulk._aliases.get(alias) )
+ return false;
+ } else if( m instanceof MemMergeNode mmm &&
+ mmm.in(1) instanceof BulkMemPhiNode bulk &&
+ bulk._aliases.get(alias) &&
+ (alias >= mmm.nIns() || mmm.in(alias)==null) )
+ return false;
+ return true;
+ }
+
+
+ // True if the MemMerge has no precise slice still covered by this Phi.
+ private boolean canPeek(MemMergeNode mmm) {
+ return mmm.in(1)!=null && missingAlias(mmm)==0;
+ }
+
+ // Return an alias required by an input but still covered by this Phi, or 0.
+ private int inputAlias(Node n) {
+ return switch(n) {
+ case ProjNode proj -> 0;
+ case ConstantNode con -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw new AssertionError("Unexpected memory input "+n);
+ };
+ }
+
+ // Return an alias required by a user but still covered by this Phi, or 0.
+ private int outputAlias(Node use) {
+ // This decision depends backwards on an immediate user's alias/set.
+ // Normal graph-neighbor enqueueing flows from defs to uses, so retain
+ // an explicit forward dependency to revisit this Phi when the user
+ // sharpens.
+ use.addDepForwards(this);
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case MemPhiNode phi -> unsplit(phi._alias);
+ default -> {
+ assert use instanceof ReturnNode
+ : "Unexpected bulk-memory user "+use;
+ yield 0;
+ }
+ };
+ }
+
+ // Alias zero/one is bulk, and an already excluded precise alias cannot
+ // trigger another split of this Phi.
+ private int unsplit(int alias) {
+ if( alias <= 1 ) return 0;
+ assert !_aliases.get(alias);
+ return alias;
+ }
+
+ // First alias excluded by the neighboring bulk Phi but not by this one.
+ private int missingAlias(BulkMemPhiNode bulk) {
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // Every explicit slot requests that alias, including a partial allocation
+ // input whose slot still points to this bulk Phi.
+ private int missingAlias(MemMergeNode mmm) {
+ for( int alias=2; alias<mmm.nIns(); alias++ )
+ if( mmm.in(alias)!=null &&
+ mmm.alias(alias)!=mmm.in(1) &&
+ !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // Slice out given alias
+ private MemMergeNode slice( int alias ) {
+ assert !_aliases.get(alias);
+ // Inputs are the same; MemPhi will sharpen his own inputs. If a
+ // parallel precise Phi already exists, reuse it instead of building a
+ // duplicate for the same Region/alias pair.
+ Node mem = precisePhi(alias);
+
+ BitSet aliases = ((BitSet)_aliases.clone());
+ aliases.set(alias);
+ BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases);
+ for( int i=0; i<nIns(); i++ )
+ bphi.addDef(in(i));
+ // Installing bphi creates a new bulk user with a different exclusion
+ // set. Queue the original memory inputs before bphi itself can peep
+ // away and hide those neighbor relationships.
+ for( int i=1; i<bphi.nIns(); i++ )
+ IterPeeps.add(bphi.in(i));
+ // Do not peephole bphi here, as BulkMemPhi can recursively start a
+ // second bulk rewrite before this one has finished. Let the worklist
+ // discover any further splits
+ bphi.setType(TypeMem.BOT);
+ Node bulk = bphi;
+ IterPeeps.add(bulk);
+ IterPeeps.add(mem);
+ return aggregate(bulk,alias,mem);
+ }
+
+ // Build aggregate memory over a bulk Phi, preserving every precise slice
+ // which the bulk has already excluded. The named alias is newly supplied
+ // (or replaces its old slice).
+ MemMergeNode aggregate(Node bulk, int alias, Node precise) {
+ MemMergeNode mmm = new MemMergeNode(bulk);
+ for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
+ { assert old != alias; mmm.alias(old,precisePhi(old));}
+ mmm.alias(alias,precise);
+ assert checkMerge(mmm);
+ return mmm;
+ }
+
+ // Find or make the precise Phi parallel to this bulk Phi at the same merge.
+ private Node precisePhi(int alias) {
+ MemPhiNode mphi = _findPhi(alias);
+ if( mphi!=null ) return mphi;
+
+ mphi = new MemPhiNode("$"+alias,alias);
+ mphi.addDef(region());
+ // Due to cycles, must set before calling peephole
+ mphi.setType(TypeMem.make(alias,Type.BOTTOM));
+ for( int i=1; i<nIns(); i++ )
+ mphi.addDef(IterPeeps.add(preciseInput(in(i),alias)));
+ return IterPeeps.add(mphi);
+ }
+
+ MemPhiNode _findPhi(int alias) {
+ for( Node use : region()._outputs )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias )
+ return mphi;
+ return null;
+ }
+
+ // Select an alias already split out of a predecessor's bulk memory.
+ private Node preciseInput(Node mem, int alias) {
+ if( mem instanceof MemMergeNode mmm )
+ return mmm.alias(alias);
+ if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) )
+ return bulk.precisePhi(alias);
+ return mem;
+ }
+
+ // Every alias excluded by a bulk default must be supplied explicitly.
+ static boolean checkMerge(MemMergeNode mmm) {
+ if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true;
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( alias >= mmm.nIns() || mmm.in(alias)==null )
+ return false;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) {
+ return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
+ }
+
+ @Override int hash() { return _aliases.hashCode(); }
+}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 99a6d72c..4c2013b9 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -1,11 +1,10 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Utils;
-import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeMem;
-import com.seaofnodes.simple.type.TypeStruct;
import java.util.BitSet;
+import java.util.ArrayList;
/**
* Load represents extracting a value from inside a memory object,
@@ -25,6 +24,25 @@ public class LoadNode extends MemOpNode {
super(name, alias, glb, mem, ptr, off);
}
+ @Override boolean canDrop(MemOpNode other, Node dep) {
+ return super.canDrop(other,dep) && !clobbered(dep);
+ }
+
+ // Check only immediate memory users. Stores clobber memory; Phis and
+ // aggregates might lead to a clobber, so stop rather than search further.
+ private boolean clobbered(Node dep) {
+ Node mem = mem();
+ mem.addDepForwards(dep);
+ for( Node use : mem._outputs ) {
+ if( use==null ) continue;
+ use.addDepForwards(dep);
+ if( use instanceof StoreNode || use instanceof PhiNode ||
+ use instanceof MemMergeNode ||
+ use instanceof NewNode ) return true;
+ }
+ return false;
+ }
+
// Debugger label
@Override public String label() { return "ld_"+mlabel(); }
@Override
@@ -32,29 +50,29 @@ public class LoadNode extends MemOpNode {
@Override
public Type compute() {
- if( mem()._type instanceof TypeMem mem &&
- // No constant folding if ptr might null-check
- _declaredType != mem._t && err()==null ) {
- assert mem._alias == _alias;
- return _declaredType.join(mem._t);
- }
- return _declaredType;
+ Type t = MemMergeNode.contents(mem(),_alias,this);
+ // No constant folding if the pointer still needs a null check.
+ return err()==null ? _declaredType.join(t) : _declaredType;
}
@Override
public Node idealize() {
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,IterPeeps.add(merge.alias(_alias)));
+ return this;
+ }
Node ptr = ptr();
// Simple Load-after-Store on same address.
if( mem() instanceof StoreNode st &&
- ptr == st.ptr() && off() == st.off() ) { // Must check same object
+ _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object
assert _name.equals(st._name); // Equiv class aliasing is perfect
return st.val();
}
// Simple Load-after-New on same address.
if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn &&
- ptr == nnn.proj(1) ) // Must check same object
+ ptr == nnn.proj(0) ) // Must check same object
return new ConstantNode(_declaredType.makeInit());
// Load-after-Store on same address, but bypassing provably unrelated
@@ -63,12 +81,16 @@ public class LoadNode extends MemOpNode {
Node mem = mem();
outer:
while( true ) {
+ mem.addDep(this);
switch( mem ) {
+ case MemMergeNode merge:
+ mem = merge.alias(_alias);
+ break;
case StoreNode st:
- if( ptr == st.ptr() && off() == st.off() )
+ if( _alias==st._alias && ptr == st.ptr() && off() == st.off() )
return st.val(); // Proved equal
// Can we prove unequal? Offsets do not overlap?
- if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
+ if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
!neverAlias(ptr,st.ptr()) ) // And might alias
break outer; // Cannot tell, stop trying
// Pointers cannot overlap
@@ -82,11 +104,11 @@ public class LoadNode extends MemOpNode {
return new ConstantNode(_declaredType.makeInit()); // Load from a New
if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) )
break outer; // Cannot tell, ptr not related to New
- mem = nnn1.in(_alias);// Bypass unrelated New
+ mem = nnn1.mem();// Bypass unrelated New
break;
- } else throw Utils.TODO();
+ } else break outer;
default:
- throw Utils.TODO();
+ break outer;
}
}
@@ -97,7 +119,7 @@ public class LoadNode extends MemOpNode {
// if( pred ) ptr.x = e0; val = pred ? e0
// else ptr.x = e1; : e1;
// val = ptr.x; ptr.x = val;
- if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
+ if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
off() instanceof ConstantNode ) {
// Profit on RHS/Loop backedge
if( profit(memphi,2) ||
@@ -113,7 +135,9 @@ public class LoadNode extends MemOpNode {
}
private Node ld( int idx ) {
Node mem = mem(), ptr = ptr();
- return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off());
+ ld.setDef(0,in(0));
+ return ld.peephole();
}
private static boolean neverAlias( Node ptr1, Node ptr2 ) {
return ptr1.in(0) != ptr2.in(0) &&
@@ -127,8 +151,34 @@ public class LoadNode extends MemOpNode {
private boolean profit(PhiNode phi, int idx) {
Node px = phi.in(idx);
if( px==null ) return false;
- if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) { px.addDep(this); return true; }
- if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) { px.addDep(this); return true; }
+ px.addDep(this);
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true;
+ if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true;
return false;
}
+
+ // Memory writers which must follow this read. A packaging node is not a
+ // write, but can hide New's input, so follow this alias through aggregates.
+ public ArrayList<Node> antiDeps() {
+ ArrayList<Node> deps = new ArrayList<>();
+ antiDeps(mem(),deps,new BitSet());
+ return deps;
+ }
+
+ private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) {
+ if( visit.get(mem._nid) ) return;
+ visit.set(mem._nid);
+ for( Node use : mem._outputs ) {
+ if( use instanceof MemMergeNode merge ) {
+ Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
+ if( (slice==mem) || (slice==null && merge.in(1)==mem) )
+ antiDeps(merge,deps,visit);
+ } else if( (use instanceof StoreNode st && st._alias==_alias) ||
+ (use instanceof NewNode nnn && nnn.field(_alias)!=null) ||
+ (use instanceof MemPhiNode phi && phi._alias==_alias) ||
+ (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
+ if( !deps.contains(use) ) deps.add(use);
+ }
+ }
+ }
}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
new file mode 100644
index 00000000..22d97dae
--- /dev/null
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -0,0 +1,88 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import java.util.BitSet;
+
+/** A default slice and explicit alias overrides. A null default means partial
+ * memory: absent aliases are not covered, rather than having unknown contents.
+ */
+public class MemMergeNode extends Node {
+ public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; }
+ public MemMergeNode(Node bulk, int alias, Node precise) {
+ this(bulk);
+ alias(alias,precise);
+ }
+
+ public Node alias(int alias) {
+ Node n = alias < nIns() ? in(alias) : null;
+ assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias);
+ n = n==null ? in(1) : n;
+ assert n!=null : "Alias not covered: "+alias;
+ return n;
+ }
+
+ public void alias(int alias, Node mem) {
+ assert alias > 1;
+ while( nIns()<=alias ) addDef(null);
+ setDef(alias,mem);
+ }
+
+ // Scalar contents of one alias. New's struct supplies the initialized type;
+ // join it with the incoming contents for all previously allocated objects.
+ // Phis use their cached types, so this query does not recurse around loops.
+ static Type contents(Node mem, int alias, Node dep) {
+ mem.addDep(dep);
+ if( mem instanceof MemMergeNode merge )
+ return contents(merge.alias(alias),alias,dep);
+ if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) {
+ assert proj._idx==1 && nnn.field(alias)!=null;
+ nnn.addDep(dep);
+ return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeInit());
+ }
+ // There are no heap arguments in this chapter. Before the first New,
+ // the set of allocated objects (and hence stored values) is empty.
+ if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP;
+ if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP;
+ if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t;
+ return Type.BOTTOM;
+ }
+
+ @Override public String label() { return "MemMerge"; }
+ @Override public boolean isMem() { return true; }
+ @Override public Type compute() { return TypeMem.BOT; }
+ @Override StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("MEM[");
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)!=null ) {
+ sb.append(i==1 ? "default:" : i+":");
+ in(i)._print0(sb,visited).append(" ");
+ }
+ return sb.append("]");
+ }
+
+ @Override public Node idealize() {
+ boolean progress=false, allDefault=true;
+ for( int i=2; i<nIns(); i++ ) {
+ if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; }
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,IterPeeps.add(mem.alias(i)));
+ progress=true;
+ }
+ if( in(i)!=null ) allDefault=false;
+ }
+ if( allDefault && in(1)!=null ) return in(1);
+
+ if( in(1) instanceof MemMergeNode mem ) {
+ for( int i=2; i<mem.nIns(); i++ )
+ if( mem.in(i)!=null && (i>=nIns() || in(i)==null) )
+ alias(i,mem.in(i));
+ IterPeeps.add(mem.in(1));
+ setDef(1,mem.in(1));
+ return this;
+ }
+ assert BulkMemPhiNode.checkMerge(this);
+ return progress ? this : null;
+ }
+}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index c7a1efec..9c222ad2 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -32,6 +32,13 @@ public abstract class MemOpNode extends Node {
public Node ptr() { return in(2); }
public Node off() { return in(3); }
+ // Extra conditions for factoring matching memory operations through a Phi.
+ // The caller has already checked the opcode, input shape and control.
+ boolean canDrop(MemOpNode other, Node dep) {
+ ptr().addDep(dep);
+ return _alias==other._alias && _declaredType==other._declaredType && err()==null;
+ }
+
@Override
boolean eq(Node n) {
MemOpNode mem = (MemOpNode)n; // Invariant
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..6aae00f9
--- /dev/null
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,42 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+
+/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */
+public class MemPhiNode extends PhiNode {
+ public final int _alias;
+
+ public MemPhiNode(String label, int alias, Node... inputs) {
+ super(label,TypeMem.make(alias,Type.BOTTOM),inputs);
+ assert alias > 1;
+ _alias = alias;
+ }
+
+ @Override public String label() { return "MemPhi_"+_alias; }
+
+ @Override public Type compute() {
+ assert BulkMemPhiNode.checkMem(this,_alias);
+ if( !(region() instanceof RegionNode r) )
+ return region()._type==Type.XCONTROL ? TypeMem.TOP : _type;
+ if( r.inProgress() ) return _declaredType;
+ Type t = Type.TOP;
+ for( int i=1; i<nIns(); i++ )
+ if( r.in(i).addDep(this)._type!=Type.XCONTROL )
+ t = t.meet(MemMergeNode.contents(in(i),_alias,this));
+ return TypeMem.make(_alias,t);
+ }
+
+ @Override public Node idealize() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,IterPeeps.add(mem.alias(_alias)));
+ return this;
+ }
+ return super.idealize();
+ }
+
+ @Override boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); }
+ @Override int hash() { return _alias; }
+}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java
index f06564cd..d5154d90 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -3,29 +3,30 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.type.*;
import java.util.BitSet;
-/**
- * Allocation! Allocate a chunk of memory, and pre-zero it.
- * The inputs include control and size, and ALL aliases being set.
- * The output is large tuple, one for every alias plus the created pointer.
- * New is expected to be followed by projections for every alias.
+/** Allocate and zero an object. Inputs {ctrl, $mem, size}; results {ptr, $mem}.
+ * The memory input and result cover only the aliases in the allocated struct.
*/
public class NewNode extends Node implements MultiNode {
- public TypeMemPtr _ptr;
-
- public NewNode(TypeMemPtr ptr, Node... nodes) {
- super(nodes);
- // Control in slot 0
- assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL;
- // Malloc-length in slot 1
- assert nodes[1]._type instanceof TypeInteger;
- // Memory slices in remaining slots
- for( int i=2; i<nodes.length; i++ )
- assert nodes[i]._type instanceof TypeMem;
+ public final TypeMemPtr _ptr;
+ public NewNode(TypeMemPtr ptr, Node ctrl, Node mem, Node size) {
+ super(ctrl,mem,size);
+ assert ctrl._type==Type.CONTROL || ctrl._type==Type.XCONTROL;
+ assert mem._type instanceof TypeMem;
+ assert size._type instanceof TypeInteger;
_ptr = ptr;
}
+ public Node mem() { return in(1); }
+ public Node size() { return in(2); }
+
+ public Field field(int alias) {
+ for( Field f : _ptr._obj._fields )
+ if( f._alias==alias ) return f;
+ return null;
+ }
+
@Override public String label() {
return "new_" + (_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str());
}
@@ -38,13 +39,7 @@ public class NewNode extends Node implements MultiNode {
@Override
public TypeTuple compute() {
- Field[] fs = _ptr._obj._fields;
- Type[] ts = new Type[fs.length+2];
- ts[0] = Type.CONTROL;
- ts[1] = _ptr;
- for( int i=0; i<fs.length; i++ )
- ts[i+2] = TypeMem.make(fs[i]._alias,fs[i]._type.makeInit()).meet( in(i+2)._type );
- return TypeTuple.make(ts);
+ return TypeTuple.make(_ptr,TypeMem.BOT);
}
@Override
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java
index 688f34c9..7f0415ae 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -16,14 +16,14 @@ import java.util.function.Function;
* The Node class provides common functionality used by all subtypes.
* Subtypes of Node specialize by overriding methods.
*/
-public abstract class Node implements OutNode {
+public abstract class Node implements OutNode, Cloneable {
/**
* Each node has a unique dense Node ID within a compilation context
* The ID is useful for debugging, for using as an offset in a bitvector,
* as well as for computing equality of nodes (to be implemented later).
*/
- public final int _nid;
+ public int _nid;
/**
* Inputs to the node. These are use-def references to Nodes.
@@ -32,7 +32,7 @@ public abstract class Node implements OutNode {
* Ordering is required because e.g. "a/b" is different from "b/a".
* The first input (offset 0) is often a {@link #isCFG} node.
*/
- public final ArrayList<Node> _inputs;
+ public ArrayList<Node> _inputs;
/**
* Outputs reference Nodes that are not null and have this Node as an
@@ -43,7 +43,7 @@ public abstract class Node implements OutNode {
* walked in either direction. These outputs are typically used for
* efficient optimizations but otherwise have no semantics meaning.
*/
- public final ArrayList<Node> _outputs;
+ public ArrayList<Node> _outputs;
/**
@@ -207,6 +207,7 @@ public abstract class Node implements OutNode {
// Error is 'use' does not exist; ok for 'use' to be null.
protected boolean delUse( Node use ) {
Utils.del(_outputs, Utils.find(_outputs, use));
+ moveDepsToWorklist(); // User-count and anti-dependence queries can now change.
return _outputs.isEmpty();
}
@@ -448,6 +449,14 @@ public abstract class Node implements OutNode {
* being added must benefit from this node being peepholed.
*/
Node addDep( Node dep ) {
+ return addDep(dep,false);
+ }
+
+ // A bulk Phi inspects its users' partitions. Def-to-use propagation does
+ // not revisit that producer when an immediate user's partition changes.
+ Node addDepForwards(Node dep) { return addDep(dep,true); }
+
+ private Node addDep(Node dep, boolean forwards) {
// Running peepholes during the big assert cannot have side effects
// like adding dependencies.
if( IterPeeps.midAssert() ) return this;
@@ -455,8 +464,8 @@ public abstract class Node implements OutNode {
if( obs != null ) obs.dep(this, dep);
if( _deps==null ) _deps = new ArrayList<>();
if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list
- if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors
- if( Utils.find(_outputs,dep)!= -1 ) return this;
+ if( !forwards && Utils.find(_inputs,dep) != -1 ) return this;
+ if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this;
_deps.add(dep);
return this;
}
@@ -585,6 +594,20 @@ public abstract class Node implements OutNode {
// Does not need to be implemented in isCFG() nodes.
Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
+ // Exact-class copy preserving operation attributes, with fresh identity
+ // and no edges, dependencies, or GVN membership.
+ public final Node copyEmpty() {
+ Node n;
+ try { n = (Node)clone(); }
+ catch( CloneNotSupportedException e ) { throw new AssertionError(e); }
+ n._nid = UNIQUE_ID++;
+ n._inputs = new ArrayList<>();
+ n._outputs = new ArrayList<>();
+ n._deps = null;
+ n._hash = 0;
+ return n;
+ }
+
// Report any post-optimize errors
String err() { return null; }
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 36c0fe56..f92458f1 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -16,6 +16,10 @@ public class PhiNode extends Node {
public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; }
+ public static PhiNode make(String label, Type type, Node... inputs) {
+ return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs);
+ }
+
@Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
@Override
@@ -65,24 +69,9 @@ public class PhiNode extends Node {
if (live != null)
return live;
- // Pull "down" a common data op. One less op in the world. One more
- // Phi, but Phis do not make code.
- // Phi(op(A,B),op(Q,R),op(X,Y)) becomes
- // op(Phi(A,Q,X), Phi(B,R,Y)).
- Node op = in(1);
- if( op.nIns()==3 && op.in(0)==null && same_op() ) {
- assert !(op instanceof CFGNode);
- Node[] lhss = new Node[nIns()];
- Node[] rhss = new Node[nIns()];
- lhss[0] = rhss[0] = in(0); // Set Region
- for( int i=1; i<nIns(); i++ ) {
- lhss[i] = in(i).in(1);
- rhss[i] = in(i).in(2);
- }
- Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole();
- Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole();
- return op.copy(phi_lhs,phi_rhs);
- }
+ // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...).
+ Node down;
+ if( same_op() && (down=drop_same_op())!=null ) return down;
// If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
if( nIns()==3 ) {
@@ -112,12 +101,72 @@ public class PhiNode extends Node {
}
private boolean same_op() {
- for( int i=2; i<nIns(); i++ )
- if( in(1).getClass() != in(i).getClass() )
- return false;
+ Node op = in(1);
+ if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
+ op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false;
+ // A bulk Phi must split its aliases before factoring precise Stores.
+ if( this instanceof BulkMemPhiNode ) return false;
+ // Bulk splitting currently identifies parallel slices by Region/alias.
+ // Factoring can introduce another slice at a different memory point;
+ // wait until that Region's bulk partitioning has finished.
+ if( op instanceof MemOpNode )
+ for( Node use : region()._outputs )
+ if( use instanceof BulkMemPhiNode ) {
+ use.addDepForwards(this);
+ return false;
+ }
+ Node busy = null;
+ for( int i=1; i<nIns(); i++ ) {
+ Node n = in(i);
+ if( region().in(i).addDep(this)._type==Type.XCONTROL ||
+ op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false;
+ if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false;
+ // Moving a Store must remove the old effect, not duplicate it.
+ // Allow one shared arm: the other arms disappear into the single
+ // factored operation, usually reducing the total operation count.
+ n.addDepForwards(this);
+ if( n.nOuts()>1 ) {
+ for( Node use : n._outputs )
+ if( use!=null && use!=this ) use.addDepForwards(this);
+ if( n instanceof StoreNode || busy!=null ) return false;
+ busy=n;
+ }
+ for( int j=1; j<n.nIns(); j++ )
+ if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false;
+ }
return true;
}
+ private Node drop_same_op() {
+ Node op = in(1), cp = op.copyEmpty();
+ cp._type = null;
+ cp.addDef(null);
+ for( int j=1; j<op.nIns(); j++ ) {
+ Node x = op.in(j);
+ boolean different = false;
+ for( int i=2; i<nIns(); i++ )
+ if( in(i).in(j)!=x ) different=true;
+ if( different ) {
+ Node[] ins = new Node[nIns()];
+ ins[0] = region();
+ Type t = Type.TOP;
+ for( int i=1; i<nIns(); i++ ) {
+ ins[i] = in(i).in(j);
+ t = t.meet(ins[i]._type);
+ }
+ PhiNode phi = j==1 && op instanceof MemOpNode mem
+ ? new MemPhiNode(_label,mem._alias,ins)
+ : PhiNode.make(_label,t.glb(),ins);
+ x = phi.peephole();
+ }
+ cp.addDef(x);
+ }
+ // Factoring must not widen the result (e.g. correlated And operands).
+ if( cp.compute().isa(compute()) ) return cp;
+ cp.kill();
+ return null;
+ }
+
/**
* If only single unique input, return it
*/
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index 9e8b2611..e0bee5f4 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -68,7 +68,7 @@ public class RegionNode extends CFGNode {
if( !hasPhi() && // No Phi users, just a control user
in(1) instanceof CProjNode p1 &&
in(2) instanceof CProjNode p2 &&
- p1.in(0)==p2.in(0) &&
+ p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) &&
p1.in(0) instanceof IfNode iff )
return iff.ctrl();
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index e12f1fef..725c704f 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -4,33 +4,17 @@ import com.seaofnodes.simple.type.*;
import java.util.BitSet;
-/**
- * The Return node has two inputs. The first input is a control node and the
- * second is the data node that supplies the return value.
- * <p>
- * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible.
- * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements.
- * <p>
- * The Return's output is the value from the data node.
- */
+/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */
public class ReturnNode extends CFGNode {
public ReturnNode(Node ctrl, Node data, ScopeNode scope) {
- super(ctrl, data);
- // Add memory slices to Return, so all memory updates are live-on-exit.
- // We look up memory slices by the naming convention that they start with $.
- // We could also use implicit knowledge that all memory projects are at offset >= 2
- if( scope != null ) {
- String[] names = scope.reverseNames();
- for (String name: names) {
- if (!name.equals("$ctrl") && name.startsWith("$"))
- addDef(scope.lookup(name));
- }
- }
+ // A synthetic never-taken loop exit has no source scope.
+ super(ctrl, scope == null ? null : scope.lookup("$mem"), data);
}
public Node ctrl() { return in(0); }
- public Node expr() { return in(1); }
+ public Node mem () { return in(1); }
+ public Node expr() { return in(2); }
@Override
public String label() { return "Return"; }
@@ -44,7 +28,8 @@ public class ReturnNode extends CFGNode {
@Override
public Type compute() {
- return TypeTuple.make(ctrl()._type,expr()._type);
+ // Return exposes the complete memory state.
+ return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type);
}
@Override
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 9c4c4403..957202fd 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -141,7 +141,7 @@ public class ScopeNode extends Node {
// Set real Phi in the loop head
// The phi takes its one input (no backedge yet) from a recursive
// lookup, which might have insert a Phi in every loop nest.
- : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole());
+ : loop.setDef(idx,PhiNode.make(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole());
setDef(idx,old);
}
return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer
@@ -205,7 +205,7 @@ public class ScopeNode extends Node {
if( in(i) != that.in(i) ) // No need for redundant Phis
// If we are in lazy phi mode we need to a lookup
// by name as it will trigger a phi creation
- setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole());
+ setDef(i, PhiNode.make(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole());
that.kill(); // Kill merged scope
IterPeeps.add(r);
return r.unkeep().peephole();
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java
index 5964e19f..eaf00be8 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -1,53 +1,16 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.*;
-import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
-/**
- * The Start node represents the start of the function.
- *
- * Start initially has 1 input (arg) from outside and the initial control.
- * In ch10 we also add mem aliases as structs get defined; each field in struct
- * adds a distinct alias to Start's tuple.
- */
+/** Start supplies control, whole memory, and the argument, in that order. */
public class StartNode extends CFGNode implements MultiNode {
- // This field is NOT final, because the tuple expands as we add memory aliases
- TypeTuple _args;
+ private final TypeTuple _args;
public StartNode(Type[] args) {
super();
- _args = TypeTuple.make(args);
- _type = _args;
- }
-
- /**
- * Creates a projection for each of the struct's fields, using the field alias
- * as the key.
- */
- public void addMemProj( TypeStruct ts, ScopeNode scope) {
- if( ts._fields.length==0 ) return;
- // Expand args type for more memory projections
- Type[] args = _args._types;
- int max = args.length;
- for( Field f : ts._fields )
- max = Math.max(max,f._alias);
- args = Arrays.copyOf(args, max+1);
- // For each of the fields we now add a mem projection.
- for( Field f : ts._fields ) {
- TypeMem tm_decl = TypeMem.make(f._alias,f._type.glb());
- args[f._alias] = tm_decl.dual();
- String name = Parser.memName(f._alias);
- Node n = new ProjNode(this, f._alias, name); // No 'compute' until ScopeNode gets typed
- n._type = args[f._alias];
- scope.define(name, tm_decl, n);
- }
- for (int i = 0; i < args.length; i++)
- if (args[i] == null)
- args[i] = Type.TOP; // We parsed a nested struct/array. Avoid nulls until a caller replaces this alias.
_type = _args = TypeTuple.make(args);
}
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index ec7e0694..5c74fa61 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.type.*;
import java.util.BitSet;
@@ -26,6 +27,10 @@ public class StoreNode extends MemOpNode {
}
// Debugger label
+ @Override boolean canDrop(MemOpNode other, Node dep) {
+ return super.canDrop(other,dep) && _init==((StoreNode)other)._init;
+ }
+
@Override public String label() { return "st_"+mlabel(); }
@Override public boolean isMem() { return true; }
@@ -38,21 +43,22 @@ public class StoreNode extends MemOpNode {
@Override
public Type compute() {
- Type val = val()._type;
- TypeMem mem = (TypeMem)mem()._type; // Invariant
- Type t = mem._alias==_alias
- ? val.meet(mem._t) // Meet into existing memory
- : Type.BOTTOM;
+ Type t = val()._type.meet(MemMergeNode.contents(mem(),_alias,this));
return TypeMem.make(_alias,t);
}
@Override
public Node idealize() {
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,IterPeeps.add(merge.alias(_alias)));
+ return this;
+ }
+
// Simple store-after-store on same address. Should pick up the
// required init-store being stomped by a first user store.
if( mem() instanceof StoreNode st &&
- ptr()==st.ptr() && // Must check same object
+ ptr()==st.ptr() && _alias==st._alias && // Must check same object
off()==st.off() && // And same offset
ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
// Must have exactly one use of "this" or you get weird
diff --git a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
index f5efd3e8..99920d24 100644
--- a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
+++ b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -54,7 +54,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> {
return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
if( n instanceof RegionNode && i == 0 ) return Role.ASSOC;
if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL;
- if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM;
+ if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof NewNode) ) return Role.MEM;
Node def = n.in(i);
if( def != null && isMem(def) ) return Role.MEM;
return Role.DATA;
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java
index a3109ff7..14d31245 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java
@@ -84,7 +84,10 @@ public class Chapter04Test {
StopNode stop = parser.parse();
ReturnNode ret = stop.ret();
assertTrue(ret.in(0) instanceof CProjNode);
- assertTrue(ret.in(1) instanceof ProjNode);
+ assertTrue(ret.in(1) instanceof ProjNode);
+ assertEquals(1, ((ProjNode)ret.in(1))._idx);
+ assertTrue(ret.in(2) instanceof ProjNode);
+ assertEquals(2, ((ProjNode)ret.in(2))._idx);
}
@Test
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java
index 9d154413..ca7bb482 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java
@@ -22,7 +22,7 @@ else {
return a;""");
StopNode ret = parser.parse().iterate();
- assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString());
+ assertEquals("return Phi(Region21,(arg+2),(arg-3));", ret.toString());
}
@Test
@@ -37,7 +37,7 @@ if (arg == 1) {
}
return c;""", TypeInteger.BOT);
StopNode ret = parser.parse().iterate();
- assertEquals("return Phi(Region19,4,3);", ret.toString());
+ assertEquals("return Phi(Region20,4,3);", ret.toString());
}
@Test
@@ -65,7 +65,7 @@ else
b=a+1;
return a+b;""");
StopNode ret = parser.parse().iterate();
- assertEquals("return ((arg*2)+Phi(Region23,2,3));", ret.toString());
+ assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString());
}
@Test
@@ -80,7 +80,7 @@ else
a=b+1;
return a+b;""");
StopNode ret = parser.parse().iterate();
- assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString());
+ assertEquals("return ((Phi(Region35,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString());
}
@Test
@@ -100,7 +100,7 @@ else
return a;
""", TypeInteger.BOT);
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString());
+ assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,4,5));", stop.toString());
}
@Test
@@ -116,7 +116,7 @@ if( arg==0 )
return arg+a+b;
""", TypeInteger.BOT);
StopNode stop = parser.parse();
- assertEquals("return ((arg+Phi(Region17,1,0))+Phi(Region33,2,0));", stop.toString());
+ assertEquals("return ((arg+Phi(Region18,1,0))+Phi(Region34,2,0));", stop.toString());
}
@Test
@@ -130,7 +130,7 @@ if( arg==1 )
}
return a;""");
StopNode ret = parser.parse().iterate();
- assertEquals("return (arg==Phi(Region19,3,2));", ret.toString());
+ assertEquals("return (arg==Phi(Region20,3,2));", ret.toString());
}
@Test
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java
index ae37a867..d2c46df5 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java
@@ -65,7 +65,7 @@ else
b=5;
return b;""", TypeInteger.BOT);
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region39,42,5);", stop.toString());
+ assertEquals("return Phi(Region40,42,5);", stop.toString());
}
@Test
@@ -84,7 +84,7 @@ else
b=5;
return b;""", TypeInteger.BOT);
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region32,2,5);", stop.toString());
+ assertEquals("return Phi(Region33,2,5);", stop.toString());
}
@Test
@@ -144,7 +144,7 @@ else
return a;
""", TypeInteger.BOT);
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString());
+ assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString());
}
@Test
@@ -200,7 +200,7 @@ if( arg ) {
return a+b;
""");
StopNode ret = parser.parse().iterate();
- assertEquals("return Phi(Region28,4,1);", ret.toString());
+ assertEquals("return Phi(Region29,4,1);", ret.toString());
}
@@ -251,7 +251,7 @@ if( arg ) {
return a+b+c;
""");
StopNode ret = parser.parse().iterate();
- assertEquals("return (Phi(Region40,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString());
+ assertEquals("return (Phi(Region41,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString());
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java
index 29aa7eaf..443b0d17 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java
@@ -17,7 +17,7 @@ public class Chapter07Test {
return arg;
""");
StopNode stop = parser.parse();
- assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString());
+ assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -34,7 +34,7 @@ if(arg){}else{
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString());
+ assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString());
}
@Test
@@ -54,7 +54,7 @@ while(i < arg) {
return sum;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString());
+ assertEquals("return Phi(Loop11,0,Phi(Loop23,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString());
}
@Test
@@ -70,7 +70,7 @@ while(a < 10) {
return b;
""");
StopNode stop = parser.parse();
- assertEquals("return Phi(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString());
+ assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -89,7 +89,7 @@ while(a < 10) {
return b;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString());
+ assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -106,7 +106,7 @@ while(a < 10) {
return a;
""");
StopNode stop = parser.parse();
- assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString());
+ assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -122,7 +122,7 @@ while(a < 10) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString());
+ assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -135,7 +135,7 @@ while(arg) a = 2;
return a;
""");
StopNode stop = parser.parse();
- assertEquals("return Phi(Loop9,1,2);", stop.toString());
+ assertEquals("return Phi(Loop10,1,2);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -148,7 +148,7 @@ while(arg) a = 2;
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,1,2);", stop.toString());
+ assertEquals("return Phi(Loop10,1,2);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -164,7 +164,7 @@ while(a < 10) {
return a;
""");
StopNode stop = parser.parse();
- assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString());
+ assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -180,7 +180,7 @@ while(a < 10) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString());
+ assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -197,7 +197,7 @@ while(a < 10) {
return a;
""");
StopNode stop = parser.parse();
- assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString());
+ assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -214,7 +214,7 @@ while(a < 10) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString());
+ assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java
index 7760ae09..89ce8313 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java
@@ -23,7 +23,7 @@ while(arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString());
+ assertEquals("return Phi(Region38,Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add),Add);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof RegionNode);
Assert.assertEquals(5L, Evaluator.evaluate(stop, 1));
Assert.assertEquals(10L, Evaluator.evaluate(stop, 6));
@@ -46,7 +46,7 @@ while(arg < 10) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString());
+ assertEquals("return Phi(Loop10,1,Phi(Region44,Phi_a,(Phi_a+1)));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -65,7 +65,7 @@ while(arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region35,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString());
+ assertEquals("return Phi(Region36,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof RegionNode);
}
@@ -81,7 +81,7 @@ while(arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString());
+ assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof RegionNode);
}
@@ -100,7 +100,7 @@ while(arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString());
+ assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -117,7 +117,7 @@ while(arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString());
+ assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode);
}
@@ -185,7 +185,7 @@ while(arg < 10) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString());
+ assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof RegionNode);
}
@@ -201,7 +201,7 @@ while(1) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString());
+ assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString());
assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 );
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java
index 64bbf245..49ee75a2 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java
@@ -69,7 +69,7 @@ while (arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString());
+ assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString());
Assert.assertEquals(11L, Evaluator.evaluate(stop, 1));
}
@@ -86,7 +86,7 @@ while (arg < 10) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString());
+ assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString());
Assert.assertEquals(13L, Evaluator.evaluate(stop, 1));
}
@@ -115,7 +115,7 @@ while (arg) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop16,arg,(Phi_arg+1));", stop.toString());
+ assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString());
}
@Test
@@ -185,7 +185,7 @@ while(arg) {
return a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString());
+ assertEquals("return Phi(Loop12,0,(-(Phi_a+3)));", stop.toString());
}
@Test
@@ -283,7 +283,7 @@ return v1+v0;
public void testFuzz8() {
Parser parser = new Parser("while(arg) arg = arg - 1; return arg;");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop8,arg,(Phi_arg-1));", stop.toString());
+ assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString());
}
@Test
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index bd56a944..a5e3a9e0 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -1,6 +1,11 @@
package com.seaofnodes.simple;
+import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.node.StopNode;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.evaluator.Evaluator;
+import java.util.BitSet;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -161,7 +166,7 @@ while (arg) {
return bar.a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString());
+ assertEquals("return Phi(Loop15,0,(Phi_a+2));", stop.toString());
}
@Test
@@ -213,7 +218,7 @@ if( bar ) bar.a = 1;
return bar;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region33,(*void)Phi(Region20,null,Bar),null);", stop.toString());
+ assertEquals("return Phi(Region36,(*void)Phi(Region22,null,Bar),null);", stop.toString());
}
@Test
@@ -229,7 +234,7 @@ else bar.a = 1;
return rez;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region39,4,3);", stop.toString());
+ assertEquals("return Phi(Region42,4,3);", stop.toString());
}
@Test
@@ -283,7 +288,7 @@ while( i.x < i.len ) {
return sum;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop18,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString());
+ assertEquals("return Phi(Loop19,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString());
}
@@ -304,7 +309,7 @@ while(arg) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,s0,Phi(Region37,s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop15,s0,Phi(Region42,s0,Phi_ret));", stop.toString());
}
@Test
@@ -322,7 +327,7 @@ while(arg) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop17,s0,Phi(Region38,s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop21,s0,Phi(Region43,s0,Phi_ret));", stop.toString());
}
@@ -339,7 +344,7 @@ while(arg < 10) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,s0,Phi(Region35,s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop15,s0,Phi(Region39,s0,Phi_ret));", stop.toString());
}
@Test
@@ -457,4 +462,120 @@ return 0;
assertEquals("return 0;", stop.toString());
}
+ @Test public void testReadBeforeStores() {
+ var stop = new Parser("""
+ struct S { int x; int y; }
+ S a = new S; S b = new S;
+ a.x = 11; b.x = 22;
+ S p = a; if (arg) p = b;
+ int before = p.x;
+ a.y = 55; b.y = 66;
+ a.x = 33; b.x = 44;
+ return before*100 + p.x;
+ """).parse().iterate();
+ assertEquals(1133L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0));
+ assertEquals(2244L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1));
+ }
+
+ static final String NESTED_MEMORY = """
+ struct S { int x; int y; int z; }
+ S s = new S;
+ s.x = 5; s.y = 7; s.z = 11;
+ while (arg > 0) {
+ s.x = s.x + 1;
+ int j = 3;
+ while (j > 0) {
+ j = j - 1;
+ if (j == 1) continue;
+ s.y = s.y + arg;
+ if (arg == 2) break;
+ s.z = s.z + 1;
+ }
+ arg = arg - 1;
+ }
+ return s;
+ """;
+
+ @Test public void testMemoryAcrossNestedLoops() {
+ var stop = new Parser(NESTED_MEMORY).parse().iterate();
+ for (int arg=0; arg<7; arg++) {
+ var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj)
+ com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg);
+ org.junit.Assert.assertArrayEquals(new Object[] {
+ 5L+arg, 7L+arg*(arg+1)-(arg>=2 ? 2 : 0), 11L+2*arg-(arg>=2 ? 2 : 0)
+ }, obj.fields());
+ }
+ }
+
+ @Test public void testMemoryAtEarlyReturns() {
+ var stop = new Parser("""
+ struct S { int x; int y; int z; }
+ S s = new S;
+ s.x = 3; s.y = 5; s.z = 7;
+ if (arg) { s.x = 11; s.z = 13; return s; }
+ s.y = 17;
+ return s;
+ """).parse().iterate();
+ for (int arg=0; arg<2; arg++) {
+ var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj)
+ com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg);
+ org.junit.Assert.assertArrayEquals(arg==0 ? new Object[]{3L,17L,7L} : new Object[]{11L,5L,13L}, obj.fields());
+ }
+ }
+
+ @Test public void testKeepLoadsAtMerge() {
+ StopNode stop = new Parser("""
+ struct S { int x; }
+ S a = new S; S b = new S;
+ a.x = arg; b.x = arg+1;
+ S p = a; S q = b;
+ if (arg<0) { p=b; q=a; }
+ int v;
+ if (arg>1) v=p.x; else v=q.x;
+ return v;
+ """).parse().iterate();
+ // The one-step safety check stops at the memory aggregate.
+ assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
+ assertEquals(-1L,Evaluator.evaluate(stop,-1));
+ assertEquals(1L,Evaluator.evaluate(stop,0));
+ assertEquals(3L,Evaluator.evaluate(stop,3));
+ }
+
+ @Test public void testDropStores() {
+ StopNode stop = new Parser("""
+ struct S { int x; }
+ S s = new S;
+ if (arg) s.x=arg+1; else s.x=arg+2;
+ return s;
+ """).parse().iterate();
+ assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet()));
+ assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]);
+ assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]);
+ }
+
+ @Test public void testKeepReadsBeforeWrites() {
+ StopNode stop = new Parser("""
+ struct S { int x; int y; }
+ S a = new S; S b = new S;
+ a.x=arg; b.x=arg+1;
+ S p=a; S q=b;
+ if (arg<0) { p=b; q=a; }
+ int v;
+ if (arg>1) { v=p.x; p.x=41; p.y=5; }
+ else { v=q.x; q.x=42; }
+ return v;
+ """).parse().iterate();
+ assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
+ assertEquals(-1L,Evaluator.evaluate(stop,-1));
+ assertEquals(1L,Evaluator.evaluate(stop,0));
+ assertEquals(3L,Evaluator.evaluate(stop,3));
+ }
+
+ private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) {
+ if (n==null || seen.get(n._nid)) return 0;
+ seen.set(n._nid);
+ int cnt=kind.isInstance(n) ? 1 : 0;
+ for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen);
+ return cnt;
+ }
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java
new file mode 100644
index 00000000..6f094cfb
--- /dev/null
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java
@@ -0,0 +1,64 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.evaluator.Evaluator;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter10bTest {
+ @Test public void testIndependentField() {
+ var stop = new Parser("""
+ struct S { int x; int y; }
+ S s = new S;
+ s.x = 42;
+ while (arg > 0) { s.y = s.y + arg; arg = arg - 1; }
+ return s.x;
+ """).parse().iterate();
+ assertEquals("return 42;",stop.toString());
+ assertEquals(42L,Evaluator.evaluate(stop,5));
+ assertNull(stop.walk(n -> n instanceof LoadNode ? n : null));
+ }
+
+ @Test public void testParallelMemoryPhis() {
+ var stop = new Parser("""
+ struct S { int x; int y; int z; }
+ S a = new S; S b = new S;
+ S p = a; if (arg) p = b;
+ while (arg > 0) {
+ a.x = p.x + arg;
+ a.y = p.y + 2;
+ a.z = p.z + 3;
+ arg = arg - 1;
+ }
+ return a;
+ """).parse().iterate();
+ int[] phis = {0};
+ stop.walk(n -> {
+ if (n instanceof MemPhiNode) 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<m.nIns() && m.in(a)!=null);
+ assertFalse(n instanceof PhiNode && n.isMem() &&
+ !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode));
+ return null;
+ });
+ assertTrue(phis[0]>=3);
+ assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields());
+ }
+
+ @Test public void testLateSliceWorklist() throws ReflectiveOperationException {
+ // Seed 97 exposed a store selecting a bulk predecessor without queuing
+ // it. Keep both the worklist assertion and the final heap check.
+ for (int seed : new int[]{0,97,123,456}) {
+ var parser = new Parser(Chapter10Test.NESTED_MEMORY);
+ var field = IterPeeps.class.getDeclaredField("WORK");
+ field.setAccessible(true);
+ Object work = field.get(null);
+ field = work.getClass().getDeclaredField("_R");
+ field.setAccessible(true);
+ ((java.util.Random)field.get(work)).setSeed(seed);
+ var stop = parser.parse().iterate();
+ assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields());
+ }
+ }
+}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java
index 76581308..3421340f 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java
@@ -325,7 +325,7 @@ while( prime <= arg ) {
return primeCount;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region87,Phi_primeCount,Phi(Region81,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString());
+ assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString());
assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below
assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2
assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3
@@ -370,7 +370,7 @@ if (arg) {
return i;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region40,.f,0);", stop.toString());
+ assertEquals("return Phi(Region46,.f,0);", stop.toString());
}
@Test
@@ -477,7 +477,7 @@ while(arg) {
return arg;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop17,arg,Phi(Region42,.f,0));", stop.toString());
+ assertEquals("return Phi(Loop21,arg,Phi(Region48,.f,0));", stop.toString());
}
@Test
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 4eaf81df..998053ec 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -64,7 +64,7 @@ while( 1 ) {
return guess;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString());
+ assertEquals("return Phi(Loop10,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString());
assertEquals(3.0, Evaluator.evaluate(stop, 9));
assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2));
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java
index daf981bd..3313e731 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java
@@ -35,7 +35,7 @@ while( arg ) {
return head.next.i;
""");
try { parser.parse().iterate(); fail(); }
- catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); }
}
@Test
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 048cb93a..d64423de 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -59,7 +59,7 @@ while( b ) b = b + 456;// Truncate
return b;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop9,123,((Phi_b+456)&255));", stop.toString());
+ assertEquals("return Phi(Loop10,123,((Phi_b+456)&255));", stop.toString());
}
@Test
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 6ec7cc1a..8f9d2d14 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -12,6 +12,70 @@ import static org.junit.Assert.fail;
public class Chapter15Test {
+ @Test public void testPartialAllocationMemory() {
+ StopNode stop = new Parser("""
+ struct Other { int a; int b; int c; }
+ struct S { int x; flt y; S? next; }
+ Other other = new Other;
+ other.c = 7;
+ S a = new S;
+ a.x = arg;
+ a.y = 1.5;
+ S b = new S;
+ // S's aliases are global identities, not tuple or input positions.
+ // Bypass b's zeroing for a, preserving the unrelated Other slice.
+ return a.x + a.y + b.x + b.y + other.c;
+ """).parse().iterate();
+ assertEquals(8.5, Evaluator.evaluate(stop,0));
+ assertEquals(12.5, Evaluator.evaluate(stop,4));
+
+ stop = new Parser("""
+ struct S { int x; flt y; S? next; }
+ S a = new S; S b = new S;
+ S p = a; if (arg) p = b;
+ // The memory result carries distinct int, float and pointer facts.
+ if (p.next != null) return 99;
+ return p.x + p.y;
+ """).parse().iterate();
+ assertEquals("return 0.0;",stop.toString());
+ assertEquals(0.0,Evaluator.evaluate(stop,1));
+ }
+
+ @Test public void testReadAcrossAllocation() {
+ StopNode stop = new Parser("""
+ struct S { int x; }
+ S a = new S; S b = new S;
+ a.x = 11; b.x = 22;
+ S p = a; if (arg) p = b;
+ int before = p.x;
+ S c = new S;
+ a.x = 33; b.x = 44;
+ // The partial input to c must not hide the read/write ordering.
+ // p can alias either object, so node inequality cannot bypass New.
+ return before*100 + p.x + c.x;
+ """).parse().iterate();
+ assertEquals(1133L,Evaluator.evaluate(stop,0));
+ assertEquals(2244L,Evaluator.evaluate(stop,1));
+ }
+
+ @Test public void testAllocationInLoop() {
+ StopNode stop = new Parser("""
+ struct S { int x; flt y; S? next; }
+ S old = new S; old.x = 7;
+ S last = old;
+ while (arg > 0) {
+ S fresh = new S;
+ fresh.x = last.x + 1;
+ fresh.next = last;
+ last = fresh;
+ arg = arg - 1;
+ }
+ return old.x*100 + last.x;
+ """).parse().iterate();
+ assertEquals(707L,Evaluator.evaluate(stop,0));
+ assertEquals(712L,Evaluator.evaluate(stop,5));
+ }
+
@Test
public void testJig() {
Parser parser = new Parser("""
@@ -34,7 +98,7 @@ if (arg) {
return b[0] + b[1];
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region69,1,0);", stop.toString());
+ assertEquals("return Phi(Region72,1,0);", stop.toString());
assertEquals(0L, Evaluator.evaluate(stop, 0));
assertEquals(1L, Evaluator.evaluate(stop, 1));
}
@@ -151,7 +215,7 @@ else {
return rez;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region118,1.2,Phi(Region115,2.3,.y));", stop.toString());
+ assertEquals("return Phi(Region122,1.2,Phi(Region119,2.3,.y));", stop.toString());
assertEquals(3.14, Evaluator.evaluate(stop, 0));
}
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
index 34921b94..9eed23de 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
@@ -187,7 +187,7 @@ public class Evaluator {
Object[] body=null;
int num;
if( type.isAry() ) {
- long sz = (Long)val(alloc.in(1));
+ long sz = (Long)val(alloc.size());
long n = offToIdx(sz, type);
if( n < 0 )
throw new NegativeArraySizeException(""+n);
@@ -215,11 +215,7 @@ public class Evaluator {
assert c instanceof TypeMemPtr;
}
}
- Object[] mems = new Object[type._fields.length+2];
- // mems[0] is control
- mems[1] = new Obj(type,body); // the ref
- // mems[2+...] are memory aliases
- return mems;
+ return new Object[]{new Obj(type,body),MEMORY};
}
private Object load(LoadNode load) {
@@ -312,6 +308,7 @@ public class Evaluator {
case ToFloatNode cast -> (double)vall(cast.in(1));
case LoadNode load -> load(load);
case StoreNode store -> store(store);
+ case MemMergeNode merge -> MEMORY;
case NewNode alloc -> alloc(alloc);
case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx];
case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx];
@@ -325,8 +322,8 @@ public class Evaluator {
public Object evaluate(long parameter, int loops) {
if (start == null) return Status.TIMEOUT;
var s = new Object[start.compute()._types.length];
- s[1] = parameter;
- for(int i=2;i<s.length;i++) s[i] = MEMORY;
+ s[1] = MEMORY;
+ s[2] = parameter;
values[start._nid] = s;
int i=0;
Scheduler.Block block = this.startBlock;
@@ -337,7 +334,7 @@ public class Evaluator {
case null:
return Status.FALLTHROUGH;
case ReturnNode ret:
- return val(ret.in(1));
+ return val(ret.expr());
case IfNode ifn:
block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1];
if (block == null) return Status.FALLTHROUGH;
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
index 59c15d3f..125f2f5e 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
@@ -31,6 +31,8 @@ import java.util.*;
*/
public class Scheduler {
+ private final HashMap<Node,ArrayList<NodeData>> readers = new HashMap<>();
+
/**
* A basic block with a schedule of the containing nodes.
*
@@ -252,7 +254,7 @@ public class Scheduler {
// but they did not refine the placement
// so do that now.
var mem = l.in(1);
- for(var out : mem._outputs) {
+ for(var out : l.antiDeps()) {
if (out instanceof PhiNode p) {
var r = p.in(0);
for (int i = 1; i < p.nIns(); i++) {
@@ -270,13 +272,9 @@ public class Scheduler {
for(var in:data.node._inputs) {
if (in!=null) update(d(in), data.block);
}
- if (data.node instanceof StoreNode s) {
- // Store nodes have anti-deps to load nodes.
- // So decrease the uses of these loads when the store is placed.
- for (var out: s.in(1)._outputs) {
- if (out instanceof LoadNode) od(out).ifPresent(this::decUsers);
- }
- }
+ var loads = readers.get(data.node);
+ if (loads != null)
+ for (var load : loads) decUsers(load);
}
// Now all nodes should be placed and have a block assigned
@@ -442,7 +440,6 @@ public class Scheduler {
private void doMarkAlive(Node node) {
var cfgQueue = new Stack<NodeData>();
var dataQueue = new Stack<NodeData>();
- var mem = new Stack<NodeData>();
markAlive(cfgQueue, node, true);
// Mark all CFG nodes.
while (!cfgQueue.isEmpty()) {
@@ -467,16 +464,16 @@ public class Scheduler {
} else {
for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) markAlive(dataQueue, in, false);
}
- if (node instanceof StoreNode) mem.push(data);
- }
- // Handle store nodes and increase load with an anti-dep to the store.
- while (!mem.isEmpty()) {
- var data = mem.pop();
- node = data.node;
- for(var out:node.in(1)._outputs) {
- if (out instanceof LoadNode) od(out).ifPresent(d->d.users++);
- }
}
+ // Writes, including New behind an aggregate, schedule before their
+ // anti-dependent reads in this reverse scheduling pass.
+ for (var load : data.values())
+ if (load.node instanceof LoadNode l)
+ for (var use : l.antiDeps())
+ if ((use instanceof StoreNode || use instanceof NewNode) && data.containsKey(use)) {
+ load.users++;
+ readers.computeIfAbsent(use,k->new ArrayList<>()).add(load);
+ }
}
/**