summaryrefslogtreecommitdiff
path: root/chapter23/src/main/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-30 19:25:04 -0700
committerCliff Click <cliffc@acm.org>2026-09-30 19:25:04 -0700
commitc4dcf6f751b35c7a2a3b46fa2d69b95d37d082a6 (patch)
treee5ad31706399578310cfbb3b0387a77cf83372ba /chapter23/src/main/java/com
parenta996996a1ad776089a4cb9cbf7744f5f4e8dde6d (diff)
downloadSimple-c4dcf6f751b35c7a2a3b46fa2d69b95d37d082a6.tar.gz
Simple-c4dcf6f751b35c7a2a3b46fa2d69b95d37d082a6.zip
Finish forward-port mem ops thru ch25
Diffstat (limited to 'chapter23/src/main/java/com')
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/Parser.java111
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java55
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java240
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java197
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java11
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java210
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java35
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java44
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java66
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/Node.java13
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java113
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java13
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java90
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java11
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java35
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java5
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java7
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java2
21 files changed, 743 insertions, 521 deletions
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java
index 5b3f4d1a..2dc83905 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter23/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 the sys import
_lexer = new Lexer(com.seaofnodes.simple.sys.SYS);
@@ -214,7 +214,7 @@ public class Parser {
// Build a multi-exit return point for all function returns
RegionNode r = new RegionNode( 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);
@@ -227,11 +227,8 @@ public class Parser {
// external calls.
_scope.push(new Kind.Func());
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<ids.length; i++ ) {
Type t = sig.arg(i);
@@ -246,7 +243,7 @@ public class Parser {
// Last expression is the return except for the top-level main
if( ctrl()._type==Type.CONTROL )
if( fun.sig() != _code._main )
- fun.addReturn(ctrl(), _scope.mem().merge(), last);
+ fun.addReturn(ctrl(), _scope.mem(), last);
else {
fun.setDef(1,XCTRL); // Kill default main
_code.addAll(fun._outputs);
@@ -627,9 +624,8 @@ public class Parser {
*/
private Node parseReturn() {
var expr = require(parseAsgn(), ";");
- // Need default memory, since it can be lazy, need to force
- // a non-lazy Phi
- _fun.addReturn(ctrl(), _scope.mem().merge(), expr);
+ // Force the lazy whole-memory Phi before closing this exit.
+ _fun.addReturn(ctrl(), _scope.mem(), expr);
ctrl(XCTRL); // Kill control
return expr;
}
@@ -1380,31 +1376,31 @@ public class Parser {
Field[] fs = obj._fields;
if( fs==null )
throw error("Unknown struct type '" + obj._name + "'");
- int len = fs.length;
- Node[] ns = new Node[2+len];
- ns[0] = ctrl(); // Control in slot 0
- // Total allocated length in bytes
- ns[1] = size;
- // Memory aliases for every field
- for( int i = 0; i < len; i++ )
- if( !fs[i]._one )
- ns[2+i] = memAlias(fs[i]._alias);
- Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep();
- for( int i = 0; i < len; i++ )
- if( !fs[i]._one )
- memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole());
- Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep();
-
- // Initial nonzero values for every field
- for( int i = 0; i < len; i++ ) {
- Node val = init.get( i + idx );
+ Node prior = mem().keep();
+ MemMergeNode input = new MemMergeNode((Node)null);
+ for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior);
+ // Allocation zeroes its fields; nonzero initializers become explicit Stores.
+ Node[] ns = new Node[3];
+ ns[0] = ctrl();
+ ns[1] = input.peephole();
+ ns[2] = size;
+ Node nnn = new NewNode(TypeMemPtr.make(obj),ns).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 : fs ) if( !f._one ) after.alias(f._alias,out);
+ mem(after.peephole());
+ out.unkeep();
+ nnn.unkeep();
+ prior.unkeep();
+ for( int i=0; i<fs.length; i++ ) {
+ Node val = init.get(idx+i);
if( !fs[i]._one && val._type != val._type.makeZero() ) {
- Node mem = memAlias(fs[i]._alias);
- Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._t,mem,ptr,off(obj,fs[i]._fname),val,true).peephole();
- memAlias(fs[i]._alias,st);
+ Field f = fs[i];
+ Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole();
+ mem(new MemMergeNode(mem(),f._alias,st).peephole());
}
}
-
return ptr.unkeep();
}
@@ -1430,13 +1426,18 @@ public class Parser {
return con(str);
}
- // 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.mem(alias ); }
- private void memAlias(int alias, Node st) { _scope.mem(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.mem(); }
+ private void mem(Node n) { _scope.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(loc(),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; this can be a field expression, an array
@@ -1494,6 +1495,7 @@ public class Parser {
if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
+ expr.keep();
// Field offset; fixed for structs, computed for arrays
Node off = (name.equals("[]") // If field is an array body
// Array index math
@@ -1506,21 +1508,14 @@ public class Parser {
Node val = parseAsgn().keep();
Node lift = liftExpr( val, tf, f._final, false );
- Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, 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());
+ store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null);
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
- // Keep expr for possible store update
- expr.keep();
// Load field
Node load = f._one && INITS.containsKey( base._name )
? INITS.get(base._name).in(base.find(name))
- : new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr, off);
+ : new LoadNode(loc(),name, f._alias, tf, mem(), expr, 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.
@@ -1530,13 +1525,8 @@ public class Parser {
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
char ch = _lexer.matchOperAssign();
if( ch!=0 ) {
- Node op = opAssign(ch,load, tf );
- Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off.unkeep(), op, 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, peep(st));
+ Node op = opAssign(ch,load,tf);
+ store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null);
load = postfix(ch) ? load.unkeep() : op;
// And use the original loaded value as the result
return load;
@@ -1647,7 +1637,7 @@ public class Parser {
}
// Control & memory after parsing args
args.set(0,ctrl().keep());
- args.set(1,_scope.mem().merge().keep());
+ args.set(1,_scope.mem().keep());
args.push(fcn); // Function pointer
// Unkeep them all
for( Node arg : args )
@@ -1668,11 +1658,8 @@ public class Parser {
call.peephole(); // Rerun peeps after CallEnd, allows early inlining
// Control from CallEnd
ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
- // Memory from CallEnd
- MemMergeNode mem = new MemMergeNode(true);
- mem.addDef(null); // Alias#0
- mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
- _scope.mem(mem);
+ // A call may update any alias, so its memory result is the new whole state.
+ _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
// Call result
return new ProjNode(cend,2,"#2").peephole();
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index b8a8d2b3..1d802cae 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -50,7 +50,7 @@ public abstract class GlobalCodeMotion {
var cons = new ArrayList<Node>();
for( Node con : start._outputs )
if( con!=null && !(con instanceof CFGNode) &&
- (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) {
+ (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) {
globals.put(con,true);
cons.add(con.keep()); // Preserve original inputs while rewiring users.
}
@@ -172,16 +172,8 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof MemOpNode ld && ld._isLoad )
- for( Node memuse : ld.mem()._outputs )
- if( late[memuse._nid]==null &&
- // New makes new memory, never crushes load memory
- !(memuse instanceof NewNode) &&
- // Load-use directly defines memory
- (memuse._type instanceof TypeMem ||
- // Load-use directly defines memory
- memuse instanceof CallNode ||
- // 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
@@ -193,9 +185,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 MemOpNode ld && ld._isLoad && late[ld._nid]==null )
- work.push(ld);
+ wakeLoads(def,late,work,new BitSet());
}
if( n instanceof LoopNode loop )
for( Node phi : loop._outputs )
@@ -255,23 +245,32 @@ 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 MemOpNode ld && ld._isLoad && 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, MemOpNode 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 MemOpNode st:
- if( !st._isLoad && load._alias == st._alias ) {
- assert late[mem._nid] != null;
- lca = anti_dep(load,late[mem._nid],lca,st,anti);
- }
- break; // Loads do not cause anti-deps on other loads
- case CallNode call:
- assert late[call._nid]!=null;
- lca = anti_dep(load,late[call._nid],lca,call,anti);
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],lca,st,anti);
+ break;
+ case CallNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],lca,st,anti);
break;
case PhiNode phi:
// Repeat anti-dep for matching Phi inputs.
@@ -280,10 +279,10 @@ public abstract class GlobalCodeMotion {
if( phi.in(i)==load.mem() )
lca = anti_dep(load,phi.region().cfg(i),lca,null,anti);
break;
- case NewNode st: break;
- case ReturnNode ret: break; // Load must already be ahead of Return
- case MemMergeNode ret: break; // Mem uses now on ScopeMin
- case NeverNode never: break;
+ case NewNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],lca,st,anti);
+ break;
default: throw Utils.TODO();
}
}
@@ -295,7 +294,7 @@ public abstract class GlobalCodeMotion {
// Stores are already placed. Constrain the load at that block only.
if( anti[stblk._nid]==load._nid ) {
lca = stblk.domLCA(lca,null);
- if( lca==stblk && st!=null && st._inputs.find(load) == -1 )
+ if( lca==stblk && st!=null && !(st instanceof CallNode) && st._inputs.find(load) == -1 )
st.addDef(load); // Same-block load must precede the store.
}
return lca;
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..7d4f1fb8
--- /dev/null
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,240 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+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); }
+
+ public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; }
+
+ @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,CodeGen.CODE.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 ParmNode parm -> 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.
+ addDepForwards(use);
+ 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 || use instanceof CallNode || use instanceof ParmNode
+ : "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++ )
+ CodeGen.CODE.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;
+ CodeGen.CODE.add(bulk);
+ CodeGen.CODE.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(CodeGen.CODE.add(preciseInput(in(i),alias)));
+ return CodeGen.CODE.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/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index e752c9ce..8b526be2 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -1,7 +1,7 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
@@ -24,6 +24,25 @@ public class LoadNode extends MemOpNode {
super(loc, name, alias, true, glb, mem, ptr, off);
}
+ @Override boolean canDrop(MemOpNode other, Node dep) {
+ return super.canDrop(other,dep) && !clobbered(dep);
+ }
+
+ // Check only immediate memory users. Writers clobber memory; Phis and
+ // aggregates might lead to a clobber, so stop rather than search further.
+ private boolean clobbered(Node dep) {
+ Node mem = mem();
+ dep.addDepForwards(mem);
+ for( Node use : mem._outputs ) {
+ if( use==null ) continue;
+ dep.addDepForwards(use);
+ if( use instanceof StoreNode || use instanceof PhiNode ||
+ use instanceof MemMergeNode ||
+ use instanceof NewNode || use instanceof CallNode ) return true;
+ }
+ return false;
+ }
+
// Debugger label
@Override public String label() { return "ld_"+mlabel(); }
@Override
@@ -42,7 +61,7 @@ public class LoadNode extends MemOpNode {
t = ary.elem(); // TODO: if offset is known, can peek the constant
}
// Lift from declared type and memory input
- t = t.join(_declaredType).join(mem._t);
+ t = t.join(_declaredType).join(MemMergeNode.contents(mem(),_alias,this));
if( _declaredType.isFinal() )
t = t.makeRO(); // Deep final applied
return t;
@@ -50,35 +69,19 @@ public class LoadNode extends MemOpNode {
@Override
public Node idealize() {
- Node ptr = ptr();
- Node mem = mem();
-
- if( mem instanceof CastNode cast ) {
+ if( mem() instanceof CastNode cast ) {
setDef(1,cast.in(1));
return this;
}
-
- // Simple Load-after-Store on same address.
- if( mem instanceof StoreNode st &&
- ptr == st.ptr() && off() == st.off() ) { // Must check same object
- assert _name.equals(st._name); // Equiv class aliasing is perfect
- return extend(st.val());
- }
-
- // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( mem instanceof MemMergeNode mem2 ) {
- Node memA = mem2.alias(_alias);
- for( Node ld : memA._outputs )
- if( ld instanceof LoadNode )
- CodeGen.CODE.add(ld);
- setDef(1,memA);
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
return this;
}
+ Node ptr = ptr();
+ Node mem = mem();
- // 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
- return zero(nnn); // Load zero from new
+ Node win = find(mem,ptr,null,null);
+ if( win!=null ) return folded(win);
// Uplift control to a prior dominating load.
for( Node memuse : mem._outputs )
@@ -87,50 +90,7 @@ public class LoadNode extends MemOpNode {
cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier
return ld;
- // Load-after-Store on same address, but bypassing provably unrelated
- // stores. This is a more complex superset of the above two peeps.
- // "Provably unrelated" is really weak.
- if( ptr instanceof ReadOnlyNode ro )
- ptr = ro.in(1);
- outer:
- while( true ) {
- switch( mem ) {
- case StoreNode st:
- if( ptr == addDep(st.ptr()) && off() == st.off() )
- return extend(castRO(st.val())); // Proved equal
- // Can we prove unequal? Offsets do not overlap?
- if( !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
- mem = st.mem(); // Proved never equal
- break;
- case PhiNode phi:
- // Assume related
- addDep(phi);
- break outer;
- case ConstantNode top: break outer; // Assume shortly dead
- case ProjNode mproj: // Memory projection
- switch( mproj.in(0) ) {
- case NewNode nnn1:
- if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) )
- return zero(nnn1);
- if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) )
- break outer; // Cannot tell, ptr not related to New
- mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New
- break;
- case StartNode start: break outer;
- case CallEndNode cend: break outer; // TODO: Bypass no-alias call
- default: throw Utils.TODO();
- }
- break;
- case CastNode cast: mem = cast.in(1); break;
- case MemMergeNode merge: mem = merge.alias(_alias); break;
-
- default:
- throw Utils.TODO();
- }
- }
+ if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
// Push a Load up through a Phi, as long as it collapses on at least
// one arm. If at a Loop, the backedge MUST collapse - else we risk
@@ -139,19 +99,27 @@ 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 &&
+ // Array access control must not be moved across the merge.
+ in(0)==null &&
// Offset can be hoisted
off() instanceof ConstantNode &&
// Pointer can be hoisted
hoistPtr(ptr,memphi) ) {
- // Profit on RHS/Loop backedge
- if( profit(memphi,2) ||
- // Else must not be a loop to count profit on LHS.
- (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
- Node ld1 = ld(1);
- Node ld2 = ld(2);
- return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2);
+ // Returning to this memory means no change only if the pointer
+ // is also loop invariant. A pointer Phi may select another object.
+ Node stop = memphi.region() instanceof LoopNode &&
+ !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null;
+ if( find(memphi.in(2),ptr,stop,new BitSet())!=null ||
+ (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) {
+ PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep();
+ phi.setType(_type); // Backedge searches may return this value Phi.
+ for( int i=1; i<memphi.nIns(); i++ ) {
+ Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr;
+ phi.addDef(load(memphi.in(i),p,stop,phi));
+ }
+ return CodeGen.CODE.add(phi.unkeep());
}
}
@@ -166,9 +134,67 @@ public class LoadNode extends MemOpNode {
return castRO(new ConstantNode(zero).peephole());
}
- private Node ld( int idx ) {
- Node mem = mem(), ptr = ptr();
- return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ // Search only; no new nodes or rewiring. Return a Store/New that folds,
+ // the unchanged loop memory, or a merge whose every arm folds. Null fails.
+ // Register dependencies so a later pointer/offset rewrite retries the query.
+ private Node find(Node mem, Node ptr, Node stop, BitSet visit) {
+ if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
+ while( mem!=null ) {
+ addDep(mem);
+ if( mem==stop ) return mem;
+ switch( mem ) {
+ case MemMergeNode merge: mem=merge.alias(_alias); break;
+ case CastNode cast: mem=cast.in(1); break;
+ case StoreNode st:
+ addDep(st.ptr());
+ if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st;
+ if( _alias==st._alias && !neverAlias(ptr,st.ptr()) &&
+ !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null;
+ mem=st.mem();
+ break;
+ case ProjNode proj:
+ if( !(proj.in(0) instanceof NewNode nnn) ) return null;
+ if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn;
+ if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null;
+ mem=nnn.mem(); // Distinct allocations cannot overlap.
+ break;
+ case MemPhiNode phi:
+ addDep(phi.region());
+ // Only the original loop closes a successful cycle. Another
+ // loop or an unfinished merge needs a separate proof.
+ if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode ||
+ r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null;
+ if( visit.get(phi._nid) ) return phi; // Already proved every arm.
+ for( int i=1; i<phi.nIns(); i++ )
+ if( find(phi.in(i),ptr,stop,visit)==null ) return null;
+ visit.set(phi._nid);
+ return phi;
+ default: return null;
+ }
+ }
+ return null;
+ }
+
+ private Node folded(Node win) {
+ return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win);
+ }
+
+ // Materialize only after the profitability search succeeds. The value Phi
+ // stands for a load which comes back around the loop with unchanged memory.
+ private Node load(Node mem, Node ptr, Node stop, PhiNode value) {
+ Node win=find(mem,ptr,stop,new BitSet());
+ if( win!=null ) {
+ if( win==stop ) return value;
+ if( win instanceof MemPhiNode mp ) {
+ PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep();
+ for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value));
+ return phi.unkeep().peephole();
+ }
+ return folded(win).peephole();
+ }
+ LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off());
+ ld.setDef(0,in(0));
+ return ld.peephole();
}
private static boolean neverAlias( Node ptr1, Node ptr2 ) {
@@ -197,16 +223,6 @@ public class LoadNode extends MemOpNode {
return false;
}
- // Profitable if we find a matching Store on this Phi arm.
- 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() ) return true;
- if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true;
- addDep(px);
- return false;
- }
-
// Read-Only is a deep property, and cannot be cast-away
private Node castRO(Node rez) {
if( ptr()._type.isFinal() && !rez._type.isFinal() )
@@ -228,4 +244,5 @@ public class LoadNode extends MemOpNode {
Node shl = new ShlNode(null,val,shf.keep()).peephole();
return new SarNode(null,shl,shf.unkeep());
}
+
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java
index 6c69a56e..db52c156 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -57,11 +57,12 @@ public class LoopNode extends RegionNode {
// Now fold control into the exit. Might have 1 valid exit, or an
// XCtrl or a bunch of prior NeverNode exits.
Node top = new ConstantNode(Type.TOP).peephole();
- Node memout = new MemMergeNode(false);
- memout.addDef(f); // placeholder for control
+ MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole());
+ memout.setDef(0,f);
for( Node u : _outputs )
- if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
- memout.addDef(phi);
+ if( u instanceof BulkMemPhiNode ) memout.setDef(1,u);
+ else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi);
+ memout.init();
Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
if( ctrl!=null && ctrl._type != Type.XCONTROL ) {
@@ -72,7 +73,7 @@ public class LoopNode extends RegionNode {
// Nope, insert an aligned exit layer
RegionNode r = new RegionNode(_loc,null,ctrl).init();
ctrl = r; r._ltree = stop._ltree;
- mem = new PhiNode(r,mem ).init();
+ mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init();
expr = new PhiNode(r,expr).init();
}
// Append new Never exit
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index 20aeb454..e56eeb06 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -5,176 +5,86 @@ import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
import java.util.*;
-/**
- * Memory Merge - a merge of many aliases into a "fat memory". All aliases
- * are here, but most will be lazy - take the default fat memory.
+/** 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(MemMergeNode mem) { super(mem); }
- /*
- * In-Progress means this is being used by the Parser to track memory
- * aliases. No optimizations are allowed. When no longer "in progress"
- * normal peeps work.
- */
- public final boolean _inProgress;
-
- public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; }
- public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; }
-
-
- // If being used by a Scope, this is "in progress" from the Parser.
- // Otherwise, it's a memory merge
- boolean inProgress() { return _inProgress; }
+ 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;
+ }
- @Override public String label() { return "ALLMEM"; }
- @Override public boolean isMem() { return true; }
+ public void alias(int alias, Node mem) {
+ assert alias > 1;
+ while( nIns()<=alias ) addDef(null);
+ setDef(alias,mem);
+ }
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- sb.append(_inProgress ? "Merge[" : "MEM[ ");
- for( int j=2; j<nIns(); j++ ) {
- sb.append(j);
- sb.append(":");
- Node n = in(j);
- while( n instanceof ScopeNode loop ) {
- sb.append("Lazy_");
- n = loop.mem().alias(j);
- }
- if( n==null ) sb.append("___ ");
- else n._print0(sb, visited).append(" ");
+ // Scalar contents of one alias. New zeroes the allocated fields;
+ // 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) {
+ dep.addDep(mem);
+ if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,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;
+ dep.addDep(nnn);
+ return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero());
}
- sb.setLength(sb.length()-1);
- return sb.append("]");
+ // Function parameters and call results may contain arbitrary heap values.
+ // Their bulk memory types do not imply empty or zero-filled storage.
+ 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;
}
- // Make a memory merge: no longer a Scope really, tracking memory state but
- // not related to the parser in any way.
- public Node merge() {
- // Force default memory to not be lazy
- MemMergeNode merge = new MemMergeNode(false);
- for( Node n : _inputs )
- merge.addDef(n);
+ @Override public String label() { return "MemMerge"; }
+ @Override public boolean isMem() { return true; }
+ @Override public Type compute() { return TypeMem.BOT; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("MEM[");
for( int i=1; i<nIns(); i++ )
- merge._mem(i,null);
- return merge.peephole();
+ if( in(i)!=null ) {
+ sb.append(i==1 ? "default:" : i+":");
+ in(i)._print0(sb,visited).append(" ");
+ }
+ return sb.append("]");
}
-
- @Override public Type compute() { return TypeMem.BOT; }
-
@Override public Node idealize() {
- if( inProgress() ) return null;
-
- // Fold defaults into the default
boolean progress=false, allDefault=true;
for( int i=2; i<nIns(); i++ ) {
- if( in(i) instanceof CastNode cast )
- { setDef(i,cast.in(1)); progress=true; }
- if( in(1) == in(i) ) { setDef(i,null); progress=true; }
- else { allDefault=false; }
+ if( in(i) instanceof CastNode cast ) { setDef(i,cast.in(1)); progress=true; }
+ if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; }
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,CodeGen.CODE.add(mem.alias(i)));
+ progress=true;
+ }
+ if( in(i)!=null ) allDefault=false;
}
+ if( allDefault && in(1)!=null ) return in(1);
- // If not merging any memory (all memory is just the default)
- if( allDefault )
- return in(1); // Become default memory
-
- // Collapse stacked all-mem
if( in(1) instanceof MemMergeNode mem ) {
- // Goal is to swap my default mem with mem's default mem
- for( int i=2; i<mem.nIns(); i++ ) {
- if( mem.in(i) != null ) {
- // deeper default mem has a non-default
- if( i>=nIns() || in(i)==null )
- setDefX(i,mem.in(i));
- }
- }
+ for( int i=2; i<mem.nIns(); i++ )
+ if( mem.in(i)!=null && (i>=nIns() || in(i)==null) )
+ alias(i,mem.in(i));
+ CodeGen.CODE.add(mem.in(1));
setDef(1,mem.in(1));
return this;
}
-
+ assert BulkMemPhiNode.checkMerge(this);
return progress ? this : null;
}
-
-
- public Node in( Var v ) { return in(v._idx); }
-
- public Node alias( int alias ) {
- return alias < nIns() && in(alias)!=null ? in(alias) : in(1);
- }
-
- Node alias( int alias, Node st ) {
- while( alias >= nIns() ) addDef(null);
- return setDef(alias,st);
- }
-
- // Read or update from memory.
- // A shared implementation allows us to create lazy phis both during
- // lookups and updates; the lazy phi creation is part of chapter 8.
- Node _mem( int alias, Node st ) {
- // Memory projections are made lazily; if one does not exist
- // then it must be START.proj(1)
- Node old = alias(alias);
- if( old instanceof ScopeNode loop ) {
- MemMergeNode loopmem = loop.mem();
- Node memdef = loopmem.alias(alias);
- // Lazy phi!
- old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region()
- // Loop already has a real Phi, use it
- ? memdef
- // 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.
- : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() );
- alias(alias,old);
- }
- // Memory projections are made lazily; expand as needed
- return st==null ? old : alias(alias,st); // Not lazy, so this is the answer
- }
-
-
- void _merge( MemMergeNode that, RegionNode r) {
- int len = Math.max(nIns(),that.nIns());
- for( int i = 1; i < len; i++)
- if( alias(i) != that.alias(i) ) { // No need for redundant Phis
- // If we are in lazy phi mode we need to a lookup
- // by alias as it will trigger a phi creation
- Node lhs = this._mem(i,null);
- Node rhs = that._mem(i,null);
- alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole());
- }
- }
-
- // Fill in the backedge of any inserted Phis
- void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
- Node exit_def = exit.alias(1);
- for( int i=1; i<nIns(); i++ ) {
- if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) {
- assert phi.in(2)==null;
- phi.setDef(2,back.alias(i)==scope ? phi : back.alias(i)); // Fill backedge
- }
- if( exit_def == scope ) // Replace a lazy-phi on the exit path also
- exit.alias(i,in(i));
- }
- }
-
- // Now one-time do a useless-phi removal
- void _useless( ) {
- for( int i=2; i<nIns(); i++ ) {
- if( in(i) instanceof PhiNode phi ) {
- // Do an eager useless-phi removal
- Node in = phi.peephole();
- CodeGen.CODE.addAll(phi._outputs);
- phi.moveDepsToWorklist();
- if( in != phi ) {
- if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
- phi.subsume(in);
- setDef(i,in); // Set the update back into Scope
- }
- }
- }
- }
-
- @Override public boolean eq( Node n ) {
- return this==n || !_inProgress;
- }
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index ead82322..65021110 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.Utils;
import java.lang.StringBuilder;
import java.util.BitSet;
+import java.util.ArrayList;
/**
* Convenience common base for Load and Store.
@@ -78,6 +79,8 @@ public abstract class MemOpNode extends Node {
static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
String mlabel() { return mlabel(_name); }
+ @Override public boolean isMem() { return !_isLoad; }
+
public Node mem() { return in(1); }
public Node ptr() { return in(2); }
public Node off() { return in(3); }
@@ -86,6 +89,13 @@ public abstract class MemOpNode extends Node {
public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); }
public int log_size() { return _declaredType.log_size(); }
+ // 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) {
+ dep.addDep(ptr());
+ return _alias==other._alias && _declaredType==other._declaredType && err()==null;
+ }
+
@Override
public boolean eq(Node n) {
MemOpNode mem = (MemOpNode)n; // Invariant
@@ -106,4 +116,29 @@ public abstract class MemOpNode extends Node {
return null;
return Parser.error( "Might be null accessing '" + _name + "'",_loc);
}
+ // 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 CallNode ||
+ (use instanceof MemOpNode st && !st._isLoad && 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/chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..7e54f50f
--- /dev/null
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+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;
+ }
+
+ public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._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 _minType;
+ Type t = Type.TOP;
+ for( int i=1; i<nIns(); i++ )
+ if( addDep(r.in(i))._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,CodeGen.CODE.add(mem.alias(_alias)));
+ return this;
+ }
+ return super.idealize();
+ }
+
+ @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); }
+ @Override int hash() { return _alias; }
+}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java
index b0b4103b..49a82a87 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -6,33 +6,35 @@ import com.seaofnodes.simple.util.SB;
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 a zeroed 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 final TypeMemPtr _ptr;
- public final int _len;
public NewNode(TypeMemPtr ptr, Node... nodes) {
super(nodes);
assert !ptr.nullable();
_ptr = ptr;
- _len = ptr._obj._fields.length;
- // 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 || nodes[1]._type==Type.NIL;
- for( int i=0; i<_len; i++ )
- assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT );
+ assert nodes.length==3;
+ assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL;
+ assert nodes[1]._type instanceof TypeMem;
+ assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL;
}
- public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; }
+ 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._one && f._alias==alias ) return f;
+ return null;
+ }
+
+ public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; }
- public Node mem (int idx) { return in(idx+2); }
@Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); }
@Override
@@ -41,38 +43,10 @@ public class NewNode extends Node implements MultiNode {
return sb.append(_ptr._obj.str());
}
- int findAlias(int alias) {
- int idx = _ptr._obj.findAlias(alias);
- assert idx!= -1; // Error, caller should be calling
- return idx+2; // Skip ctrl, size
- }
-
- // 0 - ctrl
- // 1 - byte size
- // 2-len+2 - aliases, one per field
- // len+2 - 2*len+2 - initial values, one per field
- public Node size() { return in(1); }
@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++ ) {
- if( _ptr._obj._fields[i]._one ) {
- // Once-only fields use the declared type
- ts[i+2] = _ptr._obj._fields[i]._t;
- } else {
- // Others take from the inputs
- Type mt = in(i+2)._type;
- TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt;
- Type tfld = mem._t.meet(mem._t.makeZero());
- Type tfld2 = tfld.join(fs[i]._t );
- ts[i+2] = TypeMem.make(fs[i]._alias,tfld2);
- }
- }
- return TypeTuple.make(ts);
+ return TypeTuple.make(_ptr,TypeMem.BOT);
}
@Override
@@ -117,9 +91,9 @@ public class NewNode extends Node implements MultiNode {
_kills = kills;
}
public String op() { return "alloc"; }
- public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
+ public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; }
public RegMask outregmap() { return null; }
- public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; }
+ public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; }
public RegMask killmap() { return _kills; }
public void asm(CodeGen code, SB sb) {
sb.p("#calloc, ").p(code.reg(size()));
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java
index ab221789..c27270b6 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -232,6 +232,7 @@ public abstract class Node implements Cloneable {
// Error is 'use' does not exist; ok for 'use' to be null.
protected boolean delUse( Node use ) {
_outputs.del(_outputs.find(use));
+ moveDepsToWorklist(); // User-count and anti-dependence queries can now change.
return _outputs.isEmpty();
}
@@ -305,6 +306,7 @@ public abstract class Node implements Cloneable {
n.unlock();
int idx = n._inputs.find(this);
n._inputs.set(idx,nnn);
+ n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type.
nnn.addUse(n);
CODE.add(n);
CODE.addAll(n._outputs);
@@ -552,7 +554,12 @@ public abstract class Node implements Cloneable {
* or output of this node, that is, it is at least one step away. The node
* being added must benefit from this node being peepholed.
*/
- <N extends Node> N addDep( N dep ) {
+ <N extends Node> N addDep( N dep ) { return addDep(dep,false); }
+
+ // Keep an explicit dependency when inspecting an immediate user's shape.
+ <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); }
+
+ private <N extends Node> N addDep(N dep, boolean forwards) {
// Running peepholes during the big assert cannot have side effects
// like adding dependencies.
if( CODE._midAssert ) return dep;
@@ -560,8 +567,8 @@ public abstract class Node implements Cloneable {
if( obs != null ) obs.dep(this, dep);
if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
if( dep._deps .find(this) != -1 ) return dep; // Already on list
- if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
- if( dep._outputs.find(this) != -1 ) return dep;
+ if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
+ if( !forwards && dep._outputs.find(this) != -1 ) return dep;
dep._deps.add(this);
return dep;
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index a711fe81..8ae94260 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -33,6 +33,10 @@ public class PhiNode extends Node {
addDef(sample);
}
+ 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
@@ -94,6 +98,8 @@ public class PhiNode extends Node {
if( live != null ) {
if( live._type.isa(_type) )
return live;
+ // Let the bulk input split this alias before collapsing a precise Phi.
+ if( live instanceof BulkMemPhiNode ) return null;
// Keep the Phi upcast
return new CastNode(_type,null,live);
}
@@ -103,19 +109,9 @@ public class PhiNode extends Node {
if( r.in(i)._type == Type.XCONTROL )
return null;
- // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( _type instanceof TypeMem tmem && tmem._alias!=1 ) {
- for( int i=1; i<nIns(); i++ )
- if( in(i) instanceof MemMergeNode mem ) {
- setDef(i,mem.alias(tmem._alias));
- return this;
- }
- }
-
- // Generic "pull down op"
- Node progress;
- if( same_op() && (progress = drop_same_op()) != null )
- return progress;
+ // 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 ) {
@@ -149,50 +145,77 @@ public class PhiNode extends Node {
// Same op on all Phi paths; all ops have only the Phi as a use.
// None have a control input.
private boolean same_op() {
+ 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 ) {
+ addDepForwards(use);
+ return false;
+ }
+ Node busy = null;
for( int i=1; i<nIns(); i++ ) {
- Node op = in(i);
- if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() )
- return false; // Wrong class or CFG bound or mismatched inputs
- if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias )
- return false;
- if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users
- for( Node out : op._outputs )
- if( out!=null && out!=this )
- addDep(out);
- return false;
+ Node n = in(i);
+ if( addDep(region().in(i))._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.
+ addDepForwards(n);
+ if( n.nOuts()>1 ) {
+ for( Node use : n._outputs )
+ if( use!=null && use!=this ) addDepForwards(use);
+ if( n instanceof StoreNode || busy!=null ) return false;
+ busy=n;
}
- for( int j=1; j<in(1).nIns(); j++ )
- if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) )
- return false; // Lazy Phi input
+ 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() {
- assert !(in(1) instanceof CFGNode);
- Node op = in(1);
- Node cp = op.copy();
- cp._type = null; // Fresh type
- cp.addDef(null); // No control
-
+ Node op = in(1), cp = op.copyEmpty();
+ cp._type = null;
+ cp.addDef(null);
for( int j=1; j<op.nIns(); j++ ) {
- boolean needsPhi = false;
- Node x = op.in(j); // Jth input from sample #1
+ Node x = op.in(j);
+ boolean different = false;
for( int i=2; i<nIns(); i++ )
- if( in(i).in(j) != x )
- { needsPhi=true; break; }
- if( needsPhi ) {
- x = new PhiNode(_label,op.in(j)._type.glb(false));
- x.addDef(region());
- for( int i=1; i<nIns(); i++ )
- x.addDef(in(i).in(j));
- x = x.peephole();
+ 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);
+ }
+ // Not accepts both pointers and integers, but a Phi cannot mix them.
+ if( t==Type.BOTTOM ) {
+ for( int i=1; i<ins.length; i++ ) addDep(ins[i]);
+ cp.kill();
+ return null;
+ }
+ PhiNode phi = j==1 && op instanceof MemOpNode mem
+ ? new MemPhiNode(_label,mem._alias,ins)
+ : PhiNode.make(_label,t.glb(false),ins);
+ x = phi.peephole();
}
cp.addDef(x);
}
- // Test not running backwards, which can happen for e.g. And's
- if( cp.compute().isa(compute()) )
- return cp;
+ // Factoring must not widen the result (e.g. correlated And operands).
+ if( cp.compute().isa(compute()) ) return cp;
+ for( int i=1; i<nIns(); i++ )
+ for( int j=1; j<in(i).nIns(); j++ )
+ if( in(i).in(j)!=null ) addDep(in(i).in(j));
cp.kill();
return null;
}
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index 42b23633..14ba493e 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -48,7 +48,7 @@ public class RegionNode extends CFGNode {
if( !hasPhi() && nIns()>3 && // No Phi users, just a control user
in(1) instanceof CProjNode p1 &&
in(2) instanceof CProjNode p2 &&
- addDep(p1.in(0))==addDep(p2.in(0)) &&
+ addDepForwards(p1).in(0)==addDepForwards(p2).in(0) &&
p1.in(0) instanceof IfNode iff ) {
// Replace with the iff.ctrl directly
if( nIns()==3 ) return iff.ctrl();
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 95303994..212bb875 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -9,15 +9,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
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, rpc}. */
public class ReturnNode extends CFGNode {
public FunNode _fun;
@@ -51,7 +43,8 @@ public class ReturnNode extends CFGNode {
@Override
public Type compute() {
if( inProgress () ) return TypeTuple.RET; // In progress
- return TypeTuple.make(ctrl()._type,mem()._type,expr()._type);
+ // Inlining can delete the entry while callers still use this return.
+ return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type);
}
@Override public Node idealize() {
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 04666956..cddb9f24 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -12,9 +12,9 @@ import java.util.*;
/**
* The Scope node is purely a parser helper - it tracks names to nodes with a
- * stack of variables.
+ * stack of variable records.
*/
-public class ScopeNode extends MemMergeNode {
+public class ScopeNode extends Node {
/**
* The control is a name that binds to the currently active control
@@ -24,8 +24,12 @@ public class ScopeNode extends MemMergeNode {
public static final String ARG0 = "arg";
public static final String MEM0 = "$mem";
- // All active/live variables in all nested scopes, all run together.
- // Maps 1-to-1 to the _inputs array.
+ public Node in(Var v) { return in(v._idx); }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+ @Override public Node idealize() { return null; }
+
+ // All active/live variables in all nested scopes, all run together
public final Ary<Var> _vars;
// Lexical scope is typed:
@@ -50,10 +54,10 @@ public class ScopeNode extends MemMergeNode {
// A new ScopeNode
public ScopeNode() {
- super(true);
- _vars = new Ary<>(Var .class);
- _kinds = new Ary<>(Kind.class);
- _guards = new Ary<>(Node.class);
+ _type = Type.BOTTOM;
+ _vars = new Ary<>(Var .class);
+ _kinds = new Ary<>(Kind .class);
+ _guards = new Ary<>(Node .class);
}
@Override public String label() { return "Scope"; }
@@ -85,7 +89,7 @@ public class ScopeNode extends MemMergeNode {
public Node ctrl() { return in(0); }
- public MemMergeNode mem() { return (MemMergeNode)in(1); }
+ public Node mem() { return in(lookup(MEM0)); }
public Var var(int i) { return _vars.at(i); }
/**
@@ -99,7 +103,7 @@ public class ScopeNode extends MemMergeNode {
* @return Node that was bound
*/
public <N extends Node> N ctrl(N n) { return setDef(0,n); }
- public Node mem(Node n) { return setDef(1,n); }
+ public Node mem(Node n) { update(MEM0,n); return n; }
public void push( Kind kind ) {
kind._lexSize = _vars.size();
@@ -206,12 +210,6 @@ public class ScopeNode extends MemMergeNode {
return true;
}
- // Read from memory
- public Node mem( int alias ) { return mem()._mem(alias,null); }
- // Write to memory
- public void mem( int alias, Node st ) { mem()._mem(alias,st); }
-
-
/**
* Lookup a name in all scopes starting from most deeply nested.
*
@@ -247,7 +245,7 @@ public class ScopeNode extends MemMergeNode {
// 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(v._idx,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
+ : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
setDef(v._idx,old);
}
//assert !v._final || st==null;
@@ -285,21 +283,9 @@ public class ScopeNode extends MemMergeNode {
n.keep();
dup.addDef(ctrl()); // Control input is just copied
- // Memory input is a shallow copy
- MemMergeNode memdup = new MemMergeNode(true), mem = mem();
- memdup.addDef(null);
- memdup.addDef(loop ? this : mem.in(1));
- for( int i=2; i<mem.nIns(); i++ )
- // For lazy phis on loops we use a sentinel
- // that will trigger phi creation on update
- memdup.addDef(loop ? this : mem.in(i));
- dup.addDef(memdup);
-
- // Copy of other inputs
- for( int i=2; i<nIns(); i++ )
- // For lazy phis on loops we use a sentinel
- // that will trigger phi creation on update
- dup.addDef(loop && !var(i)._final ? this : in(i));
+ // Memory is one binding, with the same lazy loop Phi as scalar names.
+ for( int i=1; i<nIns(); i++ )
+ dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
return dup;
}
@@ -313,22 +299,21 @@ public class ScopeNode extends MemMergeNode {
*/
public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
- mem()._merge(that.mem(),r);
- this ._merge(that ,r);
+ _merge(that,r);
that.kill(); // Kill merged scope
CodeGen.CODE.add(r);
return r.unkeep();
}
private void _merge(ScopeNode that, RegionNode r) {
- for( int i = 2; i < nIns(); i++)
+ for( int i = 1; i < nIns(); i++)
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
Var v = var(i);
Node lhs = this.in(this.update(v,null));
Node rhs = that.in(that.update(v,null));
- setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole());
+ setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole());
}
}
@@ -353,23 +338,17 @@ public class ScopeNode extends MemMergeNode {
assert ctrl instanceof LoopNode loop && loop.inProgress();
ctrl.setDef(2,back.ctrl());
- mem()._endLoopMem( this, back.mem(), exit.mem() );
- this ._endLoop ( this, back , exit );
+ _endLoop(this,back,exit);
back.kill(); // Loop backedge is dead
// Now one-time do a useless-phi removal
- mem()._useless();
- this ._useless();
-
- // The exit mem's lazy default value had been the loop top,
- // now it goes back to predating the loop.
- exit.mem().setDef(1,mem().in(1));
+ _useless();
}
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
- for( int i=2; i<nIns(); i++ ) {
- if( var(i)._final ) continue; // Final vars did not get modified in the loop
- if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
+ for( int i=1; i<nIns(); i++ ) {
+ if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop
+ if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
if( back.in(i) != scope ) {
PhiNode phi = (PhiNode)in(i);
assert phi.region()==scope.ctrl() && phi.in(2)==null;
@@ -381,6 +360,23 @@ public class ScopeNode extends MemMergeNode {
}
+ // Now one-time do a useless-phi removal
+ void _useless( ) {
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi ) {
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ CodeGen.CODE.addAll(phi._outputs);
+ phi.moveDepsToWorklist();
+ if( in != phi ) {
+ if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
+ phi.subsume(in);
+ setDef(i,in); // Set the update back into Scope
+ }
+ }
+ }
+ }
+
// Up-casting: using the results of an If to improve a value.
// E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
public void addGuards( Node ctrl, Node pred, boolean invert ) {
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java
index 6ed2acd3..186adfb7 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -3,18 +3,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.*;
-import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import static com.seaofnodes.simple.util.Utils.TODO;
-/**
- * The Start node represents the start of the function.
- * <p>
- * 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 LoopNode implements MultiNode {
final Type _arg;
@@ -44,7 +37,7 @@ public class StartNode extends LoopNode implements MultiNode {
@Override public TypeTuple compute() {
- return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg);
+ return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg);
}
@Override public Node idealize() { return null; }
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 74c27dc8..ac437788 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -1,5 +1,6 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.*;
import com.seaofnodes.simple.type.*;
import java.util.BitSet;
@@ -25,6 +26,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,24 +43,18 @@ public class StoreNode extends MemOpNode {
@Override
public Type compute() {
Type val = val()._type;
- Type mem0 = mem()._type;
- if( mem0 == Type.TOP ) return TypeMem.TOP;
- TypeMem mem = (TypeMem)mem0; // Invariant
- if( mem == TypeMem.TOP ) return TypeMem.TOP;
- Type t = Type.BOTTOM; // No idea on field contents
- // Same alias, lift val to the declared type and then meet into other fields
- if( mem._alias == _alias ) {
- assert !_declaredType.isFRef();
- // Sharpen memory value; required for narrowing stores where the parser inserts
- // zero/sign masking and somebody reads the TypeMem type.
- val = val.join(_declaredType);
- t = val.meet(mem._t);
- }
+ assert !_declaredType.isFRef();
+ Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this));
return TypeMem.make(_alias,t);
}
@Override
public Node idealize() {
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
+ return this;
+ }
+
if( mem() instanceof CastNode cast ) {
setDef(1,cast.in(1));
@@ -65,8 +64,8 @@ public class StoreNode extends MemOpNode {
// 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
- off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index)
+ 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
// non-serializable memory effects in the worse case.
@@ -76,12 +75,6 @@ public class StoreNode extends MemOpNode {
return this;
}
- // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( mem() instanceof MemMergeNode mem ) {
- setDef(1,mem.alias(_alias));
- return this;
- }
-
// Value is automatically truncated by narrow store
if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
int log = _declaredType.log_size();
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index bca3dfa4..c39c5f74 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -673,6 +673,8 @@ public class arm extends Machine {
case NotNode not -> new NotARM(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmARM(parm);
+ case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read -> new ReadOnlyMach(read);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 48cb5d23..88ec9ebd 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -345,6 +345,8 @@ public class riscv extends Machine {
case NotNode not -> new NotRISC(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmRISC(parm);
+ case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read-> new ReadOnlyMach(read);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
index 441cf693..d0fb0081 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -24,6 +24,11 @@ public class LoadX86 extends MemOpX86 {
}
static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) {
+ // Allocation may put a full-width integer in an XMM register (or a
+ // floating value in a GPR). Move its bits using the selected register bank.
+ if( dst>=x86_64_v2.XMM_OFFSET && decl.log_size()==3 ) decl=TypeFloat.F64;
+ else if( dst<x86_64_v2.XMM_OFFSET && decl instanceof TypeFloat )
+ decl = decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT;
if( decl == TypeFloat.F32) enc.add1(0xF3);
if( decl == TypeFloat.F64) enc.add1(0xF2);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 7ac7fc19..cf9288b8 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -363,6 +363,8 @@ public class x86_64_v2 extends Machine {
case NotNode not -> new NotX86(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmX86(parm);
+ case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read-> new ReadOnlyMach(read);
@@ -396,9 +398,8 @@ public class x86_64_v2 extends Machine {
if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3)
return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs);
-// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) {
-// throw Utils.TODO(); // Swap load sides
-// }
+ if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 )
+ return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs);
// Attempt a full LEA-style break down.
// Returns one of AddX86, AddIX86, LeaX86, or LHS
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
index 7f7107ec..6f19fa9d 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -67,7 +67,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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM;
+ if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM;
Node def = n.in(i);
if( def != null && isMem(def) ) return Role.MEM;
return Role.DATA;