summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/seaofnodes/simple/Ary.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/CodeGen.java123
-rw-r--r--src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/GraphVisualizer.java20
-rw-r--r--src/main/java/com/seaofnodes/simple/IRPrinter.java170
-rw-r--r--src/main/java/com/seaofnodes/simple/IterPeeps.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/JSViewer.java394
-rw-r--r--src/main/java/com/seaofnodes/simple/ListScheduler.java110
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java679
-rw-r--r--src/main/java/com/seaofnodes/simple/SB.java74
-rw-r--r--src/main/java/com/seaofnodes/simple/SimpleWebSocket.java148
-rw-r--r--src/main/java/com/seaofnodes/simple/Var.java55
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AndNode.java20
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BoolNode.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CFGNode.java34
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CProjNode.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndNode.java92
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java142
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CastNode.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConstantNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CtrlNode.java14
-rw-r--r--src/main/java/com/seaofnodes/simple/node/DivNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FRefNode.java35
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunNode.java133
-rw-r--r--src/main/java/com/seaofnodes/simple/node/IfNode.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java35
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LogicalNode.java29
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoopNode.java48
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java (renamed from src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java)104
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusFNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusNode.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MultiNode.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MultiUse.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NewNode.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java60
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NotNode.java24
-rw-r--r--src/main/java/com/seaofnodes/simple/node/OrNode.java20
-rw-r--r--src/main/java/com/seaofnodes/simple/node/OutNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ParmNode.java47
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PhiNode.java66
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ProjNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RegionNode.java28
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReturnNode.java104
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SarNode.java23
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java166
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShlNode.java25
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShrNode.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StartNode.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StopNode.java40
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java30
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StructNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ToFloatNode.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XCtrlNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XorNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Field.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Type.java105
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFloat.java99
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java140
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeInteger.java89
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java41
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java97
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeNil.java62
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypePtr.java53
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeRPC.java100
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeStruct.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeTuple.java72
69 files changed, 3293 insertions, 1027 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..47616b51
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/CodeGen.java
@@ -0,0 +1,123 @@
+ package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.HashMap;
+
+public class CodeGen {
+
+ 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() { return parse(false); }
+ public CodeGen parse(boolean disable) {
+ assert _phase == null;
+ _phase = Phase.Parse;
+ _linker.clear();
+
+ P = new Parser(_src,_arg);
+ Node._disablePeephole = disable;
+ _stop = P.parse();
+ _start = Parser.START; // Capture global start
+ JSViewer.show();
+ P = null; // No longer parsing
+ return this;
+ }
+
+ public CodeGen opto() {
+ assert _phase == Phase.Parse;
+ _phase = Phase.Opto;
+
+ // Optimization
+ IterPeeps.iterate(_stop);
+
+ // TODO:
+ // Optimistic
+ return this;
+ }
+
+ public CodeGen typeCheck() {
+ // Demand phase Opto for cleaning up dead control flow at least,
+ // required for the following GCM. Note that peeps can be disabled,
+ // but still the dead CFG will get cleaned.
+ assert _phase == Phase.Opto;
+ _phase = Phase.TypeCheck;
+
+ // Type check
+ Parser.ParseException err = _stop.walk( Node::err );
+ if( err != null )
+ throw err;
+ return this;
+ }
+
+
+ public CodeGen GCM() { return GCM(false); }
+ public CodeGen GCM( boolean show) {
+ assert _phase == Phase.TypeCheck;
+ _phase = Phase.Schedule;
+
+ // Build the loop tree, fix never-exit loops
+ _start.buildLoopTree(_stop);
+ if( show )
+ System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null));
+
+ // 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 _stop.p(9999);
+ }
+
+ // 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 9698abd9..d3ee62c3 100644
--- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
+++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java
@@ -10,10 +10,9 @@ 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);
+ schedLate(stop);
}
@@ -22,10 +21,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);
@@ -40,12 +39,14 @@ public abstract class GlobalCodeMotion {
}
// Post-Order of CFG
- private static void _rpo_cfg(Node n, BitSet visit, ArrayList<CFGNode> rpo) {
- if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) )
+ private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, ArrayList<CFGNode> rpo) {
+ if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) )
return; // Been there, done that
+ if( def instanceof CallNode && cfg instanceof FunNode )
+ return; // Ignore linked function calls
visit.set(cfg._nid);
- for( Node use : cfg._outputs )
- _rpo_cfg(use,visit,rpo);
+ for( Node useuse : cfg._outputs )
+ _rpo_cfg(cfg,useuse,visit,rpo);
rpo.add(cfg);
}
@@ -55,7 +56,7 @@ public abstract class GlobalCodeMotion {
visit.set(n._nid);
// Schedule not-pinned not-CFG inputs before self. Since skipping
// Pinned, this never walks the backedge of Phis (and thus spins around
- // a data-only loop, eventually attempting relying on some pre-visited-
+ // a data-only loop), eventually attempting relying on some pre-visited-
// not-post-visited data op with no scheduled control.
for( Node def : n._inputs )
if( def!=null && !(def instanceof PhiNode) )
@@ -66,7 +67,7 @@ public abstract class GlobalCodeMotion {
CFGNode early = Parser.START; // Maximally early, lowest idepth
if( n.in(0) instanceof CFGNode cfg ) early = cfg;
for( int i=1; i<n.nIns(); i++ )
- if( n.in(i).cfg0().idepth() > early.idepth() )
+ if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() )
early = n.in(i).cfg0(); // Latest/deepest input
n.setDef(0,early); // First place this can go
}
@@ -108,6 +109,8 @@ public abstract class GlobalCodeMotion {
if( n instanceof LoadNode ld )
for( Node memuse : ld.mem()._outputs )
if( late[memuse._nid]==null &&
+ // New makes new memory, never crushes load memory
+ !(memuse instanceof NewNode) &&
// Load-use directly defines memory
(memuse._type instanceof TypeMem ||
// Load-use indirectly defines memory
@@ -164,8 +167,8 @@ public abstract class GlobalCodeMotion {
CFGNode found=null;
for( int i=1; i<phi.nIns(); i++ )
if( phi.in(i)==n )
- if( found==null ) found = phi.region().cfg(i);
- else Utils.TODO(); // Can be more than once
+ found = phi.region().cfg(i)._idom(found,null);
+
assert found!=null;
return found;
}
@@ -189,7 +192,7 @@ public abstract class GlobalCodeMotion {
assert late[st._nid]!=null;
lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st);
break;
- case NewNode st:
+ case CallNode st:
assert late[st._nid]!=null;
lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st);
break;
@@ -200,9 +203,10 @@ public abstract class GlobalCodeMotion {
if( phi.in(i)==load.mem() )
lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null);
break;
+ case NewNode st: break;
case LoadNode ld: break; // Loads do not cause anti-deps on other loads
case ReturnNode ret: break; // Load must already be ahead of Return
- case ScopeMinNode ret: break; // Mem uses now on ScopeMin
+ case MemMergeNode ret: break; // Mem uses now on ScopeMin
case NeverNode never: break;
default: throw Utils.TODO();
}
diff --git a/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
index de6d65a3..5d6d3328 100644
--- a/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
+++ b/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
@@ -27,9 +27,9 @@ public class GraphVisualizer {
// Since the graph has cycles, we need to create a flat list of all the
// nodes in the graph.
- Collection<Node> all = findAll(stop, scope);
+ Collection<Node> all = findAll(xScopes, stop, scope);
StringBuilder sb = new StringBuilder();
- sb.append("digraph chapter11 {\n");
+ sb.append("digraph chapter18 {\n");
sb.append("/*\n");
sb.append(stop._src);
sb.append("\n*/\n");
@@ -81,7 +81,7 @@ public class GraphVisualizer {
// Just the Nodes first, in a cluster no edges
sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
for (Node n : all) {
- if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeMinNode || n==Parser.XCTRL)
+ if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL)
continue; // Do not emit, rolled into MultiNode or Scope cluster already
if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue;
if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue;
@@ -137,7 +137,7 @@ public class GraphVisualizer {
for (Node n : all) {
if (n instanceof RegionNode region) {
sb.append("\t\t{ rank=same; ");
- sb.append(region).append(";");
+ sb.append(region.uniqueName()).append(";");
for (Node phi : region._outputs)
if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";");
sb.append("}\n");
@@ -163,7 +163,7 @@ public class GraphVisualizer {
sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name
// Special for memory ScopeMinNode
if( level==0 ) {
- ScopeMinNode n = scope.mem();
+ MemMergeNode n = scope.mem();
sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n");
}
sb.append("\t\t").append(scopeName).append(" [label=<\n");
@@ -263,12 +263,12 @@ public class GraphVisualizer {
/**
* Finds all nodes in the graph.
*/
- private Collection<Node> findAll(Node stop, Node scope) {
+ public static Collection<Node> findAll(Stack<ScopeNode> ss, Node... ns) {
final HashMap<Integer, Node> all = new HashMap<>();
- for( Node n : stop._inputs )
+ for( Node n : ns )
walk(all, n);
- if( scope != null )
- for( Node n : scope._inputs )
+ if( ss != null )
+ for( Node n : ss )
walk(all, n);
return all.values();
}
@@ -276,7 +276,7 @@ public class GraphVisualizer {
/**
* Walk a subgraph and populate distinct nodes in the all list.
*/
- private void walk(HashMap<Integer,Node> all, Node n) {
+ private static void walk(HashMap<Integer,Node> all, Node n) {
if(n == null ) return;
if (all.get(n._nid) != null) return; // Been there, done that
all.put(n._nid, n);
diff --git a/src/main/java/com/seaofnodes/simple/IRPrinter.java b/src/main/java/com/seaofnodes/simple/IRPrinter.java
index 7ef55a3f..aa498b16 100644
--- a/src/main/java/com/seaofnodes/simple/IRPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/IRPrinter.java
@@ -1,7 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.node.*;
-import java.lang.StringBuilder;
+import com.seaofnodes.simple.type.TypeFunPtr;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
@@ -11,116 +11,101 @@ public class IRPrinter {
// Print a node on 1 line, columnar aligned, as:
// NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE
// 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt
- public static void _printLine( Node n, StringBuilder sb ) {
- sb.append("%4d %-7.7s ".formatted(n._nid,n.label()));
- if( n._inputs==null ) {
- sb.append("DEAD\n");
- return;
- }
+ public static SB printLine( Node n, SB sb ) {
+ sb.p("%4d %-7.7s ".formatted(n._nid,n.label()));
+ if( n._inputs==null )
+ return sb.p("DEAD\n");
for( Node def : n._inputs )
- sb.append(def==null ? "____ " : "%4d ".formatted(def._nid));
+ sb.p(def==null ? "____" : "%4d".formatted(def._nid))
+ // Lazy Phi indicator
+ .p(n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " ");
for( int i = n._inputs.size(); i<4; i++ )
- sb.append(" ");
- sb.append(" [[ ");
+ sb.p(" ");
+ sb.p(" [[ ");
for( Node use : n._outputs )
- sb.append(use==null ? "____ " : "%4d ".formatted(use._nid));
+ sb.p(use==null ? "____ " : "%4d ".formatted(use._nid));
int lim = 6 - Math.max(n._inputs.size(),4);
for( int i = n._outputs.size(); i<lim; i++ )
- sb.append(" ");
- sb.append(" ]] ");
- if( n._type!= null ) sb.append(n._type.str());
- sb.append("\n");
- }
-
- private static StringBuilder nodeId(StringBuilder sb, Node n) {
- sb.append("%%%d".formatted(n._nid));
- if (n instanceof ProjNode proj) {
- sb.append(".").append(proj._idx);
- }
- return sb;
- }
-
- // Print a node on 1 line, format is inspired by LLVM
- // %id: TYPE = NODE(inputs ....)
- // Nodes as referred to as %id
- public static void _printLineLlvmFormat( Node n, StringBuilder sb ) {
- nodeId(sb, n).append(": ");
- if( n._inputs==null ) {
- sb.append("DEAD\n");
- return;
- }
- if( n._type!= null ) n._type.typeName(sb);
- sb.append(" = ").append( n.label() ).append( "(" );
- for( int i = 0; i < n._inputs.size(); i++ ) {
- Node def = n.in(i);
- if (i > 0)
- sb.append(", ");
- if (def == null) sb.append("_");
- else nodeId(sb, def);
- }
- sb.append(")").append("\n");
+ sb.p(" ");
+ sb.p(" ]] ");
+ if( n._type!= null ) sb.p(n._type.str());
+ return sb.p("\n");
}
- public static void printLine( Node n, StringBuilder sb, boolean llvmFormat ) {
- if (llvmFormat) _printLineLlvmFormat( n, sb );
- else _printLine ( n, sb );
- }
public static String prettyPrint(Node node, int depth) {
- return Parser.SCHEDULED
- ? prettyPrintScheduled( node, depth, false )
- : prettyPrint( node, depth, false );
+ return CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Schedule.ordinal()
+ ? _prettyPrintScheduled( node, depth )
+ : _prettyPrint( node, depth );
}
// Another bulk pretty-printer. Makes more effort at basic-block grouping.
- public static String prettyPrint( Node node, int depth, boolean llvmFormat ) {
+ private static String _prettyPrint( Node node, int depth ) {
// First, a Breadth First Search at a fixed depth.
BFS bfs = new BFS(node,depth);
// Convert just that set to a post-order
ArrayList<Node> rpos = new ArrayList<>();
BitSet visit = new BitSet();
for( int i=bfs._lim; i< bfs._bfs.size(); i++ )
- postOrd( bfs._bfs.get(i), rpos, visit, bfs._bs);
+ postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs);
// Reverse the post-order walk
- StringBuilder sb = new StringBuilder();
+ SB sb = new SB();
boolean gap=false;
for( int i=rpos.size()-1; i>=0; i-- ) {
Node n = rpos.get(i);
if( n instanceof CFGNode || n.isMultiHead() ) {
- if( !gap ) sb.append("\n"); // Blank before multihead
- printLine( n, sb, llvmFormat ); // Print head
+ if( !gap ) sb.p("\n"); // Blank before multihead
+ if( n instanceof FunNode fun ) {
+ TypeFunPtr sig = fun.sig();
+ sig.print(sb.p("--- ").p(sig._name==null ? "" : sig._name).p(" "),false).p("----------------------\n");
+ }
+ printLine( n, sb ); // Print head
while( --i >= 0 ) {
Node t = rpos.get(i);
if( !t.isMultiTail() ) { i++; break; }
- printLine( t, sb, llvmFormat );
+ printLine( t, sb );
+ }
+ if( n instanceof ReturnNode ret ) {
+ TypeFunPtr sig = ret.fun().sig();
+ sig.print(sb.p("--- ").p(sig._name==null ? "" : sig._name).p(" "),false).p("----------------------\n");
+ }
+ if( !(n instanceof CallNode) ) {
+ sb.p("\n"); // Blank after multitail
+ gap = true;
}
- sb.append("\n"); // Blank after multitail
- gap = true;
} else {
- printLine( n, sb, llvmFormat );
+ printLine( n, sb );
gap = false;
}
}
return sb.toString();
}
- private static void postOrd(Node n, ArrayList<Node> rpos, BitSet visit, BitSet bfs) {
+ private static void postOrd(Node n, Node prior, ArrayList<Node> rpos, BitSet visit, BitSet bfs) {
if( !bfs.get(n._nid) )
return; // Not in the BFS visit
+ if( n instanceof FunNode && !(prior instanceof StartNode) )
+ return; // Only visit Fun from Start
if( visit.get(n._nid) ) return; // Already post-order walked
visit.set(n._nid);
// First walk the CFG, then everything
if( n instanceof CFGNode ) {
for( Node use : n._outputs )
- if( use instanceof CFGNode && use.nOuts() >= 1 && !(use._outputs.get( 0 ) instanceof LoopNode) )
- postOrd(use, rpos,visit,bfs);
+ // Follow CFG, not across call/function borders, and not around backedges
+ if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) &&
+ use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) )
+ postOrd(use, n, rpos,visit,bfs);
for( Node use : n._outputs )
- if( use instanceof CFGNode )
- postOrd(use,rpos,visit,bfs);
+ // Follow CFG, not across call/function borders
+ if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) )
+ postOrd(use,n,rpos,visit,bfs);
}
+ // Follow all outputs
for( Node use : n._outputs )
- if( use != null )
- postOrd(use, rpos,visit,bfs);
+ if( use != null &&
+ !(n instanceof CallNode && use instanceof FunNode) &&
+ (n instanceof FunNode || !(use instanceof ParmNode)) )
+ postOrd(use, n, rpos,visit,bfs);
// Post-order
rpos.add(n);
}
@@ -188,7 +173,7 @@ public class IRPrinter {
}
// Bulk pretty printer, knowing scheduling information is available
- public static String prettyPrintScheduled( Node node, int depth, boolean llvmFormat ) {
+ private static String _prettyPrintScheduled( Node node, int depth ) {
// Backwards DFS walk to depth.
HashMap<Integer,Integer> ds = new HashMap<>();
ArrayList<Node> ns = new ArrayList<>();
@@ -201,28 +186,29 @@ public class IRPrinter {
}
}
// Print by block with least idepth
- StringBuilder sb = new StringBuilder();
- ArrayList<Node> bns = new ArrayList<>();
+ SB sb = new SB();
+ Ary<Node> bns = new Ary<>(Node.class);
while( !ds.isEmpty() ) {
CFGNode blk = null;
for( Node n : ns ) {
CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0();
- if( blk==null || cfg.idepth() < blk.idepth() )
+ if( blk==null || cfg.idepth() < blk.idepth() || (blk instanceof FunNode && !(cfg instanceof FunNode)))
blk = cfg;
}
- ds.remove(blk._nid);
+ Integer d = ds.remove(blk._nid);
ns.remove(blk);
// Print block header
- sb.append("%-13.13s".formatted(label(blk)+":"));
- sb.append( " ".repeat(4) ).append(" [[ ");
+ sb.p("%-13.13s".formatted(label(blk)+":"));
+ sb.p( " ".repeat(4) ).p(" [[ ");
if( blk instanceof StartNode ) ;
else if( blk instanceof RegionNode || blk instanceof StopNode )
- for( int i=(blk instanceof StopNode ? 0 : 1); i<blk.nIns(); i++ )
+ for( int i=(blk instanceof StopNode ? 3 : 1); i<blk.nIns(); i++ )
label(sb,blk.cfg(i));
else
label(sb,blk.cfg(0));
- sb.append(" ]] \n");
+ sb.p(" ]] \n");
+ printLine(blk,sb);
// Collect block contents that are in the depth limit
bns.clear();
@@ -230,29 +216,30 @@ public class IRPrinter {
for( Node use : blk._outputs ) {
Integer i = ds.get(use._nid);
if( i!=null && !(use instanceof CFGNode cfg && cfg.blockHead()) ) {
- bns.add(use);
+ if( bns.find(use)==-1 )
+ bns.add(use);
xd = Math.min(xd,i);
}
}
// Print Phis up front, if any
for( int i=0; i<bns.size(); i++ )
if( bns.get(i) instanceof PhiNode phi )
- printLine( phi, sb, llvmFormat,bns,i--,ds,ns);
+ printLine( phi, sb,bns,i--,ds,ns);
// Print block contents in depth order, bumping depth until whole block printed
for( ; !bns.isEmpty(); xd++ )
for( int i=0; i<bns.size(); i++ ) {
Node n = bns.get(i);
if( ds.get(n._nid)==xd ) {
- printLine( n, sb, llvmFormat, bns, i--, ds,ns );
+ printLine( n, sb, bns, i--, ds,ns );
if( n instanceof MultiNode && !(n instanceof CFGNode) ) {
for( Node use : n._outputs ) {
- printLine(use,sb,llvmFormat,bns,bns.indexOf(use),ds,ns);
+ printLine(use,sb,bns,bns.indexOf(use),ds,ns);
}
}
}
}
- sb.append("\n");
+ sb.p("\n");
}
return sb.toString();
}
@@ -260,11 +247,18 @@ public class IRPrinter {
private static void _walk( HashMap<Integer,Integer> ds, ArrayList<Node> ns, Node node, int d ) {
Integer nd = ds.get(node._nid);
if( nd!=null && d <= nd ) return; // Been there, done that
- if( ds.put(node._nid,d) == null )
+ Integer old = ds.put(node._nid,d) ;
+ if( old == null )
ns.add(node);
if( d == 0 ) return; // Depth cutoff
for( Node def : node._inputs )
- if( def != null )
+ if( def != null &&
+ !(node instanceof LoopNode loop && loop.back()==def) &&
+ // Don't walk into or out of functions
+ !(node instanceof CallEndNode && def instanceof ReturnNode) &&
+ !(node instanceof FunNode && def instanceof CallNode) &&
+ !(node instanceof ParmNode && !(def instanceof FunNode))
+ )
_walk(ds,ns,def,d-1);
}
@@ -272,13 +266,13 @@ public class IRPrinter {
if( blk instanceof StartNode ) return "START";
return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid;
}
- static void label( StringBuilder sb, CFGNode blk ) {
+ static void label( SB sb, CFGNode blk ) {
if( !blk.blockHead() ) blk = blk.cfg(0);
- sb.append( "%-9.9s ".formatted( label( blk ) ) );
+ sb.p( "%-9.9s ".formatted( label( blk ) ) );
}
- static void printLine( Node n, StringBuilder sb, boolean llvmFormat, ArrayList<Node> bns, int i, HashMap<Integer,Integer> ds, ArrayList<Node> ns ) {
- printLine( n, sb, llvmFormat );
- if( i != -1 ) Utils.del(bns,i);
+ static void printLine( Node n, SB sb, Ary<Node> bns, int i, HashMap<Integer,Integer> ds, ArrayList<Node> ns ) {
+ printLine( n, sb );
+ if( i != -1 ) bns.del(i);
ds.remove(n._nid);
ns.remove(n);
}
diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 36f053f9..998bd4f0 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;
@@ -86,13 +86,13 @@ public abstract class IterPeeps {
}
// If there are distant neighbors, move to worklist
n.moveDepsToWorklist();
+ JSViewer.show(); // Show again
assert progressOnList(stop); // Very expensive assert
}
if( n.isUnused() && !(n instanceof StopNode) )
n.kill(); // Just plain dead
}
- return stop;
}
// Visit ALL nodes and confirm the invariant:
@@ -114,7 +114,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;
@@ -182,6 +182,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/JSViewer.java b/src/main/java/com/seaofnodes/simple/JSViewer.java
new file mode 100644
index 00000000..7422767a
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/JSViewer.java
@@ -0,0 +1,394 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.io.IOException;
+import java.util.Stack;
+import java.nio.file.Paths;
+import java.util.Collection;
+
+public class JSViewer implements AutoCloseable {
+ // Display programs in an endless loop
+ public static void main( String[] args ) throws Exception {
+ try(var js = new JSViewer()) {
+ js.run();
+ }
+ }
+
+ static boolean SHOW;
+
+ // WebSocket to the browser for display
+ static SimpleWebSocket SERVER;
+
+ static int N; // Dot frames
+ static CodeGen CODE; //
+
+ JSViewer() throws Exception {
+ // Launch server; handshake
+ SERVER = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ;
+ }
+
+ void run( ) throws Exception {
+ SERVER.put("!");
+ while( true ) {
+ String src = SERVER.get();
+ switch( src ) {
+ case null: return;
+ case "null": return;
+ case "+": break; // Client requests more frames, but we send them all anyway
+ default:
+ System.out.println(src);
+ try {
+ N=0;
+ // Use parser scope, xscope when building views
+ CODE = new CodeGen(src);
+ SHOW = true;
+ show();
+ // Parse program, generating views at every parse point
+ CODE.parse();
+ // No longer user parse internal state when building views
+ CODE.opto();
+
+ // Catch and ignore Parser errors
+ } catch(RuntimeException re) {
+ System.err.println(re);
+ } finally {
+ SHOW = false;
+ SERVER.put("#"); // Final frame
+ }
+ break;
+ }
+ }
+ }
+
+ @Override public void close() throws IOException {
+ SERVER.close();
+ SERVER=null;
+ }
+
+ public static void show() { if( SERVER!=null && SHOW ) _show(); }
+ private static void _show() {
+ boolean midParse = CODE.P!=null;
+ // Skip util we at least get the Parse object made
+ if( !midParse && (CODE._phase==null || CODE._phase == CodeGen.Phase.Parse) )
+ return;
+ Stack<ScopeNode> xScopes = midParse ? CODE.P._xScopes : null;
+
+ Collection<Node> all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null);
+ SB sb = new SB();
+ sb.p("digraph view_").p(N++).p(" {\n").ii();
+ if( midParse )
+ sb.i().p("// POS: ").p(CODE.P.pos()).nl();
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("ordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.i().p("concentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.i().p("compound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( midParse )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all);
+
+ // Walk the active Scope edges
+ if( midParse )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+
+ sb.p("}\n").di();
+ // Tell client another DOT frame
+ String dot = sb.toString();
+ System.out.println(dot);
+ try {
+ SERVER.put(dot);
+ } catch( IOException ioe ) {
+ try { SERVER.close();} catch( IOException ignored ){}
+ }
+ }
+
+
+ private static void nodes(SB sb, Collection<Node> all) {
+ // Just the Nodes first, in a cluster no edges
+ sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name
+ for( Node n : all ) {
+ if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode )
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ sb.i().p(n.uniqueName()).p(" [ ");
+ if( n instanceof MultiNode && !(n instanceof StartNode) ) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb. p("shape=plaintext label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ // Row over single cell for node label
+ cell(sb.i().p("<TR>"), n.glabel(), n, null).p("</TR>\n");
+ // Row over single cell, for nested table
+ sb.i().p("<TR><TD>\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").p("\n");
+ sb.i().p("<TR>");
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid)));
+ boolean empty_row=true;
+ for( Node use : n._outputs )
+ if( use instanceof MultiUse muse ) {
+ cell(sb,use.glabel(),use,"p"+muse.idx());
+ empty_row=false;
+ }
+ // At least one cell on row
+ if( empty_row ) sb.p("<TD></TD>");
+ sb. p("</TR>").p("\n");
+ sb.i().p("</TABLE>").p("\n").di();
+ sb.i().p("</TD></TR>\n");
+ sb.i().p("</TABLE>>\n").di();
+ sb.i().p("];\n");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow ");
+ node(sb,n.glabel(),n);
+ sb.p("];\n");
+ }
+ }
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.i().p("{ rank=same; ");
+ sb.p(region.uniqueName()).p(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";");
+ sb.p("}\n");
+ }
+ }
+ sb.di().i().p("}").nl(); // End Node cluster
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private static void scope( SB sb, ScopeNode scope ) {
+ sb.i().p("node [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope._lexSize.size()+1; // One more than lexsize
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name
+ sb.i().p(scopeName).p(" [label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ int lexStart = level==0 ? 0 : scope._lexSize.at(level-1);
+
+ // Special for memory ScopeMinNode
+ MemMergeNode n = scope.nIns()>1 ? scope.mem() : null;
+ if( level==0 && n!=null && n.nIns()>2 ) {
+ sb.i().p("<TR>");
+ for( int m=2; m<n.nIns(); m++ )
+ cell(sb,"#"+m,n.alias(m),"m"+m);
+ sb.p("</TR>\n"); // End scope level
+ }
+
+ // Scope variables, empty if none
+ if( lexStart<last ) {
+ sb.i().p("<TR>\n");
+ for( int j=lexStart; j<last; j++ ) {
+ if( scope.in(j)==null ) continue;
+ var v = scope._vars.at(j);
+ cell(sb.i(),v._name,scope.in(j),makePortName(scopeName, v._name)).nl();
+ }
+ last = lexStart;
+ sb.i().p("</TR>\n");
+ }
+ sb.i().p("</TABLE>>];\n").di();
+ // Scope label
+ sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n");
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ for( int i=0; i<max; i++ )
+ sb.di().i().p("}\n");
+ }
+
+ // Append a cell, with color
+ private static SB cell(SB sb, String text, Node n, String port) {
+ sb.p("<TD");
+ String color = color(n);
+ sb.p(" BGCOLOR=\"").p(color).p("\"");
+ if( port!=null ) sb.p(" PORT=\"").p(port).p("\"");
+ return colorcell(sb.p(">"),text,n,dark(color)).p("</TD>");
+ }
+ private static SB node(SB sb, String text, Node n) {
+ String color = color(n);
+ sb.p("style=filled fillcolor=").p(color).p(" ");
+ String shape = switch( n ) {
+ case FunNode fun -> "shape=box peripheries=2";
+ case CFGNode cfg -> "shape=box";
+ default -> "";
+ };
+ sb.p(shape).p(" ");
+ return colorcell(sb.p("label=<"),text,n,dark(color)).p(">");
+ }
+
+ private static String color(Node n) {
+ return switch( n ) {
+ case CFGNode cfg -> "yellow";
+ case NewNode nnn -> "lightgreen";
+ case StructNode s -> "lightgreen";
+ default -> typeColor(n._type);
+ };
+ }
+ private static String typeColor(Type t) {
+ return t==null ? "white" : switch( t ) {
+ case TypeMem mem -> "blue";
+ case TypeMemPtr tmp -> "green";
+ case TypeFunPtr tfp -> "orange";
+ case TypeRPC rpc -> "antiquewhite";
+ case TypeInteger ti -> "lightblue";
+ case TypeFloat tf -> "aqua";
+ case Type tn -> tn==Type.NIL ? "lightgray" : "red";
+ };
+ }
+ private static final String[] DARKS = new String[]{"blue","green","black"};
+ private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; }
+
+
+
+ // Called with an open div
+ private static SB colorcell(SB sb, String text, Node n, boolean dark) {
+ if( dark ) sb.p("<font color=\"white\">");
+ sb.p(text);
+ if( n._type!=null ) {
+ sb.p("<br /><font point-size=\"10\">");
+ n._type.gprint(sb);
+ sb.p("</font>");
+ }
+ if( dark ) sb.p("</font>");
+ return sb;
+ }
+
+
+ private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private static void nodeEdges(SB sb, ScopeNode scope, Collection<Node> all) {
+ // All them edge labels
+ String scopeName = scope!=null ? makeScopeName(scope, 0) : null;
+ sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ if( n instanceof ConstantNode ||
+ n instanceof XCtrlNode ||
+ // ProjNodes handled by Multi;
+ n instanceof ProjNode ||
+ n instanceof CProjNode ||
+ // ScopeNodes are done separately
+ n instanceof ScopeNode ||
+ n instanceof MemMergeNode
+ )
+ continue;
+ if( n.isDead() )
+ throw Utils.TODO();
+ // Normal edges
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ sb.i().p(n.uniqueName()).p(" -> ");
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.p(def.uniqueName());
+ sb.p(" [style=dotted taillabel=").p(i).p("];\n");
+ } else {
+ // Most edges land here use->def
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else sb.p(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.p("[taillabel=").p(i);
+ // Color the edge
+ nodeEdgeColor(sb,def._type);
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.p(" constraint=false");
+ sb.p("];\n");
+ }
+ }
+
+ // Bonus edge if hooked by parser
+ if( (n.iskeep() || n.isUnused()) && scopeName != null ) {
+ sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n");
+ }
+ // Force functions above return
+ if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() )
+ sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n");
+ }
+ }
+
+ private static void nodeEdgeColor(SB sb, Type t) {
+ sb.p(" color=").p(typeColor(t));
+ }
+
+ // Walk the scope edges
+ private static void scopeEdges( SB sb, ScopeNode scope ) {
+ sb.i().p("edge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ if( v._idx==1 ) continue; // Memory done separately
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) )
+ level++;
+ String scopeName = makeScopeName(scope, level);
+ sb.i()
+ .p(scopeName).p(":")
+ .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .p(" -> ");
+ defPort(sb,def).p(";\n");
+ }
+
+ // Memory
+ if( scope.nIns()>1 ) {
+ MemMergeNode n = scope.mem();
+ for( int i=2; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(i);
+ if( def==null ) continue;
+ String scopeName = makeScopeName(scope, 0);
+ sb.i().p(scopeName).p(":m").p(i).p(" -> ");
+ defPort(sb,def).p(";\n");
+ }
+ }
+
+ }
+
+ private static SB defPort(SB sb, Node def) {
+ return def instanceof MultiUse muse
+ ? sb.p(def.in(0).uniqueName()).p(":p").p(muse.idx())
+ : sb.p(def.uniqueName());
+ }
+}
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 5a44b63e..c16e3267 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -2,7 +2,6 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
-
import java.util.*;
/**
@@ -24,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
@@ -31,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;
@@ -82,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
@@ -91,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
@@ -101,26 +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) {
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(); }
@@ -128,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);
@@ -154,32 +150,111 @@ public class Parser {
public StopNode parse() { return parse(false); }
public StopNode parse(boolean show) {
_xScopes.push(_scope);
- // Enter a new scope for the initial control and arguments
- _scope.push();
- ScopeMinNode mem = new ScopeMinNode();
- mem.addDef(null);
- _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole());
- mem.addDef( new ProjNode(START, 1, ScopeNode.MEM0).peephole());
- _scope.define(ScopeNode.MEM0, TypeMem.TOP , false, mem .peephole());
- _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole());
-
- // Parse whole program
- parseBlock(false);
+ _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);
- if( ctrl()._type==Type.CONTROL )
- STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole());
- _scope.kill();
+ ctrl(XCTRL);
+ _scope.mem(new MemMergeNode(false));
+
+ // 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();
if( show ) showGraph();
return STOP;
}
/**
+ * Parses a function body, assuming the header is parsed.
+ */
+ private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, 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 PhiNode(ScopeNode.MEM0,TypeMem.BOT,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
+ // Private mem alias tracking per function
+ MemMergeNode mem = new MemMergeNode(true);
+ mem.addDef(null); // Alias#0
+ mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases
+ _scope.mem(mem);
+ // All args, "as-if" called externally
+ for( int i=0; i<ids.length; i++ ) {
+ Type t = sig.arg(i);
+ _scope.define(ids[i], t, false, 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().merge(), 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>
@@ -188,14 +263,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();
}
/**
@@ -209,7 +286,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();
@@ -218,6 +294,11 @@ public class Parser {
else if (matchx("struct") ) return parseStruct();
else if (matchx("#showGraph")) return require(showGraph(),";");
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();
}
@@ -246,13 +327,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);
@@ -272,7 +353,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).
@@ -319,7 +400,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
@@ -359,7 +440,7 @@ public class Parser {
_xScopes.pop();
_xScopes.push(exit);
_scope = exit;
- return _scope;
+ return peep(expr.unkeep());
}
private ScopeNode jumpTo(ScopeNode toScope) {
@@ -376,13 +457,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
@@ -391,11 +472,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
@@ -403,8 +484,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;
@@ -438,16 +521,20 @@ 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
_xScopes.push(fScope); // For graph visualization we need all scopes
// 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
@@ -457,31 +544,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;
}
@@ -497,9 +586,11 @@ public class Parser {
*/
private Node parseReturn() {
var expr = require(parseAsgn(), ";");
- Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole());
+ // Need default memory, since it can be lazy, need to force
+ // a non-lazy Phi
+ _fun.addReturn(ctrl(), _scope.mem().merge(), expr);
ctrl(XCTRL); // Kill control
- return ret;
+ return expr;
}
/**
@@ -525,22 +616,24 @@ public class Parser {
Node expr = parseAsgn();
// Final variable to update
- ScopeMinNode.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.inCon() )
+ if( _scope.in(def._idx)._type!=Type.TOP && def._final &&
+ // Inside a constructor, final assign is OK, outside nope
+ !(_scope.inCon() && def._idx >= _scope._lexSize.last()) )
throw error("Cannot reassign final '"+name+"'");
// Lift expression, based on type
- Node lift = liftExpr(expr, def.type(), def._final);
+ Node lift = liftExpr(expr.keep(), def.type(), def._final);
// Update
_scope.update(name,lift);
// Return un-lifted expr
- return expr;
+ return expr.unkeep();
}
// Make finals deep; widen ints to floats; narrow wide int types.
@@ -557,13 +650,13 @@ public class Parser {
// Auto-narrow wide ints to narrow ints
expr = zsMask(expr,t);
// Type is sane
- if( !expr._type.isa(t) )
+ if( expr._type!=Type.BOTTOM && !expr._type.shallowISA(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;
}
@@ -592,6 +685,7 @@ public class Parser {
// Has var/val instead of a user-declared type
boolean inferType = t==Type.TOP || t==Type.BOTTOM;
boolean hasBang = match("!");
+ Lexer loc = loc();
String name = requireId();
// Optional initializing expression follows
@@ -599,29 +693,59 @@ public class Parser {
Node expr;
if( match("=") ) {
expr = parseAsgn();
- // `val` is always final
+ // TOP means val and val is always final
xfinal = (t==Type.TOP) ||
- // var is always not-final, final if no Bang AND TMP since primitives are not-final by default
- (t!=Type.BOTTOM && !hasBang && (t instanceof TypeMemPtr));
+ // BOTTOM is var and var is always not-final
+ (t!=Type.BOTTOM &&
+ // no Bang AND
+ !hasBang &&
+ // not-null (expecting null to be set to not-null)
+ expr._type != Type.NIL &&
+ // Pointers are final by default; int/flt are not-final by default.
+ (t instanceof TypeNil));
// var/val, then type comes from expression
- if( inferType )
- t = expr._type.glb();
+ if( inferType ) {
+ if( expr._type==Type.NIL )
+ throw error("a not-null/non-zero expression");
+ t = expr._type;
+ if( !xfinal ) t = t.glb(); // 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(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 -> ZERO;
+ case TypeFloat tf -> con(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, xfinal);
+
if( xfinal && t instanceof TypeMemPtr tmp )
t = tmp.makeRO();
- // 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;
}
@@ -634,15 +758,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();
@@ -651,21 +774,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));
- ScopeMinNode.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;
}
@@ -677,23 +800,28 @@ public class Parser {
// t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?]
private Type type() {
int old1 = pos();
+ // 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 null;
+
// 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
// 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 = TypeMemPtr.make(tmp._obj,true);
+ if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
} else if( match("[]") ) {
t2 = typeAry(t2);
} else
@@ -705,9 +833,12 @@ public class Parser {
// Check valid forward ref, after parsing all the type extra bits.
// Cannot check earlier, because cannot find required 'id' until after "[]?" syntax
int old2 = pos();
+ 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,t1);
@@ -716,7 +847,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);
@@ -729,15 +860,36 @@ public class Parser {
return tary;
}
- // Fixup forward refs lazily. Basically a Union-Find flavored read
- // barrier.
- Type lazyFRef(Type t) {
- //if( !t.isFRef() ) return t;
- //Type def = Parser.TYPES.get(((TypeMemPtr)t)._obj._name);
- throw Utils.TODO();
-
+ // 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:
@@ -764,9 +916,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);
@@ -816,11 +968,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;
@@ -856,7 +1011,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);
@@ -883,11 +1038,13 @@ public class Parser {
int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post
String name = _lexer.matchId();
if( name!=null ) {
- ScopeMinNode.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;
}
@@ -912,46 +1069,76 @@ 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
- ScopeMinNode.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 1 -> new AddNode(rvalue,con( 1));
- case (char)-1 -> new AddNode(rvalue,con(-1));
- default -> throw Utils.TODO();
+ case 1, (char)-1,
+ '+' -> new AddNode(lhs,rhs);
+ case '-' -> new SubNode(lhs,rhs);
+ case '*' -> new MulNode(lhs,rhs);
+ case '/' -> new DivNode(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,false);
}
- ScopeMinNode.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);
- ScopeMinNode.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;
}
@@ -963,6 +1150,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);
@@ -986,17 +1175,17 @@ 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
for( int i=idx; i<init.size(); i++ )
- if( /*init.at(i)._type == Type.TOP ||*/ init.at(i)._type == Type.BOTTOM )
+ if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM )
throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
Node ptr = newStruct(tmp._obj, con(tmp._obj.offset(fs.length)), idx, init );
if( hasConstructor )
@@ -1035,8 +1224,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);
}
@@ -1049,33 +1238,32 @@ public class Parser {
public static String memName(int alias) { return ("$"+alias).intern(); }
/**
- * 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 + "'");
@@ -1092,7 +1280,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();
@@ -1101,7 +1289,7 @@ public class Parser {
Node val = parseAsgn().keep();
Node lift = liftExpr( val, tf, f._final );
- Node st = new StoreNode(name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false);
+ Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false);
// Arrays include control, as a proxy for a safety range check.
// Structs don't need this; they only need a NPE check which is
// done via the type system.
@@ -1110,25 +1298,24 @@ public class Parser {
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
- Node load = new LoadNode(name, f._alias, tf.glb(), memAlias(f._alias), expr.keep(), off);
+ Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), 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);
- Node st = new StoreNode(name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, val, false);
+ // 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);
+ Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false);
// Arrays include control, as a proxy for a safety range check.
// Structs don't need this; they only need a NPE check which is
// done via the type system.
if( base.isAry() ) st.setDef(0,ctrl());
memAlias(f._alias, peep(st));
+ load = postfix(ch) ? load.unkeep() : op;
// And use the original loaded value as the result
} else expr.unkill();
off.unkill();
@@ -1147,15 +1334,87 @@ 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);
+ _lexer.skipWhiteSpace();
+ Lexer loc = loc(); // First argument location
+ while( true ) {
+ Type t = type(); // Arg type
+ if( t==null ) break;
+ 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,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().merge().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());
+ // Memory from CallEnd
+ MemMergeNode mem = new MemMergeNode(true);
+ mem.addDef(null); // Alias#0
+ mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ _scope.mem(mem);
+ // Call result
+ return new ProjNode(cend,2,null).peephole();
+ }
/**
* Parse integer literal
@@ -1166,7 +1425,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
@@ -1185,13 +1444,21 @@ public class Parser {
private boolean peek(char ch) { return _lexer.peek(ch); }
private boolean peekIsId() { return _lexer.peekIsId(); }
- private int pos() { return _lexer._position; }
+ // 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() {
@@ -1216,24 +1483,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
*/
@@ -1248,6 +1517,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() {
@@ -1267,9 +1546,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;
}
@@ -1283,13 +1569,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;
}
}
@@ -1301,6 +1587,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;
@@ -1323,7 +1610,7 @@ public class Parser {
skipWhiteSpace();
if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 )
return false;
- _position++;
+ inc();
return true;
}
@@ -1360,8 +1647,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)));
}
@@ -1412,7 +1700,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"
public char matchOperAssign() {
skipWhiteSpace();
if( _position+2 >= _input.length ) return 0;
@@ -1426,4 +1713,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; assert loc!=null; }
+ }
}
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/SimpleWebSocket.java b/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
new file mode 100644
index 00000000..1ce766ff
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
@@ -0,0 +1,148 @@
+package com.seaofnodes.simple;
+
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+// Why-Oh-Why is this needed?
+
+// Pass in a URI and a port.
+// Opens the client; client has to open a websocket at the given port.
+// Connects, handshakes.
+// Thereafter you can call get/put to swap frames with client.
+
+class SimpleWebSocket extends ServerSocket {
+ final InputStream _in;
+ final OutputStream _out;
+
+ SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException {
+ super(port);
+ // Launch client
+ java.awt.Desktop.getDesktop().browse(uri);
+ // Look for client; get in/out streams
+ Socket sock = accept();
+ _in = sock. getInputStream();
+ _out = sock.getOutputStream();
+
+
+ // Establish web socket with handshake; easier with a buffered reader
+ BufferedReader br = new BufferedReader(new InputStreamReader(_in));
+ String key = null;
+ while( br.ready() ) {
+ String line = br.readLine();
+ if( line.startsWith("Sec-WebSocket-Key: ") )
+ key = line.substring(19);
+ }
+ assert key!=null;
+ // Magic handshake
+ byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
+ + "Connection: Upgrade\r\n"
+ + "Upgrade: websocket\r\n"
+ + "Sec-WebSocket-Accept: "
+ + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 )))
+ + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 );
+ _out.write(response,0,response.length);
+ }
+
+ final byte[] _ins = new byte[1024];
+ final byte[] _str = new byte[1024];
+ final byte[] _key = new byte[4];
+ int _x, _len;
+ private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; }
+
+ // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version
+ private void flipRead() throws IOException {
+ // Move _x to _len to offset 0
+ System.arraycopy(_ins,_x,_ins,0,(_len-_x));
+ _len = _len-_x;
+ _x=0;
+ // Read more
+ int len = _in.read(_ins,_len,_ins.length-_len);
+ if( len == -1 )
+ throw Utils.TODO(); // EOF
+ _len += len;
+ }
+ // Block until you read at least len
+ private void atLeast(int len) throws IOException {
+ if( _x+len <= _len ) return;
+ flipRead();
+ }
+
+
+ // Classic read from browser client. Encoded, so decoded here
+ public String get() throws IOException {
+ atLeast(1); // Need 1 byte for opcode
+ int op = rawget();
+ if( (op&0x80)!=0x80 ) // FIN bit not set?
+ throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message
+ op &= 0x7F; // Strip FIN bit
+ switch( op&15) {
+ case 1: {
+ atLeast(5); // Min message length
+ // Message length
+ int len = rawget()-128;
+ if( len > 125 ) {
+ if( len > 126 )
+ throw Utils.TODO(); // Multi-byte length
+ len = (rawget()<<8) | rawget();
+ }
+ atLeast(len+4); // Rest of message
+ // Encoding key
+ _key[0] = (byte)rawget();
+ _key[1] = (byte)rawget();
+ _key[2] = (byte)rawget();
+ _key[3] = (byte)rawget();
+
+ // Decode bytes
+ for( int i=0; i<len; i++ )
+ _str[i] = (byte)(rawget() ^ _key[i & 3]);
+
+ return new String(_str,0,len);
+ }
+ // Client closes
+ case 8: return null;
+ // Ping
+ case 9:
+ // Send a PONG back. TODO: echo PING data in the PONG
+ _out.write(0x8A); // FIN + PONG
+ _out.write(0x00); // LENGTH==0
+ return get();
+ // Not (yet) implemented: continuation frames, ping/pong
+ default:
+ throw Utils.TODO("Unexpected op from client: "+op); // Multi-part message
+ }
+ }
+
+ // Classic put to browser client. No encoding.
+ public void put(String msg) throws IOException {
+ int len = msg.length();
+ if( len <= 125 ) {
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(len);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ return;
+ }
+ if( len > 65535 )
+ throw Utils.TODO("MOAR LENGTH");
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(126); // Length is 16b
+ _out.write(len>>>8);
+ _out.write(len&255);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ }
+
+ @Override public void close() throws IOException {
+ if( _out != null ) {
+ _out.write(0b10001000);
+ _out.flush();
+ }
+ super.close();
+ }
+}
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..e71d7ef2
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/Var.java
@@ -0,0 +1,55 @@
+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;
+ }
+ 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 0834a381..e6704ea6 100644
--- a/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -48,7 +48,7 @@ public class AddNode extends Node {
// 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 )
+ if( t2 == TypeInteger.ZERO )
return lhs;
// Add of same to a multiply by 2
diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 17a4e0ca..36e3a949 100644
--- a/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -1,25 +1,20 @@
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 public String op() { return "&"; }
@Override public String glabel() { return "&"; }
@Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append("&"), visited);
- return sb.append(")");
- }
-
- @Override
public Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
@@ -59,10 +54,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 238be00e..ae968ca1 100644
--- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -2,10 +2,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.TypeInteger;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.type.*;
import java.util.BitSet;
import static com.seaofnodes.simple.type.TypeInteger.*;
@@ -35,15 +32,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;
}
@@ -55,7 +56,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 ) {
@@ -66,7 +67,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));
}
@@ -93,6 +94,7 @@ abstract public class BoolNode extends Node {
public static class LT extends BoolNode {
public LT(Node lhs, Node rhs) { super(lhs,rhs); }
String op() { return "<" ; }
+ public String glabel() { return "&lt;"; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1._max < i2._min ) return TRUE;
if( i1._min >= i2._max ) return FALSE;
@@ -104,6 +106,7 @@ abstract public class BoolNode extends Node {
public static class LE extends BoolNode {
public LE(Node lhs, Node rhs) { super(lhs,rhs); }
String op() { return "<="; }
+ public String glabel() { return "&lt;="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1._max <= i2._min ) return TRUE;
if( i1._min > i2._max ) return FALSE;
diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 052ad8fc..b051b3ce 100644
--- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -28,6 +28,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
@@ -82,21 +93,25 @@ public abstract class CFGNode extends Node {
// Start is a LoopNode which contains all at depth 1.
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++;
// 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
@@ -109,7 +124,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;
}
}
@@ -129,4 +144,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 d2f1bbe1..01119310 100644
--- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeTuple;
import java.util.BitSet;
-public class CProjNode extends CFGNode {
+public class CProjNode extends CFGNode implements MultiUse {
// Which slice of the incoming multipart value
public final int _idx;
@@ -21,6 +21,7 @@ public class CProjNode extends CFGNode {
}
@Override public String label() { return _label; }
+ @Override public int idx() { return _idx; }
@Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
@@ -48,7 +49,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..07a2b5a3
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -0,0 +1,92 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+
+/**
+ * CallEnd
+ */
+public class CallEndNode extends CFGNode implements MultiNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ private boolean _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
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("cend( ");
+ sb.append( in(0) instanceof CallNode ? "Call, " : "----, ");
+ for( int i=1; i<nIns()-1; i++ )
+ in(i)._print0(sb,visited).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+
+ @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);
+ 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..246398ee
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -0,0 +1,142 @@
+package com.seaofnodes.simple.node;
+
+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;
+import java.util.BitSet;
+
+/**
+ * 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
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ String fname = null;
+ Node fptr = fptr();
+ if( fptr._type instanceof TypeFunPtr tfp && tfp.isConstant() )
+ fname = tfp._name;
+ if( fname==null ) fptr._print0(sb,visited);
+ else sb.append(fname);
+ sb.append("( ");
+ for( int i=2; i<nIns()-1; i++ )
+ in(i)._print0(sb,visited).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+
+ 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)) )
+ 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 462d6cba..d8a968c1 100644
--- a/src/main/java/com/seaofnodes/simple/node/CastNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CastNode.java
@@ -1,9 +1,9 @@
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.TypeInteger;
-import com.seaofnodes.simple.IterPeeps;
-
import java.util.BitSet;
// Upcast (join) the input to a t. Used after guard test to lift an input.
@@ -22,6 +22,7 @@ public class CastNode extends Node {
public String uniqueName() { return "Cast_" + _nid; }
@Override public boolean isPinned() { return true; }
+ @Override public boolean isConst() { return true; }
@Override
StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -37,4 +38,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 845b9d08..e03bfb0e 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.SB;
import com.seaofnodes.simple.type.Type;
import java.util.BitSet;
@@ -18,24 +19,31 @@ import java.util.BitSet;
* The Constant's value is the value stored in it.
*/
public class ConstantNode extends Node {
- Type _con;
+ public final Type _con;
public ConstantNode( Type type ) {
super(Parser.START);
_con = type;
}
- @Override
- public String label() { return "#"+_con; }
+ 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 public String label() { return "#"+_con; }
+ @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); }
@Override
public String uniqueName() { return "Con_" + _nid; }
@Override
StringBuilder _print1(StringBuilder sb, BitSet visited) {
- return _con.print(sb);
+ return sb.append(_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..162417bb
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -0,0 +1,14 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public class CtrlNode extends CFGNode {
+ public CtrlNode() { super(Parser.START); }
+ @Override public String label() { return "Ctrl"; }
+ @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("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/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 76e98678..0d5ed297 100644
--- a/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -2,7 +2,6 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
-
import java.util.BitSet;
public class DivNode extends Node {
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..f0c4045d
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Var;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+/**
+ * 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 public String uniqueName() { return "FRef_" + _nid; }
+
+ @Override StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append("FRef_").append(_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..1f0c76eb
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -0,0 +1,133 @@
+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;
+
+ 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;
+ }
+
+ // Bypass Region idom, always assume depth == 1, one more than Start
+ @Override public int idepth() { return (_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 513b4721..eafcdf5b 100644
--- a/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -1,9 +1,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.BitSet;
import java.util.HashSet;
@@ -28,23 +26,27 @@ 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
if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
return TypeTuple.IF_NEITHER;
+ if( _disablePeephole ) return TypeTuple.IF_BOTH;
Node pred = pred();
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;
@@ -57,7 +59,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 1528f2e9..eee5169d 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -1,7 +1,7 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Utils;
import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Utils;
import com.seaofnodes.simple.type.*;
import java.util.BitSet;
@@ -19,8 +19,8 @@ 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);
}
// GraphVis DOT code (must be valid Java identifiers) and debugger labels
@@ -70,7 +70,7 @@ public class LoadNode extends MemOpNode {
while( true ) {
switch( mem ) {
case StoreNode st:
- if( ptr == st.ptr() && off() == st.off() )
+ if( ptr == st.ptr().addDep(this) && off() == st.off() )
return castRO(st.val()); // Proved equal
// Can we prove unequal? Offsets do not overlap?
if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
@@ -79,19 +79,27 @@ 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.in(_alias);// Bypass unrelated New
break;
- } else if( mproj.in(0) instanceof StartNode ) {
- break outer;
- } else throw Utils.TODO();
+ case StartNode start: break outer;
+ case CallEndNode cend: break outer; // TODO: Bypass no-alias call
+ default: throw Utils.TODO();
+ }
+ break;
+ case MemMergeNode merge: mem = merge.alias(_alias); break;
+
default:
throw Utils.TODO();
}
@@ -125,7 +133,7 @@ public class LoadNode extends MemOpNode {
private Node ld( int idx ) {
Node mem = mem(), ptr = ptr();
- return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
}
private static boolean neverAlias( Node ptr1, Node ptr2 ) {
@@ -158,8 +166,9 @@ public class LoadNode extends MemOpNode {
private boolean profit(PhiNode phi, int idx) {
Node px = phi.in(idx);
if( px==null ) return false;
- if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) { px.addDep(this); return true; }
- if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) { px.addDep(this); return true; }
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true;
+ if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true;
+ px.addDep(this);
return false;
}
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..ccc1f6b5
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/LogicalNode.java
@@ -0,0 +1,29 @@
+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 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
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+
+ @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 ecda9c63..f70a1012 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,11 +23,6 @@ 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=idom().idepth()+1) : _idepth; }
// Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
@@ -34,7 +30,7 @@ public class LoopNode extends RegionNode {
// 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 StopNode 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,7 +38,7 @@ 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() )
+ if( other!=null && other.loopDepth() < loopDepth() )
return stop; // Found an exit, not an infinite loop
}
x = x.idom();
@@ -50,12 +46,40 @@ public class LoopNode extends RegionNode {
// 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));
+
+ // 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();
+ 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;
+ }
+ ret.setDef(0,ctrl);
+ ret.setDef(1,mem );
+ ret.setDef(2,expr);
+
return stop;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index 9134f9c3..8a08853c 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -2,58 +2,44 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.Var;
import com.seaofnodes.simple.type.*;
import java.util.*;
-
import static com.seaofnodes.simple.Utils.TODO;
-/* */
-public class ScopeMinNode extends Node {
+/**
+ * Memory Merge - a merge of many aliases into a "fat memory". All aliases
+ * are here, but most will be lazy - take the default fat memory.
+ */
+public class MemMergeNode extends Node {
- /** The tracked fields are now complex enough to deserve a array-of-structs layout
+ /*
+ * In-Progress means this is being used by the Parser to track memory
+ * aliases. No optimizations are allowed. When no longer "in progress"
+ * normal peeps work.
*/
- public 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 final boolean _inProgress;
+
+ public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; }
- public ScopeMinNode() { _type = TypeMem.BOT; }
+ // If being used by a Scope, this is "in progress" from the Parser.
+ // Otherwise, it's a memory merge
+ boolean inProgress() { return _inProgress; }
- @Override public String label() { return "MEM"; }
+ @Override public String label() { return "ALLMEM"; }
+ @Override public boolean isMem() { return true; }
@Override
StringBuilder _print1(StringBuilder sb, BitSet visited) {
- sb.append("MEM[ ");
+ sb.append(_inProgress ? "Merge[" : "MEM[ ");
for( int j=2; j<nIns(); j++ ) {
sb.append(j);
sb.append(":");
Node n = in(j);
while( n instanceof ScopeNode loop ) {
sb.append("Lazy_");
- n = loop.in(j);
+ n = loop.mem(j);
}
if( n==null ) sb.append("___ ");
else n._print0(sb, visited).append(" ");
@@ -62,14 +48,33 @@ public class ScopeMinNode extends Node {
return sb.append("]");
}
+ // Make a memory merge: no longer a Scope really, tracking memory state but
+ // not related to the parser in any way.
+ public Node merge() {
+ // Force default memory to not be lazy
+ MemMergeNode merge = new MemMergeNode(false);
+ for( Node n : _inputs )
+ merge.addDef(n);
+ merge._mem(1,null);
+ return merge.peephole();
+ }
+
@Override public Type compute() { return TypeMem.BOT; }
- @Override public Node idealize() { return null; }
+ @Override public Node idealize() {
+ if( inProgress() ) return null;
+
+ // If not merging any memory (all memory is just the default)
+ if( nIns()==2 )
+ return in(1); // Become default memory
+
+ return null;
+ }
public Node in( Var v ) { return in(v._idx); }
- Node alias( int alias ) {
+ public Node alias( int alias ) {
return in(alias<nIns() && in(alias)!=null ? alias : 1);
}
@@ -78,7 +83,6 @@ public class ScopeMinNode extends Node {
return setDef(alias,st);
}
-
// Read or update from memory.
// A shared implementation allows us to create lazy phis both during
// lookups and updates; the lazy phi creation is part of chapter 8.
@@ -87,7 +91,7 @@ public class ScopeMinNode extends Node {
// then it must be START.proj(1)
Node old = alias(alias);
if( old instanceof ScopeNode loop ) {
- ScopeMinNode loopmem = loop.mem();
+ MemMergeNode loopmem = loop.mem();
Node memdef = loopmem.alias(alias);
// Lazy phi!
old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region()
@@ -104,28 +108,27 @@ public class ScopeMinNode extends Node {
}
- void _merge(ScopeMinNode that, RegionNode r) {
+ void _merge( MemMergeNode that, RegionNode r) {
int len = Math.max(nIns(),that.nIns());
for( int i = 2; i < len; i++)
if( alias(i) != that.alias(i) ) { // No need for redundant Phis
// If we are in lazy phi mode we need to a lookup
- // by name as it will trigger a phi creation
- //Var v = _vars.at(i);
+ // by alias as it will trigger a phi creation
Node lhs = this._mem(i,null);
Node rhs = that._mem(i,null);
- alias(i, new PhiNode(Parser.memName(i), TypeMem.BOT, r, lhs, rhs).peephole());
+ alias(i, new PhiNode(Parser.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole());
}
}
// Fill in the backedge of any inserted Phis
- void _endLoopMem( ScopeNode scope, ScopeMinNode back, ScopeMinNode exit ) {
- for( int i=2; i<back.nIns(); i++ ) {
- if( back.in(i) != scope ) {
- PhiNode phi = (PhiNode)in(i);
- assert phi.region()==scope.ctrl() && phi.in(2)==null;
- phi.setDef(2,back.in(i)); // Fill backedge
+ void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
+ Node exit_def = exit.alias(1);
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) {
+ assert phi.in(2)==null;
+ phi.setDef(2,back.in(i)==scope ? phi : back.in(i)); // Fill backedge
}
- if( exit.alias(i) == scope ) // Replace a lazy-phi on the exit path also
+ if( exit_def == scope ) // Replace a lazy-phi on the exit path also
exit.alias(i,in(i));
}
}
@@ -147,4 +150,7 @@ public class ScopeMinNode extends Node {
}
}
+ @Override boolean eq( Node n ) {
+ return this==n || !_inProgress;
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 82aab8ac..f9df2a40 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);
}
@@ -43,13 +48,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 9727f58b..2b749ba3 100644
--- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
@@ -22,7 +22,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 760a4274..dd2cbd26 100644
--- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -21,13 +21,11 @@ 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 TypeInteger.TOP.meet(in(1)._type);
+ return TypeInteger.BOT;
}
@Override
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 ddb1e09a..34d695f4 100644
--- a/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -17,15 +17,16 @@ public class NewNode extends Node implements MultiNode {
public NewNode(TypeMemPtr ptr, Node... nodes) {
super(nodes);
+ assert !ptr.nullable();
_ptr = ptr;
_len = ptr._obj._fields.length;
// Control in slot 0
assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL;
// Malloc-length in slot 1
- assert nodes[1]._type instanceof TypeInteger;
+ assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL;
for( int i=0; i<_len; i++ ) {
// Memory slices for all fields.
- assert nodes[2+ i]._type instanceof TypeMem;
+ assert nodes[2+ i]._type.isa(TypeMem.BOT);
// Value slices for all fields.
assert nodes[2 + _len + i]._type != null;
}
@@ -57,7 +58,8 @@ public class NewNode extends Node implements MultiNode {
ts[0] = Type.CONTROL;
ts[1] = _ptr;
for( int i=0; i<fs.length; i++ ) {
- TypeMem mem = (TypeMem)in(i+2)._type;
+ Type mt = in(i+2)._type;
+ TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt;
Type tfld = in(2+_len+i)._type.meet(mem._t);
ts[i+2] = TypeMem.make(fs[i]._alias,tfld);
}
diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java
index 3827fdac..4c6cce16 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -1,9 +1,6 @@
package com.seaofnodes.simple.node;
-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.*;
@@ -14,7 +11,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 implements OutNode {
+public abstract class Node {
/**
* Each node has a unique dense Node ID within a compilation context
@@ -93,8 +90,9 @@ public abstract class Node implements OutNode {
@Override
public final String toString() { return print(); }
- // This is a *deep* print. We print with a tik-tok style; the common
- // _print0 calls the per-Node _print1, which calls back to _print0;
+ // This is a *deep* print. We print with a mutually recursive tik-tok
+ // style; the common _print0 calls the per-Node _print1, which calls back
+ // to _print0;
public final String print() {
return _print0(new StringBuilder(), new BitSet()).toString();
}
@@ -116,6 +114,7 @@ public abstract class Node implements OutNode {
public boolean isMultiHead() { return false; }
public boolean isMultiTail() { return false; }
+ public boolean isConst () { return false; }
// ------------------------------------------------------------------------
// Graph Node & Edge manipulation
@@ -127,7 +126,7 @@ public abstract class Node implements OutNode {
*/
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(); }
@@ -164,9 +163,11 @@ public abstract class Node implements OutNode {
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;
@@ -301,12 +302,12 @@ public abstract class Node implements OutNode {
* Always returns some not-null Node (often this).
*/
public final Node peephole( ) {
- if (_disablePeephole) {
- _type = compute();
- return this; // Peephole optimizations turned off
- }
+ if( _type==null ) // Brand-new node, never peeped before
+ JSViewer.show();
Node n = peepholeOpt();
- return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress
+ if( n!=null ) // Made progress?
+ JSViewer.show(); // Show again
+ return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress
}
/**
@@ -332,9 +333,18 @@ public abstract class Node implements OutNode {
// 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();
+ // Peepholes can be turned off - except for cleaning up dead CFG paths.
+ // These need to clean up so the following code motion algorithms don't
+ // get confused by dead or infinite paths.
+ if( _disablePeephole && !(this instanceof RegionNode) && !(this instanceof PhiNode) )
+ return old==_type ? null : this; // Peephole optimizations turned off
+
+ // Replace constant computations from non-constants with a constant
+ // node. If peeps are disabled, still allow high Phis to collapse;
+ // they typically come from dead Regions, and we want the Region to
+ // collapse, which requires the Phis to die first.
+ if( _type.isHighOrConst() && !isConst() && (!_disablePeephole || _type.isHigh()) )
+ return ConstantNode.make(_type).peepholeOpt();
// Global Value Numbering
if( _hash==0 ) {
@@ -357,8 +367,9 @@ public abstract class Node implements OutNode {
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;
@@ -409,6 +420,7 @@ public abstract class Node implements OutNode {
return old;
}
+ public <N extends Node> N init() { _type = compute(); return (N)this; }
/**
* This function rewrites the current Node into a more "idealized" form.
@@ -577,9 +589,7 @@ public abstract class Node implements OutNode {
// 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;
}
@@ -606,7 +616,7 @@ public abstract class Node implements OutNode {
Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
// 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 664d7c7b..b454de66 100644
--- a/src/main/java/com/seaofnodes/simple/node/NotNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -18,25 +18,13 @@ public class NotNode extends Node {
}
@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) : i0;
- 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 b572346f..9b03dc5d 100644
--- a/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -1,25 +1,20 @@
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 public String op() { return "|"; }
@Override public String glabel() { return "|"; }
@Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append("|"), visited);
- return sb.append(")");
- }
-
- @Override
public Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
@@ -56,10 +51,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..784b9146
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+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); }
+
+ @Override public String glabel() { return _label; }
+
+ public FunNode fun() { return (FunNode)in(0); }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( "main".equals(fun().sig()._name) && _label.equals("arg") )
+ return sb.append("arg");
+ sb.append("Parm_").append(_label).append("(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+
+ @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 cb266ca3..9aed8287 100644
--- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -1,14 +1,12 @@
package com.seaofnodes.simple.node;
-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.*;
import java.util.BitSet;
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.
@@ -16,6 +14,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);
+ }
+
@Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
@Override public String glabel() { return "&phi;_"+_label; }
@@ -47,7 +53,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.
@@ -67,13 +73,21 @@ public class PhiNode extends Node {
Node live = singleUniqueInput();
if (live != null)
return live;
+ // No more fancy peeps
+ if( _disablePeephole )
+ return null;
+
+ // No bother if region is going to fold dead paths soon
+ for( int i=1; i<nIns(); i++ )
+ if( r.in(i)._type == Type.XCONTROL )
+ return null;
// Pull "down" a common data op. One less op in the world. One more
// Phi, but Phis do not make code.
// Phi(op(A,B),op(Q,R),op(X,Y)) becomes
// op(Phi(A,Q,X), Phi(B,R,Y)).
Node op = in(1);
- if( op.nIns()==3 && op.in(0)==null && same_op() ) {
+ if( !isMem() && op.nIns()==3 && op.in(0)==null && same_op() ) {
assert !(op instanceof CFGNode);
Node[] lhss = new Node[nIns()];
Node[] rhss = new Node[nIns()];
@@ -84,7 +98,11 @@ public class PhiNode extends Node {
}
Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole();
Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole();
- return op.copy(phi_lhs,phi_rhs);
+ Node down = op.copy(phi_lhs,phi_rhs);
+ // Test not running backwards, which can happen for e.g. And's
+ if( down.compute().isa(compute()) )
+ return down;
+ down.kill();
}
// If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
@@ -97,14 +115,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;
}
@@ -149,7 +169,7 @@ public class PhiNode extends Node {
}
// True if last input is null
- public final boolean inProgress() {
+ public boolean inProgress() {
return in(nIns()-1) == null;
}
@@ -157,4 +177,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 73c1fa15..ea211dd2 100644
--- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.type.TypeTuple;
import java.util.BitSet;
-public class ProjNode extends Node {
+public class ProjNode extends Node implements MultiUse {
// Which slice of the incoming multipart value
public final int _idx;
@@ -21,11 +21,19 @@ public class ProjNode extends Node {
}
@Override public String label() { return _label; }
+ @Override public int idx() { return _idx; }
@Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _label != null ) return sb.append(_label);
+ if( in(0) instanceof CallEndNode cend && cend.call()!=null )
+ return cend.call()._print0(sb,visited);
+ return sb.append("LONELY PROJ");
+ }
- @Override public CFGNode cfg0() { return in(0).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 fb39af17..ea053c02 100644
--- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -10,8 +10,11 @@ import java.util.BitSet;
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,18 @@ 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( !_disablePeephole && // Optional
+ !hasPhi() && // No Phi users, just a control user
in(1) instanceof CProjNode p1 &&
in(2) instanceof CProjNode p2 &&
p1.in(0).addDep(this)==p2.in(0).addDep(this) &&
- 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 +91,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;
@@ -109,12 +118,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 b586a44f..e543f9ea 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -1,7 +1,9 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.SB;
+import com.seaofnodes.simple.Utils;
import com.seaofnodes.simple.type.*;
-
import java.util.BitSet;
/**
@@ -15,16 +17,18 @@ import java.util.BitSet;
*/
public class ReturnNode extends CFGNode {
- public ReturnNode(Node ctrl, Node data, ScopeNode scope) {
- // Add memory slices to Return, so all memory updates are live-on-exit.
- super(ctrl, data);
- if( scope!=null )
- for( int i=2; i<scope.mem().nIns(); i++ )
- addDef(scope.mem().in(i));
+ 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 expr() { return in(1); }
+ 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"; }
@@ -36,15 +40,89 @@ public class ReturnNode extends CFGNode {
return sb.append(";");
}
+ // No one unique control follows; can be many call end sites
+ @Override public CFGNode uctrl() { return null; }
+
@Override
public Type compute() {
- return TypeTuple.make(ctrl()._type,expr()._type);
+ if( inProgress () ) return TypeTuple.RET; // In progress
+ if( _fun.isDead() ) return TypeTuple.RET.dual(); // Dead another way
+ return TypeTuple.make(ctrl()._type,mem()._type,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();
+ }
+
+ // Gather parse-time return types for error reporting
+ private Type mt = Type.TOP;
+ 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;
+ mt = mt.meet(t);
+ 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 mt==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/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 5dc26ad3..4a71ec06 100644
--- a/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -1,23 +1,17 @@
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 public String op() { return ">>"; }
- @Override public String glabel() { return ">>"; }
-
- @Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append(">>"), visited);
- return sb.append(")");
- }
+ @Override public String glabel() { return "&gt;&gt;"; }
@Override
public Type compute() {
@@ -52,10 +46,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 86aa7665..d0d86b79 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -1,17 +1,14 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.IterPeeps;
-import com.seaofnodes.simple.Ary;
+import com.seaofnodes.simple.*;
import com.seaofnodes.simple.type.*;
import java.util.*;
-import static com.seaofnodes.simple.Utils.TODO;
-
/**
* The Scope node is purely a parser helper - it tracks names to nodes with a
- * stack of hashmaps.
+ * stack of variables.
*/
-public class ScopeNode extends ScopeMinNode {
+public class ScopeNode extends MemMergeNode {
/**
* The control is a name that binds to the currently active control
@@ -24,20 +21,22 @@ public class ScopeNode extends ScopeMinNode {
// All active/live variables in all nested scopes, all run together
public final Ary<Var> _vars;
- // Since of each nested lexical scope
+ // 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;
// A new ScopeNode
public ScopeNode() {
+ super(true);
_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);
}
@@ -50,7 +49,7 @@ public class ScopeNode extends ScopeMinNode {
for( int i=0; i<nIns(); i++ ) {
if( j < _lexSize._len && i == _lexSize.at(j) ) { sb.append("| "); j++; }
Var v = _vars.get(i);
- v.type().print(sb);
+ sb.append(v.type().print(new SB()));
sb.append(" ");
if( v._final ) sb.append("!");
sb.append(v._name);
@@ -70,7 +69,7 @@ public class ScopeNode extends ScopeMinNode {
public Node ctrl() { return in(0); }
- public ScopeMinNode mem() { return (ScopeMinNode)in(1); }
+ public MemMergeNode mem() { return (MemMergeNode)in(1); }
/**
* The ctrl of a ScopeNode is always bound to the currently active
@@ -83,29 +82,60 @@ public class ScopeNode extends ScopeMinNode {
* @return Node that was bound
*/
public <N extends Node> N ctrl(N n) { return setDef(0,n); }
+ public Node mem(Node n) { return setDef(1,n); }
- public 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);
}
- public boolean inCon() { return _inCons.last(); }
+
+ // 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);
+ }
+ }
+
+
+ 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;
}
@@ -113,13 +143,24 @@ public class ScopeNode extends ScopeMinNode {
/**
* 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;
}
@@ -154,7 +195,7 @@ public class ScopeNode extends ScopeMinNode {
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!
@@ -168,6 +209,7 @@ public class ScopeNode extends ScopeMinNode {
: loop.setDef(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
setDef(v._idx,old);
}
+ assert !v._final || st==null;
if( st!=null ) setDef(v._idx,st); // Set new value
return v;
}
@@ -194,7 +236,7 @@ public class ScopeNode extends ScopeMinNode {
// 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
@@ -204,7 +246,7 @@ public class ScopeNode extends ScopeMinNode {
dup.addDef(ctrl()); // Control input is just copied
// Memory input is a shallow copy
- ScopeMinNode memdup = new ScopeMinNode(), mem = mem();
+ MemMergeNode memdup = new MemMergeNode(true), mem = mem();
memdup.addDef(null);
memdup.addDef(loop ? this : mem.in(1));
for( int i=2; i<mem.nIns(); i++ )
@@ -217,7 +259,7 @@ public class ScopeNode extends ScopeMinNode {
for( int i=2; i<nIns(); i++ )
// For lazy phis on loops we use a sentinel
// that will trigger phi creation on update
- dup.addDef(loop ? this : in(i));
+ dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
return dup;
}
@@ -229,8 +271,8 @@ public class ScopeNode extends ScopeMinNode {
* @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());
mem()._merge(that.mem(),r);
this ._merge(that ,r);
that.kill(); // Kill merged scope
@@ -250,6 +292,20 @@ public class ScopeNode extends ScopeMinNode {
}
}
+ // 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 ) {
@@ -272,6 +328,8 @@ public class ScopeNode extends ScopeMinNode {
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
for( int i=2; 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;
@@ -289,38 +347,38 @@ public class ScopeNode extends ScopeMinNode {
assert ctrl instanceof CFGNode;
_guards.add(ctrl); // Marker to distinguish 0,1,2 guards
// 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
// Invert the If conditional
if( invert )
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;
// 0,1 or 2 guards
while( true ) {
@@ -330,6 +388,7 @@ public class ScopeNode extends ScopeMinNode {
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
@@ -353,16 +412,17 @@ public class ScopeNode extends ScopeMinNode {
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 6fd951e4..21dbd503 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -5,19 +5,13 @@ 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 public String op() { return "<<"; }
- @Override public String glabel() { return "<<"; }
-
- @Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append("<<"), visited);
- return sb.append(")");
- }
+ @Override public String glabel() { return "&lt;&lt;"; }
@Override
public Type compute() {
@@ -44,10 +38,10 @@ public class ShlNode extends Node {
if( (shl.value()&63)==0 )
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) );
}
}
@@ -55,10 +49,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 22eb45a0..789fb8a8 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -1,23 +1,17 @@
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 public String op() { return ">>>"; }
- @Override public String glabel() { return ">>>"; }
-
- @Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append(">>>"), visited);
- return sb.append(")");
- }
+ @Override public String glabel() { return "&gt;&gt;&gt;"; }
@Override
public Type compute() {
@@ -48,5 +42,5 @@ public class ShrNode extends Node {
return null;
}
- @Override Node copy(Node lhs, Node rhs) { return new ShrNode(lhs,rhs); }
+ @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 ab6e4931..50cba348 100644
--- a/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -18,7 +18,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"; }
@@ -29,7 +29,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.TOP,_arg);
diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java
index 867225cb..feb8347f 100644
--- a/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -1,11 +1,7 @@
package com.seaofnodes.simple.node;
+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;
-import com.seaofnodes.simple.GraphVisualizer;
-
import java.util.BitSet;
public class StopNode extends CFGNode {
@@ -15,6 +11,7 @@ public class StopNode extends CFGNode {
public StopNode(String src) {
super();
_src = src;
+ _type = compute();
}
@Override
@@ -24,7 +21,8 @@ public class StopNode extends CFGNode {
@Override
StringBuilder _print1(StringBuilder sb, BitSet visited) {
- if( ret()!=null ) return ret()._print0(sb, visited);
+ // For the sake of many old tests, and single value prints as "return val"
+ if( ret()!=null ) return ret()._print0(sb,visited);
sb.append("Stop[ ");
for( Node ret : _inputs )
ret._print0(sb, visited).append(" ");
@@ -33,10 +31,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,7 +47,7 @@ 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;
@@ -57,29 +56,8 @@ public class StopNode extends CFGNode {
@Override public int idepth() {
if( _idepth!=0 ) 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 _idepth=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(false); }
- public StopNode iterate(boolean show) { return IterPeeps.iterate(this).typeCheck().GCM(show ); }
- StopNode typeCheck() {
- String err = walk( Node::err );
- if( err != null ) throw new RuntimeException(err);
- return this;
- }
- StopNode GCM(boolean show) {
- Parser.START.buildLoopTree(this);
- if( show )
- System.out.println(new GraphVisualizer().generateDotOutput(this,null,null));
- 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 62b49a1a..0fa7d591 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -1,8 +1,9 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.Utils;
import com.seaofnodes.simple.type.*;
-
import java.util.BitSet;
/**
@@ -20,8 +21,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;
}
@@ -42,6 +43,7 @@ public class StoreNode extends MemOpNode {
public Type compute() {
Type val = val()._type;
TypeMem mem = (TypeMem)mem()._type; // Invariant
+ if( mem == TypeMem.TOP ) return TypeMem.TOP;
Type t = Type.BOTTOM; // No idea on field contents
// Same alias, lift val to the declared type and then meet into other fields
if( mem._alias == _alias ) {
@@ -71,7 +73,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 &&
@@ -79,7 +81,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; }"
@@ -90,10 +92,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;
@@ -111,13 +115,13 @@ 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;
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- if( tmp._obj.field(_name)._final )
- return "Cannot modify final field '"+_name+"'";
+ if( 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 db170ab1..076b342a 100644
--- a/src/main/java/com/seaofnodes/simple/node/StructNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -1,5 +1,6 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.SB;
import com.seaofnodes.simple.type.*;
import java.util.BitSet;
@@ -18,7 +19,7 @@ public class StructNode extends Node {
sb.append(_ts._name).append(" {");
for( int i=0; i<nIns(); i++ ) {
sb.append(_ts._fields[i]._fname).append(":");
- (in(i)==null ? Type.BOTTOM : in(i)._type).print(sb);
+ sb.append((in(i)==null ? Type.BOTTOM : in(i)._type).print(new SB()));
sb.append("; ");
}
sb.setLength(sb.length()-2);
diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 5d6c2643..339c65ca 100644
--- a/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -3,7 +3,6 @@ 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 SubNode extends Node {
@@ -25,6 +24,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())
@@ -35,9 +37,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 1d147cf6..1f4d67e0 100644
--- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -20,9 +20,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 83779dd2..3c6a5df3 100644
--- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -5,9 +5,11 @@ import com.seaofnodes.simple.type.Type;
import java.util.BitSet;
public class XCtrlNode extends CFGNode {
- public XCtrlNode( ) { super(Parser.START); }
+ public XCtrlNode() { super(Parser.START); }
@Override public String label() { return "Xctrl"; }
@Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("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 191933f9..37d7147d 100644
--- a/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -1,25 +1,19 @@
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 public String op() { return "^"; }
@Override public String glabel() { return "^"; }
@Override
- StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append("^"), visited);
- return sb.append(")");
- }
-
- @Override
public Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
@@ -56,10 +50,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/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java
index 349c1fb0..ffa9d2a6 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 _final ? 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 4ebb2b6d..b6757ef2 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; }
// Is forward-reference
public boolean isFRef() { return false; }
@@ -208,11 +224,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..cf92d3d7
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -0,0 +1,140 @@
+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) {
+ 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 ? "&rarr; " : "-> "),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 33c6a8ce..9ed4c7a8 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,24 +49,25 @@ 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) {
- 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) {
+ 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) {
+ 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 786a7be5..d7a8b17d 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:
@@ -23,76 +25,81 @@ public class TypeMemPtr extends Type {
// (TOP ,true ) - a nil
public final TypeStruct _obj;
- 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;
- _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); }
- public TypeMemPtr makeFrom(boolean nil) { return nil==_nil ? this : make(_obj, nil); }
+ 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); }
+ public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil,_obj); }
@Override public TypeMemPtr makeRO() { return makeFrom(_obj.makeRO()); }
@Override public boolean isFinal() { return _obj.isFinal(); }
// An abstract pointer, pointing to either a Struct or an Array.
- // Can also be null or not.
- public static TypeMemPtr BOT = make(TypeStruct.BOT,true);
+ // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
+ public static TypeMemPtr BOT = make((byte)3, TypeStruct.BOT);
public static TypeMemPtr TOP = BOT.dual();
- // An abstract null (can be Struct or Array) or not-null C void*
- public static TypeMemPtr NULLPTR = make(TypeStruct.TOP,true);
- public static TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null
+ public static TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT);
- public static TypeMemPtr TEST= make(TypeStruct.TEST,false);
- public static void gather(ArrayList<Type> ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); }
+ public static TypeMemPtr TEST= make((byte)2, TypeStruct.TEST);
+ public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); }
@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);
+ return TypeMemPtr.make(xmeet0(that), (TypeStruct)_obj.meet(that._obj));
}
@Override
- public TypeMemPtr dual() { return TypeMemPtr.make(_obj.dual(), !_nil); }
+ public TypeMemPtr dual() { return TypeMemPtr.make( dual0(), _obj.dual()); }
- @Override public TypeMemPtr glb() { return make(_obj.glb(),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); }
+
+
+ // 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 ) 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()); }
// 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 _obj.hashCode() ^ (_nil ? 1024 : 0); }
+ @Override int hash() { return _obj.hashCode() ^ super.hash(); }
- @Override
- boolean eq(Type t) {
+ @Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _obj == ptr._obj && _nil == ptr._nil;
- }
-
- // [void,name,MANY]*[,?]
- @Override
- public StringBuilder print(StringBuilder sb) {
- if( this== NULLPTR) return sb.append("null");
- if( this== VOIDPTR) return sb.append("*void");
- return _obj.print(sb.append("*")).append(_nil ? "?" : "");
+ return _obj == ptr._obj && super.eq(ptr);
}
@Override public String str() {
- if( this== NULLPTR) return "null";
- if( this== VOIDPTR) return "*void";
- return "*"+_obj.str()+(_nil ? "?" : "");
+ if( this== NOTBOT) return "*void";
+ if( this== BOT) return "*void?";
+ return x()+"*"+_obj.str()+q();
}
+ @Override public SB print(SB sb) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) return sb.p("*void?");
+ return _obj.print(sb.p(x()).p("*")).p(q());
+ }
+ @Override public SB gprint(SB sb) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) 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 b3dbcad0..ee206579 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 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,14 +180,15 @@ public class TypeStruct extends Type {
}
@Override
- public StringBuilder print(StringBuilder sb) {
- sb.append(_name);
+ public SB print(SB sb) {
+ 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 ) { return sb.p(_name); }
@Override public String str() { return _name; }
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] )