From 684c23f1c5257886a219d540ac4ec0e574b7e49b Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Tue, 29 Sep 2026 16:16:13 -0700 Subject: Bring forward the lazy mem alias Integrate with ch15 NewNode creating aliases --- chapter13/README.md | 25 ++- .../com/seaofnodes/simple/GlobalCodeMotion.java | 17 +- .../main/java/com/seaofnodes/simple/Parser.java | 41 ++-- .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 237 +++++++++++++++++++++ .../java/com/seaofnodes/simple/node/LoadNode.java | 30 ++- .../com/seaofnodes/simple/node/MemMergeNode.java | 60 ++++++ .../java/com/seaofnodes/simple/node/MemOpNode.java | 7 + .../com/seaofnodes/simple/node/MemPhiNode.java | 36 ++++ .../main/java/com/seaofnodes/simple/node/Node.java | 35 ++- .../java/com/seaofnodes/simple/node/PhiNode.java | 90 ++++++-- .../com/seaofnodes/simple/node/RegionNode.java | 2 +- .../com/seaofnodes/simple/node/ReturnNode.java | 28 +-- .../java/com/seaofnodes/simple/node/ScopeNode.java | 4 +- .../java/com/seaofnodes/simple/node/StartNode.java | 46 +--- .../java/com/seaofnodes/simple/node/StoreNode.java | 12 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 5 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 14 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 12 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 26 +-- .../java/com/seaofnodes/simple/Chapter08Test.java | 16 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 136 +++++++++++- .../java/com/seaofnodes/simple/Chapter10bTest.java | 64 ++++++ .../java/com/seaofnodes/simple/Chapter11Test.java | 8 +- .../java/com/seaofnodes/simple/Chapter12Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 2 +- .../com/seaofnodes/simple/evaluator/Evaluator.java | 7 +- .../com/seaofnodes/simple/evaluator/Scheduler.java | 11 +- 28 files changed, 798 insertions(+), 185 deletions(-) create mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java create mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java create mode 100644 chapter13/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java create mode 100644 chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java (limited to 'chapter13') diff --git a/chapter13/README.md b/chapter13/README.md index c54bfa70..1c492468 100644 --- a/chapter13/README.md +++ b/chapter13/README.md @@ -1,5 +1,10 @@ # Chapter 13: References +Memory retains one `$mem` binding and lazy partitions. Typed initialization +stores now include each field's declared type and initialization flag, while +`MemMerge` preserves the other fields. Start and Return keep control in slot 0 +and memory in slot 1. + # Table of Contents 1. [References](#references) @@ -64,21 +69,23 @@ p.tree.la = null; // Accessing unknown field 'la' from 'null' It turns out `p.tree` is `null`, so we can't access the field `la` from it. ```java // name = "tree" -// memAlias(alias) = StoreNode +// mem() is the current aggregate; its tree alias selects the initializing Store. // in(3) = {ConstantNode@1651} "null" -return parsePostfix(new LoadNode(name, alias, declaredType.glb(), memAlias(alias), expr).peephole()); +return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole()); ``` `LoadNode::idealize` -In LoadNode idealize we return st.val(). +LoadNode first selects its alias through any MemMerge, then returns `st.val()` +when it finds a matching Store to the same object. ```java // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr() == st.ptr() ) { // Must check same object + ptr() == st.ptr() && _alias == st._alias ) { // Same object and field assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); // // <<-- {ConstantNode@1651} "null" } ``` -`mem(): ` refers to `memAlias(alias)`. Therefore, in the next iteration, we will end up with: +After alias selection, the Load's `mem()` refers to that initializing Store. +In the next iteration, we will end up with: ```java ... @@ -98,9 +105,9 @@ When we are creating the struct, we set up the initial value to Null: ```java private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - int alias = START._aliasStarts.get(obj._name); + int alias = _aliases.get(obj._name); for( Field field : obj._fields ) { - memAlias(alias, new StoreNode(field._fname, alias, field._type, ctrl(), memAlias(alias), n, new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); alias++; } return n.unkeep(); @@ -114,7 +121,7 @@ for( Field field : obj._fields ) { // field._type = *FamilyTree? // REMEMBER: // @Override public TypeMemPtr makeInit() { return NULLPTR; } - memAlias(..., new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); alias++; } ``` @@ -256,4 +263,4 @@ for( ; lca != early.idom(); lca = lca.idom() ) if( better(lca,best) ) best = lca; assert !(best instanceof IfNode); -``` \ No newline at end of file +``` diff --git a/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index a270f026..78a0dcde 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -149,7 +149,7 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) for( Node memuse : ld.mem()._outputs ) - if( memuse._type instanceof TypeMem && late[memuse._nid]==null ) + if( antiUse(ld,memuse) && late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -219,6 +219,17 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // Only a store or memory Phi covering this alias can constrain a load. + // MemMerge packages slices without overwriting them. + private static boolean antiUse(LoadNode load, Node use) { + return switch( use ) { + case StoreNode st -> st._alias==load._alias; + case MemPhiNode phi -> phi._alias==load._alias; + case BulkMemPhiNode phi -> !phi.isSplit(load._alias); + default -> false; + }; + } + 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 @@ -226,6 +237,7 @@ public abstract class GlobalCodeMotion { anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.mem()._outputs ) { + if( !antiUse(load,mem) ) continue; switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -238,9 +250,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/chapter13/src/main/java/com/seaofnodes/simple/Parser.java b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java index b3bd1ada..cc668f18 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java @@ -36,6 +36,10 @@ public class Parser { public StopNode STOP; + // Field identities are independent of the memory graph and Start's tuple. + private final HashMap _aliases = new HashMap<>(); + private int _alias = 2; + // Debugger Printing. public static boolean SCHEDULED; // True if debug printer can use schedule info @@ -99,7 +103,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 = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); XCTRL= new XCtrlNode().peephole().keep(); @@ -126,7 +130,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()); + _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 ) STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); @@ -214,8 +219,9 @@ public class Parser { require("}"); // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); - TYPES.put(typeName, ts); - START.addMemProj(ts, _scope); // Insert memory edges + TYPES.put(typeName, ts); // Insert the struct name in the collection of all struct names + _aliases.put(typeName,_alias); + _alias += ts._fields.length; return null; } @@ -596,21 +602,24 @@ public class Parser { */ private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - int alias = START._aliasStarts.get(obj._name); + int alias = _aliases.get(obj._name); for( Field field : obj._fields ) { - memAlias(alias, new StoreNode(field._fname, alias, field._type, ctrl(), memAlias(alias), n, new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); alias++; } return n.unkeep(); } - // 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 val, boolean init) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,glb,ctrl(),prior,ptr,val,init).peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -632,7 +641,7 @@ public class Parser { TypeStruct base = (TypeStruct)TYPES.get(ptr._obj._name); int idx = base==null ? -1 : base.find(name); if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - int alias = START._aliasStarts.get(ptr._obj._name)+idx; + int alias = _aliases.get(ptr._obj._name)+idx; if( match("=") ) { // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment @@ -640,13 +649,13 @@ public class Parser { else { Node val = parseExpression(); Type glb = base._fields[idx]._type; - memAlias(alias, new StoreNode(name, alias,glb, ctrl(), memAlias(alias), expr, val, false).peephole()); + store(name,alias,glb,expr,val,false); return expr; // "obj.a = expr" returns the expression while updating memory } } Type declaredType = base._fields[idx]._type; - return parsePostfix(new LoadNode(name, alias, declaredType.glb(), memAlias(alias), expr).peephole()); + return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole()); } /** diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..8d512f21 --- /dev/null +++ b/chapter13/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= 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 missingAlias(mmm,false)==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,false); + 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,true); + 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; + } + + // First precise MemMerge slice still covered by this Phi. When inspecting + // a user, ignore slots which point back to this Phi. + private int missingAlias(MemMergeNode mmm, boolean user) { + for( int alias=2; alias= 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)); + for( int i=1; i= 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/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java index ff468b72..f1d4884a 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.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.Type; import java.util.BitSet; @@ -22,6 +23,24 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, null, memSlice, memPtr); } + @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 ) return true; + } + return false; + } + @Override public String label() { return _name; } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } @@ -33,10 +52,15 @@ public class LoadNode extends MemOpNode { @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } + // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr() == st.ptr() ) { // Must check same object + ptr() == st.ptr() && _alias==st._alias ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } @@ -48,7 +72,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 ) { // Profit on RHS/Loop backedge if( profit(memphi,2) || // Else must not be a loop to count profit on LHS. @@ -69,6 +93,6 @@ public class LoadNode extends MemOpNode { // Profitable if we find a matching Store on this Phi arm. private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); - return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr(); + return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _alias==st1._alias; } } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..d7fed402 --- /dev/null +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -0,0 +1,60 @@ +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; + +/** All memory: a default slice and explicit overrides for individual aliases. */ +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); + return n==null ? in(1) : n; + } + + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } + + @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() || 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/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 95319c24..7c6afb0c 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -24,6 +24,13 @@ public abstract class MemOpNode extends Node { public Node mem() { return in(1); } public Node ptr() { return in(2); } + // 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/chapter13/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..c806bede --- /dev/null +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,36 @@ +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),inputs); + assert alias > 1; + _alias = alias; + } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + Type t = super.compute(); + return t==Type.TOP || t==TypeMem.TOP ? TypeMem.TOP : TypeMem.make(_alias); + } + + @Override public Node idealize() { + for( int i=1; i _inputs; + public ArrayList _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 _outputs; + public ArrayList _outputs; /** @@ -210,6 +210,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(); } @@ -451,6 +452,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; @@ -458,8 +467,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; } @@ -591,6 +600,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/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java index dbe74f57..277c16d1 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter13/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_"+_label; } @Override @@ -65,23 +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 && !op.isCFG() && same_op() ) { - Node[] lhss = new Node[nIns()]; - Node[] rhss = new Node[nIns()]; - lhss[0] = rhss[0] = in(0); // Set Region - for( int i=1; i 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 ) { @@ -111,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i1 ) { + 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 - * In this presentation, Return functions as a Stop node, since multiple return statements are not possible. - * The Stop node will be introduced in Chapter 6 when we implement if statements. - *

