diff options
| author | Cliff Click <cliffc@acm.org> | 2024-11-26 11:01:47 -0800 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-08 16:52:27 -0700 |
| commit | 84a70fd744f83e2d701e15fc3d17109eb59ac707 (patch) | |
| tree | 493c241c2142d1244639be8cc4a41ffc0b51d4ab /src/main/java/com/seaofnodes | |
| parent | 19b9102cf7290de1e70c962e556caccc4e0fd5d9 (diff) | |
| download | Simple-84a70fd744f83e2d701e15fc3d17109eb59ac707.tar.gz Simple-84a70fd744f83e2d701e15fc3d17109eb59ac707.zip | |
Chapter 18: Functions and Calls
Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com>
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: tannal <tannal2409@gmail.com>
Co-authored-by: hadha <helsgithub15@gmail.com>
Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com>
Co-authored-by: Symious <14933944+symious@users.noreply.github.com>
Co-authored-by: RobertObkircher <robert.obkircher@gmail.com>
Co-authored-by: Jun Kawasaki <jun784@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/main/java/com/seaofnodes')
72 files changed, 2752 insertions, 940 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Ary.java b/src/main/java/com/seaofnodes/simple/Ary.java index 8654d41e..b5184948 100644 --- a/src/main/java/com/seaofnodes/simple/Ary.java +++ b/src/main/java/com/seaofnodes/simple/Ary.java @@ -57,18 +57,6 @@ public class Ary<E> extends AbstractList<E> implements List<E> { return true; } - ///** Slow, linear-time, element insert. Preserves order. - // * @param i index to insert at, between 0 and _len inclusive. - // * @param e Element to insert - // */ - //public void insert( int i, E e ) { - // if( i < 0 || i>_len ) - // throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); - // if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); - // System.arraycopy(_es,i,_es,i+1,(_len++)-i); - // _es[i] = e; - //} - /** Fast, constant-time, element removal. Does not preserve order * @param i element to be removed * @return element removed */ @@ -79,20 +67,6 @@ public class Ary<E> extends AbstractList<E> implements List<E> { return tmp; } - ///** Element removal, using '=='. Does not preserve order. - // * @param e element to be removed - // * @return element removed */ - //public E del( E e ) { - // for( int i=0; i<_len; i++ ) { - // E tmp = _es[i]; - // if( tmp==e ) { - // _es[i]=_es[--_len]; - // return tmp; - // } - // } - // return null; - //} - /** Slow, linear-time, element removal. Preserves order. * @param i element to be removed * @return element removed */ @@ -129,6 +103,16 @@ public class Ary<E> extends AbstractList<E> implements List<E> { return this; } + public void swap( int i, int j ) { + range_check(i); + range_check(j); + if( i==j ) return; + E tmp = _es[i]; + _es[i] = _es[j]; + _es[j] = tmp; + } + + /** @param c Collection to be added */ public boolean addAll( Collection<? extends E> c ) { if( c==null ) return false; diff --git a/src/main/java/com/seaofnodes/simple/CodeGen.java b/src/main/java/com/seaofnodes/simple/CodeGen.java new file mode 100644 index 00000000..864b3a1a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/CodeGen.java @@ -0,0 +1,137 @@ + package com.seaofnodes.simple; + +import com.seaofnodes.graph.GraphObserver; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import java.util.HashMap; + +public class CodeGen { + public GraphObserver<Node> _obs; + + // Inlining can insert CFG edges and invalidate cached depth ordering. + private char _iDepthVersion; + public char iDepthVersion() { return _iDepthVersion; } + public void invalidateIDepthCaches() { + assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535"; + ++_iDepthVersion; + } + + + + public enum Phase { + Parse, Opto, TypeCheck, Schedule, LocalSched, RegAlloc; + } + public Phase _phase; + + public final String _src; + // Compile-time known initial argument type + public final TypeInteger _arg; + + public Parser P; + public StartNode _start; + public StopNode _stop ; + + + // "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 + public static CodeGen CODE; + + CodeGen( String src, TypeInteger arg ) { _phase = null; _src = src; _arg=arg; CODE=this; } + public CodeGen( String src ) { this(src,TypeInteger.BOT); } + + public CodeGen parse() { + assert _phase == null; + _phase = Phase.Parse; + _linker.clear(); + + P = new Parser(_src,_arg); + _stop = P.parse(); + _start = Parser.START; // Capture global start + if( _obs != null ) _obs.phase("Parse"); + P = null; // No longer parsing + return this; + } + + public CodeGen opto() { + assert _phase == Phase.Parse; + _phase = Phase.Opto; + + // Optimization + IterPeeps.iterate(_stop); + + // TODO: + // Optimistic + if( _obs != null ) _obs.phase("Opto"); + return this; + } + + public CodeGen typeCheck() { + // Demand phase Opto for cleaning up dead control flow at least, + // required for the following GCM. + assert _phase == Phase.Opto; + _phase = Phase.TypeCheck; + + // Type check + Parser.ParseException err = _stop.walk(n -> { + Parser.ParseException msg = n.err(); + if( msg != null && _obs != null ) _obs.error(n, msg.getMessage()); + return msg; + }); + if( err != null ) + throw err; + if( _obs != null ) _obs.phase("TypeCheck"); + return this; + } + + + public CodeGen GCM() { + assert _phase == Phase.TypeCheck; + _phase = Phase.Schedule; + + // Build the loop tree, fix never-exit loops + _start.buildLoopTree(_stop); + + + // TODO: + // loop unroll, peel, RCE, etc + GlobalCodeMotion.buildCFG(_start,_stop); + return this; + } + + // 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(); } + Node expr() { return _stop.ret().expr(); } + String print() { return _stop.print(); } + + // Debugging helper + @Override public String toString() { + return IRPrinter.prettyPrint(this); + } + + // Debugging helper + public Node f(int idx) { return _stop.find(idx); } +} diff --git a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index eba99128..8221ede1 100644 --- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -10,10 +10,60 @@ public abstract class GlobalCodeMotion { // Arrange that the existing isCFG() Nodes form a valid CFG. The // Node.use(0) is always a block tail (either IfNode or head of the // following block). There are no unreachable infinite loops. - public static void buildCFG( StopNode stop ) { - schedEarly(); - Parser.SCHEDULED = true; - schedLate( stop); + public static void buildCFG( StartNode start, StopNode stop ) { + schedEarly(start); + breakUpGlobalConstants(start); + schedLate(stop); + } + + + // After early scheduling, every global chain member has Start in slot 0. + // Give each function one private copy of the entire dependency graph. + private static void breakUpGlobalConstants( StartNode start ) { + var globals = new IdentityHashMap<Node,Boolean>(); + var cons = new ArrayList<Node>(); + for( Node con : start._outputs ) + if( con!=null && !(con instanceof CFGNode) && + con.isConst() ) { + globals.put(con,true); + cons.add(con.keep()); // Preserve original inputs while rewiring users. + } + + var copies = new IdentityHashMap<FunNode,IdentityHashMap<Node,Node>>(); + for( Node con : cons ) + for( Node use : con._outputs.asAry() ) { + if( use==null || globals.containsKey(use) ) continue; + for( int i=0; i<use.nIns(); i++ ) { + if( use.in(i)!=con ) continue; + FunNode fun = useFun(use,i); + if( fun==null ) continue; // Unknown-caller types stay global. + var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>()); + use.setDef(i,cloneGlobal(con,fun,globals,local)); + } + } + for( Node con : cons ) con.unkill(); + } + + private static Node cloneGlobal(Node con, FunNode fun, IdentityHashMap<Node,Boolean> globals, + IdentityHashMap<Node,Node> copies) { + Node copy = copies.get(con); + if( copy!=null ) return copy; + copies.put(con,copy=con.copyEmpty()); + copy.addDef(fun); + for( int i=1; i<con.nIns(); i++ ) { + Node def = con.in(i); + copy.addDef(globals.containsKey(def) ? cloneGlobal(def,fun,globals,copies) : def); + } + return copy; + } + + private static FunNode useFun(Node use, int idx) { + if( use instanceof ReturnNode ret ) return ret.fun(); + // Calls are still linked: a Parm input is evaluated in its caller. + CFGNode cfg = use instanceof ParmNode parm ? parm.fun().cfg(idx) : use.cfg0(); + while( cfg!=null && !(cfg instanceof FunNode) ) + cfg = cfg.idom(); + return (FunNode)cfg; } @@ -22,10 +72,10 @@ public abstract class GlobalCodeMotion { // (except at loops). Since defs are visited first - and hoisted as early // as possible, when we come to a use we place it just after its deepest // input. - private static void schedEarly() { + private static void schedEarly(StartNode start) { ArrayList<CFGNode> rpo = new ArrayList<>(); BitSet visit = new BitSet(); - _rpo_cfg(Parser.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); @@ -43,12 +93,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); } @@ -166,6 +218,7 @@ public abstract class GlobalCodeMotion { for( int i=1; i<phi.nIns(); i++ ) if( phi.in(i)==n ) found = phi.region().cfg(i).domLCA(found,null); // Can be more than one matching input. + assert found!=null; return found; } @@ -202,7 +255,7 @@ public abstract class GlobalCodeMotion { assert late[st._nid]!=null; lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; - case NewNode st: + case CallNode st: assert late[st._nid]!=null; lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; @@ -213,6 +266,10 @@ 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 NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -221,14 +278,12 @@ public abstract class GlobalCodeMotion { // private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { - // Preserve the full store range for the earlier evaluator scheduler. - // It places nodes independently of GCM and may hoist this store. for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well + if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved, + st.addDef(load); // Add anti-dep as well; Calls already end their block. return lca; // Cap this stores' anti-dep to here } } diff --git a/src/main/java/com/seaofnodes/simple/IRPrinter.java b/src/main/java/com/seaofnodes/simple/IRPrinter.java index 7e93e9da..c7ca8cf4 100644 --- a/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ b/src/main/java/com/seaofnodes/simple/IRPrinter.java @@ -2,6 +2,9 @@ package com.seaofnodes.simple; import com.seaofnodes.print.IRAdapter; import com.seaofnodes.simple.node.*; +import java.util.ArrayList; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.CodeGen; /** This chapter's read-only IR facts and the familiar debugger entry points. */ public final class IRPrinter extends IRAdapter<Node> { @@ -12,9 +15,13 @@ public final class IRPrinter extends IRAdapter<Node> { @Override public Kind kind(Node n) { if( n instanceof StartNode ) return Kind.START; if( n instanceof StopNode ) return Kind.STOP; + if( n instanceof FunNode ) return Kind.FUN; if( n instanceof LoopNode ) return Kind.LOOP; if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof ParmNode ) return Kind.PARM; if( n instanceof PhiNode ) return Kind.PHI; + if( n instanceof CallEndNode ) return Kind.CALL_END; + if( n instanceof CallNode ) return Kind.CALL; if( n instanceof ReturnNode ) return Kind.RETURN; if( n instanceof ConstantNode ) return Kind.CONSTANT; if( n instanceof CProjNode ) return Kind.CPROJ; @@ -28,6 +35,20 @@ public final class IRPrinter extends IRAdapter<Node> { if( n instanceof CProjNode p ) return p._idx; return n._nid; } + @Override public String functionName(Node n) { return n.label(); } + @Override public String signature(Node n) { + FunNode fun=(FunNode)n; + return fun.sig().str(); + } public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); } + public static SB printLine(Node n, SB sb) { return sb.p(PRINT.line(n)); } + + public static String prettyPrint(CodeGen code) { + var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>(); + var funs=new ArrayList<Node>(); + for( Node n : code._start._outputs ) if( n instanceof FunNode ) funs.add(n); + units.add(new com.seaofnodes.print.IRPrinter.Unit<>(null,null,null,funs)); + return PRINT.program(code._start,code._stop,units); + } } diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index 5a80698a..1792dada 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -57,7 +57,7 @@ public abstract class IterPeeps { /** * Iterate peepholes to a fixed point */ - public static StopNode iterate(StopNode stop) { + public static void iterate( StopNode stop) { assert progressOnList(stop); int cnt=0; @@ -65,7 +65,7 @@ public abstract class IterPeeps { while( (n=WORK.pop()) != null ) { if( n.isDead() ) continue; cnt++; // Useful for debugging, searching which peephole broke things - var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + var obs = CodeGen.CODE == null ? null : CodeGen.CODE._obs; if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( x != null ) { @@ -98,7 +98,6 @@ public abstract class IterPeeps { if( obs != null ) obs.after(n, x, true); } - return stop; } // Visit ALL nodes and confirm the invariant: @@ -120,7 +119,7 @@ public abstract class IterPeeps { int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; Node changed = stop.walk( n -> { Node m = n; - if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=5) ) { // Types must be forwards, even if on worklist + if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=6) ) { // Types must be forwards, even if on worklist if( WORK.on(n) ) return null; m = n.peepholeOpt(); if( m==null ) return null; @@ -188,6 +187,7 @@ public abstract class IterPeeps { * True if Node is on the WorkList */ boolean on( E x ) { return _on.get(x._nid); } + boolean isEmpty() { return _len==0; } /** * Removes a random Node from the WorkList; null if WorkList is empty diff --git a/src/main/java/com/seaofnodes/simple/ListScheduler.java b/src/main/java/com/seaofnodes/simple/ListScheduler.java new file mode 100644 index 00000000..252a8f8e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/ListScheduler.java @@ -0,0 +1,110 @@ + package com.seaofnodes.simple; + +import com.seaofnodes.simple.IterPeeps.WorkList; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import java.util.*; + +public abstract class ListScheduler { + + private static final BitSet VISIT = new BitSet(); + private static int[] BCNTS; + public static void sched( CodeGen code ) { + assert VISIT.isEmpty(); + BCNTS = new int[Node.UID()]; + walk(code._start,VISIT); + VISIT.clear(); + } + + // Visit all CFG nodes + static void walk( CFGNode cfg, BitSet visit ) { + if( visit.get(cfg._nid) ) return; + visit.set(cfg._nid); + local(cfg); + for( Node n : cfg._outputs ) + if( n instanceof CFGNode c ) + walk(c,visit); + } + + // Local schedule this one CFG + static void local( CFGNode bb ) { + if( !bb.blockHead() || bb instanceof StopNode ) return; + Node[] uses = bb._outputs._es; + int ulen = bb._outputs._len; + + // Count block-locals + for( int i=0; i<ulen; i++ ) { + Node use = uses[i]; + if( use.in(0)==bb && !(use instanceof PhiNode) ) { + // Normal basic block member: in(0)==bb and not a Phi + for( Node def : use._inputs ) + // Has defs from same block? Then block-local inputs + if( def != null && def.cfg0()==bb ) // Same block? + BCNTS[use._nid]++; // Raise block-local input count + } + } + + // Nodes are ready if they are not used by other nodes in this block. + // Move ready up front. We will never look at remaining nodes, later + // nodes are found by looking at users of ready nodes. + int ready = 0; + for( int i=0; i<ulen; i++ ) + if( BCNTS[uses[i]._nid]==0 ) // No block-local inputs? + uses[ready++] = uses[i]; // Move into ready set + assert ready > 0; + + // Classic list scheduler. Behind sched is scheduled. Between sched + // and ready have zero counts and are ready to schedule. Ahead of + // ready is undefined. + // + // As nodes are scheduled, the nodes they use decrement their _bcnt. Once + // that gets to 0, all the nodes using it are scheduled, so it's ready. + int sched = 0; + while( sched < ulen ) { + assert sched < ready : "Scheduling nodes that are not ready. Probably due to duplicate nodes in the user list."; + int pick = best(uses, sched,ready); // Pick best + Node best = uses[pick]; // Swap best to head of ready + uses[pick] = uses[sched]; + uses[sched++] = best; // And move it into the scheduled set + // Lower ready count of users + for( Node use : best._outputs ) + if( use instanceof ProjNode ) { + for( Node useuse : use._outputs ) + ready = ready(bb,useuse,ready); + } else + ready = ready(bb, use,ready); + } + } + + private static int ready(CFGNode bb, Node use, int ready) { + if( use != null && use.in(0)==bb && !(use instanceof PhiNode) ) { + assert BCNTS[use._nid] >= 1; + if( --BCNTS[use._nid] == 0 ) + bb._outputs._es[ready++] = use; // Became ready, move into ready set + } + return ready; + } + + + // Pick best between sched and ready. + private static int best( Node[] uses, int sched, int ready ) { + int pick = sched; + int score = score(uses[pick]); + for( int i=sched+1; i<ready; i++ ) { + int nscore = score(uses[i]); + if( nscore > score ) + { score = nscore; pick = i; } + } + return pick; + } + + // Highest score wins. Max at 1000, min at 10, except specials. + private static int score( Node n ) { + if( n.isMultiTail() ) return 1001; // Pinned just behind the multi-head + if( n instanceof CFGNode ) return n instanceof ReturnNode ? 2 : 1; // Pinned at block exit + + // Nothing special + return 500; + } + +} diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index c02f0305..82ad8d04 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,10 +1,7 @@ package com.seaofnodes.simple; -import com.seaofnodes.graph.GraphObserver; - import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; - import java.util.*; /** @@ -16,12 +13,6 @@ import java.util.*; */ public class Parser { - // Current compilation context; the observer belongs to the parser. - public static Parser PARSER; - public GraphObserver<Node> _obs; - public int pos() { return _lexer._position; } - - /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -32,6 +23,7 @@ public class Parser { public static StartNode START; public static ConstantNode ZERO; // Very common node, cached here + public static ConstantNode NIL; // Very common node, cached here public static XCtrlNode XCTRL; // Very common node, cached here // Next available memory alias number @@ -39,9 +31,6 @@ public class Parser { public StopNode STOP; - // Debugger Printing. - public static boolean SCHEDULED; // True if debug printer can use schedule info - // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; @@ -90,7 +79,6 @@ public class Parser { add("while"); }}; - /** * We clone ScopeNodes when control flows branch; it is useful to have * a list of all active ScopeNodes for purposes of visualization of the SoN graph @@ -99,6 +87,7 @@ public class Parser { ScopeNode _continueScope; ScopeNode _breakScope; // Merge all the while-breaks here + FunNode _fun; // Current function being parsed // Mapping from a type name to a Type. The string name matches // `type.str()` call. No TypeMemPtrs are in here, because Simple does not @@ -109,27 +98,25 @@ public class Parser { public final HashMap<String, StructNode> INITS; + public Parser(String source) { this(source, TypeInteger.BOT); } + public Parser(String source, TypeInteger arg) { - PARSER = this; Node.reset(); IterPeeps.reset(); - SCHEDULED = false; + Type.reset(); _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; START = new StartNode(arg); STOP = new StopNode(source); - ZERO = con(0).keep(); + ZERO = con(TypeInteger.ZERO).keep(); + NIL = con(Type.NIL).keep(); XCTRL= new XCtrlNode().peephole().keep(); ALIAS = 2; // alias 0 for the control, 1 for memory TYPES = defaultTypes(); INITS = new HashMap<>(); } - public Parser(String source) { - this(source, TypeInteger.BOT); - } - @Override public String toString() { return _lexer.toString(); } @@ -137,9 +124,9 @@ public class Parser { return new HashMap<>() {{ put("bool",TypeInteger.U1 ); put("byte",TypeInteger.U8 ); - put("f32" ,TypeFloat .B32); - put("f64" ,TypeFloat .BOT); - put("flt" ,TypeFloat .BOT); + put("f32" ,TypeFloat .F32); + put("f64" ,TypeFloat .F64); + put("flt" ,TypeFloat .F64); put("i16" ,TypeInteger.I16); put("i32" ,TypeInteger.I32); put("i64" ,TypeInteger.BOT); @@ -162,28 +149,111 @@ public class Parser { public StopNode parse() { _xScopes.push(_scope); - // Enter a new scope for the initial control and arguments - _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, new ProjNode(START, 1, ScopeNode.MEM0).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); + _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); - // Parse whole program - parseBlock(false); + ctrl(XCTRL); + _scope.mem(con(TypeMem.BOT)); - if( ctrl()._type==Type.CONTROL ) - STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole()); - _scope.kill(); + // Parse whole program, as-if function header "{ int arg -> body }" + parseFunctionBody(TypeFunPtr.MAIN,loc(),"arg"); + + if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); + + // Clean up and reset _xScopes.pop(); + _scope.kill(); for( StructNode init : INITS.values() ) init.unkeep().kill(); INITS.clear(); - if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); return STOP; } /** + * Parses a function body, assuming the header is parsed. + */ + private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, String... ids) { + return parseFunctionBody(sig, loc, null, ids); + } + + private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String... ids) { + // Stack parser state on the local Java stack, and unstack it later + Node oldctrl = ctrl().keep(); + Node oldmem = _scope.mem().keep(); + FunNode oldfun = _fun; + ScopeNode breakScope = _breakScope; _breakScope = null; + ScopeNode continueScope = _continueScope; _continueScope = null; + + FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),START,sig)); + // Once the function header is available, install in linker table - + // allowing recursive functions. Linker matches on declared args and + // exact fidx, and ignores the return (because the fidx will only match + // the exact single function). + CodeGen.CODE.link(fun); + + Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole(); + + // Build a multi-exit return point for all function returns + RegionNode r = new RegionNode((Node)null,null).init(); + assert r.inProgress(); + 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); + assert ret.inProgress(); + STOP.addDef(ret); + + // Pre-call the function from Start, with worse-case arguments. This + // represents all the future, yet-to-be-parsed functions calls and + // external calls. + _scope.push(ScopeNode.Kind.Function); + ctrl(fun); // Scope control from function + // 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); + _scope.define(ids[i], t, finals==null ? t instanceof TypeNil : finals.get(i), new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc); + } + + // Parse the body + Node last=ZERO; + while (!peek('}') && !_lexer.isEOF()) + last = parseStatement(); + + // Last expression is the return + if( ctrl()._type==Type.CONTROL ) + fun.addReturn(ctrl(), _scope.mem(), last); + + // Pop off the inProgress node on the multi-exit Region merge + assert r.inProgress(); + r ._inputs.pop(); + rmem._inputs.pop(); + rrez._inputs.pop(); + r._loc = loc(); // Final position + assert !r.inProgress(); + + // Force peeps, which have been avoided due to inProgress + ret.setDef(1,rmem.peephole()); + ret.setDef(2,rrez.peephole()); + ret.setDef(0,r.peephole()); + ret = (ReturnNode)ret.peephole(); + + // Function scope ends + _scope.pop(); + _fun = oldfun; + _breakScope = breakScope; + _continueScope = continueScope; + // Reset control and memory to pre-function parsing days + ctrl(oldctrl.unkeep()); + _scope.mem(oldmem.unkeep()); + + return ret; + } + + /** * Parses a block * * <pre> @@ -192,14 +262,16 @@ public class Parser { * Does not parse the opening or closing '{}' * @return a {@link Node} or {@code null} */ - private Node parseBlock(boolean inCon) { + private Node parseBlock(ScopeNode.Kind kind) { // Enter a new scope - _scope.push(inCon); + _scope.push(kind); + Node last = ZERO; while (!peek('}') && !_lexer.isEOF()) - parseStatement(); + last = parseStatement(); // Exit scope + last.keep(); _scope.pop(); - return null; + return last.unkeep(); } /** @@ -213,7 +285,6 @@ public class Parser { private Node parseStatement() { if( false ) return null; else if (matchx("return") ) return parseReturn(); - else if (match ("{") ) return require(parseBlock(false),"}"); else if (matchx("if") ) return parseIf(); else if (matchx("while") ) return parseWhile(); else if (matchx("for") ) return parseFor(); @@ -221,6 +292,11 @@ public class Parser { else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); else if (matchx(";") ) return null; // Empty statement + // Break ambiguity around leading function types and starting a block + else if (peek('{') && !isTypeFun() ) { + match("{"); + return require(parseBlock(ScopeNode.Kind.Block),"}"); + } // Declaration or normal assignment/expression else return parseDeclarationStatement(); } @@ -249,13 +325,13 @@ public class Parser { */ private Node parseFor() { // { var x=init,y=init,...; - // while( pred ) { + // while( test ) { // body; // next; // } // } require("("); - _scope.push(); // Scope for the index variables + _scope.push(ScopeNode.Kind.Block); // Scope for the index variables if( !match(";") ) // Can be empty init "for(;test;next) body" parseDeclarationStatement(); // Non-empty init Node rez = parseLooping(true); @@ -275,7 +351,7 @@ public class Parser { // used as an indicator to switch off peepholes of the region and // associated phis; see {@code inProgress()}. - ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here + ctrl(new LoopNode(loc(),ctrl()).peephole()); // Note we set back edge to null here // At loop head, we clone the current Scope (this includes all // names in every nesting level within the Scope). @@ -322,7 +398,7 @@ public class Parser { // Our current scope is the body Scope ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate - parseStatement(); // Parse loop body + Node expr = parseStatement().keep(); // Parse loop body _scope.removeGuards(ifT); // Merge the loop bottom into other continue statements @@ -362,7 +438,7 @@ public class Parser { _xScopes.pop(); _xScopes.push(exit); _scope = exit; - return _scope; + return peep(expr.unkeep()); } private ScopeNode jumpTo(ScopeNode toScope) { @@ -379,13 +455,13 @@ public class Parser { return cur; // toScope is either the break scope, or a scope that was created here assert toScope._lexSize.size() <= _breakScope._lexSize.size(); - toScope.ctrl(toScope.mergeScopes(cur).peephole()); + toScope.ctrl(toScope.mergeScopes(cur,loc()).peephole()); return toScope; } - private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } + private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); } - private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } + private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; } private Node parseBreak () { checkLoopActive(); // At the time of the break, and loop-exit conditions are only valid if @@ -394,11 +470,11 @@ public class Parser { _breakScope.removeGuards(_breakScope.ctrl()); _breakScope = require(jumpTo(_breakScope ),";"); _breakScope.addGuards(_breakScope.ctrl(), null, false); - return _breakScope; + return ZERO; } // Look for an unbalanced `)`, skipping balanced - private Type skipAsgn() { + private void skipAsgn() { int paren=0; while( true ) // Next X char handles skipping complex comments @@ -406,8 +482,10 @@ public class Parser { case Character.MAX_VALUE: throw Utils.TODO(); case ')': - if( --paren<0 ) - return posT(pos()-1); // Leave the `)` behind + if( --paren<0 ) { + posT( pos() - 1 ); + return; // Leave the `)` behind + } break; case '(': paren ++; break; default: break; @@ -441,7 +519,6 @@ public class Parser { Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current - int ndefs = _scope.nIns(); ScopeNode fScope = _scope.dup(); // Duplicate current scope fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes @@ -449,9 +526,14 @@ public class Parser { // Parse the true side ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection _scope.addGuards(ifT,pred,false); // Up-cast predicate - Node lhs = stmt ? parseStatement() : parseAsgn().keep(); // Parse true-side + Node lhs = (stmt ? parseStatement() : parseAsgn()).keep(); // Parse true-side _scope.removeGuards(ifT); + // See if a one-sided def was made: "if(pred) int x = 1;" and throw. + // See if any forward-refs were made, and copy them to the other side: + // "pred ? n*fact(n-1) : 1" + fScope.balanceIf(_scope); + ScopeNode tScope = _scope; // Parse the false side @@ -461,31 +543,33 @@ public class Parser { // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); boolean doRHS = match(fside); - Node rhs = doRHS + Node rhs = (doRHS ? (stmt ? parseStatement() : parseAsgn()) - : (stmt ? null : con(lhs._type.makeZero())); + : con(lhs._type.makeZero())).keep(); _scope.removeGuards(ifF); if( doRHS ) fScope = _scope; pred.unkeep(); - // Check for `if(pred) int x=17;` - if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) - throw error("Cannot define a new name on one arm of an if"); - // Check the trinary widening int/flt if( !stmt ) { - rhs = widenInt( rhs, lhs._type ).keep(); + rhs = widenInt( rhs.unkeep(), lhs._type ).keep(); lhs = widenInt( lhs.unkeep(), rhs._type ).keep(); - rhs.unkeep(); } - // Merge results _scope = tScope; _xScopes.pop(); // Discard pushed from graph display + // See if a one-sided def was made: "if(pred) int x = 1;" and throw. + // See if any forward-refs were made, and copy them to the other side: + // "pred ? n*fact(n-1) : 1" + tScope.balanceIf(fScope); - RegionNode r = ctrl(tScope.mergeScopes(fScope)); - Node ret = stmt ? r : peep(new PhiNode("",lhs._type.meet(rhs._type),r,lhs.unkeep(),rhs)); + // Merge results + RegionNode r = ctrl(tScope.mergeScopes(fScope,loc())); + Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type),r,lhs.unkeep(),rhs.unkeep())); + // Immediately fail e.g. `arg ? 7 : ptr` + ParseException err; + if( !stmt && (err=ret.err()) !=null ) throw err; r.peephole(); return ret; } @@ -501,9 +585,10 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control - return ret; + return expr; } @@ -522,22 +607,24 @@ public class Parser { Node expr = parseAsgn(); // Final variable to update - ScopeNode.Var def = _scope.lookup(name); + Var def = _scope.lookup(name); if( def==null ) throw error("Undefined name '" + name + "'"); // TOP fields are for late-initialized fields; these have never // been written to, and this must be the final write. Other writes // outside the constructor need to check the final bit. - if( _scope.in(def._idx)._type!=Type.TOP && def._final && !_scope.canInit(def) ) + if( _scope.in(def._idx)._type!=Type.TOP && def._final && + // Inside a constructor, final assign is OK, outside nope + !_scope.canInit(def) ) throw error("Cannot reassign final '"+name+"'"); // Lift expression, based on type - Node lift = liftExpr(expr, def.type()); + Node lift = liftExpr(expr.keep(), def.type()); // Update _scope.update(name,lift); // Return un-lifted expr - return expr; + return expr.unkeep(); } // Apply declared access permissions; widen floats and narrow integers. @@ -553,13 +640,13 @@ public class Parser { // Auto-narrow wide ints to narrow ints expr = zsMask(expr,t); // Type is sane - if( !expr._type.isa(t) || !expr._type.accessISA(t) ) + if( expr._type!=Type.BOTTOM && (!expr._type.shallowISA(t) || !expr._type.accessISA(t)) ) throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); return expr; } private Node widenInt( Node expr, Type t ) { - return expr._type instanceof TypeInteger && t instanceof TypeFloat + return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat ? peep(new ToFloatNode(expr)) : expr; } @@ -581,12 +668,23 @@ public class Parser { return require(n,";"); } + // Widen a reassignable binding without discarding the initializer's + // permissions. Lattice glb includes readonly memory and is too broad here. + private Type inferredType(Type t) { + if( t instanceof TypeMemPtr tmp ) { + Type base = TYPES.get(tmp._obj._name); + TypeMemPtr ptr = (TypeMemPtr)base; + return (tmp._ro ? ptr.makeRO() : ptr).makeNullable(); + } + return t.glb(); + } + // The binding modifier sits immediately before the name. It never // changes permissions on the referenced object. private boolean bindingFinal(Type t) { if( match("!") ) return false; if( match("~") ) return true; - return t==Type.TOP || t instanceof TypeMemPtr; + return t==Type.TOP || t instanceof TypeNil; } /** Parse final: [!|~]var['=' asgn] @@ -596,6 +694,7 @@ public class Parser { // Has var/val instead of a user-declared type boolean inferType = t==Type.TOP || t==Type.BOTTOM; boolean xfinal = bindingFinal(t); + Lexer loc = loc(); String name = requireId(); // Optional initializing expression follows @@ -603,22 +702,45 @@ public class Parser { if( match("=") ) { expr = parseAsgn(); // var/val, then type comes from expression - if( inferType ) - t = expr._type instanceof TypeMemPtr ptr ? ptr.makeFrom(true) : expr._type.glb(); + if( inferType ) { + if( expr._type==Type.NIL ) + throw error("a not-null/non-zero expression"); + t = expr._type; + if( !xfinal ) t = inferredType(t); // Widen if not final + } + if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) + tfp.setName(name); } else { - if( inferType && !_scope.inCon() ) + // Need an expression to infer the type. + // Also, if not-null then need an initializing expression. + // Allowed in a class def, because type/init will happen in the constructor. + if( (inferType || (t instanceof TypeNil tn && !tn.nullable() )) && !_scope.inCon() ) throw errorSyntax("=expression"); - expr = con(xfinal && !(t instanceof TypeMemPtr) ? Type.TOP : t.makeInit()); + // Initial value for uninitialized struct fields. + expr = switch( t ) { + // Nullable pointers get a NIL; not-null get a TOP which + // signals that they *must* be initialized in the constructor. + case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP); + case TypeInteger ti -> xfinal ? con(Type.TOP) : ZERO; + case TypeFloat tf -> con(xfinal ? Type.TOP : TypeFloat.FZERO); + // Bottom signals type inference: they must be initialized in + // the constructor and that's when we'll discover the type. + case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); } + }; } // Lift expression, based on type Node lift = liftExpr(expr, t); - // Define a new name, - if( !_scope.define(name,t,xfinal,lift) ) - throw error("Redefining name '" + name + "'"); - return null; + // Lift type to the declaration. This will report as an error later if + // we cannot lift the type. + if( !lift._type.isa(t) ) + lift = peep(new CastNode(t,null,lift)); + // Define a new name + if( !_scope.define(name,t,xfinal,lift, loc) ) + throw error("Redefining name '" + name + "'", loc); + return lift; } @@ -631,15 +753,14 @@ public class Parser { * @return The statement following the struct */ private Node parseStruct() { - if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); + if (_xScopes.size() > 1) throw error("struct declarations can only appear in top level scope"); String typeName = requireId(); Type t = TYPES.get(typeName); if( t!=null && !(t instanceof TypeMemPtr tmp && tmp.isFRef() ) ) - throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + throw error("struct '" + typeName + "' cannot be redefined"); // A Block scope parse, and inspect the scope afterward for fields. - _scope.push(true); - _xScopes.push(_scope); + _scope.push(ScopeNode.Kind.Constructor); require("{"); while (!peek('}') && !_lexer.isEOF()) parseStatement(); @@ -648,21 +769,21 @@ public class Parser { int lexlen = _scope._lexSize.last(); int varlen = _scope._vars._len; StructNode s = new StructNode(); - Field[] fs = new Field[varlen-lexlen]; + Ary<Field> fs = new Ary<>(Field.class); for( int i=lexlen; i<varlen; i++ ) { s.addDef(_scope.in(i)); - ScopeNode.Var v = _scope._vars.at(i); - fs[i-lexlen] = Field.make(v._name,v.type(),ALIAS++,v._final); + Var v = _scope._vars.at(i); + if( !v.isFRef() ) // Promote to outer scope, not defined here + fs.push(Field.make(v._name,v.type(),ALIAS++,v._final)); } - TypeStruct ts = s._ts = TypeStruct.make(typeName, fs); + TypeStruct ts = s._ts = TypeStruct.make(typeName, fs.asAry()); TYPES.put(typeName, TypeMemPtr.make(ts)); INITS.put(typeName,s.peephole().keep()); // Done with struct/block scope require("}"); require(";"); - _xScopes.pop(); _scope.pop(); - return null; + return ZERO; } @@ -676,14 +797,20 @@ public class Parser { int old1 = pos(); boolean writable = match("!"); boolean readonly = !writable && match("~"); + // Only type with a leading `{` is a function pointer... + if( peek('{') ) return typeFunPtr(); + + // Otherwise you get a type name String tname = _lexer.matchId(); if( tname==null ) return posT(old1); + // Convert the type name to a type. Type t0 = TYPES.get(tname); // No new types as keywords if( t0 == null && KEYWORDS.contains(tname) ) return posT(old1); - Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.makeFRef(tname)) : t0; // Null: assume a forward ref type + if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference + Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.makeFRef(tname)) :t0; // Null: assume a forward ref type // A qualifier before the name applies to the struct reference, not // to an enclosing array. Arrays have their own [] / [~] qualifier. if( t1 instanceof TypeMemPtr tmp ) { @@ -694,12 +821,11 @@ public class Parser { // Nest arrays and '?' as needed Type t2 = t1; while( true ) { - assert !(t2 instanceof TypeStruct); if( match("?") ) { if( !(t2 instanceof TypeMemPtr tmp) ) throw error("Type "+t0+" cannot be null"); - if( tmp._nil ) throw error("Type "+t2+" already allows null"); - t2 = tmp.makeFrom(true); + if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); + t2 = tmp.makeNullable(); } else if( match("[~]") ) { TypeMemPtr ary = typeAry(t2); t2 = ary.makeFrom(ary._obj.makeRO()).makeRO(); @@ -716,8 +842,10 @@ public class Parser { int old2 = pos(); if( !match("!") ) match("~"); String id = _lexer.matchId(); + if( !(peek(',') || peek(';')) ) + return posT(old1); pos(old2); // Reset lexer to reparse - if( id==null || _scope.lookup(id)!=null ) + if( id==null ) return posT(old1); // Reset lexer to reparse // Yes a forward ref, so declare it TYPES.put(tname,((TypeMemPtr)t1).withAccess(false)); @@ -726,7 +854,7 @@ public class Parser { // Make an array type of t private TypeMemPtr typeAry( Type t ) { - if( t instanceof TypeMemPtr tmp && !tmp._nil ) + if( t instanceof TypeMemPtr tmp && tmp.notNull() ) throw error("Arrays of reference types must always be nullable"); String tname = "["+t.str()+"]"; Type ta = TYPES.get(tname); @@ -747,6 +875,37 @@ public class Parser { return tary; } + // A function type is `{ type... -> type }` or `{ type }`. + private Type typeFunPtr() { + int old = pos(); // Record lexer position + match("{"); // Skip already-peeked '{' + Type t0 = type(); // Either return or first arg + if( t0==null ) return posT(old); // Not a function + if( match("}") ) // No-arg function { -> type } + return TypeFunPtr.make(match("?"),TypeTuple.BOT,t0); + Ary<Type> ts = new Ary<>(Type.class); + ts.push(t0); // First argument + while( true ) { + if( match("->") ) { // End of arguments, parse return + Type ret = type(); + if( ret==null || !match("}") ) + return posT(old); // Not a function + return TypeFunPtr.make(match("?"),TypeTuple.make(ts.asAry()),ret); + } + Type t1 = type(); + if( t1==null ) return posT(old); // Not a function + ts.push(t1); + } + } + + // True if a TypeFunPtr, without advancing parser + private boolean isTypeFun() { + int old = pos(); + if( typeFunPtr()==null ) return false; + pos(old); + return true; + } + /** * Parse an expression of the form: * @@ -772,9 +931,9 @@ public class Parser { Node lhs = parseComparison(); while( true ) { if( false ) ; - else if( match("&") ) lhs = new AndNode(lhs,null); - else if( match("|") ) lhs = new OrNode(lhs,null); - else if( match("^") ) lhs = new XorNode(lhs,null); + else if( match("&") ) lhs = new AndNode(loc(),lhs,null); + else if( match("|") ) lhs = new OrNode(loc(),lhs,null); + else if( match("^") ) lhs = new XorNode(loc(),lhs,null); else break; lhs.setDef(2,parseComparison()); lhs = peep(lhs); @@ -824,11 +983,14 @@ public class Parser { Node lhs = parseAddition(); while( true ) { if( false ) ; - else if( match("<<") ) lhs = new ShlNode(lhs,null); - else if( match(">>>")) lhs = new ShrNode(lhs,null); - else if( match(">>") ) lhs = new SarNode(lhs,null); + else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null); + else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null); + else if( match(">>") ) lhs = new SarNode(loc(),lhs,null); else break; lhs.setDef(2,parseAddition()); + ParseException err; + if( (err=lhs.err()) != null ) + throw err; lhs = peep(lhs.widen()); } return lhs; @@ -864,7 +1026,7 @@ public class Parser { * @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { - var lhs = parseUnary(); boolean mul; + var lhs = parseUnary(); while( true ) { if( false ) ; else if( match("*") ) lhs = new MulNode(lhs,null); @@ -891,11 +1053,13 @@ public class Parser { int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post String name = _lexer.matchId(); if( name!=null ) { - ScopeNode.Var n = _scope.lookup(name); - if( n != null ) { + Var n = _scope.lookup(name); + if( n != null && !(n.type() instanceof TypeMemPtr) ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); - Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); + Node expr = n.type() instanceof TypeFloat + ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta)))) + : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); _scope.update(n,expr); return expr; } @@ -920,52 +1084,82 @@ public class Parser { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); if( matchx("true" ) ) return con(1); if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return con(TypeMemPtr.NULLPTR); - if( match("(") ) return require(parseAsgn(), ")"); + if( matchx("null" ) ) return NIL; + if( match ("(" ) ) return require(parseAsgn(), ")"); if( matchx("new" ) ) return alloc(); + if( match ("{" ) ) return require(func(),"}"); // Expect an identifier now - ScopeNode.Var n = requireLookupId("an identifier or expression"); + Var n = requireLookupId(); Node rvalue = _scope.in(n); if( rvalue._type == Type.BOTTOM ) - throw error("Cannot read uninitialized field '"+n._name+"'"); + if( rvalue instanceof FRefNode ) return rvalue; + else throw error("Cannot read uninitialized field '"+n._name+"'"); // Check for assign-update, x += e0; char ch = _lexer.matchOperAssign(); if( ch==0 ) return rvalue; - if( n._final ) - throw error("Cannot reassign final '"+n._name+"'"); + Node op = opAssign(ch,rvalue,n._final,n.type(),n._name); + _scope.update(n,op); + return postfix(ch) ? rvalue.unkeep() : op; + } + + // Check for assign-update, "x += e0". + // Returns "x + e0"; the caller assigns value. + // if ch is postfix, then lhs is kept and caller will unkeep and return it. + private Node opAssign(char ch, Node lhs, boolean xfinal, Type t, String name ) { + if( xfinal ) + throw error("Cannot reassign final '"+name+"'"); + // RHS of the update. + lhs.keep(); // Alive across parseAsgn + Node rhs = + (byte)ch == 1 ? con( 1) : // var++ + (byte)ch == -1 ? con(-1) : // var-- + parseAsgn(); // var op= rhs + if( !postfix(ch) ) lhs.unkeep(); // Allow to die in next peep + // 4 cases: + // int + int ==>> narrow int + // int + flt ==>> error, caller must fail assigning flt into int + // flt + int ==>> use float op, wrap toFloat() + // flt + flt ==>> use float op Node op = switch(ch) { - case '+' -> new AddNode(rvalue,null); - case '-' -> new SubNode(rvalue,null); - case '*' -> new MulNode(rvalue,null); - case '/' -> new DivNode(rvalue,null); - case '&' -> new AndNode(rvalue,null); - case '|' -> new OrNode(rvalue,null); - case '^' -> new XorNode(rvalue,null); - case Lexer.SHL -> new ShlNode(rvalue,null); - case Lexer.SAR -> new SarNode(rvalue,null); - case Lexer.SHR -> new ShrNode(rvalue,null); - case 1 -> new AddNode(rvalue,con( 1)); - case (char)-1 -> new AddNode(rvalue,con(-1)); + case 1, (char)-1, + '+' -> new AddNode(lhs,rhs); + case '-' -> new SubNode(lhs,rhs); + case '*' -> new MulNode(lhs,rhs); + case '/' -> new DivNode(lhs,rhs); + case '&' -> new AndNode(loc(),lhs,rhs); + case '|' -> new OrNode(loc(),lhs,rhs); + case '^' -> new XorNode(loc(),lhs,rhs); + case Lexer.SHL -> new ShlNode(loc(),lhs,rhs); + case Lexer.SAR -> new SarNode(loc(),lhs,rhs); + case Lexer.SHR -> new ShrNode(loc(),lhs,rhs); default -> throw Utils.TODO(); }; - // Return pre-value (x+=1) or post-value (x++) - boolean pre = op.in(2)==null; - // Parse RHS argument as needed - if( pre ) - { op.keep().setDef(2,parseAsgn()); op.unkeep(); } - else rvalue.keep(); // Keep post-value across peeps - op = zsMask(peep(op),n.type()); - _scope.update(n,op); - return pre ? op : rvalue.unkeep(); + // Convert to float ops, or narrow int types; error if not declared type. + // Also, if postfix LHS is still keep() + return liftExpr(peep(op.widen()),t); } - ScopeNode.Var requireLookupId(String msg) { + + // Expect an ID here. If not found, assume a forward reference function + Var requireLookupId() { String id = _lexer.matchId(); if( id == null || KEYWORDS.contains(id) ) - throw errorSyntax(msg); - ScopeNode.Var n = _scope.lookup(id); - if( n==null ) - throw error("Undefined name '" + id + "'"); + throw errorSyntax("an identifier or expression"); + Var n = _scope.lookup(id); + // If missing, assume a forward reference + if( n==null ) { + _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc()); + n = _scope.lookup(id); + } else { + // Lookup worked on an out-of-function value. + if( _scope.outOfFunction(n) ) { + // No closures, so this has to be a final constant (which + // includes forward refs) + Node def = _scope.in(n._idx); + if( !(def instanceof FRefNode) && !(n._final && def._type.isConstant()) ) + throw error("Variable '"+n._name+"' is out of function scope and must be a final constant"); + } + } return n; } @@ -977,6 +1171,8 @@ public class Parser { if( t==null ) throw error("Expected a type"); // Parse ary[ length_expr ] if( match("[") ) { + if( !t.makeZero().isa(t) ) + throw error("Cannot allocate a non-nullable, since arrays always zero/null fill"); Node len = parseAsgn(); if( !(len._type instanceof TypeInteger) ) throw error("Cannot allocate an array with length "+len._type); @@ -1000,12 +1196,12 @@ public class Parser { if( hasConstructor ) { idx = _scope.nIns(); // Push a scope, and pre-assign all struct fields. - _scope.push(); + _scope.push(ScopeNode.Kind.Block); + Lexer loc = loc(); for( int i=0; i<fs.length; i++ ) - _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i)); + _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc); // Parse the constructor body - parseBlock(true); - require("}"); + require(parseBlock(ScopeNode.Kind.Constructor),"}"); init = _scope._inputs; } // Check that all fields are initialized @@ -1053,8 +1249,8 @@ public class Parser { private Node newArray(TypeStruct ary, Node len) { int base = ary.aryBase (); int scale= ary.aryScale(); - Node size = peep(new AddNode(con(base),peep(new ShlNode(len.keep(),con(scale))))); - ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._type.makeInit())); + Node size = peep(new AddNode(con(base),peep(new ShlNode(null,len.keep(),con(scale))))); + ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._type.makeZero())); return newStruct(ary,size,0,ALTMP); } @@ -1066,7 +1262,7 @@ public class Parser { if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) throw error("Cannot modify final field '"+name+"'"); Node prior = mem().keep(); - Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init); + 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()); @@ -1074,33 +1270,32 @@ public class Parser { } /** - * Parse postfix expression. For now this is just a field - * expression, but in future could be array index too. + * Parse postfix expression; this can be a field expression, an array + * lookup or a postfix operator like '#' * * <pre> - * expr ('.' IDENTIFIER)* [ = expr ] - * expr # - * expr ('[' expr ']')* = [ = expr ] + * expr ('.' FIELD)* [ = expr ] // Field reference + * expr '#' // Postfix unary read operator + * expr ['++' | '--' ] // Postfix unary write operator + * expr ('[' expr ']')* = [ = expr ] // Array reference + * expr '(' [args,]* ')' // Function call * </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 parsePostfix(require(functionCall(expr),")")); else return expr; // No postfix + if( expr._type==Type.NIL ) + throw error("Accessing unknown field '" + name + "' from 'null'"); + // Sanity check expr for being a reference if( !(expr._type instanceof TypeMemPtr ptr) ) throw error("Expected reference but got " + expr._type.str()); - // 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 ) - // Exit out via parsing the trailing expression - return matchOpx('=','=') ? parseAsgn() : parsePostfix(con(Type.TOP)); - // Sanity check field name for existing TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name); if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr + "'"); @@ -1118,7 +1313,7 @@ public class Parser { // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math - ? peep(new AddNode(con(base.aryBase()),peep(new ShlNode(require(parseAsgn(),"]"),con(base.aryScale()))))) + ? peep(new AddNode(con(base.aryBase()),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale()))))) // Struct field offsets are hardwired : con(base.offset(fidx))).keep(); @@ -1131,20 +1326,19 @@ public class Parser { return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(name, f._alias, (tf instanceof TypeMemPtr ? tf : tf.glb()), mem(), expr, off); + Node load = 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. if( base.isAry() ) load.setDef(0,ctrl()); load = peep(load); - // ary[idx]++ or ptr.fld++ - if( matchx("++") || matchx("--") ) { - if( f._final && !f._fname.equals("[]") ) - throw error("Cannot reassign final '"+f._fname+"'"); - Node inc = peep(new AddNode(load,con( _lexer.peek(-1)=='+' ? 1 : -1))); - Node val = zsMask(inc,tf); - store(name,f._alias,tf,expr.unkeep(),off,val,false,base.isAry() ? ctrl() : null); + // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" + char ch = _lexer.matchOperAssign(); + if( ch!=0 ) { + Node op = opAssign(ch,load,f._final,tf,name); + store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null); + load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result } else expr.unkill(); off.unkill(); @@ -1165,15 +1359,86 @@ public class Parser { return peep(new RoundF32Node(val)); } if( t0._min==0 ) // Unsigned - return peep(new AndNode(val,con(t0._max))); + return peep(new AndNode(null,val,con(t0._max))); // Signed extension int shift = Long.numberOfLeadingZeros(t0._max)-1; Node shf = con(shift); if( shf._type==TypeInteger.ZERO ) return val; - return peep(new SarNode(peep(new ShlNode(val,shf.keep())),shf.unkeep())); + return peep(new SarNode(null,peep(new ShlNode(null,val,shf.keep())),shf.unkeep())); } + /** + * Parse a function body; the caller will parse the surrounding "{}" + * + * <pre> + * { [type arg,]* -> expr } + * { expr } // The no-argument function + * </pre> + */ + private Node func() { + Ary<Type> ts = new Ary<>(Type.class); + Ary<String> ids = new Ary<>(String.class); + java.util.BitSet finals = new java.util.BitSet(); + _lexer.skipWhiteSpace(); + Lexer loc = loc(); // First argument location + while( true ) { + Type t = type(); // Arg type + if( t==null ) break; + finals.set(ids.size(),bindingFinal(t)); + String id = requireId(); + ts .push(t ); // Push type/arg pairs + ids.push(id); + match(","); + } + require("->"); + // Make a concrete function type, with a fidx + TypeFunPtr tfp = TypeFunPtr.makeFun(TypeTuple.make(ts.asAry()),Type.BOTTOM); + ReturnNode ret = parseFunctionBody(tfp,loc,finals,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 instanceof FRefNode) && !expr._type.isa(TypeFunPtr.BOT) ) + throw error("Expected a function but got "+expr._type.glb().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().keep()); + args.push(expr); // Function pointer + // Unkeep them all + for( Node arg : args ) + arg.unkeep(); + // Into the call + CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole(); + + // Post-call setup + CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + // Control from CallEnd + ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); + // 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,null).peephole(); + } /** * Parse integer literal @@ -1184,7 +1449,7 @@ public class Parser { * </pre> */ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - public static Node con( long con ) { return con(TypeInteger.constant(con)); } + public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); } public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } public Node peep( Node n ) { // Peephole, then improve with lexically scoped guards @@ -1203,12 +1468,21 @@ public class Parser { private boolean peek(char ch) { return _lexer.peek(ch); } private boolean peekIsId() { return _lexer.peekIsId(); } + // ch is +/- 1, means oper++ or oper-- means postfix + private static boolean postfix(char ch) { + return (byte)ch== 1 || (byte)ch== -1; + } + + public int pos() { return _lexer._position; } private int pos(int pos) { int old = _lexer._position; _lexer._position = pos; return old; } private Type posT(int pos) { _lexer._position = pos; return null; } + // Source code location + Lexer loc() { return new Lexer(_lexer); } + // Require and return an identifier private String requireId() { @@ -1233,24 +1507,26 @@ public class Parser { throw errorSyntax(syntax); } - RuntimeException errorSyntax(String syntax) { - return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); - } - - public static RuntimeException error( String errorMessage ) { - return new RuntimeException(errorMessage); - } - //////////////////////////////////// // Lexer components - private static class Lexer { + // Lexer provides low level access to the raw file bytes, peeks and matches + // short strings, parses numbers, skips comments and whitespace, tracks + // line numbers, allows the parse position to be saved and restored, and + // serves as a location indicator for errors. + + public static class Lexer { // Input buffer; an array of text bytes read from a file or a string private final byte[] _input; // Tracks current position in input buffer private int _position = 0; + // + private int _line_number = 1; + // Start of current line + private int _line_start = 0; + /** * Record the source text for lexing */ @@ -1265,6 +1541,16 @@ public class Parser { _input = buf; } + /** + * Copy a lexer from a lexer + */ + private Lexer(Lexer l) { + _input = l._input; + _position = l._position; + _line_number = l._line_number; + _line_start = l._line_start; + } + // Very handy in the debugger, shows the unparsed program @Override public String toString() { @@ -1284,9 +1570,16 @@ public class Parser { // Just crash if misused public byte peek(int off) { return _input[_position+off]; } + private void inc() { + if( _position++ < _input.length && _input[_position-1] == '\n' ) { + _line_number++; + _line_start = _position; + } + } + // Does not honor LF, so caller must roll back position on a LF private char nextChar() { char ch = peek(); - _position++; + inc(); return ch; } @@ -1300,13 +1593,13 @@ public class Parser { */ private void skipWhiteSpace() { while( true ) { - if( isWhiteSpace() ) _position++; + if( isWhiteSpace() ) inc(); // Skip // to end of line else if( _position+2 < _input.length && _input[_position ] == '/' && _input[_position+1] == '/') { - _position += 2; - while( !isEOF() && _input[_position] != '\n' ) _position++; + inc(); inc(); + while( !isEOF() && _input[_position] != '\n' ) inc(); } else break; } } @@ -1318,6 +1611,7 @@ public class Parser { // then advance the cursor past syntax. // Return false otherwise, and do not advance the cursor. boolean match(String syntax) { + assert syntax.indexOf('\n')==-1; // No newlines in match skipWhiteSpace(); int len = syntax.length(); if (_position + len > _input.length) return false; @@ -1340,7 +1634,7 @@ public class Parser { skipWhiteSpace(); if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 ) return false; - _position++; + inc(); return true; } @@ -1377,8 +1671,9 @@ public class Parser { int len = isLongOrDouble(); if( len > 0 ) { if( len > 1 && _input[old]=='0' ) - throw error("Syntax error: integer values cannot start with '0'"); - return TypeInteger.constant(Long.parseLong(new String(_input,old,len))); + throw error("Syntax error: integer values cannot start with '0'",this); + long i = Long.parseLong(new String(_input,old,len)); + return TypeInteger.constant(i); } return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); } @@ -1429,7 +1724,6 @@ public class Parser { // Next oper= character, or 0. // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535) - // Disallow e.g. "arg--1" which should parse as "arg - -1" // Distinct tags for the multi-character compound operators. static final char SHL = 2, SAR = 3, SHR = 4; @@ -1449,4 +1743,18 @@ public class Parser { return 0; } } + + ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); } + private ParseException _errorSyntax(String msg) { + return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken()); + } + ParseException error(String msg) { return error(msg,_lexer); } + public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); } + + public static class ParseException extends RuntimeException { + public final Lexer _loc; + // file:line:charoff err + //String msg = "src:"+_line_number+":"+(_position-_line_start)+" "+errorMessage; + ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; } + } } diff --git a/src/main/java/com/seaofnodes/simple/SB.java b/src/main/java/com/seaofnodes/simple/SB.java new file mode 100644 index 00000000..6f5a29ee --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/SB.java @@ -0,0 +1,74 @@ +package com.seaofnodes.simple; + +/** Tight/tiny StringBuilder wrapper. + * Short short names on purpose; so they don't obscure the printing. + * Can't believe this wasn't done long, long ago. */ +public final class SB { + private final StringBuilder _sb; + private int _indent = 0; + public SB( ) { _sb = new StringBuilder( ); } + public SB(String s) { _sb = new StringBuilder(s); } + public SB p( String s ) { _sb.append(s); return this; } + public SB p( float s ) { + if( Float.isNaN(s) ) + _sb.append( "Float.NaN"); + else if( Float.isInfinite(s) ) { + _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY"); + } else _sb.append(s); + return this; + } + public SB p( double s ) { + if( Double.isNaN(s) ) + _sb.append("Double.NaN"); + else if( Double.isInfinite(s) ) { + _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY"); + } else _sb.append(s); + return this; + } + public SB p( char s ) { _sb.append(s); return this; } + public SB p( int s ) { _sb.append(s); return this; } + public SB p( long s ) { _sb.append(s); return this; } + public SB p( boolean s) { _sb.append(s); return this; } + // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and + // suddenly call the autoboxed version. + public SB pobj( Object s ) { _sb.append(s.toString()); return this; } + public SB i( int d ) { for( int i=0; i<d+_indent; i++ ) p(" "); return this; } + public SB i( ) { return i(0); } + public SB ip(String s) { return i().p(s); } + public SB s() { _sb.append(' '); return this; } + + // Increase indentation + public SB ii( int i) { _indent += i; return this; } + public SB ii() { return ii(1); } + // Decrease indentation + public SB di( int i) { _indent -= i; return this; } + public SB di() { return di(1); } + + public SB nl( ) { return p(System.lineSeparator()); } + // Last printed a nl + public boolean was_nl() { + int len = _sb.length(); + String nl = System.lineSeparator(); + int nlen = nl.length(); + if( len < nlen ) return false; + for( int i=0; i<nlen; i++ ) + if( _sb.charAt(len-nlen+i)!=nl.charAt(i) ) + return false; + return true; + } + + // Delete last char. Useful when doing string-joins and JSON printing and an + // extra separater char needs to be removed: + // + // sb.p('['); + // for( Foo foo : foos ) + // sb.p(foo).p(','); + // sb.unchar().p(']'); // remove extra trailing comma + // + public SB unchar() { return unchar(1); } + public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; } + + public SB clear() { _sb.setLength(0); return this; } + public int len() { return _sb.length(); } + @Override public String toString() { return _sb.toString(); } +} diff --git a/src/main/java/com/seaofnodes/simple/Var.java b/src/main/java/com/seaofnodes/simple/Var.java new file mode 100644 index 00000000..6e621875 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/Var.java @@ -0,0 +1,57 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.type.*; + + +/** + * The tracked fields are now complex enough to deserve a array-of-structs layout + */ +public class Var { + + public final String _name; // Declared name + public int _idx; // index in containing scope + private Type _type; // Declared type + public boolean _final; // Final field + public Parser.Lexer _loc; // Source location + public boolean _fref; // Forward ref + + public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) { + this(idx,name,type,xfinal,loc,false); + } + public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc, boolean fref) { + _idx = idx; + _name = name; + _type = type; + _final = xfinal; + _loc = loc; + _fref = fref; + } + // Raw declared type, without resolving a forward reference while printing. + public Type _type() { return _type; } + public Type type() { + if( !_type.isFRef() ) return _type; + // Update self to no longer use the forward ref type + Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); + return (_type=_type.meet(def)); + } + public Type lazyGLB() { + Type t = type(); + return t instanceof TypeMemPtr ? t : t.glb(); + } + + // Forward reference variables (not types) must be BOTTOM and + // distinct from inferred variables + public boolean isFRef() { return _fref; } + + public void defFRef( Type type, boolean xfinal, Parser.Lexer loc ) { + assert isFRef() && xfinal; + _type = type; + _final = true; + _loc = loc; + _fref = false; + } + + @Override public String toString() { + return _type.toString()+(_final ? " ": " !")+_name; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java index 0f9ecec9..f88c2175 100644 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; - public class AddNode extends Node { public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @@ -37,12 +37,12 @@ public class AddNode extends Node { public Node idealize () { Node lhs = in(1); Node rhs = in(2); - if( rhs.err() != null ) return null; + if( rhs.err()!=null ) return null; Type t2 = rhs._type; // Add of 0. We do not check for (0+x) because this will already // canonicalize to (x+0) - if( t2 instanceof TypeInteger i && i.isConstant() && i.value()==0 && lhs._type instanceof TypeInteger ) + if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger ) return lhs; // Add of same to a multiply by 2 @@ -178,9 +178,10 @@ public class AddNode extends Node { @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } @Override Node copyF() { return new AddFNode(null,null); } - @Override public String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; + @Override public Parser.ParseException err() { + if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; + if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type,null); + if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type,null); return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java index d80e25e7..7cb16c71 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -1,16 +1,16 @@ package com.seaofnodes.simple.node; - +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; -public class AndNode extends Node { - public AndNode(Node lhs, Node rhs) { super(null, lhs, rhs); } +public class AndNode extends LogicalNode { + public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } @Override public String label() { return "And"; } - - @Override protected String format() { return "(%1&%2)"; } + @Override public String op() { return "&"; } @Override public Type compute() { @@ -52,10 +52,5 @@ public class AndNode extends Node { return null; } - @Override Node copy(Node lhs, Node rhs) { return new AndNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '&' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '&' " + in(2)._type; - return null; - } + @Override Node copy(Node lhs, Node rhs) { return new AndNode(_loc,lhs,rhs); } } diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 6efde623..aa577f61 100644 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -4,10 +4,7 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.*; import static com.seaofnodes.simple.type.TypeInteger.*; @@ -30,15 +27,19 @@ abstract public class BoolNode extends Node { public TypeInteger compute() { Type t1 = in(1)._type; Type t2 = in(2)._type; + // Exactly equals? if( t1.isHigh() || t2.isHigh() ) return BOOL.dual(); + if( in(1)==in(2) ) + // LT fails, both EQ and LE succeed + return this instanceof LT ? TypeInteger.FALSE : TypeInteger.TRUE; if( t1 instanceof TypeInteger i1 && t2 instanceof TypeInteger i2 ) return doOp(i1,i2); if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 && f1.isConstant() && f2.isConstant() ) - return TypeInteger.constant(doOp(f1.value(), f2.value()) ? 1 : 0); + return doOp(f1.value(), f2.value()) ? TypeInteger.TRUE : TypeInteger.FALSE; return BOOL; } @@ -49,7 +50,7 @@ abstract public class BoolNode extends Node { public Node idealize() { // Compare of same if( in(1)==in(2) ) - return new ConstantNode(doOp(ZERO,ZERO)); + return this instanceof LT ? Parser.ZERO : new ConstantNode(TypeInteger.TRUE); // Equals pushes constant to the right; 5==X becomes X==5. if( this instanceof EQ ) { @@ -60,7 +61,7 @@ abstract public class BoolNode extends Node { return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); } // Equals X==0 becomes a !X - if( (in(2)._type == ZERO || in(2)._type == TypeMemPtr.NULLPTR) ) + if( (in(2)._type == ZERO || in(2)._type == Type.NIL) ) return new NotNode(in(1)); } diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index 84f97189..de5f8b4c 100644 --- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -94,6 +94,7 @@ public class BulkMemPhiNode extends PhiNode { 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); @@ -116,7 +117,7 @@ public class BulkMemPhiNode extends PhiNode { case MemOpNode mem -> unsplit(mem._alias); case MemPhiNode phi -> unsplit(phi._alias); default -> { - assert use instanceof ReturnNode + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode : "Unexpected bulk-memory user "+use; yield 0; } diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java index e85fc6ae..49398838 100644 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.Utils; @@ -28,6 +29,17 @@ public abstract class CFGNode extends Node { // Block head is Start, Region, CProj, but not e.g. If, Return, Stop public boolean blockHead() { return false; } + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. + public CFGNode uctrl() { + CFGNode c = null; + for( Node n : _outputs ) + if( n instanceof CFGNode cfg ) + { assert c==null; c = cfg; } + return c; + } + + // ------------------------------------------------------------------------ /** * Immediate dominator tree depth, used to approximate a real IDOM during @@ -37,13 +49,17 @@ public abstract class CFGNode extends Node { * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>} */ public char _idepth; - public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } + public char _idepthVersion; + public int idepth() { return validIDepth() ? _idepth : cacheIDepth(idom().idepth()+1); } // Zero depth means uncached. Check before narrowing so overflow cannot wrap. final int cacheIDepth(int depth) { assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; + _idepthVersion = CodeGen.CODE.iDepthVersion(); return _idepth = (char)depth; } + final boolean validIDepth() { return _idepth!=0 && _idepthVersion==CodeGen.CODE.iDepthVersion(); } + // Return the immediate dominator of this Node and compute dom tree depth. public CFGNode idom(Node dep) { return cfg(0); } @@ -83,24 +99,36 @@ public abstract class CFGNode extends Node { // ------------------------------------------------------------------------ // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves // also refer to *their* containing LoopNode, as well as have their depth. - // Start is a LoopNode which contains all at depth 1. + // Start contains the function bodies at depth 0; real loops add depth. public void buildLoopTree(StopNode stop) { _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); - _bltWalk(2,stop, new BitSet()); + _bltWalk(2,null,stop, new BitSet()); } - private int _bltWalk(int pre, StopNode stop, BitSet post) { + int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { // Pre-walked? if( _pre!=0 ) return pre; _pre = pre++; + // Seed the function's outer tree before walking its body. Synthetic + // exits can be added before the post-order walk reaches the entry. + if( this instanceof FunNode ) _ltree = stop._ltree; + if( this instanceof ReturnNode ) { + _ltree = fun._ltree; + post.set(_nid); + return pre; // Stop/Start links describe the outside world, not a loop. + } // Pre-walk for( Node use : _outputs ) - if( use instanceof CFGNode usecfg ) - pre = usecfg._bltWalk(pre,stop,post); + 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( 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 @@ -113,7 +141,7 @@ public abstract class CFGNode extends Node { if( ltree._par == null ) // This loop never had an If test choose to take its // exit, i.e. it is a no-exit infinite loop. - ltree._par = ltree._head.forceExit(stop)._ltree; + ltree._par = ltree._head.forceExit(fun,stop)._ltree; ltree = ltree._par; } } @@ -133,4 +161,13 @@ 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) || + (this instanceof ReturnNode && usecfg instanceof CallEndNode); + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java index 53348b5a..70238f8c 100644 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; -public class CProjNode extends CFGNode { +public class CProjNode extends CFGNode implements MultiUse { // Which slice of the incoming multipart value public final int _idx; @@ -22,6 +22,7 @@ public class CProjNode extends CFGNode { } @Override public String label() { return _label; } + @Override public int idx() { return _idx; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); } @@ -49,7 +50,8 @@ public class CProjNode extends CFGNode { if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not ) return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); - return null; + // Copy of some other input + return ((MultiNode)ctrl()).pcopy(_idx); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java new file mode 100644 index 00000000..2da09cfc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -0,0 +1,95 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.*; + +/** + * CallEnd + */ +public class CallEndNode extends CFGNode implements MultiNode { + + // When set true, this Call/CallEnd/Fun/Return is being trivially inlined + private boolean _folding; + public boolean folding() { return _folding; } + + public CallEndNode(CallNode call) { super(call); } + + @Override + public String label() { return "CallEnd"; } + @Override public boolean isMultiHead() { return true; } + @Override public boolean blockHead() { return true; } + + CallNode call() { return (CallNode)in(0); } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("cend( "); + p.p( in(0) instanceof CallNode ? "Call, " : "----, "); + for( int i=1; i<nIns()-1; i++ ) + p.n(in(i)).p(","); + return p.unchar(',').unchar(' ').close(); + } + + @Override + public Type compute() { + if( !(in(0) instanceof CallNode call) ) + return TypeTuple.RET.dual(); + Type ret = call.fptr().addDep(this)._type instanceof TypeFunPtr tfp ? tfp.ret() : Type.BOTTOM; + return TypeTuple.make(call._type,TypeMem.BOT,ret); + } + + @Override + public Node idealize() { + + // Trivial inlining: call site calls a single function; single function + // is only called by this call site. + if( !_folding && nIns()==2 ) { + CallNode call = call(); + Node fptr = call.fptr(); + if( fptr.nOuts() == 1 && // Only user is this call + fptr instanceof ConstantNode && // We have an immediate call + // Function is being called, and its not-null + fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && + // Arguments are correct + call.err()==null ) { + ReturnNode ret = (ReturnNode)in(1); + FunNode fun = ret.fun(); + // Expecting Start, and the Call + if( fun.nIns()==3 ) { + assert fun.in(1) instanceof StartNode && fun.in(2)==call; + // Disallow self-recursive inlining (loop unrolling by another name) + CFGNode idom = call; + while( !(idom instanceof FunNode fun2) ) + idom = idom.idom(); + if( idom != fun ) { + // Trivial inline: rewrite + _folding = true; + // Rewrite Fun so the normal RegionNode ideal collapses + fun._folding = true; + fun.setDef(1,Parser.XCTRL); // No default/unknown StartNode caller + fun.setDef(2,call.ctrl()); // Bypass the Call; + fun.ret().setDef(3,null); // Return is folding also + IterPeeps.addAll(fun._outputs); + CodeGen.CODE.invalidateIDepthCaches(); + return this; + } + } else { + fun.addDep(this); + } + } else { // Function ptr has multiple users (so maybe multiple call sites) + fptr.addDep(this); + } + } + + return null; + } + + @Override public Node pcopy(int idx) { + return _folding ? in(1).in(idx) : null; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java new file mode 100644 index 00000000..10b04b38 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -0,0 +1,142 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.CodeGen; +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; + +/** + * Call + */ +public class CallNode extends CFGNode { + + // Source location for late reported errors + public final Parser.Lexer _loc; + + public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } + + @Override + public String label() { return "Call"; } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + String fname = null; + Node fptr = fptr(); + if( fptr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) + fname = tfp._name; + if( fname==null ) p.n(fptr); + else p.p(fname); + p.p("( "); + for( int i=2; i<nIns()-1; i++ ) + p.n(in(i)).p(","); + return p.unchar(',').unchar(' ').close(); + } + + Node ctrl() { return in(0); } + Node mem () { return in(1); } + // Args mapped 1-to-1 on inputs, so conceptually start at 2 + Node arg(int idx) { return in(idx); } + // Same arg accounting as TFPs, although the numbering starts at 2 + int nargs() { return nIns()-3; } // Minus control, memory, fptr + // args from input 2 to last; last is function input + public Node fptr() { return _inputs.last(); } + + // Find the Call End from the Call + public CallEndNode cend() { + // Always in use slot 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; + } + + @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. + if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) { + // 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.link(tfp0); + if( fun!=null && !fun._folding && !linked(fun) ) + link(fun); + } + } + } + + return null; + } + + // True if Fun is linked to this Call + boolean linked( FunNode fun ) { + for( Node n : fun._inputs ) + if( n == this ) + return true; + return false; + } + + + // Link so this calls fun + private void link( FunNode fun ) { + assert !linked(fun); + fun.addDef(this); + for( Node use : fun._outputs ) + if( use instanceof ParmNode parm ) + parm.addDef(parm._idx==0 ? cend() : arg(parm._idx)); + // Call end points to function return + IterPeeps.add(cend()).addDef(fun.ret()); + assert linked(fun); + } + + @Override + public Parser.ParseException err() { + if( !(fptr()._type instanceof TypeFunPtr tfp) ) + throw Utils.TODO(); + if( !tfp.notNull() ) + return Parser.error( "Might be null calling "+tfp, _loc); + if( nargs() != tfp.nargs() ) + return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc); + + // Check for args + for( int i=0; i<tfp.nargs(); i++ ) + if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) + return Parser.error( "Argument #"+i+" isa "+arg(i+2)._type+", but must be a "+tfp.arg(i), _loc); + + return null; + } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java index 9ade5a50..6bf8ee90 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -2,10 +2,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.IterPeeps; - // Upcast (join) the input to a t. Used after guard test to lift an input. // Can also be used to make a type-assertion if ctrl is null. @@ -20,6 +20,7 @@ public class CastNode extends Node { @Override public String label() { return "("+_t.str()+")"; } + @Override public boolean isConst() { return true; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()).n(in(1)); @@ -34,4 +35,21 @@ public class CastNode extends Node { public Node idealize() { return in(1)._type.isa(_t) ? in(1) : null; } + + @Override + boolean eq(Node n) { + CastNode cast = (CastNode)n; // Contract + return _t==cast._t; + } + + @Override + int hash() { return _t.hashCode(); } + + @Override + public Parser.ParseException err() { + // Has a condition to test, so OK + if( in(0) != null ) return null; + // No condition to test, so this must optimize away + throw Utils.TODO(); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 426736ed..02d0289b 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.type.Type; @@ -19,25 +20,30 @@ import com.seaofnodes.simple.type.Type; * The Constant's value is the value stored in it. */ public class ConstantNode extends Node { - final Type _con; + public final Type _con; public ConstantNode( Type type ) { super(Parser.START); _con = type; } + public static Node make( Type type ) { + if( type==Type. CONTROL ) return new CtrlNode(); + if( type==Type.XCONTROL ) return new XCtrlNode(); + return new ConstantNode(type); + } + @Override protected String repeatName() { return _con==null || _con.toString().length()<=32 ? null : uniqueName(); } - @Override - public String label() { return "Con"; } - + @Override public String label() { return "Con"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(_con); + return p.p(_con.print(new SB())); } @Override public boolean isMultiTail() { return true; } + @Override public boolean isConst() { return true; } @Override public Type compute() { return _con; } diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java new file mode 100644 index 00000000..9268381a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -0,0 +1,15 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; + +public class CtrlNode extends CFGNode { + public CtrlNode() { super(Parser.START); } + @Override public String label() { return "Ctrl"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); } + @Override public boolean isConst() { return true; } + @Override public Type compute() { return Type.CONTROL; } + @Override public Node idealize() { return null; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java index c678ac3e..a3f4318d 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -29,7 +29,7 @@ public class DivFNode extends Node { Type t1 = lhs._type; Type t2 = rhs._type; - // Div of 1. + // Div of 1. if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) return lhs; diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java index 5b31d216..70631c0a 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -4,7 +4,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; - public class DivNode extends Node { public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java new file mode 100644 index 00000000..bf1c5a00 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Var; +import com.seaofnodes.simple.type.Type; + +/** + * A Forward Reference. Its any final constant, including functions. When + * the Def finally appears its plugged into the forward reference, which then + * peepholes to the Def. + */ +public class FRefNode extends ConstantNode { + public static final Type FREF_TYPE = Type.BOTTOM; + public final Var _n; + public FRefNode( Var n ) { super(FREF_TYPE); _n = n; } + + @Override public String label() { return "FRef"+_n; } + + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p("FRef_").p(_n); + } + + @Override public Node idealize() { + // When FRef finds its definition, idealize to it + return nIns()==1 ? null : in(1); + } + + public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } + + @Override boolean eq(Node n) { return this==n; } + @Override int hash() { return _n._idx; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java new file mode 100644 index 00000000..ec60f215 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -0,0 +1,134 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import java.util.BitSet; + +public class FunNode extends RegionNode { + + // When set true, this Call/CallEnd/Fun/Return is being trivially inlined + boolean _folding; + public boolean folding() { return _folding; } + + private TypeFunPtr _sig; // Initial signature + private ReturnNode _ret; // Return pointer + + public FunNode( Parser.Lexer loc, StartNode start, TypeFunPtr sig ) { super(loc,null,start); _sig=sig; } + + @Override + public String label() { return _sig._name == null ? "$fun" : _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. + @Override public CFGNode uctrl() { + for( Node n : _outputs ) + if( n instanceof CFGNode cfg && + (cfg instanceof RegionNode || cfg.cfg0()==this) ) + return cfg; + return null; + } + + public ParmNode rpc() { + ParmNode rpc = null; + for( Node n : _outputs ) + if( n instanceof ParmNode parm && parm._idx==0 ) + { assert rpc==null; rpc=parm; } + return rpc; + } + + // 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 + return Type.CONTROL; + } + + @Override + public Node idealize() { + // When can we assume no callers? Or no other callers (except main)? + // In a partial compilation, we assume Start gets access to any/all + // top-level public structures and recursively what they point to. + // This in turn is valid arguments to every callable function. + // + // In a total compilation, we can start from Start and keep things + // more contained. + + + // If no default/unknown caller, use the normal RegionNode ideal rules + // to collapse + if( unknownCallers() ) return null; + Node progress = super.idealize(); + if( progress!=null ) { + IterPeeps.add( CodeGen.CODE._stop); + IterPeeps.add( _ret ); + } + return progress; + } + + // Unfolded functions are roots; inlining reconnects their depth to the caller. + @Override public int idepth() { return _folding ? super.idepth() : 1; } + // Bypass Region idom, always assume idom is Start + @Override public CFGNode idom(Node dep) { return cfg(1); } + + // Always in-progress until we run out of unknown callers + 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); } + + // Build the function body + public BitSet body() { + + // Reverse up (stop to start) CFG only, collect bitmap. + BitSet cfgs = new BitSet(); + cfgs.set(_nid); + walkUp(ret(),cfgs ); + + // Top down (start to stop) all flavors. CFG limit to bitmap. + // If data use bottoms out in wrong CFG, returns false - but tries all outputs. + // If any output hits an in-CFG use (e.g. phi), then keep node. + BitSet body = new BitSet(); + walkDown(this, cfgs, body, new BitSet()); + return body; + } + + private static void walkUp(CFGNode n, BitSet cfgs) { + if( cfgs.get(n._nid) ) return; + cfgs.set(n._nid); + if( n instanceof RegionNode r ) + for( int i=1; i<n.nIns(); i++ ) + walkUp(n.cfg(i),cfgs); + else walkUp(n.cfg0(),cfgs); + } + + private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) { + if( visit.get(n._nid) ) return body.get(n._nid); + visit.set(n._nid); + + if( n instanceof CFGNode && !cfgs.get(n._nid) ) + return false; + if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) + return false; + boolean in = n.in(0)!=null || n instanceof CFGNode; + for( Node use : n._outputs ) + in |= walkDown(use,cfgs,body,visit); + if( in ) body.set(n._nid); + return in; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 2b53b3a3..a3061b63 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -2,9 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeTuple; +import com.seaofnodes.simple.type.*; import java.util.HashSet; public class IfNode extends CFGNode implements MultiNode { @@ -24,6 +22,9 @@ public class IfNode extends CFGNode implements MultiNode { public Node ctrl() { return in(0); } public Node pred() { return in(1); } + // No one unique control follows + @Override public CFGNode uctrl() { return null; } + @Override public Type compute() { // If the If node is not reachable then neither is any following Proj @@ -33,14 +34,14 @@ public class IfNode extends CFGNode implements MultiNode { Type t = pred._type; // High types mean NEITHER side is reachable. // Wait until the type falls to decide which way to go. - if( t == Type.TOP || t == TypeInteger.TOP ) + if( t.isHigh() ) return TypeTuple.IF_NEITHER; // If constant is 0 then false branch is reachable // Else true branch is reachable if( t.isConstant() ) - return t.makeInit()==t ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; - // Integers allow non-zero ranges: "1-65535" - if( t.makeZero().meet(t) != t ) + return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + // If adding a zero makes a difference, the predicate must not have a zero/null + if( !t.makeZero().isa(t) ) return TypeTuple.IF_TRUE; return TypeTuple.IF_BOTH; @@ -53,7 +54,7 @@ public class IfNode extends CFGNode implements MultiNode { if( !pred()._type.isHighOrConst() ) for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof CProjNode prj ) { - setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole()); + setDef(1,new ConstantNode(TypeInteger.constant(prj._idx==0?1:0)).peephole()); return this; } return null; diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index eaab680c..5190bf08 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,6 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; @@ -22,15 +23,15 @@ public class LoadNode extends MemOpNode { * @param ptr The ptr to the struct base from where we load a field * @param off The offset inside the struct base */ - public LoadNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(name, alias, glb, mem, ptr, off); + public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { + super(loc, name, alias, glb, mem, ptr, off); } @Override boolean canDrop(MemOpNode other, Node dep) { return super.canDrop(other,dep) && !clobbered(dep); } - // Check only immediate memory users. Stores clobber memory; Phis and + // 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(); @@ -40,7 +41,7 @@ public class LoadNode extends MemOpNode { use.addDepForwards(dep); if( use instanceof StoreNode || use instanceof PhiNode || use instanceof MemMergeNode || - use instanceof NewNode ) return true; + use instanceof NewNode || use instanceof CallNode ) return true; } return false; } @@ -95,7 +96,7 @@ public class LoadNode extends MemOpNode { mem = merge.alias(_alias); break; case StoreNode st: - if( _alias==st._alias && ptr == st.ptr() && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr().addDep(this) && off() == st.off() ) return castRO(st.val()); // Proved equal // Can we prove unequal? Offsets do not overlap? if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap @@ -104,17 +105,26 @@ public class LoadNode extends MemOpNode { // Pointers cannot overlap mem = st.mem(); // Proved never equal break; - case PhiNode phi: break outer; // Assume related + case PhiNode phi: + // Assume related + phi.addDep(this); + break outer; case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: - if( mproj.in(0) instanceof NewNode nnn1 ) { + case ProjNode mproj: // Memory projection + switch( mproj.in(0) ) { + case NewNode nnn1: if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) return castRO(nnn1.in(nnn1.findAlias(_alias))); // Load from New init if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New mem = nnn1.mem();// Bypass unrelated New break; - } else break outer; + case StartNode start: break outer; + case CallEndNode cend: break outer; // TODO: Bypass no-alias call + default: throw Utils.TODO(); + } + break; + default: break outer; } @@ -148,7 +158,7 @@ public class LoadNode extends MemOpNode { private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + LoadNode ld = new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); ld.setDef(0,in(0)); return ld.peephole(); } @@ -212,7 +222,8 @@ public class LoadNode extends MemOpNode { Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; if( (slice==mem) || (slice==null && merge.in(1)==mem) ) antiDeps(merge,deps,visit); - } else if( (use instanceof StoreNode st && st._alias==_alias) || + } else if( use instanceof CallNode || + (use instanceof StoreNode st && st._alias==_alias) || (use instanceof NewNode nnn && nnn.field(_alias)!=null) || (use instanceof MemPhiNode phi && phi._alias==_alias) || (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { diff --git a/src/main/java/com/seaofnodes/simple/node/LogicalNode.java b/src/main/java/com/seaofnodes/simple/node/LogicalNode.java new file mode 100644 index 00000000..c59bd948 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/LogicalNode.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + +public abstract class LogicalNode extends Node { + // Source location for late reported errors + Parser.Lexer _loc; + + public LogicalNode(Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } + abstract String op(); + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.open().n(in(1)).p(op()).n(in(2)).close(); + } + + + @Override public Parser.ParseException err() { + if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; + if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type.glb(),_loc); + if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type.glb(), _loc); + return null; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 8fb6a4ce..f26df076 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -3,12 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeInteger; import java.util.BitSet; import java.util.HashSet; public class LoopNode extends RegionNode { - public LoopNode( Node entry ) { super(null,entry,null); } + public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); } public CFGNode entry() { return cfg(1); } public CFGNode back () { return cfg(2); } @@ -22,19 +23,14 @@ public class LoopNode extends RegionNode { return entry()._type; } - @Override - public Node idealize() { - return inProgress() ? null : super.idealize(); - } - // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) - @Override public int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } + @Override public int idepth() { return validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); } // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) @Override public CFGNode idom(Node dep) { return entry(); } // If this is an unreachable loop, it may not have an exit. If it does not // (i.e., infinite loop), force an exit to make it reachable. - public StopNode forceExit( StopNode stop ) { + public FunNode forceExit( FunNode fun, StopNode stop ) { // Walk the backedge, then immediate dominator tree util we hit this // Loop again. If we ever hit a CProj from an If (as opposed to // directly on the If) we found our exit. @@ -42,20 +38,51 @@ public class LoopNode extends RegionNode { while( x != this ) { if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) { CFGNode other = iff.cproj(1-exit._idx); - if( other.loopDepth() < loopDepth() ) - return stop; // Found an exit, not an infinite loop + if( other!=null && other.loopDepth() < loopDepth() ) + return fun; // Found an exit, not an infinite loop } x = x.idom(); } // Found a no-exit loop. Insert an exit NeverNode iff = new NeverNode(back()); for( Node use : _outputs ) - if( use instanceof PhiNode phi ) + if( use instanceof PhiNode ) iff.addDef(use); - CProjNode t = new CProjNode(iff,0,"True" ); - CProjNode f = new CProjNode(iff,1,"False"); + CProjNode t = new CProjNode(iff,0,"True" ).init(); + CProjNode f = new CProjNode(iff,1,"False").init(); setDef(2,f); - stop.addDef(new ReturnNode(t,Parser.ZERO,null)); - return stop; + + // 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(); + ReturnNode ret = fun.ret(); + iff._ltree = f._ltree = _ltree; + ret._ltree = t._ltree = fun._ltree; + Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); + if( ctrl._type != Type.XCONTROL ) { + // Perfect aligned exit? + if( !(ctrl instanceof RegionNode r && + mem instanceof PhiNode pmem && pmem.region()==r && + expr instanceof PhiNode prez && prez.region()==r ) ) { + // Nope, insert an aligned exit layer + ctrl = new RegionNode(_loc,null,ctrl).init(); + mem = new PhiNode((RegionNode)ctrl,mem ).init(); + expr = new PhiNode((RegionNode)ctrl,expr).init(); + } + // Append new Never exit + ctrl.addDef(t ); + mem .addDef(top); + expr.addDef(top); + } else { + ctrl = t; + mem = top; + expr = top; + } + ((CFGNode)ctrl)._ltree = fun._ltree; + ret.setDef(0,ctrl); + ret.setDef(1,mem ); + ret.setDef(2,expr); + + return fun; } } diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index faf456de..5cb908bb 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -43,9 +43,8 @@ public class MemMergeNode extends Node { Node init = nnn.in(nnn.findAlias(alias)).addDep(dep); return contents(nnn.mem(),alias,dep).meet(init._type); } - // There are no heap arguments in this chapter. Before the first New, - // the set of allocated objects (and hence stored values) is empty. - if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP; + // 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; diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 0bf9b6ac..c09f2725 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMemPtr; @@ -13,15 +15,18 @@ public abstract class MemOpNode extends Node { // Declared type; not final because it might be a forward-reference // which will be lazily improved when the reference is declared. public Type _declaredType; + // Source location for late reported errors + public final Parser.Lexer _loc; - public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off) { + public MemOpNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { super(null, mem, ptr, off); _name = name; _alias = alias; _declaredType = glb; + _loc = loc; } - public MemOpNode(String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value) { - this(name, alias, glb, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value) { + this(loc,name, alias, glb, mem, ptr, off); addDef(value); } @@ -50,13 +55,14 @@ public abstract class MemOpNode extends Node { int hash() { return _alias; } @Override - String err() { + public Parser.ParseException err() { Type ptr = ptr()._type; // Already an error, but better error messages come from elsewhere if( ptr == Type.BOTTOM ) return null; + if( ptr.isHigh() ) return null; // Assume it will fall to not-null // Better be a not-nil TMP - if( ptr instanceof TypeMemPtr tmp && !tmp._nil ) + if( ptr instanceof TypeMemPtr tmp && tmp.notNull() ) return null; - return "Might be null accessing '" + _name + "'"; + return Parser.error( "Might be null accessing '" + _name + "'",_loc); } } diff --git a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java index 1b45c7c5..852e646d 100644 --- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -16,7 +16,7 @@ public class MinusFNode extends Node { public Type compute() { if (in(1)._type instanceof TypeFloat i0) return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0; - return TypeFloat.BOT; + return TypeFloat.F64; } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java index b85af74e..66e26163 100644 --- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -15,19 +15,17 @@ public class MinusNode extends Node { @Override public Type compute() { - if( in(1)._type instanceof TypeInteger i0 ) { - if( i0.isHigh() ) return TypeInteger.TOP; - if( i0==TypeInteger.BOT || i0._min == Long.MIN_VALUE || i0._max == Long.MIN_VALUE ) - return TypeInteger.BOT; + if( in(1)._type.isHigh() ) return TypeInteger.TOP; + if( in(1)._type instanceof TypeInteger i0 && + !(i0==TypeInteger.BOT || i0._min == Long.MIN_VALUE || i0._max == Long.MIN_VALUE) ) return TypeInteger.make(-i0._max,-i0._min); - } - return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT; + return TypeInteger.BOT; } @Override public Node idealize() { // -(-x) is x - if( in(1) instanceof MinusNode minus ) + if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type instanceof TypeInteger ) return minus.in(1); return null; diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index 50404922..2c1ba3de 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -1,10 +1,10 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; - public class MulNode extends Node { public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @@ -53,9 +53,10 @@ public class MulNode extends Node { } @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } @Override Node copyF() { return new MulFNode(null,null); } - @Override public String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '"+label()+"' " + in(2)._type; + @Override public Parser.ParseException err() { + if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; + if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type,null); + if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type,null); return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/src/main/java/com/seaofnodes/simple/node/MultiNode.java index ac9c0045..17d00881 100644 --- a/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -1,6 +1,11 @@ package com.seaofnodes.simple.node; -public interface MultiNode extends OutNode { +import com.seaofnodes.simple.Ary; + + +public interface MultiNode { + + abstract Ary<Node> outs(); // Find a projection by index default ProjNode proj( int idx ) { @@ -17,4 +22,8 @@ public interface MultiNode extends OutNode { return prj; return null; } + + // Return not-null if this projection index is a ideal copy. + // Called by ProjNode ideal and used to collapse Multis. + default Node pcopy(int idx) { return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/MultiUse.java b/src/main/java/com/seaofnodes/simple/node/MultiUse.java new file mode 100644 index 00000000..436d3432 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/MultiUse.java @@ -0,0 +1,5 @@ +package com.seaofnodes.simple.node; + +public interface MultiUse { + abstract int idx(); +} diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java index 51341c41..cd639d7e 100644 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -14,11 +14,12 @@ public class NewNode extends Node implements MultiNode { public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); + assert !ptr.nullable(); _ptr = ptr; assert nodes.length==3+ptr._obj._fields.length; assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; assert nodes[1]._type instanceof TypeMem; - assert nodes[2]._type instanceof TypeInteger; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; for( int i=3; i<nodes.length; i++ ) assert nodes[i]._type!=null; } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index e70fe495..114a652a 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,12 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.BaseNode; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.CodeGen; -import com.seaofnodes.simple.Ary; -import com.seaofnodes.simple.IRPrinter; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; import java.util.*; @@ -18,7 +15,7 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable { +public abstract class Node extends BaseNode<Node> implements Cloneable { /** * Inputs to the node. These are use-def references to Nodes. @@ -71,6 +68,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable public boolean isMultiHead() { return false; } public boolean isMultiTail() { return false; } + public boolean isConst () { return false; } // ------------------------------------------------------------------------ // Graph Node & Edge manipulation @@ -84,7 +82,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable */ public Node in(int i) { return _inputs.get(i); } public Node out(int i) { return _outputs.get(i); } - @Override public Ary<Node> outs() { return _outputs; } + public final Ary<Node> outs() { return _outputs; } public int nIns() { return _inputs.size(); } @@ -121,9 +119,11 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable new_def.addUse(this); // Set the new_def over the old (killed) edge _inputs.set(idx,new_def); - if( old_def != null && // If the old def exists, remove a def->use edge - old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def + if( old_def != null ) { // If the old def exists, remove a def->use edge + if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + else IterPeeps.add(old_def);// Else old lost a use, so onto worklist + } moveDepsToWorklist(); // Return new_def for easy flow-coding return new_def; @@ -249,10 +249,10 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable * Always returns some not-null Node (often this). */ public final Node peephole() { - var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + var obs = CodeGen.CODE == null || IterPeeps.midAssert() || CodeGen.CODE._phase == CodeGen.Phase.Parse && CodeGen.CODE.P == null ? null : CodeGen.CODE._obs; if( obs != null ) obs.before(this); Node n = peepholeOpt(); - Node rez = n == null ? this : deadCodeElim(n.peephole()); + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); if( obs != null ) obs.after(this, n == null ? null : rez, false); return rez; } @@ -280,9 +280,9 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable // Compute initial or improved Type Type old = setType(compute()); - // Replace constant computations from non-constants with a constant node - if( !(this instanceof ConstantNode) && !(this instanceof XCtrlNode) && _type.isHighOrConst() ) - return (_type==Type.XCONTROL ? new XCtrlNode() : new ConstantNode(_type)).peepholeOpt(); + // Replace constant computations from non-constants with a constant node. + if( _type.isHighOrConst() && !isConst() ) + return ConstantNode.make(_type).peepholeOpt(); // Global Value Numbering if( _hash==0 ) { @@ -305,8 +305,9 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable if( n != null ) // Something changed return n; // Report progress - if( old==_type ) ITER_NOP_CNT++; - return old==_type ? null : this; // Report progress + if( old!=_type ) return this; // Report progress; + ITER_NOP_CNT++; + return null; // No progress } public static int ITER_CNT, ITER_NOP_CNT; @@ -357,6 +358,8 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable return old; } + public <N extends Node> N init() { _type = compute(); return (N)this; } + /** * This function rewrites the current Node into a more "idealized" form. * This is the bulk of our peephole rewrite rules, and we use this to @@ -430,7 +433,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; - var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + var obs = CodeGen.CODE == null || IterPeeps.midAssert() || CodeGen.CODE._phase == CodeGen.Phase.Parse && CodeGen.CODE.P == null ? null : CodeGen.CODE._obs; if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list @@ -528,9 +531,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable // Swap inputs without letting either input go dead during the swap. Node swap12() { unlock(); // Hash is order dependent - Node tmp = in(1); - _inputs.set(1,in(2)); - _inputs.set(2,tmp); + _inputs.swap(1,2); return this; } @@ -555,14 +556,14 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable // 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. + // Exact-class copy with fresh identity and no edges. Unlike machine + // rematerialization copies, this never registers or copies input edges. public final Node copyEmpty() { Node n; try { n = (Node)clone(); } - catch( CloneNotSupportedException e ) { throw new AssertionError(e); } - n._nid = UNIQUE_ID++; - n._inputs = new Ary<>(Node.class); + catch( Exception e ) { throw new RuntimeException(e); } + n._nid = UNIQUE_ID++; // allocate unique dense ID + n._inputs = new Ary<>(Node.class); n._outputs = new Ary<>(Node.class); n._deps = null; n._hash = 0; @@ -570,7 +571,7 @@ public abstract class Node extends BaseNode<Node> implements OutNode, Cloneable } // Report any post-optimize errors - String err() { return null; } + public Parser.ParseException err() { return null; } /** * Used to allow repeating tests in the same JVM. This just resets the diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java index b9cf4904..57fa9301 100644 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -12,26 +12,13 @@ public class NotNode extends Node { @Override protected String format() { return "(!%1)"; } @Override - public Type compute() { + public TypeInteger compute() { Type t0 = in(1)._type; - if( t0.isHigh() ) return TypeInteger.BOOL.dual(); - switch( t0 ) { - case TypeInteger i0: return i0._max < 0 || i0._min > 0 ? TypeInteger.FALSE : (i0==TypeInteger.ZERO ? TypeInteger.TRUE : TypeInteger.BOOL); - case TypeFloat i0: - return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : TypeInteger.BOOL; - case TypeMemPtr p0: - // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot - // If input in null then true - // If input is not null ptr then false - if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.TRUE; - if( !p0._nil ) return TypeInteger.FALSE; - return TypeInteger.BOOL; - case Type t: - if( t0.getClass() != Type.class ) - // Only doing NOT on ints and ptrs - throw Utils.TODO(); - return TypeInteger.BOOL; - } + if( t0.isHigh() ) return TypeInteger.BOOL.dual(); + if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE; + if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE; + if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE; + return TypeInteger.BOOL; } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java index ae54a67d..1cd8bafc 100644 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -1,16 +1,16 @@ package com.seaofnodes.simple.node; - +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; -public class OrNode extends Node { - public OrNode(Node lhs, Node rhs) { super(null, lhs, rhs); } +public class OrNode extends LogicalNode { + public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } @Override public String label() { return "Or"; } - - @Override protected String format() { return "(%1|%2)"; } + @Override public String op() { return "|"; } @Override public Type compute() { @@ -49,10 +49,5 @@ public class OrNode extends Node { return null; } - @Override Node copy(Node lhs, Node rhs) { return new OrNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '|' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '|' " + in(2)._type; - return null; - } + @Override Node copy(Node lhs, Node rhs) { return new OrNode(_loc,lhs,rhs); } } diff --git a/src/main/java/com/seaofnodes/simple/node/OutNode.java b/src/main/java/com/seaofnodes/simple/node/OutNode.java deleted file mode 100644 index dbac136b..00000000 --- a/src/main/java/com/seaofnodes/simple/node/OutNode.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Ary; - -public interface OutNode { - Ary<Node> outs(); -} diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java new file mode 100644 index 00000000..7f5924cc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.Type; + +public class ParmNode extends PhiNode { + + // Argument indices are mapped one-to-one on CallNode inputs + public final int _idx; // Argument index + + public ParmNode(String label, int idx, Type declaredType, Node... inputs) { + super(label,declaredType,inputs); + _idx = idx; + } + + @Override public String label() { return MemOpNode.mlabel(_label); } + + public FunNode fun() { return (FunNode)in(0); } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( "main".equals(fun().sig()._name) && _label.equals("arg") ) + return p.p("arg"); + p.p("Parm_").p(_label).open(); + for( Node in : _inputs ) p.n(in).p(","); + return p.unchar(',').close(); + } + + + @Override + public Node idealize() { + if( inProgress() ) return null; + return super.idealize(); + } + + // Always in-progress until we run out of unknown callers + @Override public boolean inProgress() { return fun().inProgress(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 942748e7..9b5e1c26 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -2,14 +2,12 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.Utils; - +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.type.*; public class PhiNode extends Node { - final String _label; + public final String _label; // The Phi type we compute must stay within the domain of the Phi. // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. @@ -17,6 +15,14 @@ public class PhiNode extends Node { public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } + public PhiNode(RegionNode r, Node sample) { + super(r); + _label = ""; + _declaredType = sample._type; + while( nIns() < r.nIns() ) + 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); } @@ -43,7 +49,7 @@ public class PhiNode extends Node { // During parsing Phis have to be computed type pessimistically. if( r.inProgress() ) return _declaredType; // Set type to local top of the starting type - Type t = _declaredType.lub(); + Type t = _declaredType.glb().dual();//Type.TOP; for (int i = 1; i < nIns(); i++) // If the region's control input is live, add this as a dependency // to the control because we can be peeped should it become dead. @@ -78,14 +84,16 @@ public class PhiNode extends Node { // then replace with plain val. if( nIns()==3 ) { int nullx = -1; - if( in(1)._type == in(1)._type.makeInit() ) nullx = 1; - if( in(2)._type == in(2)._type.makeInit() ) nullx = 2; + if( in(1)._type == in(1)._type.makeZero() ) nullx = 1; + if( in(2)._type == in(2)._type.makeZero() ) nullx = 2; if( nullx != -1 ) { Node val = in(3-nullx); + if( val instanceof CastNode cast ) + val = cast.in(1); if( r.idom(this).addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==val ) { // Must walk the idom on the null side to make sure we hit False. CFGNode idom = (CFGNode)r.in(nullx); - while( idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); + while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); if( idom instanceof CProjNode proj && proj._idx==1 ) return val; } @@ -190,7 +198,7 @@ public class PhiNode extends Node { } // True if last input is null - public final boolean inProgress() { + public boolean inProgress() { return in(nIns()-1) == null; } @@ -198,4 +206,26 @@ public class PhiNode extends Node { @Override boolean eq( Node n ) { return !inProgress(); } + + @Override + public Parser.ParseException err() { + if( _type != Type.BOTTOM ) return null; + + // BOTTOM means we mixed e.g. int and ptr + for( int i=1; i<nIns(); i++ ) + // Already an error, but better error messages come from elsewhere + if( in(i)._type == Type.BOTTOM ) + return null; + + // Gather a minimal set of types that "cover" all the rest + boolean ti=false, tf=false, tp=false, tn=false; + for( int i=1; i<nIns(); i++ ) { + Type t = in(i)._type; + ti |= t instanceof TypeInteger x; + tf |= t instanceof TypeFloat x; + tp |= t instanceof TypeMemPtr x; + tn |= t==Type.NIL; + } + return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 668e20ad..1e192676 100644 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; -public class ProjNode extends Node { +public class ProjNode extends Node implements MultiUse { // Which slice of the incoming multipart value public final int _idx; @@ -22,11 +22,19 @@ public class ProjNode extends Node { } @Override public String label() { return _label; } + @Override public int idx() { return _idx; } @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); } + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( _label != null ) return p.p(_label); + if( in(0) instanceof CallEndNode cend && cend.call()!=null ) + return p.n(cend.call()); + return p.p("LONELY PROJ"); + } - @Override public CFGNode cfg0() { return in(0).cfg0(); } + @Override public CFGNode cfg0() { + return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0(); + } @Override public boolean isMultiTail() { return in(0).isMultiHead(); } @Override public boolean isMem() { return _type instanceof TypeMem; } @@ -37,7 +45,7 @@ public class ProjNode extends Node { return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM; } - @Override public Node idealize() { return null; } + @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); } @Override boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 27866139..945398e9 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -11,8 +11,11 @@ import java.util.Arrays; import java.util.HashSet; public class RegionNode extends CFGNode { + // Source location for late discovered errors + public Parser.Lexer _loc; - public RegionNode(Node... nodes) { super(nodes); } + public RegionNode(Node... nodes) { this(null,nodes); } + public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } @Override public String label() { return "Region"; } @@ -65,12 +68,17 @@ public class RegionNode extends CFGNode { return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" - if( !hasPhi() && // No Phi users, just a control user + if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && - p1.in(0) instanceof IfNode iff ) - return iff.ctrl(); + p1.in(0) instanceof IfNode iff ) { + // Replace with the iff.ctrl directly + if( nIns()==3 ) return iff.ctrl(); + // Just delete the path for fat Regions + setDef(1,iff.ctrl()); + return delDef(2); + } return null; } @@ -82,7 +90,7 @@ public class RegionNode extends CFGNode { return 0; // All inputs alive } - private boolean hasPhi() { + boolean hasPhi() { for( Node phi : _outputs ) if( phi instanceof PhiNode ) return true; @@ -91,7 +99,7 @@ public class RegionNode extends CFGNode { // Immediate dominator of Region is a little more complicated. @Override public int idepth() { - if( _idepth!=0 ) return _idepth; + if( validIDepth() ) return _idepth; int depth=0; for( Node n : _inputs ) if( n!=null ) @@ -108,12 +116,9 @@ public class RegionNode extends CFGNode { } // True if last input is null - public final boolean inProgress() { - return nIns()>1 && in(nIns()-1) == null; - } + public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; } // Never equal if inProgress - @Override boolean eq( Node n ) { - return !inProgress(); - } + @Override boolean eq( Node n ) { return !inProgress(); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 6c773c42..d4b4094d 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -2,20 +2,26 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; - -/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ +/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { - public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - // A synthetic never-taken loop exit has no source scope. - super(ctrl, scope == null ? null : scope.mem(), data); + public final FunNode _fun; + + public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) { + super(ctrl, mem, data, rpc); + _fun = fun; } public Node ctrl() { return in(0); } public Node mem () { return in(1); } public Node expr() { return in(2); } + public Node rpc () { return in(3); } + public FunNode fun() { return _fun; } @Override public String label() { return "Return"; } @@ -27,16 +33,87 @@ public class ReturnNode extends CFGNode { return p.p(";"); } + // No one unique control follows; can be many call end sites + @Override public CFGNode uctrl() { return null; } + @Override public Type compute() { - // Return exposes the complete memory state. + if( inProgress () ) return TypeTuple.RET; // In progress + if( _fun.isDead() ) return TypeTuple.RET.dual(); // Dead another way return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } - @Override - public Node idealize() { - if( ctrl()._type==Type.XCONTROL ) - return ctrl(); + @Override public Node idealize() { + if( inProgress () ) return null; + if( _fun.isDead() ) 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) ) { + Node top = new ConstantNode(Type.TOP).peephole(); + setDef(1,top); + setDef(2,top); + return this; + } + return null; } + + public boolean inProgress() { + return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress(); + } + + // Remember parsed type kinds for diagnostics; the optimized expression decides validity. + private boolean ti=false, tf=false, tp=false, tn=false; + + // Add a return exit to the current parsing function + void addReturn( Node ctrl, Node rmem, Node expr ) { + assert inProgress(); + + // Gather parse-time return types for error reporting + Type t = expr._type; + ti |= t instanceof TypeInteger x; + tf |= t instanceof TypeFloat x; + tp |= t instanceof TypeMemPtr x; + tn |= t==Type.NIL; + + // Merge path into the One True Return + RegionNode r = (RegionNode)ctrl(); + // Assert that the Phis are in particular outputs; not reordered or shuffled + PhiNode mem = (PhiNode)r.out(0); assert mem._declaredType == TypeMem.BOT; + PhiNode rez = (PhiNode)r.out(1); assert rez._declaredType == Type.BOTTOM; + // Pop "inProgress" null off + r ._inputs.pop(); + mem._inputs.pop(); + rez._inputs.pop(); + // Add new return point + r .addDef(ctrl); + mem.addDef(rmem); + rez.addDef(expr); + // Back to being inProgress + r .addDef(null); + mem.addDef(null); + rez.addDef(null); + } + + @Override public Parser.ParseException err() { + return expr()._type==Type.BOTTOM ? mixerr(ti,tf,tp,tn,_fun._loc) : null; + } + + static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) { + if( !ti && !tf && !tp && !tn ) + return Parser.error("No defined return type", loc); + SB sb = new SB().p("No common type amongst "); + if( ti ) sb.p("int and "); + if( tf ) sb.p("f64 and "); + if( tp || tn ) sb.p("reference and "); + return Parser.error(sb.unchar(5).toString(),loc); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 6be071a2..10a0e4aa 100644 --- a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -16,7 +16,7 @@ public class RoundF32Node extends Node { public Type compute() { if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() ) return TypeFloat.constant((float)i0.value()); - if( in(1)._type==TypeFloat.BOT ) return TypeFloat.B32; + if( in(1)._type==TypeFloat.F64 ) return TypeFloat.F32; return in(1)._type; } diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java index 9f07956c..d321da42 100644 --- a/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -1,16 +1,15 @@ package com.seaofnodes.simple.node; - +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; - -public class SarNode extends Node { - public SarNode(Node lhs, Node rhs) { super(null, lhs, rhs); } +public class SarNode extends LogicalNode { + public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } @Override public String label() { return "Sar"; } - - @Override protected String format() { return "(%1>>%2)"; } + @Override public String op() { return ">>"; } @Override public Type compute() { @@ -45,10 +44,5 @@ public class SarNode extends Node { return null; } - @Override Node copy(Node lhs, Node rhs) { return new SarNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '>>' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '>>' " + in(2)._type; - return null; - } + @Override Node copy(Node lhs, Node rhs) { return new SarNode(_loc,lhs,rhs); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 753abe90..de199d2d 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -2,9 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Ary; +import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; import java.util.*; @@ -22,34 +20,6 @@ public class ScopeNode extends Node { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; - /** The tracked fields are now complex enough to deserve a array-of-structs layout - */ - public static class Var { - public final int _idx; // index in containing scope - public final String _name; // Declared name - private Type _type; // Declared type - public final boolean _final; // Final field - public Var(int idx, String name, Type type, boolean xfinal) { - _idx = idx; - _name = name; - _type = type; - _final = xfinal; - } - public Type type() { - if( !_type.isFRef() ) return _type; - // Update self to no longer use the forward ref type - Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); - return (_type=_type.meet(def)); - } - public Type lazyGLB() { - Type t = type(); - return t instanceof TypeMemPtr ? t : t.glb(); - } - @Override public String toString() { - return _type.toString()+(_final ? " ": " !")+_name; - } - } - public Node in(Var v) { return in(v._idx); } @Override public Type compute() { return Type.BOTTOM; } @@ -61,8 +31,9 @@ public class ScopeNode extends Node { // Size of each nested lexical scope public final Ary<Integer> _lexSize; - // True if parsing inside of a constructor - public final Ary<Boolean> _inCons; + // Lexical scope is one of normal Block, constructor or function + public enum Kind { Block, Constructor, Function }; + public final Ary<Kind> _kinds; // Extra guards; tested predicates and casted results private final Ary<Node> _guards; @@ -72,7 +43,7 @@ public class ScopeNode extends Node { _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); - _inCons = new Ary<>(Boolean.class); + _kinds = new Ary<>(Kind .class); _guards = new Ary<>(Node .class); } @@ -85,7 +56,7 @@ public class ScopeNode extends Node { for( int i=0; i<nIns(); i++ ) { if( j < _lexSize._len && i == _lexSize.at(j) ) { p.p("| "); j++; } Var v = _vars.get(i); - p.p(v.type()); + p.p(v._type().print(new SB())); p.p(" "); if( v._final ) p.p("!"); p.p(v._name); @@ -104,7 +75,6 @@ public class ScopeNode extends Node { public Node ctrl() { return in(0); } public Node mem() { return in(lookup(MEM0)); } - public void mem(Node n) { update(MEM0,n); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -117,38 +87,69 @@ public class ScopeNode extends Node { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } + public Node mem(Node n) { update(MEM0,n); return n; } - public void push() { push(false); } - public void push(boolean inCon) { - assert _lexSize._len==_inCons._len; + public void push(Kind kind) { + assert _lexSize._len==_kinds._len; _lexSize.push(_vars.size()); - _inCons.push(inCon); + _kinds .push(kind); } + + // Pop a lexical scope public void pop() { - assert _lexSize._len==_inCons._len; + assert _lexSize._len==_kinds._len; + promote(); int n = _lexSize.pop(); - _inCons.pop(); + _kinds.pop(); popUntil(n); _vars.setLen(n); } + + // Look for forward references in the last lexical scope and promote to the + // next outer lexical scope. At the last scope declare them an error. + public void promote() { + int n = _lexSize.last(); + for( int i=n; i<nIns(); i++ ) { + Var v = _vars.at(i); + if( !v.isFRef() ) continue; + if( _lexSize._len==1 ) + throw Parser.error("Undefined name '" + v._name + "'",v._loc); + _vars.swap(n,i); + _inputs.swap(n,i); + v._idx = n; + n++; + _lexSize.set(_lexSize._len-1,n); + } + } + + // Allocation fields live in the scope immediately outside the constructor // block. Nested blocks retain that permission; surrounding bindings do not. public boolean canInit(Var v) { - for( int i=_inCons._len-1; i>0; i-- ) - if( _inCons.at(i) ) + for( int i=_kinds._len-1; i>0; i-- ) + if( _kinds.at(i)==Kind.Constructor ) return v._idx >= _lexSize.at(i-1) && v._idx < _lexSize.at(i); return false; } - public boolean inCon() { return _inCons.last(); } + public boolean inCon() { return _kinds.last() == Kind.Constructor; } + + // Is v outside any current function scope? + public boolean outOfFunction( Var v ) { + for( int i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- ) + if( _kinds.at(i)==Kind.Function ) + return true; + return false; + } + // Find name in reverse, return an index into _vars or -1. Linear scan // instead of hashtable, but probably doesn't matter until the scan // typically hits many dozens of variables. int find( String name ) { for( int i=_vars.size()-1; i>=0; i-- ) - if( _vars.get(i)._name.equals(name) ) + if( _vars.at(i)._name.equals(name) ) return i; return -1; } @@ -156,13 +157,24 @@ public class ScopeNode extends Node { /** * Create a new variable name in the current scope */ - public boolean define( String name, Type declaredType, boolean xfinal, Node init ) { - assert name.charAt(0)!='$' || _lexSize.size()==1; // Later scopes do not define memory - if( _lexSize._len > 1 ) - for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) - if( _vars.get(i)._name.equals(name) ) - return false; // Double define - _vars.add(new Var(nIns(),name,declaredType,xfinal)); + public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) { + assert _lexSize.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory + if( _lexSize._len > 0 ) + for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) { + Var n = _vars.at(i); + if( n._name.equals(name) ) { + if( !n.isFRef() ) return false; // Double define + FRefNode fref = (FRefNode)in(n._idx); // Get forward ref + if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant + n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location + setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also + } + } + Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL); + _vars.add(v); + // Creating a forward reference + if( init==Parser.XCTRL ) + init = new FRefNode(v).init(); addDef(init); return true; } @@ -191,7 +203,7 @@ public class ScopeNode extends Node { update(_vars.at(idx),n); } - public Var update( ScopeNode.Var v, Node st ) { + public Var update( Var v, Node st ) { Node old = in(v._idx); if( old instanceof ScopeNode loop ) { // Lazy Phi! @@ -205,6 +217,7 @@ public class ScopeNode extends Node { : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } + assert !v._final || st==null || canInit(v); if( st!=null ) setDef(v._idx,st); // Set new value return v; } @@ -231,7 +244,7 @@ public class ScopeNode extends Node { // 3) Ensure that the order of defs is the same to allow easy merging dup._vars .addAll(_vars ); dup._lexSize.addAll(_lexSize); - dup._inCons .addAll(_inCons ); + dup._kinds .addAll(_kinds ); dup._guards .addAll(_guards ); // The dup'd guards all need dup'd keepers, to keep proper accounting // when later removing all guards @@ -242,7 +255,7 @@ public class ScopeNode extends Node { // Memory is one binding, with the same lazy loop Phi as scalar names. for( int i=1; i<nIns(); i++ ) - dup.addDef(loop ? this : in(i)); + dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -254,8 +267,8 @@ public class ScopeNode extends Node { * @param that The ScopeNode to be merged into this * @return A new node representing the merge point */ - public RegionNode mergeScopes(ScopeNode that) { - RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep()); + public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { + RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); _merge(that,r); that.kill(); // Kill merged scope IterPeeps.add(r); @@ -274,6 +287,20 @@ public class ScopeNode extends Node { } } + // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;". + // Forward refs are copied to the other side, "as if" they were there all along. + public void balanceIf( ScopeNode scope ) { + for( int i = nIns(); i < scope.nIns(); i++ ) { + Var n = scope._vars.at(i); + if( n.isFRef() ) { // RHS has forward refs + _vars.add(n); // Copy to LHS + addDef(scope.in(i)); + } else + throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); + } + } + + // peephole the backedge scope into this loop head scope // We set the second input to the phi from the back edge (i.e. loop body) public void endLoop(ScopeNode back, ScopeNode exit ) { @@ -290,6 +317,8 @@ public class ScopeNode extends Node { // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { 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; @@ -324,7 +353,7 @@ public class ScopeNode extends Node { assert ctrl instanceof CFGNode; _guards.add(ctrl); // Marker between guard sets // add pred & its cast to the normal input list, with special Vars - if( ctrl._type == Type.XCONTROL || pred==null || pred.isDead() ) + if( pred==null || pred.isDead() ) return; // Dead, do not add any guards _addGuards(ctrl,pred,invert); } @@ -344,31 +373,31 @@ public class ScopeNode extends Node { pred = pred instanceof NotNode not ? not.in(1) : IterPeeps.add(new NotNode(pred).peephole()); // This is a zero/null test. // Compute the positive test type. - Type tnz = pred._type.nonZero(); - Type tcast = tnz.join(pred._type); - if( tcast != pred._type ) { - Node cast = new CastNode(tnz,ctrl,pred.keep()).peephole().keep(); - _guards.add(pred); - _guards.add(cast); - replace(pred,cast); - } + Type tnz = pred._type.nonZero(); + if( tnz!=null ) + _addGuard(tnz,ctrl,pred); // Compute the negative test type. if( pred instanceof NotNode not ) { Node npred = not.in(1); Type tzero = npred._type.makeZero(); - Type tzcast= tzero.join(npred._type); - if( tzcast != npred._type ) { - Node cast = new CastNode(tzero,ctrl,npred.keep()).peephole().keep(); - _guards.add(npred); - _guards.add( cast); - replace(npred,cast); - } + _addGuard(tzero,ctrl,npred); } } + private void _addGuard(Type guard, Node ctrl, Node pred) { + Type tcast = guard.join(pred._type); + if( tcast != pred._type && !tcast.isHigh() ) { + Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep(); + _guards.add(pred); + _guards.add(cast); + replace(pred,cast); + } + } + + // Remove matching pred/cast pairs from this guarded region. - public void removeGuards( Node ctrl ) { + public ScopeNode removeGuards( Node ctrl ) { assert ctrl instanceof CFGNode; // Pop the guards up to this region's marker. while( true ) { @@ -378,6 +407,7 @@ public class ScopeNode extends Node { g .unkill(); // Pop/kill cast _guards.pop().unkill(); // Pop/kill pred } + return this; } // If we find a guarded instance of pred, replace with the upcasted version @@ -401,16 +431,17 @@ public class ScopeNode extends Node { if( !(n instanceof CFGNode) ) n.unkill(); _guards.clear(); - super.kill(); + // Can have lazy uses remaining + if( isUnused() ) + super.kill(); } - private Node replace( Node old, Node cast ) { + private void replace( Node old, Node cast ) { assert old!=null && old!=cast; for( int i=0; i<nIns(); i++ ) if( in(i)==old ) setDef(i,cast); - return cast; } } diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 89d205c5..84c19247 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -1,16 +1,15 @@ package com.seaofnodes.simple.node; - import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; -public class ShlNode extends Node { - public ShlNode(Node lhs, Node rhs) { super(null, lhs, rhs); } +public class ShlNode extends LogicalNode { + public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } @Override public String label() { return "Shl"; } - - @Override protected String format() { return "(%1<<%2)"; } + @Override public String op() { return "<<"; } @Override public Type compute() { @@ -37,10 +36,10 @@ public class ShlNode extends Node { if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger ) return lhs; // (x + c) << i => (x << i) + (c << i) - if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() && c!=TypeInteger.ZERO ) { + if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof TypeInteger c && c.isConstant() ) { long sum = c.value() << shl.value(); if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) - return new AddNode( new ShlNode(add.in(1),rhs).peephole(), Parser.con(sum) ); + return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) ); } } @@ -48,10 +47,5 @@ public class ShlNode extends Node { return null; } - @Override Node copy(Node lhs, Node rhs) { return new ShlNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '<<' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '<<' " + in(2)._type; - return null; - } + @Override Node copy(Node lhs, Node rhs) { return new ShlNode(_loc,lhs,rhs); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 22b42f3e..587e1631 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -1,16 +1,15 @@ package com.seaofnodes.simple.node; - +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; - -public class ShrNode extends Node { - public ShrNode(Node lhs, Node rhs) { super(null, lhs, rhs); } +public class ShrNode extends LogicalNode { + public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } @Override public String label() { return "Shr"; } - - @Override protected String format() { return "(%1>>>%2)"; } + @Override public String op() { return ">>>"; } @Override public Type compute() { @@ -41,10 +40,5 @@ public class ShrNode extends Node { return null; } - @Override Node copy(Node lhs, Node rhs) { return new ShrNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '>>>' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '>>>' " + in(2)._type; - return null; - } + @Override Node copy(Node lhs, Node rhs) { return new ShrNode(_loc,lhs,rhs); } } diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java index cefa2d72..8bdb60cf 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -11,7 +11,7 @@ public class StartNode extends LoopNode implements MultiNode { final Type _arg; - public StartNode(Type arg) { super(null); _arg = arg; _type = compute(); } + public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); } @Override public String label() { return "Start"; } @@ -21,7 +21,19 @@ public class StartNode extends LoopNode implements MultiNode { @Override public boolean isMultiHead() { return true; } @Override public boolean blockHead() { return true; } - @Override public CFGNode cfg0() { return this; } + @Override public CFGNode cfg0() { return null; } + + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. For Start, its "main" + @Override public CFGNode uctrl() { + // Find "main", its the start. + CFGNode C = null; + for( Node use : _outputs ) + if( use instanceof FunNode fun && fun.sig().isa(TypeFunPtr.MAIN) ) + { assert C==null; C = fun; } + return C; + } + @Override public TypeTuple compute() { return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index d0eb5750..a15125a1 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -2,11 +2,8 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.GlobalCodeMotion; - public class StopNode extends CFGNode { @@ -15,6 +12,7 @@ public class StopNode extends CFGNode { public StopNode(String src) { super(); _src = src; + _type = compute(); } @Override @@ -24,6 +22,7 @@ public class StopNode extends CFGNode { @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + // For the sake of many old tests, and single value prints as "return val" if( ret()!=null ) return p.n(ret()); p.p("Stop[ "); for( Node ret : _inputs ) @@ -33,10 +32,11 @@ public class StopNode extends CFGNode { @Override public boolean blockHead() { return true; } + // If a single Return, return it. // Otherwise, null because ambiguous. public ReturnNode ret() { - return nIns()==1 ? (ReturnNode)in(0) : null; + return nIns()==1 && in(0) instanceof ReturnNode ret ? ret : null; } @Override @@ -48,42 +48,17 @@ public class StopNode extends CFGNode { public Node idealize() { int len = nIns(); for( int i=0; i<nIns(); i++ ) - if( in(i)._type==Type.XCONTROL ) + if( ((ReturnNode)in(i)).fun().isDead() ) delDef(i--); if( len != nIns() ) return this; return null; } @Override public int idepth() { - if( _idepth!=0 ) return _idepth; + if( validIDepth() ) return _idepth; int d=0; - for( Node n : _inputs ) - if( n!=null ) - d = Math.max(d,((CFGNode)n).idepth()+1); + for( Node ret : _inputs ) + d = Math.max(d,((ReturnNode)ret).idepth()+1); return cacheIDepth(d); } - @Override public CFGNode idom(Node dep) { return null; } - - public Node addReturn(Node node) { - return addDef(node); - } - - public StopNode iterate() { return IterPeeps.iterate(this).typeCheck().GCM(); } - public StopNode typeCheck() { - var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; - String err = walk(n -> { - String msg = n.err(); - if( msg != null && obs != null ) obs.error(n, msg); - return msg; - }); - if( err != null ) throw new RuntimeException(err); - if( obs != null ) obs.phase("TypeCheck"); - return this; - } - StopNode GCM() { - Parser.START.buildLoopTree(this); - - GlobalCodeMotion.buildCFG(this); - return this; - } } diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 962618ef..de17bbad 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -2,11 +2,12 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; - /** * Store represents setting a value to a memory based object, in chapter 10 * this means a field inside a struct. @@ -22,8 +23,8 @@ public class StoreNode extends MemOpNode { * @param off The offset inside the struct base * @param value Value to be stored */ - public StoreNode(String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { - super(name, alias, glb, mem, ptr, off, value); + public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { + super(loc, name, alias, glb, mem, ptr, off, value); _init = init; } @@ -73,7 +74,7 @@ public class StoreNode extends MemOpNode { return this; } - // Simple store-after-new on same address. Should pick up the + // Simple store-after-new on same address. Should pick up // an init-store being stomped by a first user store. if( mem() instanceof ProjNode st && st .in(0) instanceof NewNode nnn && ptr() instanceof ProjNode ptr && ptr.in(0) == nnn && @@ -81,7 +82,7 @@ public class StoreNode extends MemOpNode { // Cannot fold a store of a single element over the array body initializer value !(tmp._obj.isAry() && tmp._obj._fields[1]._alias==_alias) && // Very sad strong cutout: val has to be legal to hoist to a New - // input, which means it cannot depend on the New. Many many + // input, which means it cannot depend on the New. Many, many // things are legal here but difficult to check without doing a // full dominator check. Example failure: // "struct C { C? c; }; C self = new C { c=self; }" @@ -92,10 +93,12 @@ public class StoreNode extends MemOpNode { // Folding away a broken store err()==null ) { nnn.setDef(nnn.findAlias(_alias),val()); - // Must retype the NewNode - nnn ._type = nnn. compute(); + // Must *retype* the NewNode, this is not monotonic in isolation + // but is monotonic counting from this Store to the New. + nnn ._type = nnn .compute(); mem()._type = mem().compute(); - return st; + IterPeeps.addAll(nnn._outputs); + return mem(); } return null; @@ -130,9 +133,9 @@ public class StoreNode extends MemOpNode { if( ctrl==null ) return null; Node mem = phi.in(1); - Node init = new LoadNode(_name,_alias,_declaredType,mem,ptr(),off()).peephole(); + Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); Node value = new PhiNode(_name,_declaredType,loop,init,val()).peephole(); - Node sink = new StoreNode(_name,_alias,_declaredType,mem,ptr(),off(),value,_init); + Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); // Break the memory cycle, killing the old store. Keep the Phi alive // even if that store was its last use, until our caller replaces it. phi.keep(); @@ -153,13 +156,15 @@ public class StoreNode extends MemOpNode { } @Override - String err() { - String err = super.err(); + public Parser.ParseException err() { + Parser.ParseException err = super.err(); if( err != null ) return err; + if( ptr()._type == Type.TOP ) + return null; // Dead store TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( tmp._ro || tmp._obj.field(_name)._final ) - return "Cannot modify final field '"+_name+"'"; + if( tmp._ro || (tmp._obj.field(_name)._final && !"[]".equals(_name)) ) + return Parser.error("Cannot modify final field '"+_name+"'",_loc); Type t = val()._type; - return _init || t.isa(_declaredType) ? null : "Cannot store "+t+" into field "+_declaredType+" "+_name; + return _init || t.isa(_declaredType) ? null : Parser.error("Cannot store "+t+" into field "+_declaredType+" "+_name,_loc); } } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java index 429b49cc..b9801f63 100644 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -2,6 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.type.*; /** @@ -19,7 +20,7 @@ public class StructNode extends Node { p.p(_ts._name).p(" {"); for( int i=0; i<nIns(); i++ ) { p.p(_ts._fields[i]._fname).p(":"); - p.p(in(i)==null ? Type.BOTTOM : in(i)._type); + p.p((in(i)==null ? Type.BOTTOM : in(i)._type).print(new SB())); p.p("; "); } return p.unchar("; ").p("}"); diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java index 768cb2bf..9bf6d83e 100644 --- a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -5,7 +5,6 @@ import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; - public class SubNode extends Node { public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } @@ -18,6 +17,9 @@ public class SubNode extends Node { Type t1 = in(1)._type, t2 = in(2)._type; if( t1.isHigh() || t2.isHigh() ) return TypeInteger.TOP; + // Sub of same is 0 + if( in(1)==in(2) ) + return TypeInteger.ZERO; if( t1 instanceof TypeInteger i1 && t2 instanceof TypeInteger i2 ) { if (i1.isConstant() && i2.isConstant()) @@ -28,9 +30,6 @@ public class SubNode extends Node { i2._min != Long.MIN_VALUE ) return TypeInteger.make(i1._min-i2._max,i1._max-i2._min); } - // Sub of same is 0 - if( in(1)==in(2) ) - return TypeInteger.ZERO; return TypeInteger.BOT; } diff --git a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 26cf50e1..7b0122ce 100644 --- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -15,9 +15,13 @@ public class ToFloatNode extends Node { @Override public Type compute() { - if (in(1)._type instanceof TypeInteger i0 && i0.isConstant() ) - return TypeFloat.constant(i0.value()); - return TypeFloat.BOT; + if( in(1)._type == Type.NIL ) return TypeFloat.FZERO; + if (in(1)._type instanceof TypeInteger i0 ) { + if( i0.isHigh() ) return TypeFloat.F64.dual(); + if( i0.isConstant() ) + return TypeFloat.constant(i0.value()); + } + return TypeFloat.F64; } @Override public Node idealize() { return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java index 056dfba1..3eae8548 100644 --- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -6,9 +6,11 @@ import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; public class XCtrlNode extends CFGNode { - public XCtrlNode( ) { super(Parser.START); } + public XCtrlNode() { super(Parser.START); } @Override public String label() { return "Xctrl"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); } + @Override public boolean isConst() { return true; } + @Override public boolean isMultiTail() { return true; } @Override public Type compute() { return Type.XCONTROL; } @Override public Node idealize() { return null; } } diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java index 5efe23e2..034e352a 100644 --- a/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -1,16 +1,15 @@ package com.seaofnodes.simple.node; - +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; - -public class XorNode extends Node { - public XorNode(Node lhs, Node rhs) { super(null, lhs, rhs); } +public class XorNode extends LogicalNode { + public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } @Override public String label() { return "Xor"; } - - @Override protected String format() { return "(%1^%2)"; } + @Override public String op() { return "^"; } @Override public Type compute() { @@ -49,10 +48,5 @@ public class XorNode extends Node { return null; } - @Override Node copy(Node lhs, Node rhs) { return new XorNode(lhs,rhs); } - @Override String err() { - if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '^' " + in(1)._type; - if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '^' " + in(2)._type; - return null; - } + @Override Node copy(Node lhs, Node rhs) { return new XorNode(_loc,lhs,rhs); } } diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 7ba58e6f..3e23968a 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -6,18 +6,33 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 17's view of the IR; browser, transport and layout are shared. */ +/** Chapter 18's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter<Node> { @Override protected String edgeName(Node n, int i) { return n instanceof ScopeNode scope && i < scope._vars.size() ? scope._vars.get(i)._name : null; } + @Override protected int edgeJump(Node n, int i) { + return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret + ? ref(ret.fun()) : 0; + } @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } + @Override protected boolean folding(Node n) { + if( n instanceof FunNode fun ) return fun.folding(); + if( n instanceof ReturnNode ret ) return ret.fun().folding(); + if( n instanceof CallEndNode cend ) return cend.folding(); + if( n instanceof CallNode ) + for( int i=0; i<n.nOuts(); i++ ) + if( n.out(i) instanceof CallEndNode cend && cend.folding() ) return true; + return false; + } + @Override protected Kind kind(Node n) { if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof FunNode ) return Kind.FUN; if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; if( n instanceof RegionNode ) return Kind.REGION; @@ -26,13 +41,13 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { } @Override protected Role role(Node n, int i) { - if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof CtrlNode || n instanceof XCtrlNode ) return Role.ASSOC; if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; if( n instanceof ProjNode || n instanceof CProjNode ) return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; - if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof NewNode) ) 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; diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index d774967b..b2dd3107 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -2,44 +2,35 @@ package com.seaofnodes.simple.print; import com.seaofnodes.graph.GraphCapture; import com.seaofnodes.graph.GraphViewer; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.io.IOException; -/** Chapter 17's compilation context; capture and event bookkeeping are shared. */ +/** Chapter 18's compilation context; capture and event bookkeeping are shared. */ public class SimpleGraphObserver extends GraphCapture<Node> { - private Parser _parser; - private Node _ret; - private String _phase; + private CodeGen _code; public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } public static void main(String[] args) throws IOException { GraphViewer.run(new SimpleGraphObserver()); } @Override protected void compile(String src) { - _parser = new Parser(src); - _parser._obs = this; - _phase = "Parse"; - _ret = _parser.parse(); - phase(_phase); - _phase = "Iter"; - com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); - phase(_phase); - _phase = "TypeCheck"; - _parser.STOP.typeCheck(); + _code = new CodeGen(src); + _code._obs = this; + _code.parse().opto().typeCheck(); } - @Override protected String phase() { return _phase; } - @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected String phase() { return _code._phase.name(); } + @Override protected int pos() { return _code.P != null ? _code.P.pos() : -1; } @Override protected void detach() { - if( _parser != null ) _parser._obs = null; - _parser = null; - _ret = null; + if( _code != null ) _code._obs = null; + _code = null; } - @Override protected Node scope() { return _parser._scope; } + @Override protected Node scope() { return _code.P == null ? null : _code.P._scope; } @Override protected void roots(ArrayList<Node> roots) { - roots.add(Parser.START); - roots.add(_parser.STOP); - roots.addAll(_parser._xScopes); - roots.add(_ret); + roots.add(_code._stop); + if( _code.P != null ) { + roots.add(com.seaofnodes.simple.Parser.START); + roots.addAll(_code.P._xScopes); + } } } diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java index 71c2fded..c93f82e7 100644 --- a/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/src/main/java/com/seaofnodes/simple/type/Field.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; import java.util.ArrayList; @@ -36,8 +37,9 @@ public class Field extends Type { @Override public Field makeRO() { return isFinal() ? this : make(_fname,_type.makeRO(),_alias,true); } @Override public boolean isFinal() { return _final && _type.isFinal(); } - public static final Field TEST = make("test",TypeInteger.ZERO,-2,false); - public static void gather(ArrayList<Type> ts) { ts.add(TEST); } + public static final Field TEST = make("test",Type.NIL,-2,false); + public static final Field TEST2= make("test",Type.NIL,-2,true); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); } @Override Field xmeet( Type that ) { Field fld = (Field)that; // Invariant @@ -51,12 +53,7 @@ public class Field extends Type { @Override public Field glb() { Type glb = _type.glb(); - return glb==_type ? this : make(_fname,glb,_alias,_final); - } - - @Override public Field lub() { - Type lub = _type.lub(); - return lub==_type ? this : make(_fname,lub,_alias,_final); + return (glb==_type && _final) ? this : make(_fname,glb,_alias,true); } // Override in subclasses @@ -69,8 +66,8 @@ public class Field extends Type { @Override - public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_final?"":"!").append(_fname).append(":").append(_alias).append(" : ")); + public SB print( SB sb ) { + return _type.print(sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : ")); } @Override public String str() { return _fname; } diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java index 60afb668..34f54be8 100644 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/src/main/java/com/seaofnodes/simple/type/Type.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; - import java.util.ArrayList; import java.util.HashMap; @@ -31,37 +31,50 @@ public class Type { static final byte TTOP = 1; // Top (ANY) static final byte TCTRL = 2; // Ctrl flow bottom static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) - static final byte TSIMPLE = 4; // End of the Simple Types - static final byte TINT = 5; // All Integers; see TypeInteger - static final byte TFLT = 6; // All Integers; see TypeInteger - static final byte TTUPLE = 7; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TMEM = 8; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TMEMPTR = 9; // Memory pointer type - static final byte TSTRUCT =10; // Structs; tuples with named fields - static final byte TFLD =11; // Fields into struct - static final byte TARRAY =12; // Array + static final byte TNIL = 4; // low null of all flavors + static final byte TXNIL = 5; // high or choice null + static final byte TSIMPLE = 6; // End of the Simple Types + + static final byte TPTR = 7; // All nil-able scalar values + static final byte TINT = 8; // All Integers; see TypeInteger + static final byte TFLT = 9; // All Floats ; see TypeFloat + static final byte TMEMPTR =10; // Memory pointer to a struct type + static final byte TFUNPTR =11; // Function pointer; unique signature and code address (just a bit) + static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel + static final byte TMEM =13; // All memory (alias 0) or A slice of memory - with specific alias + static final byte TSTRUCT =14; // Structs; tuples with named fields + static final byte TFLD =15; // Named fields in structs + static final byte TRPC =16; // Return Program Control (Return PC or RPC) public final byte _type; public boolean is_simple() { return _type < TSIMPLE; } - private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"}; + private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"}; protected Type(byte type) { _type = type; } public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL public static final Type TOP = new Type( TTOP ).intern(); // ANY public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl + public static final Type NIL = new Type( TNIL ).intern(); // low null of all flavors + public static final Type XNIL = new Type( TXNIL ).intern(); // high or choice null public static Type[] gather() { ArrayList<Type> ts = new ArrayList<>(); ts.add(BOTTOM); ts.add(CONTROL); - Field.gather(ts); + ts.add(NIL); + ts.add(XNIL); + TypeNil.gather(ts); + TypePtr.gather(ts); TypeInteger.gather(ts); TypeFloat.gather(ts); - TypeMem.gather(ts); TypeMemPtr.gather(ts); + TypeFunPtr.gather(ts); + TypeMem.gather(ts); + Field.gather(ts); TypeStruct.gather(ts); TypeTuple.gather(ts); + TypeRPC.gather(ts); int sz = ts.size(); for( int i = 0; i < sz; i++ ) ts.add(ts.get(i).dual()); @@ -69,18 +82,12 @@ public class Type { } // Is high or on the lattice centerline. - public boolean isHigh () { return _type==TTOP || _type==TXCTRL; } - public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; } + public boolean isHigh () { return _type==TTOP || _type==TXCTRL || _type==TXNIL; } + public boolean isHighOrConst() { return isHigh() || isConstant(); } // Strict constant values, things on the lattice centerline. // Excludes both high and low values - public boolean isConstant() { return false; } - - /** - * Display Type name in a format that's good for IR printer - */ - public StringBuilder typeName( StringBuilder sb) { return print(sb); } - + public boolean isConstant() { return _type==TNIL; } // ---------------------------------------------------------- @@ -92,7 +99,7 @@ public class Type { // to be immutable once defined. The rationale for interning is // *correctness* with cyclic type definitions. Simple structural recursive // checks go exponential with merely sharing, but with cycles they will - // stack overflow and crash. Intering means we do not need to have sharing + // stack overflow and crash. Interning means we do not need to have sharing // checks with every type compare, only during interning. // Factory method which interns "this" @@ -131,6 +138,9 @@ public class Type { if( t == this ) return this; // Same-type is always safe in the subclasses if( _type==t._type ) return xmeet(t); + // TypeNil vs TypeNil meet + if( this instanceof TypeNil ptr0 && t instanceof TypeNil ptr1 ) + return ptr0.nmeet(ptr1); // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". if( is_simple() ) return this.xmeet(t ); if( t.is_simple() ) return t .xmeet(this); @@ -145,18 +155,27 @@ public class Type { // ANY meet anything is thing; thing meet ALL is ALL if( _type==TBOT || t._type==TTOP ) return this; if( _type==TTOP || t._type==TBOT ) return t; - // 'this' is {TCTRL,TXCTRL} + + // RHS TypeNil vs NIL/XNIL + if( _type== TNIL ) return t instanceof TypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? TypePtr.PTR : BOTTOM); + if( _type== TXNIL ) return t instanceof TypeNil ptr ? ptr.meetX() : (t._type== TNIL ? TypePtr.PTR : BOTTOM); + // 'this' is only {TCTRL,TXCTRL} + // Other non-simple RHS things bottom out if( !t.is_simple() ) return BOTTOM; - // 't' is {TCTRL,TXCTRL} + // If RHS is NIL/XNIL + if( t._type==TNIL || t._type==TXNIL ) return BOTTOM; + // Both are {TCTRL,TXCTRL} and unequal to each other return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; } public Type dual() { return switch( _type ) { - case TBOT -> TOP; - case TTOP -> BOTTOM; - case TCTRL -> XCONTROL; - case TXCTRL -> CONTROL; + case TBOT -> TOP; + case TTOP -> BOTTOM; + case TCTRL ->XCONTROL; + case TXCTRL-> CONTROL; + case TNIL ->XNIL; + case TXNIL -> NIL; default -> throw Utils.TODO(); // Should not reach here }; } @@ -172,19 +191,16 @@ public class Type { // True if this "isa" t; e.g. 17 isa TypeInteger.BOT public boolean isa( Type t ) { return meet(t)==t; } - /** Compute greatest lower bound in the lattice */ - public Type glb() { return _type==TCTRL ? CONTROL : BOTTOM; } - /** Compute least upper bound in the lattice */ - public Type lub() { return _type==TCTRL ? XCONTROL : TOP; } + // True if this "isa" t up to named structures + public boolean shallowISA( Type t ) { return isa(t); } + + public Type nonZero() { return TypePtr.NPTR; } - // Make an initial/default version of this type. Typically, 0 for integers - // and null for nullable pointers. - public Type makeInit() { return this; } // Make a zero version of this type, 0 for integers and null for pointers. - public Type makeZero() { return null; } - // Make a non-zero version of this type, if possible. Integers attempt to - // exclude zero from their range and pointers become not-null. - public Type nonZero() { return glb(); } + public Type makeZero() { return Type.NIL; } + + /** Compute greatest lower bound in the lattice */ + public Type glb() { return Type.BOTTOM; } // Writable array slots are invariant in their element access permissions. // Otherwise an alias could insert a readonly pointer into a writable slot. @@ -212,11 +228,16 @@ public class Type { // This is a more verbose dev-friendly print. @Override public final String toString() { - return print(new StringBuilder()).toString(); + return print(new SB()).toString(); } - public StringBuilder print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;} + public SB print(SB sb) { return sb.p(str()); } + public SB gprint(SB sb) { return print(sb); } // This is used by error messages, and is a shorted print. public String str() { return STRS[_type]; } + + public static void reset() { + TypeFunPtr.reset(); + } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java index f85abbed..aa205149 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; import java.util.ArrayList; /** @@ -7,13 +9,12 @@ import java.util.ArrayList; */ public class TypeFloat extends Type { - public final static TypeFloat TOP = make((byte)-64, 0); - public final static TypeFloat T32 = make((byte)-32, 0); - public final static TypeFloat ZERO= make((byte) 0, 0); - public final static TypeFloat B32 = make((byte) 32, 0); - public final static TypeFloat BOT = make((byte) 64, 0); + public final static TypeFloat ONE = constant(1.0); + public final static TypeFloat FZERO = constant(0.0); + public final static TypeFloat F32 = make(32, 0); + public final static TypeFloat F64 = make(64, 0); - /** +/-64 for double; +/-32 for float, or 0 for constants */ + // - high -64, high -32, con 0, low +32, low +64 public final byte _sz; /** @@ -23,98 +24,72 @@ public class TypeFloat extends Type { private TypeFloat(byte sz, double con) { super(TFLT); - _sz = sz ; + _sz = sz; _con = con; } - private static TypeFloat make(byte sz, double con) { - return new TypeFloat(sz,con).intern(); + private static TypeFloat make(int sz, double con) { + return new TypeFloat((byte)sz,con).intern(); } - public static TypeFloat constant(double con) { return make((byte)0, con); } + public static TypeFloat constant(double con) { return make(0, con); } - public static void gather(ArrayList<Type> ts) { ts.add(ZERO); ts.add(BOT); ts.add(B32); ts.add(constant(3.141592653589793)); } - - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==T32 ) return sb.append("F32Top"); - if( this==B32 ) return sb.append("F32Bot"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append(_con); - } + public static void gather(ArrayList<Type> ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); } @Override public String str() { - if( this==TOP ) return "~flt"; - if( this==T32 ) return "~f32"; - if( this==B32 ) return "f32"; - if( this==BOT ) return "flt"; - return ""+_con+(isF32() ? "f" : ""); - } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("FltTop"); - if( this==T32 ) return sb.append("F32Top"); - if( this==B32 ) return sb.append("F32Bot"); - if( this==BOT ) return sb.append("FltBot"); - return sb.append(isF32() ? "F32" : "Flt"); + return switch( _sz ) { + case -64 -> "~flt"; + case -32 -> "~f32"; + case 0 -> ""+_con+((float)_con==_con ? "f" : ""); + case 32 -> "f32"; + case 64 -> "flt"; + default -> throw Utils.TODO(); + }; } + private boolean isF32() { return ((float)_con)==_con; } - boolean isF32() { return ((float)_con)==_con; } - @Override public boolean isHigh() { return _sz< 0; } - @Override public boolean isHighOrConst() { return _sz<=0; } - @Override public boolean isConstant() { return _sz==0; } - - @Override public int log_size() { - int sz = _sz==0 ? (isF32() ? 32 : 64) : Math.abs(_sz); - return _sz==32 ? 2 : 3; - } + @Override public boolean isHigh () { return _sz< 0; } + @Override public boolean isConstant() { return _sz==0; } + @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; } public double value() { return _con; } @Override - public Type xmeet(Type other) { - // Invariant from caller: 'this' != 'other' and same class (TypeFloat) + public TypeFloat xmeet(Type other) { + TypeFloat f = (TypeFloat)other; + // Invariant from caller: 'this' != 'other' and same class (TypeFloat). TypeFloat i = (TypeFloat)other; // Contract // Larger size in i1, smaller in i0 TypeFloat i0 = _sz < i._sz ? this : i; TypeFloat i1 = _sz < i._sz ? i : this; - if( i1._sz== 64 ) return BOT; + if( i1._sz== 64 ) return F64; if( i0._sz==-64 ) return i1; if( i1._sz== 32 ) - return i0._sz==0 && !i0.isF32() ? BOT : B32; + return i0._sz==0 && !i0.isF32() ? F64 : F32; if( i1._sz!= 0 ) return i1; // i1 is a constant if( i0._sz==-32 ) - return i1.isF32() ? i1 : BOT; + return i1.isF32() ? i1 : F64; // Since both are constants, and are never equals (contract) unequals // constants fall to bottom - return i0.isF32() && i1.isF32() ? B32 : BOT; + return i0.isF32() && i1.isF32() ? F32 : F64; } @Override public Type dual() { - if( _sz==0 ) return this; // Constants are a self-dual - return make((byte)-_sz,0); + return isConstant() ? this : make(-_sz,0); // Constants are a self-dual } - @Override public Type glb() { return BOT; } - @Override public Type lub() { return TOP; } - @Override public TypeFloat makeInit() { return ZERO; } - @Override public TypeFloat makeZero() { return ZERO; } + @Override public Type makeZero() { return FZERO; } + @Override public Type glb() { return F64; } @Override - int hash() { return (int)(Double.hashCode(_con) ^ _sz); } + int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); } @Override public boolean eq( Type t ) { TypeFloat i = (TypeFloat)t; // Contract - return _con==i._con && _sz==i._sz; + // Allow NaN to check for equality + return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con); } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java new file mode 100644 index 00000000..61fc71cf --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -0,0 +1,143 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Represents a Pointer to a function. + * <p></p> + * Functions have argument types and a return type, making a function + * signature. Within a signature there can be many instances of functions, and + * each function is labeled with a small integer constant. A TFP can represent + * a single signature and a set of functions; the set might contain only 1 + * function or the zeroth function (the null ptr). + * <p></p> + * Functions with different signatures cannot be mixed, and will result in a + * bottom function type, which can only be null-checked. + */ +public class TypeFunPtr extends TypeNil { + // A TypeFunPtr is Signature and a set of functions. + public final TypeTuple _sig; + public final Type _ret; + // Cheesy easy implementation for a small set; 1 bit per unique function + // within the TypeTuple. Can be upgraded to a BitSet for larger classes of + // functions. Negative means "these 63 concrete bits plus infinite unknown more" + public final long _fidxs; // 63 unique functions per signature + public String _name; // Optional debug function name; only for named single function + + private TypeFunPtr(byte nil, TypeTuple sig, Type ret, long fidxs) { + super(TFUNPTR,nil); + assert sig != null; + _sig = sig; + _ret = ret; + _fidxs = fidxs; + } + + 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).setName(_name); } + public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_sig, ret, _fidxs).setName(_name); } + public TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _sig,_ret,1L<<fidx ).setName(_name); } + + // Compute "function indices": FIDX + private static final HashMap<TypeTuple,Integer> FIDXS = new HashMap<>(); + private static int nextFIDX(TypeTuple sig) { + Integer i = FIDXS.get(sig); + int fidx = i==null ? 0 : i; + FIDXS.put(sig,fidx+1); // Track count per sig + assert fidx<64; // TODO: need a larger FIDX space + return fidx; // Return + } + public static TypeFunPtr makeFun( TypeTuple sig, Type ret ) { + return make((byte)2,sig,ret,1L<<nextFIDX(sig)); + } + + 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).setName("main"); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); } + + @Override + Type xmeet(Type t) { + TypeFunPtr that = (TypeFunPtr) t; + return TypeFunPtr.make(xmeet0(that),(TypeTuple)_sig.meet(that._sig), _ret.meet(that._ret), _fidxs | that._fidxs); + } + + @Override + public TypeFunPtr dual() { return TypeFunPtr.make(dual0(), _sig.dual(), _ret.dual(), ~_fidxs); } + + // RHS is NIL; do not deep-dual when crossing the centerline + @Override public Type meet0() { return _nil==3 ? this : make((byte)3,_sig,_ret,_fidxs); } + + @Override public TypeFunPtr glb() { return make((byte)3,_sig,_ret,-1L); } + + @Override public boolean isHigh () { return _nil <= 1 || (_nil==2 && _fidxs==0); } + @Override public boolean isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); } + + @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers + + public Type arg(int i) { return _sig._types[i]; } + public long fidxs() { return _fidxs; } + public Type ret() { return _ret; } + public int nargs() { return _sig._types.length; } + + @Override + int hash() { return Utils.fold(_sig.hashCode() ^ _ret.hashCode() ^ _fidxs ^ super.hash()); } + + @Override + boolean eq(Type t) { + TypeFunPtr ptr = (TypeFunPtr)t; // Invariant + return _sig == ptr._sig && _ret == ptr._ret && _fidxs == ptr._fidxs && super.eq(ptr); + } + + public TypeFunPtr setName(String name) { + if( name==null ) return this; + assert _fidxs > 0 && Long.bitCount(_fidxs) == 1; + // Name can be set to different things, just for debug + //assert _name==null || _name.equals(name); + _name = name; + return this; + } + + @Override public String str() { return print(new SB()).toString(); + } + @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, boolean n) { + if( this==BOT ) return sb.p("FunBot"); + if( _nil==0 && _sig==TypeTuple.TOP && _ret==Type.TOP && _fidxs==0 ) return sb.p("FunTop"); + sb.p(x()).p("{ "); + if( n && _name!=null ) sb.p(_name); + else { + if( _sig._types!=null ) + for( Type t : _sig._types ) + _print(sb,g,t).p(" "); + _print(sb.p(g ? "→ " : "-> "),g,_ret).p(" #"); + if( isHigh() ) sb.p("~"); + long fidxs = isHigh() ? ~_fidxs : _fidxs; + String fidx = fidxs==0 ? "" + : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs) + : fidxs == -1 ? "ALL" + : "b"+Long.toBinaryString(fidxs); // Just some function bits + sb.p(fidx); + } + return sb.p("}").p(q()); + } + + // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) { int fidxs = Long.numberOfTrailingZeros(fidxs); ... } + public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); } + + public static void reset() { + FIDXS.clear(); + FIDXS.put(MAIN._sig,1); + for( Type t : INTERN.keySet() ) + if( t instanceof TypeFunPtr tfp && tfp!=MAIN) + tfp._name = null; + } + +} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index b2979d7b..cccb85a5 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; import java.util.ArrayList; @@ -8,17 +9,19 @@ import java.util.ArrayList; */ public class TypeInteger extends Type { - public final static TypeInteger TOP = make(false, 0); - public final static TypeInteger BOT = make(false, 1); public final static TypeInteger ZERO= make(0,0); - public final static TypeInteger U1 = make(0,1); - public final static TypeInteger BOOL= U1; public final static TypeInteger FALSE=ZERO; public final static TypeInteger TRUE= make(1,1); + + public final static TypeInteger I1 = make(-1,0); public final static TypeInteger I8 = make(-128,127); public final static TypeInteger I16 = make(-32768,32767); public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1); + public final static TypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE); + public final static TypeInteger TOP = BOT.dual(); + public final static TypeInteger U1 = make(0,1); + public final static TypeInteger BOOL= U1; public final static TypeInteger U8 = make(0,255); public final static TypeInteger U16 = make(0,65535); public final static TypeInteger U32 = make(0,(1L<<32)-1); @@ -26,55 +29,50 @@ public class TypeInteger extends Type { /** * Describes an integer *range* - everything from min to max; both min and * max are inclusive. If min==max, this is a constant. - * + * <br> * If min <= max, this is a below center (towards bottom). * If min > max, this is an above center (towards top). */ public final long _min, _max; - private TypeInteger(long min, long max) { super(TINT); _min = min; _max = max; } - public static TypeInteger make(long lo, long hi) { return new TypeInteger(lo,hi).intern(); } - public static TypeInteger make(boolean is_con, long con) { - return make(is_con ? con : (con==0 ? Long.MAX_VALUE : Long.MIN_VALUE), - is_con ? con : (con==0 ? Long.MIN_VALUE : Long.MAX_VALUE)); + private TypeInteger(long min, long max) { + super(TINT); + _min = min; + _max = max; } - public static TypeInteger constant(long con) { return make(true, con); } + // Strict non-zero contract + public static TypeInteger make(long lo, long hi) { return new TypeInteger(lo,hi).intern(); } - public static void gather(ArrayList<Type> ts) { ts.add(ZERO); ts.add(BOT); ts.add(U1); ts.add(U8); } + public static TypeInteger constant(long con) { return make(con, con); } - // FIXME this display format is problematic - // In visualizer '#' gets prepended if its a constant - @Override - public StringBuilder print(StringBuilder sb) { return sb.append(str()); } + public static void gather(ArrayList<Type> ts) { ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); } @Override public String str() { - if( this==TOP ) return "~int"; - if( this==BOT ) return "int"; - if( this==BOOL) return ("bool"); - if( this==I8 ) return ("i8"); - if( this==I16 ) return ("i16"); - if( this==I32 ) return ("i32"); - if( this==U8 ) return ("u8"); - if( this==U16 ) return ("u16"); - if( this==U32 ) return ("u32"); if( isConstant() ) return ""+_min; - return "["+_min+"-"+_max+"]"; + long lo = _min, hi = _max; + String x = ""; + if( hi < lo ) { + lo = _max; hi = _min; + x = "~"; + } + return x+_str(lo,hi); } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - if( this==TOP ) return sb.append("IntTop"); - if( this==BOT ) return sb.append("IntBot"); - return sb.append("Int"); + private static String _str(long lo, long hi) { + if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "int"; + if( lo== 0 && hi== 1 ) return "bool"; + if( lo== -1 && hi== 0 ) return "i1"; + if( lo== -128 && hi== 127 ) return "i8"; + if( lo== -32768 && hi== 32767 ) return "i16"; + if( lo== -1L<<31 && hi==(1L<<31)-1 ) return "i32"; + if( lo== 0 && hi== 255 ) return "u8"; + if( lo== 0 && hi== 65535 ) return "u16"; + if( lo== 0 && hi==(1L<<32)-1 ) return "u32"; + return "["+lo+"-"+hi+"]"; } - @Override public boolean isHigh () { return _min > _max; } - @Override public boolean isHighOrConst() { return _min >= _max; } - @Override public boolean isConstant () { return _min == _max; } + @Override public boolean isHigh () { return _min > _max; } + @Override public boolean isConstant() { return _min == _max; } @Override public int log_size() { if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes @@ -93,8 +91,6 @@ public class TypeInteger extends Type { public long mask() { if( isHigh() ) return 0; if( isConstant() ) return _min; - //if( _min<0 ) return -1L; - //if( _max==Long.MAX_VALUE ) return -1L; // Those bit positions which differ min to max long x = _min ^ _max; // Highest '1' bit in the differ set. Since the range is from min to @@ -114,19 +110,18 @@ public class TypeInteger extends Type { } @Override public TypeInteger dual() { return make(_max,_min); } - @Override public TypeInteger glb() { return BOT; } - @Override public TypeInteger lub() { return TOP; } - @Override public TypeInteger makeInit() { return ZERO; } - @Override public TypeInteger makeZero() { return ZERO; } + @Override public TypeInteger nonZero() { if( isHigh() ) return this; + if( this==ZERO ) return null; // No sane answer if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL if( _max==0 ) return make(_min,-1); return this; } - @Override int hash() { return (int)((_min ^ (_min>>32)) *(_max ^ (_max>>32))); } - @Override - public boolean eq( Type t ) { + @Override public Type makeZero() { return ZERO; } + @Override public TypeInteger glb() { return BOT; } + @Override int hash() { return Utils.fold(_min) * Utils.fold(_max); } + @Override public boolean eq( Type t ) { TypeInteger i = (TypeInteger)t; // Contract return _min==i._min && _max==i._max; } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java index 6dcd401a..fe393a88 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; import java.lang.Long; import java.util.BitSet; @@ -13,18 +14,23 @@ public class TypeMem extends Type { // Which slice of memory? // 0 means TOP, no slice. - // -1 means BOT, all memory. + // 0 means BOT, all memory. // N means slice#N. public final int _alias; public final Type _t; // Memory contents, some scalar type - private TypeMem(int alias, Type t) { super(TMEM); _alias = alias; _t = t; } + private TypeMem(int alias, Type t) { + super(TMEM); + assert alias!=0 || (t==Type.TOP || t==Type.BOTTOM); + _alias = alias; + _t = t; + } public static TypeMem make(int alias, Type t) { return new TypeMem(alias,t).intern(); } - public static final TypeMem TOP = make(-1, Type.TOP ); - public static final TypeMem BOT = make(-1, Type.BOTTOM); + public static final TypeMem TOP = make(0, Type.TOP ); + public static final TypeMem BOT = make(0, Type.BOTTOM); - public static void gather(ArrayList<Type> ts) { ts.add(make(1,TypeInteger.ZERO)); ts.add(BOT); } + public static void gather(ArrayList<Type> ts) { ts.add(make(1,Type.NIL)); ts.add(make(1,TypeInteger.ZERO)); ts.add(BOT); } @Override TypeMem xmeet(Type t) { @@ -33,7 +39,7 @@ public class TypeMem extends Type { if( that==TOP ) return this; if( this==BOT ) return BOT; if( that==BOT ) return BOT; - int alias = _alias==that._alias ? _alias : -1; + int alias = _alias==that._alias ? _alias : 0; Type mt = _t.meet(that._t); return make(alias,mt); } @@ -43,26 +49,29 @@ public class TypeMem extends Type { return make(_alias,_t.dual()); } + @Override public boolean isHigh() { return _t.isHigh(); } @Override public Type glb() { return make(_alias,_t.glb()); } - @Override public Type lub() { return make(_alias,_t.lub()); } - @Override - int hash() { return 9876543 + _alias + _t.hashCode(); } + @Override int hash() { return 9876543 + _alias + _t.hashCode(); } - @Override - boolean eq(Type t) { + @Override boolean eq(Type t) { TypeMem that = (TypeMem) t; // Invariant return _alias == that._alias && _t == that._t; } - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("MemBot"); - if( this==TOP ) return sb.append("MemTop"); - sb.append("MEM#"); - if( _alias== 0 ) return sb.append("TOP"); - if( _alias==-1 ) return sb.append("BOT"); - return _t.print(sb.append(_alias).append(":")); + @Override public SB print(SB sb) { + if( this==BOT ) return sb.p("MemBot"); + if( this==TOP ) return sb.p("MemTop"); + sb.p("#"); + if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); + return _t.print(sb.p(_alias).p(":")); + } + @Override public SB gprint(SB sb) { + if( this==BOT ) return sb.p("MemBot"); + if( this==TOP ) return sb.p("MemTop"); + sb.p("#"); + if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); + return _t.gprint(sb.p(_alias).p(":")); } @Override public String str() { return toString(); } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 8ecc8031..61b020cb 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; import java.util.ArrayList; /** @@ -11,9 +13,9 @@ import java.util.ArrayList; * The distinguished *$BOT ptr represents union of *void and Null. * The distinguished *$TOP ptr represents the dual of *$BOT. */ -public class TypeMemPtr extends Type { +public class TypeMemPtr extends TypeNil { // A TypeMemPtr is pair (obj,nil) - // where obj is a TypeStruct, possibly TypeStruct.BOT + // where obj is a TypeStruct, possibly TypeStruct.BOT/TOP // where nil is an explicit null is allowed or not // Examples: @@ -26,25 +28,25 @@ public class TypeMemPtr extends Type { // Deep read-only access through this pointer; independent of slot finality. public boolean _ro; - public final boolean _nil; - private TypeMemPtr(TypeStruct obj, boolean nil) { - super(TMEMPTR); + private TypeMemPtr(byte nil, TypeStruct obj) { + super(TMEMPTR,nil); assert obj!=null; _obj = obj; _ro = obj==TypeStruct.BOT; - _nil = nil; } - public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); } - public static TypeMemPtr make(TypeStruct obj) { return make(obj, false); } - public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(obj, _nil).withAccess(_ro); } - public TypeMemPtr makeFrom(boolean nil) { return nil==_nil ? this : make(_obj, nil).withAccess(_ro); } + static TypeMemPtr make(byte nil, TypeStruct obj) { return new TypeMemPtr(nil, obj).intern(); } + public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); } + public static TypeMemPtr make(TypeStruct obj) { return new TypeMemPtr((byte)2, obj).intern(); } + public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj).withAccess(_ro); } + public TypeMemPtr makeNullable() { return makeFrom((byte)3); } + @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil,_obj).withAccess(_ro); } @Override public TypeMemPtr makeRO() { return withAccess(true); } @Override public boolean isFinal() { return _ro; } public TypeMemPtr withAccess(boolean ro) { - if( _ro==ro || (_obj==TypeStruct.TOP && _nil) ) return this; - TypeMemPtr ptr = new TypeMemPtr(_obj, _nil); + if( _ro==ro ) return this; + TypeMemPtr ptr = new TypeMemPtr(_nil, _obj); ptr._ro = ro; return ptr.intern(); } @@ -60,64 +62,68 @@ public class TypeMemPtr extends Type { } // An abstract pointer, pointing to either a Struct or an Array. - // Can also be null or not. - public static final TypeMemPtr BOT = make(TypeStruct.BOT,true); + // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} + public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT); public static final TypeMemPtr TOP = BOT.dual(); - // An abstract null (can be Struct or Array) or not-null C void* - public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); - public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null + public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT); - public static final TypeMemPtr TEST= make(TypeStruct.TEST,false); - public static void gather(ArrayList<Type> ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); } + public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST); + public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); } @Override - Type xmeet(Type t) { + public TypeNil xmeet(Type t) { TypeMemPtr that = (TypeMemPtr) t; - return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil).withAccess(_ro | that._ro); + return TypeMemPtr.make(xmeet0(that), (TypeStruct)_obj.meet(that._obj)).withAccess(_ro | that._ro); } @Override - public TypeMemPtr dual() { return TypeMemPtr.make(_obj.dual(), !_nil).withAccess(!_ro); } + public TypeMemPtr dual() { return TypeMemPtr.make( dual0(), _obj.dual()).withAccess(!_ro); } - @Override public TypeMemPtr glb() { return make(_obj.glb(),true ).withAccess(true); } - @Override public TypeMemPtr lub() { return make(_obj.lub(),false); } + // RHS is NIL; do not deep-dual when crossing the centerline + @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj).withAccess(_ro); } + + + // True if this "isa" t up to named structures + @Override public boolean shallowISA( Type t ) { + if( !(t instanceof TypeMemPtr that) ) return false; + if( this==that ) return true; + if( xmeet0(that)!=that._nil || (_ro && !that._ro) ) return false; + if( _obj==that._obj ) return true; + if( _obj._name.equals(that._obj._name) ) + return true; // Shallow, do not follow matching names, just assume ok + throw Utils.TODO(); // return _obj.shallowISA(that._obj); + } + + @Override public TypeMemPtr glb() { return make((byte)3,_obj.glb()).withAccess(true); } // Is forward-reference @Override public boolean isFRef() { return _obj.isFRef(); } - @Override public Type makeInit() { return _nil ? NULLPTR : Type.TOP; } - @Override public Type makeZero() { return NULLPTR; } - @Override public Type nonZero() { return VOIDPTR; } - - @Override public boolean isHigh() { return this==TOP; } - @Override public boolean isConstant() { return this==NULLPTR; } - @Override public boolean isHighOrConst() { return this==TOP || this==NULLPTR; } @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers - @Override - int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ (_nil ? 1024 : 0); } + @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash(); } - @Override - boolean eq(Type t) { + @Override boolean eq(Type t) { TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _ro==ptr._ro && _obj == ptr._obj && _nil == ptr._nil; - } - - // [void,name,MANY]*[,?] - @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("PtrBot"); - if( this==TOP ) return sb.append("PtrTop"); - if( this== NULLPTR) return sb.append("null"); - if( this== VOIDPTR) return sb.append("*void"); - return _obj.print(sb.append("*")).append(_nil ? "?" : ""); + return _ro==ptr._ro && _obj == ptr._obj && super.eq(ptr); } @Override public String str() { if( this==BOT ) return "PtrBot"; if( this==TOP ) return "PtrTop"; - if( this== NULLPTR) return "null"; - if( this== VOIDPTR) return "*void"; - return "*"+_obj.str()+(_nil ? "?" : ""); + if( this== NOTBOT) return "*void"; + return x()+"*"+_obj.str()+q(); } + @Override public SB print(SB sb) { + if( this==BOT ) return sb.p("PtrBot"); + if( this==TOP ) return sb.p("PtrTop"); + if( this== NOTBOT) return sb.p("*void"); + return _obj.print(sb.p(x()).p("*")).p(q()); + } + @Override public SB gprint(SB sb) { + if( this==BOT ) return sb.p("PtrBot"); + if( this==TOP ) return sb.p("PtrTop"); + if( this== NOTBOT) return sb.p("*void"); + return _obj.gprint(sb.p(x()).p("*")).p(q()); + } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/src/main/java/com/seaofnodes/simple/type/TypeNil.java new file mode 100644 index 00000000..aee623ba --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/TypeNil.java @@ -0,0 +1,62 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; + +/** + * Nil-able Scalar types + */ +public abstract class TypeNil extends Type { + // 0 = high-subclass choice nil + // 1 = high-subclass no nil + // 2 = low -subclass no nil + // 3 = low -subclass also nil + final byte _nil; + + TypeNil(byte t, byte nil ) { super(t); _nil = nil; } + + public static void gather(ArrayList<Type> ts) { } + + abstract TypeNil makeFrom(byte nil); + + byte xmeet0(TypeNil that) { return (byte)Math.max(_nil,that._nil); } + + byte dual0() { return (byte)(3-_nil); } + + // RHS is NIL + abstract Type meet0(); + + // RHS is XNIL + Type meetX() { + return _nil==0 ? XNIL : (_nil<=2 ? TypePtr.NPTR : TypePtr.PTR); + } + + Type nmeet(TypeNil tn) { + // Invariants: both are TypeNil subclasses and unequal classes. + // If this is TypePtr, we went to TypePtr.nmeet and not here. + // If that is TypePtr, this is not (invariant); reverse and go again. + if( tn instanceof TypePtr ts ) return ts.nmeet(this); + + // Two mismatched TypeNil, no Scalar. + if( _nil==0 && tn._nil==0 ) return XNIL; + if( _nil<=2 && tn._nil<=2 ) return TypePtr.NPTR; + return TypePtr.PTR; + } + + @Override public boolean isHigh () { return _nil <= 1; } + @Override public boolean isConstant () { return false; } + @Override public boolean isHighOrConst() { return isHigh() || isConstant(); } + + @Override public Type glb() { return Type.NIL; } + + public boolean notNull() { return _nil==1 || _nil==2; } + public boolean nullable() { return _nil==3; } + + final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; } + final String x() { return isHigh() ? "~" : ""; } + + int hash() { return _nil<<17; } + + boolean eq(TypeNil ptr) { return _nil == ptr._nil; } +} diff --git a/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/src/main/java/com/seaofnodes/simple/type/TypePtr.java new file mode 100644 index 00000000..e9091c25 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/TypePtr.java @@ -0,0 +1,53 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; + +/** + * Represents a Scalar; a single register-sized value. + */ +public class TypePtr extends TypeNil { + + private TypePtr(byte nil) { super(TPTR,nil); } + + // An abstract pointer, pointing to either a Struct or an Array. + // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} + public static TypePtr XPTR = new TypePtr((byte)0).intern(); + public static TypePtr XNPTR= new TypePtr((byte)1).intern(); + public static TypePtr NPTR = new TypePtr((byte)2).intern(); + public static TypePtr PTR = new TypePtr((byte)3).intern(); + private static final TypePtr[] PTRS = new TypePtr[]{XPTR,XNPTR,NPTR,PTR}; + + public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); } + + TypeNil makeFrom(byte nil) { throw Utils.TODO(); } + + @Override public TypeNil xmeet(Type t) { + TypePtr that = (TypePtr) t; + return PTRS[xmeet0(that)]; + } + + @Override public TypePtr dual() { return PTRS[dual0()]; } + + // High scalar loses, low scalar wins + @Override TypeNil nmeet(TypeNil tn) { + if( _nil==0 ) return tn; // High scalar loses + if( _nil==1 ) return tn.makeFrom(xmeet0(tn)); // High scalar loses + if( _nil==2 ) return tn._nil==3 ? PTR : NPTR; // Low scalar wins + return PTR; // this + } + + + // RHS is NIL + @Override Type meet0() { return isHigh() ? NIL : PTR; } + // RHS is XNIL + // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar + @Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); } + + @Override public TypePtr glb() { return PTR; } + + private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"}; + @Override public String str() { return STRS[_nil]; } + @Override public SB print(SB sb) { return sb.p(str()); } +} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java new file mode 100644 index 00000000..ec2f5f0a --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java @@ -0,0 +1,100 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.SB; +import com.seaofnodes.simple.Utils; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * Return Program Control or Return PC or RPC + */ +public class TypeRPC extends Type { + + // A set of CallEndNode IDs (or StopNode); commonly just one. + // Basically a sparse bit set + final HashSet<Integer> _rpcs; + + // If true, invert the meaning of the bits + final boolean _any; + + private TypeRPC(boolean any, HashSet<Integer> rpcs) { + super(TRPC); + _any = any; + _rpcs = rpcs; + } + private static TypeRPC make(boolean any, HashSet<Integer> rpcs) { + return new TypeRPC(any,rpcs).intern(); + } + + public static TypeRPC constant(int cend) { + HashSet<Integer> rpcs = new HashSet<>(); + rpcs.add(cend); + return make(false,rpcs); + } + + public static final TypeRPC BOT = make(true,new HashSet<>()); + private static final TypeRPC TEST2 = constant(2); + private static final TypeRPC TEST3 = constant(2); + + public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); } + + @Override public String str() { + if( _rpcs.isEmpty() ) + return _any ? "$[ALL]" : "$[]"; + if( _rpcs.size()==1 ) + for( Integer rpc : _rpcs ) + return _any ? "$[-"+rpc+"]" : "$["+rpc+"]"; + return "$["+(_any ? "-" : "")+_rpcs+"]"; + } + + @Override public boolean isConstant() { + return !_any && _rpcs.size()==1; + } + + @Override + public TypeRPC xmeet(Type other) { + TypeRPC rpc = (TypeRPC)other; + // If the two sets are equal, the _any must be unequal (invariant), + // so they cancel and all bits are set. + if( _rpcs.equals(rpc._rpcs) ) + return BOT; + // Classic union of bit sets (which might be infinite). + HashSet<Integer> lhs = _rpcs, rhs = rpc._rpcs; + // Smaller on left + if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; } + + HashSet<Integer> rpcs = new HashSet<>(); + boolean any = true; + // If both sets are infinite, intersect. + if( _any && rpc._any ) { + for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i); + + } else if( !_any && !rpc._any ) { + // if neither set is infinite, union. + rpcs.addAll( lhs ); + rpcs.addAll( rhs ); + any = false; + } else { + // If one is infinite, subtract the other from it. + HashSet<Integer> sub = _any ? rpc._rpcs : _rpcs; + HashSet<Integer> inf = _any ? _rpcs : rpc._rpcs; + for( Integer i : inf ) + if( inf.contains(i) && !sub.contains(i) ) + rpcs.add(i); + } + return make(any,rpcs); + } + + @Override + public Type dual() { return make(!_any,_rpcs); } + + @Override + int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; } + @Override + public boolean eq( Type t ) { + TypeRPC rpc = (TypeRPC)t; // Contract + return _any==rpc._any && _rpcs.equals(rpc._rpcs); + } + +} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 97664e3c..fff6a7db 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; import com.seaofnodes.simple.Utils; import java.util.ArrayList; @@ -48,7 +49,7 @@ public class TypeStruct extends Type { // Array public static TypeStruct makeAry(TypeInteger len, int lenAlias, Type body, int bodyAlias) { - assert !(body instanceof TypeMemPtr tmp && !tmp._nil); + assert body instanceof TypeInteger || body instanceof TypeFloat || (body instanceof TypeNil tn && tn.nullable()); return make("[" + body.str() + "]", Field.make("#" ,len , lenAlias,true ), Field.make("[]",body,bodyAlias,false)); @@ -57,10 +58,10 @@ public class TypeStruct extends Type { // A pair of self-cyclic types private static final TypeStruct S1F = makeFRef("S1"); private static final TypeStruct S2F = makeFRef("S2"); - public static final TypeStruct S1 = make("S1", Field.make("a", TypeInteger.BOT, -1, false), Field.make("s2",TypeMemPtr.make(S2F,false),-2, false) ); - private static final TypeStruct S2 = make("S2", Field.make("b", TypeFloat .BOT, -3, false), Field.make("s1",TypeMemPtr.make(S1F,false),-4, false) ); + public static final TypeStruct S1 = make("S1", Field.make("a", TypeInteger.BOT, -1, false), Field.make("s2",TypeMemPtr.make((byte)2,S2F),-2, false) ); + private static final TypeStruct S2 = make("S2", Field.make("b", TypeFloat .F64, -3, false), Field.make("s1",TypeMemPtr.make((byte)2,S1F),-4, false) ); - private static final TypeStruct ARY = makeAry(TypeInteger.BOT,-1,TypeFloat.BOT,-2); + private static final TypeStruct ARY = makeAry(TypeInteger.BOT,-1,TypeInteger.BOT,-2); public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); } @@ -142,22 +143,6 @@ public class TypeStruct extends Type { return false; return true; } - @Override public TypeStruct lub() { - if( _lub() ) return this; - // Need to glb each field - Field[] flds = new Field[_fields.length]; - for( int i=0; i<_fields.length; i++ ) - flds[i] = _fields[i].lub(); - return make(_name,flds); - } - private boolean _lub() { - if( _fields!=null ) - for( Field f : _fields ) - if( f.lub() != f ) - return false; - return true; - } - // Is forward-reference @Override public boolean isFRef() { return _fields==null; } @@ -195,15 +180,20 @@ public class TypeStruct extends Type { } @Override - public StringBuilder print(StringBuilder sb) { - if( this==BOT ) return sb.append("StructBot"); - if( this==TOP ) return sb.append("StructTop"); - sb.append(_name); + public SB print(SB sb) { + if( this==BOT ) return sb.p("StructBot"); + if( this==TOP ) return sb.p("StructTop"); + sb.p(_name); if( _fields == null ) return sb; // Forward reference struct, just print the name - sb.append(" {"); + sb.p(" {"); for( Field f : _fields ) - f._type.print(sb).append(f._final ? " " : " !").append(f._fname).append("; "); - return sb.append("}"); + f._type.print(sb).p(f._final ? " " : " !").p(f._fname).p("; "); + return sb.p("}"); + } + @Override public SB gprint( SB sb ) { + if( this==BOT ) return sb.p("StructBot"); + if( this==TOP ) return sb.p("StructTop"); + return sb.p(_name); } @Override public String str() { diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index a34c64db..6d3a2b69 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.SB; import java.util.ArrayList; public class TypeTuple extends Type { @@ -9,22 +10,37 @@ public class TypeTuple extends Type { private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } - private static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static final TypeTuple START = make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); - public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(START); } + public static final TypeTuple BOT = new TypeTuple(new Type[0]).intern(); + public static final TypeTuple TOP = new TypeTuple(null).intern(); + + public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); + public static final TypeTuple START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); + public static final TypeTuple MAIN = make(TypeInteger.BOT); + public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); + + public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); + public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); + public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); + public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); + + public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(MAIN); ts.add(IF_TRUE); } @Override Type xmeet(Type other) { TypeTuple tt = (TypeTuple)other; // contract from xmeet + if( this==TOP ) return other; + if( tt ==TOP ) return this ; if( _types.length != tt._types.length ) - return BOTTOM; + return BOT; Type[] ts = new Type[_types.length]; for( int i=0; i<_types.length; i++ ) ts[i] = _types[i].meet(tt._types[i]); return make(ts); } - @Override public Type dual() { + @Override public TypeTuple dual() { + if( this==TOP ) return BOT; + if( this==BOT ) return TOP; Type[] ts = new Type[_types.length]; for( int i=0; i<_types.length; i++ ) ts[i] = _types[i].dual(); @@ -39,51 +55,37 @@ public class TypeTuple extends Type { return make(ts); } - @Override public StringBuilder print(StringBuilder sb) { - sb.append("[ "); - for( Type t : _types ) - t.print(sb).append(", "); - sb.setLength(sb.length()-2); - sb.append("]"); - return sb; - } + @Override public String str() { return print(new SB()).toString(); } - @Override public String str() { - StringBuilder sb = new StringBuilder(); - sb.append("[ "); + @Override public SB print(SB sb) { + if( this==TOP ) return sb.p("[TOP]"); + if( this==BOT ) return sb.p("[BOT]"); + sb.p("[ "); for( Type t : _types ) - sb.append(t.str()).append(", "); - sb.setLength(sb.length()-2); - return sb.append("]").toString(); + t.print(sb).p(", "); + return sb.unchar(2).p("]"); } - - /** - * Display Type name in a format that's good for IR printer - */ - @Override - public StringBuilder typeName(StringBuilder sb) { - sb.append("["); + @Override public SB gprint(SB sb) { + if( this==TOP ) return sb.p("[TOP]"); + if( this==BOT ) return sb.p("[BOT]"); + sb.p("[ "); for( Type t : _types ) - t.typeName(sb).append(","); - sb.setLength(sb.length()-1); - return sb.append("]"); + t.gprint(sb).p(", "); + return sb.unchar(2).p("]"); } - public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL}); - public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL}); - public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL}); - public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL}); - @Override int hash() { int sum = 0; - for( Type type : _types ) sum ^= type.hashCode(); + if( _types!=null ) for( Type type : _types ) sum ^= type.hashCode(); return sum; } @Override boolean eq( Type t ) { TypeTuple tt = (TypeTuple)t; // Contract + if( _types==null && tt._types==null ) return true; + if( _types==null || tt._types==null ) return false; if( _types.length != tt._types.length ) return false; for( int i=0; i<_types.length; i++ ) if( _types[i]!=tt._types[i] ) |
