diff options
| author | Cliff Click <cliffc@acm.org> | 2024-12-31 16:25:28 -0800 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2025-01-15 15:07:30 -0800 |
| commit | bac24802e16c93b9f6295f2dea04a6223c211738 (patch) | |
| tree | a074b7af87ce248e09ba675a9d94fbadc67553e6 | |
| parent | 051cf6520f342003582185fceb4c474e1e7d20dc (diff) | |
| download | Simple-bac24802e16c93b9f6295f2dea04a6223c211738.tar.gz Simple-bac24802e16c93b9f6295f2dea04a6223c211738.zip | |
Compile pipeline deals with functions
Pretty-print functions.
GCM deals with functions.
Tighten linker API.
CallEnd asserts harder proper graph.
Eval2 makes (non recursive) function calls
Peeps infer sharper function return sigs.
19 files changed, 290 insertions, 131 deletions
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java b/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java index 51a6e78e..9588fb01 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/CodeGen.java @@ -20,8 +20,10 @@ public class CodeGen { public StopNode _stop ; - // "Linker" mapping from constant TypeFunPtrs to heads of function - public final HashMap<TypeFunPtr,FunNode> _linker = new HashMap<>(); + // "Linker" mapping from constant TypeFunPtrs to heads of function. These + // TFPs all have exact single fidxs and their return is wiped to BOTTOM (so + // the return is not part of the match). + private final HashMap<TypeFunPtr,FunNode> _linker = new HashMap<>(); // Last created CodeGen as a global, for easier debugging prints @@ -87,13 +89,23 @@ public class CodeGen { return this; } - public CodeGen localSched() { + // Local (basic block) scheduler phase, a classic list scheduler + public CodeGen localSched() { assert _phase == Phase.Schedule; _phase = Phase.LocalSched; ListScheduler.sched(this); return this; } + // Reverse from a constant function pointer to the IR function being called + public FunNode link( TypeFunPtr tfp ) { + assert tfp.isConstant(); + return _linker.get(tfp.makeFrom(Type.BOTTOM)); + } + + public void link(FunNode fun) { + _linker.put(fun.sig().makeFrom(Type.BOTTOM),fun); + } // Testing shortcuts Node ctrl() { return _stop.ret().ctrl(); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index f1593a81..bacbd538 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -24,7 +24,7 @@ public abstract class GlobalCodeMotion { private static void schedEarly(StartNode start) { ArrayList<CFGNode> rpo = new ArrayList<>(); BitSet visit = new BitSet(); - _rpo_cfg(start, visit, rpo); + _rpo_cfg(null, start, visit, rpo); // Reverse Post-Order on CFG for( int j=rpo.size()-1; j>=0; j-- ) { CFGNode cfg = rpo.get(j); @@ -39,12 +39,14 @@ public abstract class GlobalCodeMotion { } // Post-Order of CFG - private static void _rpo_cfg(Node n, BitSet visit, ArrayList<CFGNode> rpo) { - if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) + private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, ArrayList<CFGNode> rpo) { + if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) ) return; // Been there, done that + if( def instanceof CallNode && cfg instanceof FunNode ) + return; // Ignore linked function calls visit.set(cfg._nid); - for( Node use : cfg._outputs ) - _rpo_cfg(use,visit,rpo); + for( Node useuse : cfg._outputs ) + _rpo_cfg(cfg,useuse,visit,rpo); rpo.add(cfg); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter18/src/main/java/com/seaofnodes/simple/IRPrinter.java index adf67d8b..870a890b 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/IRPrinter.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; @@ -31,13 +32,6 @@ public class IRPrinter { return sb.p("\n"); } - private static SB nodeId(SB sb, Node n) { - sb.p("%%%d".formatted(n._nid)); - if (n instanceof ProjNode proj) { - sb.p(".").p(proj._idx); - } - return sb; - } public static String prettyPrint(Node node, int depth) { return CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Schedule.ordinal() @@ -61,14 +55,24 @@ public class IRPrinter { Node n = rpos.get(i); if( n instanceof CFGNode || n.isMultiHead() ) { if( !gap ) sb.p("\n"); // Blank before multihead + if( n instanceof FunNode fun ) { + TypeFunPtr sig = fun.sig(); + sig.print(sb.p("--- ").p(sig._name==null ? "" : sig._name).p(" "),false).p("----------------------\n"); + } printLine( n, sb ); // Print head while( --i >= 0 ) { Node t = rpos.get(i); if( !t.isMultiTail() ) { i++; break; } printLine( t, sb ); } - sb.p("\n"); // Blank after multitail - gap = true; + if( n instanceof ReturnNode ret ) { + TypeFunPtr sig = ret.fun().sig(); + sig.print(sb.p("--- ").p(sig._name==null ? "" : sig._name).p(" "),false).p("----------------------\n"); + } + if( !(n instanceof CallNode) ) { + sb.p("\n"); // Blank after multitail + gap = true; + } } else { printLine( n, sb ); gap = false; @@ -87,15 +91,20 @@ public class IRPrinter { // First walk the CFG, then everything if( n instanceof CFGNode ) { for( Node use : n._outputs ) - if( use instanceof CFGNode && use.nOuts() >= 1 && - !(use._outputs.get( 0 ) instanceof LoopNode) ) + // Follow CFG, not across call/function borders, and not around backedges + if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) && + use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) ) postOrd(use, n, rpos,visit,bfs); for( Node use : n._outputs ) - if( use instanceof CFGNode ) + // Follow CFG, not across call/function borders + if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) ) postOrd(use,n,rpos,visit,bfs); } + // Follow all outputs for( Node use : n._outputs ) - if( use != null ) + if( use != null && + !(n instanceof CallNode && use instanceof FunNode) && + (n instanceof FunNode || !(use instanceof ParmNode)) ) postOrd(use, n, rpos,visit,bfs); // Post-order rpos.add(n); @@ -247,7 +256,9 @@ public class IRPrinter { !(node instanceof LoopNode loop && loop.back()==def) && // Don't walk into or out of functions !(node instanceof CallEndNode && def instanceof ReturnNode) && - !(node instanceof FunNode && def instanceof CallNode) ) + !(node instanceof FunNode && def instanceof CallNode) && + !(node instanceof ParmNode && !(def instanceof FunNode)) + ) _walk(ds,ns,def,d-1); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java index 79d5ce4a..49667ecc 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java @@ -150,32 +150,15 @@ public class Parser { public StopNode parse() { return parse(false); } public StopNode parse(boolean show) { _xScopes.push(_scope); - //// Enter a new scope for the initial control and arguments - //_scope.push(); - //Node ctrl = new CProjNode(START, 0, ScopeNode.CTRL).peephole(); - //Node pmem = new ProjNode(START, 1, ScopeNode.MEM0).peephole(); - //Node arg = new ProjNode(START, 2, ScopeNode.ARG0).peephole(); - //ScopeMinNode mem = new ScopeMinNode(); - //mem.addDef(null); - //mem.addDef(pmem); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null); _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null); - // - //// Call main - //CallNode main = (CallNode)new CallNode(ctrl,pmem,arg,con(TypeFunPtr.MAIN)).peephole(); - //// Call End - //CallEndNode cend = (CallEndNode)new CallEndNode(main).peephole(); - //STOP.addDef(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - //STOP.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - //STOP.addDef(new ProjNode(cend,2,ScopeNode.ARG0).peephole()); + + ctrl(XCTRL); + _scope.mem(new ScopeMinNode(false)); // Parse whole program, as-if function header "{ int arg -> body }" - ReturnNode ret = parseFunctionBody("main",TypeFunPtr.MAIN,"arg"); - CodeGen.CODE._linker.put(TypeFunPtr.MAIN,ret.fun()); - //main.peephole(); - //if( cend.peephole() != null ) - // IterPeeps.addAll(cend._outputs); + ReturnNode ret = parseFunctionBody(TypeFunPtr.MAIN,"arg"); if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); @@ -194,9 +177,12 @@ public class Parser { /** * Parses a function body, assuming the header is parsed. */ - private ReturnNode parseFunctionBody(String name, TypeFunPtr sig, String... ids) { - sig.setName(name); - FunNode fun = _fun = (FunNode)peep(new FunNode(START,sig,name)); + private ReturnNode parseFunctionBody(TypeFunPtr sig, String... ids) { + Node oldctrl = ctrl().keep(); + Node oldmem = _scope.mem().keep(); + FunNode oldfun = _fun; + + FunNode fun = _fun = (FunNode)peep(new FunNode(START,sig)); // Build a multi-exit return point for all function returns RegionNode r = new RegionNode(null,null); @@ -232,11 +218,7 @@ public class Parser { // Last expression is the return if( ctrl()._type==Type.CONTROL ) - STOP.addReturn(ctrl(), _scope.mem().merge(), last, fun); - - // Function scope ends - _scope.pop(); - _fun = null; + fun.addReturn(ctrl(), _scope.mem().merge(), last); // Pop off the inProgress node on the multi-exit Region merge assert r.inProgress(); @@ -249,7 +231,23 @@ public class Parser { ret.setDef(1,rmem.peephole()); ret.setDef(2,rrez.peephole()); ret.setDef(0,r.peephole()); - return (ReturnNode)ret.peephole(); + ret = (ReturnNode)ret.peephole(); + + // Function scope ends + _scope.pop(); + _fun = oldfun; + // Reset control and memory to pre-function parsing days + ctrl(oldctrl.unkeep()); + _scope.mem(oldmem.unkeep()); + + // Once function body is complete, install in linker table. Linker + // matches on declared args and exact fidx, and ignores the return + // (because the fidx will only match the exact single function). The + // normal TFP interning ALSO matches on return, so wipe out the return + // for this arg+fidx only match. + CodeGen.CODE.link(fun); + + return ret; } /** @@ -582,7 +580,7 @@ public class Parser { var expr = require(parseAsgn(), ";"); // Need default memory, since it can be lazy, need to force // a non-lazy Phi - STOP.addReturn(ctrl(), _scope.mem().merge(), expr, _fun); + _fun.addReturn(ctrl(), _scope.mem().merge(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -694,6 +692,8 @@ public class Parser { throw error("a not-null/non-zero expression"); t = expr._type.glb(); } + if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) + tfp.setName(name); } else { if( inferType && !_scope.inCon() ) @@ -1039,7 +1039,7 @@ public class Parser { if( matchx("null" ) ) return NIL; if( match ("(" ) ) return require(parseAsgn(), ")"); if( matchx("new" ) ) return alloc(); - if( match ("{" ) ) return func(); + if( match ("{" ) ) return require(func(),"}"); // Expect an identifier now ScopeMinNode.Var n = requireLookupId("an identifier or expression"); Node rvalue = _scope.in(n); @@ -1191,25 +1191,17 @@ public class Parser { * expr ('.' IDENTIFIER)* [ = expr ] * expr # * expr ('[' expr ']')* = [ = expr ] + * expr '(' [args,]* ')' * </pre> */ private Node parsePostfix(Node expr) { - String name = null; + String name; if( match(".") ) name = requireId(); else if( match("#") ) name = "#"; else if( match("[") ) name = "[]"; + else if( match("(") ) return require(functionCall(expr),")"); else return expr; // No postfix - // Happens when parsing known dead code, which often has other typing - // issues. Since the code is dead, possibly due to inlining, lets not - // spoil the user experience with error messages. - //if( ctrl()._type==Type.XCONTROL ) { - // if( expr.isUnused() ) expr.kill(); // Losing last use of expr - // // Exit out via parsing the trailing expression - // return matchOpx( '=', '=' ) ? parseAsgn() : parsePostfix( con( Type.TOP ) ); - //} - - // if( expr._type==Type.NIL ) throw error("Accessing unknown field '" + name + "' from 'null'"); @@ -1298,7 +1290,7 @@ public class Parser { } /** - * Parse a function; the leading '{' is already parsed. + * Parse a function body; the caller will parse the surrounding "{}" * * <pre> * { [type arg,]* -> expr } @@ -1306,7 +1298,6 @@ public class Parser { * </pre> */ private Node func() { - int old = pos(); // Record lexer position Ary<Type> ts = new Ary<>(Type.class); Ary<String> ids = new Ary<>(String.class); while( true ) { @@ -1320,7 +1311,53 @@ public class Parser { require("->"); // Make a concrete function type, with a fidx TypeFunPtr tfp = TypeFunPtr.makeFun(TypeTuple.make(ts.asAry()),Type.BOTTOM); - return parseFunctionBody(null,tfp,ids.asAry()); + ReturnNode ret = parseFunctionBody(tfp,ids.asAry()); + return con(ret._fun.sig()); + } + + /** + * Parse function call arguments; caller will parse the surrounding "()" + * <pre> + * ( arg* ) + * </pre> + */ + private Node functionCall(Node expr) { + if( expr._type == Type.NIL ) + throw error("Calling a null function pointer"); + if( !(expr._type instanceof TypeFunPtr tfp) ) + throw error("Expected a function but got "+expr._type.str()); + expr.keep(); // Keep while parsing args + + Ary<Node> args = new Ary<Node>(Node.class); + args.push(null); // Space for ctrl,mem + args.push(null); + while( !peek(')') ) { + Node arg = parseAsgn(); + if( arg==null ) break; + args.push(arg.keep()); + if( !match(",") ) break; + } + // Control & memory after parsing args + args.set(0,ctrl().keep()); + args.set(1,_scope.mem().merge().keep()); + args.push(expr); // Function pointer + // Unkeep them all + for( Node arg : args ) + arg.unkeep(); + // Into the call + CallNode call = (CallNode)new CallNode(args.asAry()).peephole(); + + // Post-call setup + CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + // Control from CallEnd + ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); + // Memory from CallEnd + ScopeMinNode mem = new ScopeMinNode(true); + mem.addDef(null); // Alias#0 + mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); + _scope.mem(mem); + // Call result + return new ProjNode(cend,2,null).peephole(); } /** diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 78071920..a3bb1ae9 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -95,22 +95,23 @@ public abstract class CFGNode extends Node { _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); _bltWalk(2,null,stop, new BitSet()); } - private int _bltWalk(int pre, FunNode fun, StopNode stop, BitSet post) { + int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { // Pre-walked? if( _pre!=0 ) return pre; _pre = pre++; // Pre-walk for( Node use : _outputs ) - if( use instanceof CFGNode usecfg && !(usecfg instanceof XCtrlNode) ) + if( use instanceof CFGNode usecfg && !skip(usecfg) ) pre = usecfg._bltWalk(pre,use instanceof FunNode fuse ? fuse : fun,stop,post); // Post-order work: find innermost loop LoopTree inner = null, ltree; for( Node use : _outputs ) { if( !(use instanceof CFGNode usecfg) ) continue; - if( usecfg instanceof XCtrlNode ) continue; - if( usecfg._type == Type.XCONTROL ) continue; // Dead, only possible if peeps disabled - if( usecfg._type == TypeTuple.IF_NEITHER ) continue; // Dead again + if( skip(usecfg) ) continue; + if( usecfg._type == Type.XCONTROL || // Do not walk dead control + usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs + continue; // Child visited but not post-visited? if( !post.get(usecfg._nid) ) { // Must be a backedge to a LoopNode then @@ -143,4 +144,11 @@ public abstract class CFGNode extends Node { post.set(_nid); return pre; } + + boolean skip(CFGNode usecfg) { + // Only walk control users that are alive. + // Do not walk from a Call to linked Fun's. + return usecfg instanceof XCtrlNode || (this instanceof CallNode && usecfg instanceof FunNode); + } + } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 31c245f9..578d7f87 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -3,8 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.type.*; import java.util.BitSet; /** @@ -19,6 +18,7 @@ public class CallEndNode extends CFGNode implements MultiNode { @Override public String label() { return "CallEnd"; } + @Override public boolean isMultiHead() { return true; } @Override public boolean blockHead() { return true; } CallNode call() { return (CallNode)in(0); } @@ -36,10 +36,8 @@ public class CallEndNode extends CFGNode implements MultiNode { @Override public Type compute() { - for( Node n : _inputs ) - if( n!=null && n._type==Type.CONTROL ) - return Type.CONTROL; - return Type.XCONTROL; + Type ret = call().fptr()._type instanceof TypeFunPtr tfp ? tfp.ret() : Type.BOTTOM; + return TypeTuple.make(call()._type,TypeMem.BOT,ret); } @Override diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/CallNode.java index 3ec1cd0c..b836188c 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -21,7 +21,7 @@ public class CallNode extends CFGNode { String fname = null; Node fptr = fptr(); if( fptr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) - fname = CodeGen.CODE._linker.get(tfp)._name; + fname = tfp._name; if( fname==null ) fptr._print0(sb,visited); else sb.append(fname); sb.append("( "); @@ -36,14 +36,32 @@ public class CallNode extends CFGNode { // Args mapped 1-to-1 on inputs Node arg(int idx) { return in(idx); } // args from input 2 to last - Node fptr() { return _inputs.last(); } + public Node fptr() { return _inputs.last(); } // Find the Call End from the Call - CallEndNode cend() { + public CallEndNode cend() { // Always in use slot 0 - return (CallEndNode)out(0); + if( nOuts()>0 && out(0) instanceof CallEndNode cend ) { + assert _cend()==cend; + return cend; + } else { + assert _cend()==null; + return null; + } + } + private CallEndNode _cend() { + CallEndNode cend=null; + for( Node n : _outputs ) + if( n instanceof CallEndNode cend0 ) + { assert cend == null; cend = cend0; } + return cend; } + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. + @Override public CFGNode uctrl() { return cend(); } + + @Override public Type compute() { return ctrl()._type; @@ -51,6 +69,9 @@ public class CallNode extends CFGNode { @Override public Node idealize() { + CallEndNode cend = cend(); + if( cend==null ) return null; // Still building + // Link: call calls target function. Linking makes the target FunNode // point to this Call, and all his Parms point to the call arguments; // also the CallEnd points to the Return. @@ -58,11 +79,12 @@ public class CallNode extends CFGNode { // If fidxs is negative, then infinite unknown functions long fidxs = tfp.fidxs(); if( fidxs > 0 ) { + // Wipe out the return which matching in the linker table // Walk the (63 max) bits and link for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) { int fidx = Long.numberOfTrailingZeros(fidxs); TypeFunPtr tfp0 = tfp.makeFrom(fidx); - FunNode fun = CodeGen.CODE._linker.get(tfp0); + FunNode fun = CodeGen.CODE.link(tfp0); if( fun!=null && !fun._folding && !linked(fun) ) link(fun); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/FunNode.java index 94d7eff7..c6d318bd 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/FunNode.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 com.seaofnodes.simple.type.TypeFunPtr; @@ -9,14 +10,13 @@ public class FunNode extends RegionNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined boolean _folding; - public final TypeFunPtr _sig; // Initial signature - public String _name; // Debug label, optional - private ReturnNode _ret; // Return pointer + private TypeFunPtr _sig; // Initial signature + private ReturnNode _ret; // Return pointer - public FunNode( StartNode start, TypeFunPtr sig, String name ) { super(null,start); _sig=sig; _name=name; } + public FunNode( StartNode start, TypeFunPtr sig ) { super(null,start); _sig=sig; } @Override - public String label() { return _name; } + public String label() { return _sig._name; } // Find the one CFG user from Fun. It's not always the Return, but always // the Return *is* a CFG user of Fun. @@ -28,9 +28,20 @@ public class FunNode extends RegionNode { return null; } + // Cannot create the Return and Fun at the same time; one has to be first. + // So setting the return requires a second step. public void setRet(ReturnNode ret) { _ret=ret; } ReturnNode ret() { assert _ret!=null; return _ret; } + // Signature can improve over time + public TypeFunPtr sig() { return _sig; } + void setSig( TypeFunPtr sig ) { + assert sig.isa(_sig); + if( _sig != sig ) + IterPeeps.add(this); + _sig = sig; + } + @Override public Type compute() { // Only dead if no callers after SCCP @@ -58,4 +69,7 @@ public class FunNode extends RegionNode { public boolean unknownCallers() { return in(1) instanceof StartNode; } @Override public boolean inProgress() { return unknownCallers(); } + + // Add a new function exit point. + public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); } } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java index f139e4ca..784b9146 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -22,9 +22,9 @@ public class ParmNode extends PhiNode { @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( fun()._name.equals("main") && _label.equals("arg") ) + if( "main".equals(fun().sig()._name) && _label.equals("arg") ) return sb.append("arg"); - sb.append(_label).append("("); + sb.append("Parm_").append(_label).append("("); for( Node in : _inputs ) { if (in == null) sb.append("____"); else in._print0(sb, visited); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 9519f5ec..ea211dd2 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -24,7 +24,12 @@ public class ProjNode extends Node implements MultiUse { @Override public int idx() { return _idx; } @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); } + StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( _label != null ) return sb.append(_label); + if( in(0) instanceof CallEndNode cend && cend.call()!=null ) + return cend.call()._print0(sb,visited); + return sb.append("LONELY PROJ"); + } @Override public CFGNode cfg0() { return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0(); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 5232cfbb..adff7a89 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -38,6 +38,9 @@ public class ReturnNode extends CFGNode { return sb.append(";"); } + // No one unique control follows; can be many call end sites + @Override public CFGNode uctrl() { return null; } + @Override public Type compute() { if( inProgress() ) return TypeTuple.RET; // In progress @@ -46,6 +49,14 @@ public class ReturnNode extends CFGNode { @Override public Node idealize() { if( inProgress() ) return null; + + // Upgrade signature based on return type + Type ret = expr()._type; + TypeFunPtr fcn = _fun.sig(); + assert ret.isa(fcn.ret()); + if( ret != fcn.ret() ) + _fun.setSig(fcn.makeFrom(ret)); + // If dead (cant be reached; infinite loop), kill the exit values if( ctrl()._type==Type.XCONTROL && !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) { @@ -54,6 +65,7 @@ public class ReturnNode extends CFGNode { setDef(2,top); return this; } + return null; } @@ -104,8 +116,7 @@ public class ReturnNode extends CFGNode { SB sb = new SB().p("No common type amongst "); if( ti ) sb.p("int and "); if( tf ) sb.p("f64 and "); - if( tp ) sb.p("reference and "); - if( !tp && tn ) sb.p("nil and "); + if( tp || tn ) sb.p("reference and "); return sb.unchar(5).toString(); } } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java index ebf650fc..8482b003 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java @@ -53,7 +53,7 @@ public class ScopeMinNode extends Node { @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("MEM[ "); + sb.append(_inProgress ? "Merge[" : "MEM[ "); for( int j=2; j<nIns(); j++ ) { sb.append(j); sb.append(":"); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java index 008a910f..8210a484 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -37,7 +37,7 @@ public class StartNode extends LoopNode implements MultiNode { // Find "main", its the start. CFGNode C = null; for( Node use : _outputs ) - if( use instanceof FunNode fun && fun._sig==TypeFunPtr.MAIN ) + if( use instanceof FunNode fun && fun.sig().isa(TypeFunPtr.MAIN) ) { assert C==null; C = fun; } return C; } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java index fb78f720..009296e8 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -60,12 +60,4 @@ public class StopNode extends CFGNode { d = Math.max(d,((ReturnNode)ret).idepth()+1); return _idepth=d; } - - // Add a new function exit point. Validates Parsers function borders, then - // hands off to the guaranteed return. - public void addReturn(Node ctrl, Node mem, Node rez, FunNode fun) { - ReturnNode ret = (ReturnNode)_inputs.last(); - assert ret.fun()==fun && ret.inProgress(); - ret.addReturn(ctrl,mem,rez); - } } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter18/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 6acd6798..d5652f12 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -37,8 +37,9 @@ public class TypeFunPtr extends TypeNil { static TypeFunPtr make( byte nil, TypeTuple sig, Type ret, long fidxs ) { return new TypeFunPtr(nil,sig,ret,fidxs).intern(); } public static TypeFunPtr make( boolean nil, TypeTuple sig, Type ret ) { return make((byte)(nil ? 3 : 2),sig,ret,-1); } - @Override TypeFunPtr makeFrom( byte nil ) { return nil==_nil ? this : make(nil,_sig,_ret,_fidxs); } - public TypeFunPtr makeFrom( int fidx ) { return make(_nil,_sig,_ret,1L<<fidx); } + @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_sig,_ret, _fidxs).setName(_name); } + public TypeFunPtr makeFrom( int fidx ) { return 1L<<fidx==_fidxs ? this : make(_nil,_sig,_ret,1L<<fidx ).setName(_name); } + public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make(_nil,_sig, ret, _fidxs).setName(_name); } // Compute "function indices": FIDX private static final HashMap<TypeTuple,Integer> FIDXS = new HashMap<>(); @@ -56,7 +57,7 @@ public class TypeFunPtr extends TypeNil { public static TypeFunPtr BOT = make((byte)3,TypeTuple.BOT,Type.BOTTOM,-1); public static TypeFunPtr TEST = make((byte)2,TypeTuple.TEST,TypeInteger.BOT,1); public static TypeFunPtr TEST0 = make((byte)3,TypeTuple.TEST,TypeInteger.BOT,3); - public static TypeFunPtr MAIN = makeFun(TypeTuple.MAIN,Type.BOTTOM); + public static TypeFunPtr MAIN = makeFun(TypeTuple.MAIN,Type.BOTTOM).setName("main"); public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); } @Override @@ -71,7 +72,7 @@ public class TypeFunPtr extends TypeNil { // RHS is NIL @Override public Type meet0() { return _nil==3 ? this : make((byte)3,_sig,_ret,_fidxs); } - @Override public TypeFunPtr glb() { throw Utils.TODO(); } + @Override public TypeFunPtr glb() { return make(_nil,_sig,_ret,-1); } @Override public boolean isConstant() { return _nil==2 && Long.bitCount(_fidxs)==1; } @@ -79,6 +80,7 @@ public class TypeFunPtr extends TypeNil { public Type arg(int i) { return _sig._types[i]; } public long fidxs() { return _fidxs; } + public Type ret() { return _ret; } @Override int hash() { return Utils.fold(_sig.hashCode() ^ _ret.hashCode() ^ _fidxs ^ super.hash()); } @@ -89,19 +91,22 @@ public class TypeFunPtr extends TypeNil { return _sig == ptr._sig && _ret == ptr._ret && _fidxs == ptr._fidxs && super.eq(ptr); } - public void setName(String name) { + public TypeFunPtr setName(String name) { + if( name==null ) return this; assert _name==null || _name.equals(name); assert _fidxs > 0 && Long.bitCount(_fidxs) == 1; _name = name; + return this; } @Override public String str() { return print(new SB()).toString(); } - @Override public SB print(SB sb) { return _print(sb,false); } - @Override public SB gprint(SB sb) { return _print(sb,true ); } + @Override public SB print(SB sb) { return _print(sb,false,true); } + public SB print(SB sb, boolean n) { return _print(sb,false,n); } + @Override public SB gprint(SB sb) { return _print(sb,true ,true); } private static SB _print(SB sb, boolean g, Type t) { return g ? t.gprint(sb) : t.print(sb); } - private SB _print(SB sb, boolean g) { + private SB _print(SB sb, boolean g, boolean n) { sb.p(x()).p("{ "); - if( _name!=null ) sb.p(_name); + if( n && _name!=null ) sb.p(_name); else { for( Type t : _sig._types ) _print(sb,g,t).p(" "); diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 7063ef26..6fc2cc98 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -51,17 +51,29 @@ return x2; assertEquals("null", Eval2.eval(code, 0 ) ); } - @Ignore @Test + @Test public void testFcn0() { CodeGen code = new CodeGen(""" {int -> int}? sq = { int x -> - x*x + x*x; }; return sq; """); code.parse().opto(); - assertEquals("return fcn;", code._stop.toString()); - assertEquals("1", Eval2.eval(code, 0)); + assertEquals("Stop[ return { sq}; return (Parm_x(sq,int)*x); ]", code._stop.toString()); + assertEquals("{ sq}", Eval2.eval(code, 3)); } + @Test + public void testFcn1() { + CodeGen code = new CodeGen(""" +var sq = { int x -> + x*x; +}; +return sq(2)+sq(3); +"""); + code.parse().opto(); + assertEquals("Stop[ return (sq( 3)+sq( 2)); return (Parm_x(sq,int,3,2)*x); ]", code._stop.toString()); + assertEquals("13", Eval2.eval(code, 0)); + } } diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java index 0e5e86a4..ea69bc52 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java @@ -4,6 +4,7 @@ import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.node.*; import java.util.Arrays; +import java.util.Stack; import java.util.IdentityHashMap; import java.util.Objects; @@ -30,6 +31,9 @@ public abstract class Eval2 { // Phi cache; size is limited to number of Phis at a merge point Object[] phiCache = new Object[256]; + Stack<CallEndNode> stack = new Stack<>(); + + // Evaluate until we return or timeout. Each outer loop step computes // all data nodes under some new Control. SB sb = null; // new SB(); // TRACE @@ -68,27 +72,46 @@ public abstract class Eval2 { prior = C; C = C.uctrl(); // Next unique control target switch( prior ) { - case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout - case LoopNode loop: if( loopCnt++ > timeout ) return null; break; // Timeout - case IfNode iff: C = iff.cproj( val(iff.pred())==null || (val(iff.pred()) instanceof Long x && x==0L) ? 1 : 0); break; - case RegionNode r: break; + case CallNode call : C = call(code,call,stack); break; + case CallEndNode cend: break; case CProjNode cproj: break; - // TODO: Need a Call-stack here - case ReturnNode ret: return prettyPrint(ret.expr()._type,val(ret.expr())); + case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout + case IfNode iff : Object p = val(iff.pred()); C = iff.cproj( p==null || (p instanceof Long x && x==0L) ? 1 : 0); break; + case LoopNode loop : if( loopCnt++ > timeout ) return null; break; // Timeout + case RegionNode r : break; + case ReturnNode ret : + Node expr = ret.expr(); + if( stack.isEmpty() ) + return prettyPrint(expr._type,val(expr)); + DATA[ret._nid] = val(expr); + C = stack.pop(); + DATA[C._nid] = DATA[ret._nid]; + break; default: throw Utils.TODO(); } } + } + + // Make a call. + private static CFGNode call( CodeGen code, CallNode call, Stack<CallEndNode> stack ) { + stack.push(call.cend()); + // TODO: Stack and unstack all DATA boxes readable by recursive functions + return code.link(tfp(call.fptr())); + } + + private static String exit(ReturnNode ret) { + return prettyPrint(ret.expr()._type,val(ret.expr())); } private static boolean isArg( PhiNode phi ) { // Arg#2 into main. // TODO: Needs some love for recursive main - return phi instanceof ParmNode parm && parm.fun()._sig==TypeFunPtr.MAIN && parm._idx==2; + return phi instanceof ParmNode parm && parm.fun().sig().isa(TypeFunPtr.MAIN) && parm._idx==2; } // Take a worklist step: one data op from the current Control is updated. - private static void step(Node n, SB sb, IdentityHashMap visit) { + private static void step(Node n, SB sb, IdentityHashMap<Object,Object> visit) { // Only data nodes if( n instanceof CFGNode ) return; // Compute new value @@ -150,13 +173,19 @@ public abstract class Eval2 { static long x( Node n ) { return DATA[n._nid]==null ? 0 : (Long) DATA[n._nid]; } // Fetch and unbox as primitive double static double d( Node n ) { return DATA[n._nid]==null ? 0 : (Double)DATA[n._nid]; } + // Fetch and unbox a TypeFunPtr + static TypeFunPtr tfp(Node n) { return (TypeFunPtr)DATA[n._nid]; } // Java box ints and floats. Other things can just be null or some informative string private static Object con( Type t ) { if( t instanceof TypeInteger i ) return i.isConstant() ? (Long)i.value() : "INT"; if( t instanceof TypeFloat f ) return f.isConstant() ? (Double)f.value() : "FLT"; - // + if( t instanceof TypeMemPtr tmp ) + throw Utils.TODO(); + if( t instanceof TypeFunPtr tfp ) + { assert tfp.isConstant(); return tfp; } + // Things like Type.NIL, or TypeMem return null; } @@ -223,8 +252,8 @@ public abstract class Eval2 { // Use type to print x as a string - static String prettyPrint( Type t, Object x ) { return _print(t,x,new SB(), new IdentityHashMap()).toString(); } - static SB _print( Type t, Object x, SB sb, IdentityHashMap visit ) { + static String prettyPrint( Type t, Object x ) { return _print(t,x,new SB(), new IdentityHashMap<>()).toString(); } + static SB _print( Type t, Object x, SB sb, IdentityHashMap<Object,Object> visit ) { if( x instanceof String s ) return sb.p(s); if( x == null ) return sb.p("null"); return switch( t ) { @@ -261,6 +290,7 @@ public abstract class Eval2 { } yield sb; } + case TypeFunPtr tfp -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) diff --git a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index ff9eb1b1..8e25b133 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -344,7 +344,7 @@ public class Evaluator { assert block != null; for (; i < block.nodes().length; i++) { if (!(block.nodes()[i] instanceof PhiNode phi)) break; - var val = region instanceof FunNode fun && fun._name.equals("main") && ((ParmNode)phi)._idx==2 && exit==1 + var val = region instanceof FunNode fun && "main".equals(fun.sig()._name) && ((ParmNode)phi)._idx==2 && exit==1 ? parameter : val(phi.in(exit)); phiCache.add(val); diff --git a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 3cf63e0f..52d8f3e3 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -503,12 +503,12 @@ public class Scheduler { /** * Find the CFG output of a node * @param node The node - * @return THe CFG output of the node. + * @return The CFG output of the node. */ private static CFGNode findSingleCFGOut(Node node) { if (node instanceof StartNode) { for(var n:node._outputs) - if (n instanceof FunNode fun && fun._name.equals("main")) + if (n instanceof FunNode fun && "main".equals(fun.sig()._name) ) return fun; return null; } |