- * 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); - // We lookup 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"; } @@ -43,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/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 455f4b3a..b931e862 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -129,7 +129,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 @@ -207,7 +207,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/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java index 5f205ffd..2cedf0f6 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,59 +1,17 @@ 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.HashMap; 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; - - // The alias number of the first field in the struct. - // - public final HashMap _aliasStarts; - + private final TypeTuple _args; public StartNode(Type[] args) { super(); - _args = TypeTuple.make(args); - _type = _args; - _aliasStarts = new HashMap<>(); - } - - /** - * Creates a projection for each of the struct's fields, using the field alias - * as the key. - */ - public void addMemProj( TypeStruct ts, ScopeNode scope) { - int len = _args._types.length; - _aliasStarts.put(ts._name,len); - - // resize the tuple's type array to include all fields of the struct - int max = len + ts._fields.length; - Type[] args = Arrays.copyOf(_args._types, max); - - // The new members of the tuple get a mem type with an alias - for( int alias = len; alias < max; alias++ ) - args[alias] = TypeMem.make(alias); _type = _args = TypeTuple.make(args); - // For each of the fields we now add a mem projection. Note that the - // alias matches the slot of the field in the tuple - for( int alias = len; alias < max; alias++ ) { - String name = Parser.memName(alias); - Node n = new ProjNode(this, alias, name).peephole(); - scope.define(name, args[alias], n); - } } @Override diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 142aebc3..031240ce 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter13/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; @@ -24,6 +25,10 @@ public class StoreNode extends MemOpNode { _init = init; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "." +_name+"="; } @Override public boolean isMem() { return true; } @@ -39,11 +44,16 @@ public class StoreNode extends MemOpNode { @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 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. diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter13/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/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 9d154413..ca7bb482 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter13/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/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 633b25e0..43e46a5d 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -30,7 +30,7 @@ if (arg) return (arg < a) < 3; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((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(); + // These chapters still bind struct Stores to branch control. + assertEquals(3,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/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter13/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=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/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 41c4d063..7aab9752 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter13/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 @@ -367,7 +367,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,.f,0);", stop.toString()); + assertEquals("return Phi(Region37,.f,0);", stop.toString()); } @Test @@ -473,7 +473,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,Phi(Region37,.f,0));", stop.toString()); + assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); } @Test @@ -524,7 +524,7 @@ else if (arg == 1) s.f = 1; return s.f; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,1,Phi(Region35,1,0));", stop.toString()); + assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java index a1c7a92a..ced5606c 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter13/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/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java index d695c9a6..13ac58f4 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter13/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/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3512d2e9..40a2f78d 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -154,6 +154,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]; @@ -167,8 +168,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;id.users++); + if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias) + od(out).ifPresent(d->d.users++); } } } -- cgit v1.2.3