diff options
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/node')
115 files changed, 3800 insertions, 1894 deletions
diff --git a/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index 4ad02dbb..00000000 --- a/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class AddFNode extends ArithFNode { - public AddFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "AddF"; } - - @Override public String glabel() { return "+"; } - - @Override double doOp( double x, double y ) { return x+y; } - - @Override - public Node idealize() { - // Add of 0. - Type t2 = in(2)._type; - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return in(1); - - // Move constants to RHS: con+arg becomes arg+con - if ( in(1)._type.isConstant() && !t2.isConstant() ) - return swap12(); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java index 2bbe1053..a29ba73b 100644 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -1,16 +1,19 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import static com.seaofnodes.simple.Parser.con; - public class AddNode extends ArithNode { + @Override boolean allowFloat() { return true; } public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } + @Override public Tag serialTag() { return Tag.Add; } @Override public String label() { return "Add"; } @Override public String op() { return "+"; } - @Override long doOp( long x, long y ) { return x + y; } + @Override long doOp( long x, long y ) { return x + y; } + @Override double doOp(double x, double y ) { return x + y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Fold ranges like {0-1} + {2-3} into {2-4}. if( !overflow(x._min,y._min) && @@ -21,6 +24,11 @@ public class AddNode extends ArithNode { @Override public Node idealize () { + // Settle the one-shot arithmetic mode before applying family-specific + // algebraic identities. + if( _mode==0 ) + return super.idealize(); + Node lhs = in(1); Node rhs = in(2); if( rhs instanceof AddNode add && add.err()!=null ) @@ -29,12 +37,12 @@ public class AddNode extends ArithNode { // Add of 0. We do not check for (0+x) because this will already // canonicalize to (x+0) - if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger ) + if( t2 == TypeInteger.ZERO && lhs._type.isa(compute()) ) return lhs; // Add of same to a multiply by 2 - if( lhs==rhs ) - return new ShlNode(null,lhs,con(1)); + if( lhs==rhs && _mode==1 ) + return new ShlNode(null,lhs,CodeGen.CODE.con(1)); // Goal: a left-spine set of adds, with constants on the rhs (which then fold). @@ -44,7 +52,7 @@ public class AddNode extends ArithNode { // x+(-y) becomes x-y if( rhs instanceof MinusNode minus ) - return new SubNode(lhs,minus.in(1)); + return new SubNode(lhs,minus.in(1),_mode); // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) @@ -52,18 +60,21 @@ public class AddNode extends ArithNode { // Swap to (x + y) + z // Rotate (add add add) to remove the add on RHS if( rhs instanceof AddNode add ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); + return copy(copy(lhs,add.in(1)).peephole(), add.in(2)); // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) - if( !(lhs instanceof AddNode) ) + if( !(lhs instanceof AddNode) ) { // Rotate; look for (add (phi cons) con/(phi cons)) - return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); + if( spine_cmp(lhs,rhs,this) ) return swap12(); + Node n = phiCon(this,true); + return n == null ? super.idealize() : n; + } // Now we only see (add add non) // Dead data cycle; comes about from dead infinite loops. Do nothing, // the loop will peep as dead after a bit. - if( lhs.in(1) == lhs ) + if( lhs.nIns()>=2 && lhs.in(1) == lhs ) return null; // Do we have (x + con1) + con2? @@ -73,7 +84,7 @@ public class AddNode extends ArithNode { // because if it later became a constant then we could make this // transformation. if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole()); // Do we have ((x + (phi cons)) + con) ? @@ -87,13 +98,13 @@ public class AddNode extends ArithNode { // Do we rotate (x + y) + z // into (x + z) + y ? if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); + return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2)); return super.idealize(); } // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? + // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ? // Push constant up through the phi: x + (phi con0+con0 con1+con1...) static Node phiCon(Node op, boolean rotate) { Node lhs = op.in(1); @@ -126,7 +137,7 @@ public class AddNode extends ArithNode { for( int i=1; i<ns.length; i++ ) ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole(); String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : ""); - Node phi = new PhiNode(label,lphi._minType.meet(op._type),ns).peephole(); + Node phi = new PhiNode(label,ns).peephole(); // Rotate needs another op, otherwise just the phi return lhs==lphi ? phi : op.copy(lhs.in(1),phi); } @@ -164,6 +175,5 @@ public class AddNode extends ArithNode { return lo._nid > hi._nid; } - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java index 9957472b..f02007bc 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class AndNode extends ArithNode { - public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } + @Override public Tag serialTag() { return Tag.And; } - @Override public String label() { return "And"; } @Override public String op() { return "&"; } @Override long doOp( long x, long y ) { return x & y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { @@ -20,6 +20,7 @@ public class AndNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t1 = lhs._type; Type t2 = rhs._type; if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) ) @@ -28,7 +29,7 @@ public class AndNode extends ArithNode { // And of -1. We do not check for (-1&x) because this will already // canonicalize to (x&-1). We do not check for zero, because // the compute() call will return a zero already. - if( t2i.isConstant() && t2i.value()==-1 ) + if( t2i.isConstant() && t2i.value()==-1 && forceInt(lhs) ) return lhs; // Move constants to RHS: con*arg becomes arg*con diff --git a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/src/main/java/com/seaofnodes/simple/node/ArithFNode.java deleted file mode 100644 index dafba626..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -public abstract class ArithFNode extends Node { - public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.open().n(in(1)).p(glabel()).n(in(2)).close(); - } - - abstract double doOp(double x, double y); - - @Override - public Type compute() { - Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return Type.TOP; - if( t1 instanceof TypeFloat f1 && - t2 instanceof TypeFloat f2 ) { - if( f1.isConstant() && f2.isConstant() ) - return TypeFloat.constant(doOp(f1.value(),f2.value())); - } - return t1.meet(t2); - } - -} diff --git a/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/src/main/java/com/seaofnodes/simple/node/ArithNode.java index 54290330..bc47ebfc 100644 --- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -3,15 +3,25 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; -public abstract class ArithNode extends Node { +public abstract class ArithNode extends Node implements ModeNode { // Source location for late reported errors Parser.Lexer _loc; + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } + public ArithNode( Parser.Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { + baos.packed1(_mode); + } abstract String op(); @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -20,20 +30,106 @@ public abstract class ArithNode extends Node { abstract long doOp(long x, long y); abstract TypeInteger doOp(TypeInteger x, TypeInteger y); + double doOp(double x, double y) { throw Utils.TODO(); } + + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } + static byte mode( Type t1, Type t2 ) { + if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2; + if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1; + if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1; + if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1; + return 0; + } + + // Int-only ops, such as &, | and shifts, can collapse to one input. If + // that input, or the live arms of a Phi input, are still mode-polymorphic + // ops, settle them before returning the value so the replacement remains + // at least as narrow as the int-only op. + static boolean forceInt(Node n) { + if( n instanceof ModeNode mode && mode.mode()==0 ) { + mode.setMode((byte)1); + n.setType(n.compute()); + } + return true; + } + + // Check that a settled mode agrees with all data inputs. Takes the Node + // instead of a varargs list both to avoid allocation and to keep this ready + // to become shared mode-aware Node behavior later. + static Parser.ParseException nodeErr(Node n, Parser.Lexer loc, String op, byte mode, boolean allowFloat) { + Type bad = null; + for( int i=1; i<n.nIns(); i++ ) { + Type t = n.in(i)._type; + if( t==null ) + return null; + if( t.isHigh() ) + return null; + // A weak input is not evidence of a bad mode while the graph is + // still being built. Loop Phis commonly await their backedge. + if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM ) + return null; + bad = switch( t ) { + case TypeInteger ti -> mode==1 ? null : t; + case TypeMemPtr ti -> mode==1 ? null : t; + case TypeFunPtr ti -> mode==1 ? null : t; + case TypeFloat tf -> mode==2 ? null : t; + default -> t; + }; + if( bad != null ) break; + } + boolean unsupportedFloat = false; + if( !allowFloat ) + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i)._type instanceof TypeFloat ) { + bad = n.in(i)._type; + unsupportedFloat = true; + break; + } + if( bad == null ) + return null; + if( unsupportedFloat || n instanceof BoolNode ) + return Parser.error("Cannot '"+op+"' "+bad,loc); + if( n.nIns()==3 ) + return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc); + return Parser.error("Cannot '"+op+"' "+bad,loc); + } + + // Arithmetic subclasses which have a floating-point interpretation override. + boolean allowFloat() { return false; } - // Generic airthmetic op math: high returns high; low returns low; 2 + // Generic arithmetic op math: high returns high; low returns low; 2 // constants fold; only 2 non-constants call specialized math. @Override public final Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; - if( t1.isHigh() || t2.isHigh() ) - return Type.TOP; - if( t1 instanceof TypeInteger x && - t2 instanceof TypeInteger y ) - return x.isConstant() && y.isConstant() - ? con(x,y) - : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) ); - return TypeInteger.BOT; + if( t1==null || t2==null ) return Type.TOP; + if( t1.isHigh() || t2.isHigh() ) return Type.TOP; + if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM; + byte mode = _mode==0 ? mode(t1,t2) : _mode; + if( mode==0 ) + return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP; + if( mode==1 ) { + if( t1 instanceof TypeInteger x && + t2 instanceof TypeInteger y ) + return x.isConstant() && y.isConstant() + ? con(x,y) + : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) ); + return TypeInteger.BOT; + } + // FP handling + if( t1 instanceof TypeFloat f1 && + t2 instanceof TypeFloat f2 ) { + if( f1.isConstant() && f2.isConstant() ) + return TypeFloat.constant(doOp(f1.value(),f2.value())); + return t1.meet(t2); + } + return TypeFloat.F64; } private TypeInteger con( Type t1, Type t2 ) { @@ -46,36 +142,50 @@ public abstract class ArithNode extends Node { // push-down peephole cycle because the constants all fold first. // Returns "Phi(op(con,con),op(x,y))". - // This check is slightly different from the - // Expected to be called after other ideal checks are done. @Override public Node idealize() { + Type t1 = in(1)._type, t2 = in(2)._type; + + // Can we decide int vs flt? + if( _mode==0 ) { + byte mode = mode(t1,t2); + if( mode!=0 ) return setMode(mode).init(); + } + if( _mode==2 ) { + if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); + if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); + } + if( in(1) instanceof PhiNode lhs && in(2) instanceof PhiNode rhs && lhs.nIns() >= 2 && !lhs.inProgress() && - lhs.region()==rhs.region() && !(lhs.region() instanceof FunNode) && - lhs.nIns()>2 && // A 1-input Phi will collapse already // Disallow with self-looping phi; these will collapse - (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) { - // Profit check: only 1 instance of `this` will remain, all the - // others will fold to constants. - int cnt=0; - for( int i=1; i<lhs.nIns(); i++ ) - if( isCon(lhs,rhs,i) ) - cnt++; - if( lhs.nIns()-1 - cnt <= 1 ) { - // Profit! Push up-phi and fold - Node[] ns = new Node[lhs.nIns()]; - ns[0] = lhs.in(0); - for( int i=1; i<lhs.nIns(); i++ ) { - ns[i] = isCon(lhs,rhs,i) - ? new ConstantNode(con(lhs.in(i)._type, rhs.in(i)._type)).peephole() - : copy(lhs.in(i), rhs.in(i)).peephole(); + (lhs.nIns() > 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) { + Node r = lhs.region(); + if( r==rhs.region() && !(r instanceof FunNode) && + // For loops, the backedge *must* fold away, lest we unroll forever + (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) { + // Profit check: only 1 instance of `this` will remain, all the + // others will fold to constants. For loops, *all* must fold away + int cnt=0; + for( int i=1; i<lhs.nIns(); i++ ) + if( isCon(lhs,rhs,i) ) + cnt++; + if( lhs.nIns()-1 - cnt <= 1 ) { + // Profit! Push up-phi and fold + Node[] ns = new Node[lhs.nIns()]; + ns[0] = lhs.in(0); + for( int i=1; i<lhs.nIns(); i++ ) { + ns[i] = isCon(lhs,rhs,i) + ? ConstantNode.make(con(lhs.in(i)._type, rhs.in(i)._type)).peephole() + : copy(lhs.in(i), rhs.in(i)).peephole(); + } + String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label; + return new PhiNode(label, ns).peephole(); } - String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label; - return new PhiNode(label,lhs._minType,ns).peephole(); } } + return null; } @@ -85,14 +195,14 @@ public abstract class ArithNode extends Node { static boolean overflow( long x, long y ) { - if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow + if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow } @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,_loc); - if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc); - return null; + return nodeErr(this,_loc,op(),_mode,allowFloat()); } + + @Override public boolean eq( Node n ) { return _mode==((ArithNode)n)._mode; } + @Override public int hash() { return _mode; } } diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 0d82c5b9..73e786e3 100644 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -3,21 +3,27 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; import static com.seaofnodes.simple.type.TypeInteger.*; -abstract public class BoolNode extends Node { +abstract public class BoolNode extends Node implements ModeNode { - public BoolNode(Node lhs, Node rhs) { - super(null, lhs, rhs); + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; + + public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { + baos.packed1(_mode); } abstract public String op(); // String opcode name - @Override - public String label() { return getClass().getSimpleName(); } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.open().n(in(1)).p(op()).n(in(2)).close(); } @@ -26,15 +32,25 @@ abstract public class BoolNode extends Node { public TypeInteger compute() { Type t1 = in(1)._type; Type t2 = in(2)._type; + if( t1==null || t2==null ) + return BOOL; // Exactly equals? if( t1.isHigh() || t2.isHigh() ) return (TypeInteger)BOOL.dual(); if( in(1)==in(2) ) // LT fails, both EQ and LE succeed return this instanceof LT ? FALSE : TRUE; - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) - return doOp(i1,i2); + byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode; + if( mode==0 ) + return BOOL; + // Handle integer compares + if( mode==1 ) { + if( t1 instanceof TypeInteger i1 && + t2 instanceof TypeInteger i2 ) + return doOp(i1,i2); + return BOOL; + } + // Floating compare. Mixed inputs are converted in idealize(). if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 && f1.isConstant() && f2.isConstant() ) @@ -44,22 +60,41 @@ abstract public class BoolNode extends Node { TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); } boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } - Node copyF(Node lhs, Node rhs) { return null; } - public boolean isFloat() { return false; } + public boolean isFloat() { assert _mode!=0; return _mode==2; } + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } @Override public Node idealize() { // Compare of same if( in(1)==in(2) ) - return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE); + return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE); + // Can we decide int vs flt? + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==null || t2==null ) + return null; + if( _mode==0 ) { + byte mode = ArithNode.mode(t1,t2); + if( mode!=0 ) return setMode(mode).init(); + } + if( _mode==2 ) { + if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); + if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); + } + // Equals pushes constant to the right; 5==X becomes X==5. if( this instanceof EQ ) { if( !(in(2) instanceof ConstantNode) ) { // con==noncon becomes noncon==con if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); + // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high + return new EQ(in(2),in(1)); } // Equals X==0 becomes a !X if( (in(2)._type == ZERO || in(2)._type == Type.NIL) ) @@ -75,76 +110,76 @@ abstract public class BoolNode extends Node { return null; } + @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; } + @Override public int hash() { return _mode; } + @Override public Parser.ParseException err() { + return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT)); + } + public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } + public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } + @Override public Tag serialTag() { return Tag.EQ; } public String op() { return "=="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } - Node copyF() { return new EQF(null,null); } + @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; } + Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); } } public static class NE extends BoolNode { public NE(Node lhs, Node rhs) { super(lhs,rhs); } + public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } + @Override public Tag serialTag() { return Tag.NE; } public String op() { return "!="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); } - Node copyF() { throw Utils.TODO(); } + @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; } + Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); } } public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } + public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } + @Override public Tag serialTag() { return Tag.LT; } public String op() { return "<" ; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } - Node copyF() { return new LTF(null,null); } + @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; } + Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); } } public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } + public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } + @Override public Tag serialTag() { return Tag.LE; } public String op() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } - Node copyF() { return new LEF(null,null); } - } - - public static class EQF extends EQ { - public EQF(Node lhs, Node rhs) { super(lhs,rhs); } - @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; } - @Override public boolean isFloat() { return true; } - @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); } - } - public static class LTF extends LT { - public LTF(Node lhs, Node rhs) { super(lhs,rhs); } - @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; } - @Override public boolean isFloat() { return true; } - @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); } - } - public static class LEF extends LE { - public LEF(Node lhs, Node rhs) { super(lhs,rhs); } - @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; } - @Override public boolean isFloat() { return true; } - @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); } + @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; } + Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); } } // Unsigned less that, for range checks. Not directly user writable. public static class ULT extends BoolNode { - public ULT(Node lhs, Node rhs) { super(lhs,rhs); } + public ULT(Node lhs, Node rhs) { super(lhs,rhs,(byte)1); } + public ULT(Node lhs, Node rhs, byte mode) { + super(lhs,rhs,(byte)1); + assert mode==1; + } + @Override public Tag serialTag() { return Tag.ULT; } public String op() { return "u<" ; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index 7d4f1fb8..82c4af10 100644 --- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -1,9 +1,15 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; @@ -15,23 +21,53 @@ public class BulkMemPhiNode extends PhiNode { private static final BitSet EMPTY = new BitSet(); // Excluded Aliases; empty means NO exclusions. - // Each split clones the exclusions; published sets are never mutated. - public final BitSet _aliases; + // Directly visible by design. Empty sets share EMPTY; splitAlias performs + // copy-on-first-write and removes the node from GVN before mutation. + public BitSet _aliases; public BulkMemPhiNode(String label, Node... inputs) { this(label,EMPTY,inputs); } private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label,TypeMem.BOT,inputs); + super(label, inputs); _aliases = aliases; } + public BulkMemPhiNode(BulkMemPhiNode phi) { + super(phi,phi._label); + _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone(); + } + + public BulkMemPhiNode(RegionNode r, Node sample) { + super(r,sample); + _aliases = EMPTY; + } + public boolean isSplit(int alias) { return _aliases.get(alias); } - public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + @Override public Tag serialTag() { return Tag.BulkMemPhi; } - @Override public String label() { return "BulkPhi"+_aliases; } + @Override + public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, + IdentityHashMap<Node,Integer> anodes) { + super.packed(baos,strs,types,anodes); + baos.packed2(_aliases.cardinality()); + for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) ) + baos.packed2(alias); + } + + static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) { + Node[] ins = new Node[bais.packed1()]; + String label = strs[bais.packed2()]; + BitSet bits = new BitSet(); + for( int i=bais.packed2(); i>0; i-- ) { + int alias = bais.packed2(); + if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias); + bits.set(alias); + } + return new BulkMemPhiNode(label,bits.isEmpty() ? EMPTY : bits,ins); + } @Override public Type compute() { @@ -51,7 +87,7 @@ public class BulkMemPhiNode extends PhiNode { // "Peek through" a MemMerge that covers this alias set on its default for( int i=1; i<nIns(); i++ ) if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) { - setDef(i,CodeGen.CODE.add(mmm.in(1))); + setDef(i,mmm.in(1)); return this; } @@ -63,8 +99,8 @@ public class BulkMemPhiNode extends PhiNode { return slice(alias); } - for( int i=0; i<_outputs.size(); i++ ) { - int alias = outputAlias(_outputs.get(i)); + for( int i=0; i<nOuts(); i++ ) { + int alias = outputAlias(out(i)); if( alias!=0 ) return slice(alias); } @@ -89,19 +125,22 @@ public class BulkMemPhiNode extends PhiNode { // True if the MemMerge has no precise slice still covered by this Phi. private boolean canPeek(MemMergeNode mmm) { - return mmm.in(1)!=null && missingAlias(mmm)==0; + return missingAlias(mmm,false)==0; } // Return an alias required by an input but still covered by this Phi, or 0. private int inputAlias(Node n) { return switch(n) { + case ParmNode p -> { assert p._idx==1; yield 0; } case ProjNode proj -> 0; - case ParmNode parm -> 0; - case ConstantNode con -> 0; - case MemMergeNode mmm -> missingAlias(mmm); + case ConstantNode con -> { + assert !(con._con instanceof TypeMem tmem && tmem._alias != 1); + yield 0; + } + case MemMergeNode mmm -> missingAlias(mmm,false); case MemOpNode mem -> unsplit(mem._alias); case BulkMemPhiNode bulk -> missingAlias(bulk); - default -> throw new AssertionError("Unexpected memory input "+n); + default -> throw Utils.TODO("Should not reach here"); }; } @@ -111,16 +150,18 @@ public class BulkMemPhiNode extends PhiNode { // Normal graph-neighbor enqueueing flows from defs to uses, so retain // an explicit forward dependency to revisit this Phi when the user // sharpens. - addDepForwards(use); + use = addDepForwards(use); return switch(use) { case ScopeNode scope -> 0; - case MemMergeNode mmm -> missingAlias(mmm); + case ParmNode parm -> 0; + case MemMergeNode mmm -> missingAlias(mmm,true); case BulkMemPhiNode bulk -> missingAlias(bulk); case MemOpNode mem -> unsplit(mem._alias); case MemPhiNode phi -> unsplit(phi._alias); + case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0; default -> { - assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode - : "Unexpected bulk-memory user "+use; + assert use instanceof ReturnNode || use instanceof CallNode + : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use; yield 0; } }; @@ -142,14 +183,14 @@ public class BulkMemPhiNode extends PhiNode { return 0; } - // Every explicit slot requests that alias, including a partial allocation - // input whose slot still points to this bulk Phi. - private int missingAlias(MemMergeNode mmm) { + // First precise MemMerge slice still covered by this Phi. When inspecting + // a user, ignore slots which point back to this Phi. + private int missingAlias(MemMergeNode mmm, boolean user) { for( int alias=2; alias<mmm.nIns(); alias++ ) if( mmm.in(alias)!=null && mmm.alias(alias)!=mmm.in(1) && !_aliases.get(alias) ) - return alias; + { assert (!user || mmm.in(alias)!=this); return alias; } return 0; } @@ -160,6 +201,7 @@ public class BulkMemPhiNode extends PhiNode { // parallel precise Phi already exists, reuse it instead of building a // duplicate for the same Region/alias pair. Node mem = precisePhi(alias); + assert ((TypeMem)mem._type)._alias==alias; BitSet aliases = ((BitSet)_aliases.clone()); aliases.set(alias); @@ -174,8 +216,7 @@ public class BulkMemPhiNode extends PhiNode { // Do not peephole bphi here, as BulkMemPhi can recursively start a // second bulk rewrite before this one has finished. Let the worklist // discover any further splits - bphi.setType(TypeMem.BOT); - Node bulk = bphi; + Node bulk = bphi.init(); CodeGen.CODE.add(bulk); CodeGen.CODE.add(mem); return aggregate(bulk,alias,mem); @@ -185,11 +226,19 @@ public class BulkMemPhiNode extends PhiNode { // which the bulk has already excluded. The named alias is newly supplied // (or replaces its old slice). MemMergeNode aggregate(Node bulk, int alias, Node precise) { - MemMergeNode mmm = new MemMergeNode(bulk); + MemMergeNode mmm = new MemMergeNode(null,bulk); for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) ) { assert old != alias; mmm.alias(old,precisePhi(old));} mmm.alias(alias,precise); assert checkMerge(mmm); + mmm.init(); + // Slicing changes representation, not the set of values in memory. + // Newly exposed precise inputs can carry conservative bulk escape + // summaries; do not let those invent escapes absent from this Phi. + TypeMem old = (TypeMem)_type; + TypeMem mem = (TypeMem) mmm._type; + mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz, + mem._final,old._escFs,old._escAs); return mmm; } @@ -201,14 +250,14 @@ public class BulkMemPhiNode extends PhiNode { mphi = new MemPhiNode("$"+alias,alias); mphi.addDef(region()); // Due to cycles, must set before calling peephole - mphi.setType(TypeMem.make(alias,Type.BOTTOM)); + mphi.setType(TypeMem.BOT.makeFrom(alias)); for( int i=1; i<nIns(); i++ ) mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); return CodeGen.CODE.add(mphi); } MemPhiNode _findPhi(int alias) { - for( Node use : region()._outputs ) + for( Node use : region().outs() ) if( use instanceof MemPhiNode mphi && mphi._alias==alias ) return mphi; return null; diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 6805c1ef..fe3382a6 100644 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,13 +1,11 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; + import java.util.BitSet; -import java.util.HashMap; -import java.util.HashSet; +import java.util.IdentityHashMap; /** Control Flow Graph Nodes * <p> @@ -103,11 +101,78 @@ public abstract class CFGNode extends Node { return lhs; } + // Safe read of the control input of a data node. + public static CFGNode safeCFG(Node n) { + if( n==null ) return null; + if( !(n.in(0) instanceof CFGNode cfg) ) return null; + return cfg._safeCFG(); + } + private CFGNode _safeCFG() { + return switch(this) { + case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG(); + case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG(); + case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG(); + case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG(); + default -> this; + }; + } + + // True when this node dominates `sub` in the current idom tree. + public boolean dominates(CFGNode sub, Node dep) { + for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() ) + if( cfg == this ) + return true; + else if( dep != null ) dep.addDep(cfg); + return false; + } + + public static CFGNode deepest(CFGNode a, CFGNode b) { + if( a == null ) return b; + if( b == null ) return a; + return b.idepth() > a.idepth() ? b : a; + } + + public static CFGNode earlyCFG(Node n) { + return earlyCFG(n,new IdentityHashMap<>(),new BitSet()); + } + + public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) { + if( n == null ) return null; + CFGNode cached = cache.get(n); + if( cached != null ) return cached; + if( active.get(n._nid) ) + return safeCFG(n); + active.set(n._nid); + CFGNode early = null; + for( Node x : n._inputs ) + early = deepest(early,defCFG(x,cache,active)); + active.clear(n._nid); + cache.put(n,early); + return early; + } + + private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) { + if( n == null || n.isConst() ) return null; // no impact on early cfg + return switch( n ) { + case PhiNode phi -> phi.region(); + case CFGNode cfg -> cfg; + case ProjNode projNode -> safeCFG( n ); + default -> earlyCFG( n, cache, active ); + }; + } + + public boolean sameFun(CFGNode that) { + FunNode fun = fun(); + return fun != null && fun == that.fun(); + } + // Find nearest enclosing FunNode public FunNode fun() { CFGNode cfg = this; - while( !(cfg instanceof FunNode fun) ) + while( !(cfg instanceof FunNode fun) ) { + if( cfg==null ) return null; cfg = cfg.idom(); + } return fun; } @@ -118,13 +183,13 @@ public abstract class CFGNode extends Node { public LoopTree _ltree; public int _pre; // Pre-order numbers for loop tree finding - static class LoopTree { - LoopTree _par; - final LoopNode _head; - int _depth; + public static class LoopTree { + public LoopTree _par; + public final LoopNode _head; + public int _depth; LoopTree(LoopNode head) { _head = head; } - @Override public String toString() { return "LOOP"+_head._nid; } - int depth() { + @Override public String toString() { return label(_head); } + public int depth() { return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth; } } @@ -133,23 +198,29 @@ public abstract class CFGNode extends Node { // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves // also refer to *their* containing LoopNode, as well as have their depth. // Start contains the function bodies at depth 0; real loops add depth. - public void buildLoopTree(StartNode start, StopNode stop) { - // Unlink all linked calls. This can remove RPC constants which - // shuffled the StartNode outputs so requires a while loop. - boolean done=false; - while(!done) { - done = true; - for( Node use : start._outputs ) - if( use instanceof FunNode fun ) - for( Node c : fun._inputs ) - if( c instanceof CallNode call ) - { call.unlink_all(); done=false; } + public void buildLoopTree( Ary<FunNode> funs, StopNode stop) { + _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this); + Ary<FunNode> work = new Ary<>(FunNode.class); + + BitSet post = new BitSet(); + int pre = 1; + _pre = pre++; + for( FunNode fun : funs ) { + if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) { + work.push(fun); + if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) { + start._pre = pre++; + start._ltree = start.cfg(1)._ltree = _ltree; + post.set(start._nid); + } + }; } - _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); - _bltWalk(2,null,stop, new BitSet()); + for( int i=0; i<work._len; i++ ) + pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work); } - int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { + + int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) { // Pre-walked? if( _pre!=0 ) return pre; _pre = pre++; @@ -163,8 +234,12 @@ public abstract class CFGNode extends Node { } // Pre-walk for( Node use : _outputs ) - if( use instanceof CFGNode usecfg && !skip( usecfg ) ) - pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post ); + if( use instanceof CFGNode usecfg ) { + if( skip( usecfg ) ) { + if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 ); + } else + pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work ); + } // Post-order work: find innermost loop LoopTree inner = null, ltree; @@ -207,7 +282,7 @@ public abstract class CFGNode extends Node { return pre; } - private boolean skip(CFGNode usecfg) { + public 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 || @@ -215,8 +290,13 @@ public abstract class CFGNode extends Node { (this instanceof ReturnNode && usecfg instanceof CallEndNode); } - - public String label( CFGNode target ) { - return (target instanceof LoopNode ? "LOOP" : "L")+target._nid; + public static String label( CFGNode target ) { + return switch(target) { + case StartNode start -> start.label(); + case FunNode fun -> fun.label(); + case LoopNode loop -> "LOOP"+target._nid; + default -> "L"+target._nid; + }; } + } diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java index c62de840..12fde6c0 100644 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -2,13 +2,16 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.BAOS; -public class CProjNode extends CFGNode { +import java.util.HashMap; +import java.util.IdentityHashMap; + +public class CProjNode extends CFGNode implements Proj { // Which slice of the incoming multipart value public int _idx; @@ -22,6 +25,14 @@ public class CProjNode extends CFGNode { _label = label; } public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; } + @Override public Tag serialTag() { return Tag.CProj; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed1(_idx); + baos.packed2(_label==null ? 0 : strs.get(_label)); + } + static Node make( BAOS bais, String[] strs ) { + return new CProjNode(null, bais.packed1(), strs[bais.packed2()] ); + } @Override public String label() { return _label; } @@ -41,13 +52,13 @@ public class CProjNode extends CFGNode { public Node idealize() { if( ctrl()._type instanceof TypeTuple tt ) { if( tt._types[_idx]==Type.XCONTROL ) - return Parser.XCTRL; // We are dead + return CodeGen.CODE.XCTRL; // We are dead if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes return ctrl().in(0); // We become our input control } // Flip a negating if-test, to remove the not - if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not ) + if( ctrl() instanceof IfNode iff && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not ) return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); // Copy of some other input @@ -66,4 +77,9 @@ public class CProjNode extends CFGNode { @Override int hash() { return _idx; } + @Override public int idx() { return _idx; } + + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_label); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java index d26ea33b..05707132 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java @@ -14,13 +14,15 @@ public class CallEndMach extends CallEndNode implements MachNode { _retMask = code._mach.retMask(call().tfp()); // Kill mask is all caller-saves, and any mirror stack slots for args // in registers. - RegMaskRW kills = code._callerSave.copy(); + RegMaskRW kills = (call().external() ? code._cCallerSave : code._callerSave).copy(); // Start of stack slots int maxReg = code._mach.regs().length; // Incoming function arg slots, all low numbered in the RA int fslot = fun()._maxArgSlot; // Killed slots for this calls outgoing args - int xslot = code._mach.maxArgSlot(call().tfp()); + int xslot = call().external() + ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv) + : code._mach.maxArgSlot(call().tfp()); _xslot = (maxReg+fslot)+xslot; for( int i=0; i<xslot; i++ ) kills.set((maxReg+fslot)+i); diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 115f70c6..35180fea 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -2,9 +2,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.*; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; /** * CallEnd @@ -12,17 +16,48 @@ import com.seaofnodes.simple.type.*; public class CallEndNode extends CFGNode implements MultiNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - private boolean _folding; - public boolean folding() { return _folding; } + boolean _folding; + // +2 must clone, so await all cleanup but OK + // +1 trivial now, + // 0: take a look, + // -1: somethings wrong, but might improve + // -2: never and stop asking, + private byte _inline; public final TypeRPC _rpc; - public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); } + public CallEndNode(CallNode call, TypeRPC rpc) { + super(new Node[]{call}); + _rpc = rpc; + } public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; } + public CallEndNode(TypeRPC rpc) { + super(new Node[0]); + _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc())); + } + private CallEndNode(int nIns, TypeRPC rpc) { + super(new Node[nIns]); + _rpc = rpc; + } + @Override public Tag serialTag() { return Tag.CallEnd; } + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed1(nIns()); + // Linked CallEnds depend on Return types which depend on CallEnds; + // break the cycle + baos.packed2(types.get(_type)); + baos.packed2(types.get(_rpc)); + } + static Node make( BAOS bais, Type[] types ) { + int nIns = bais.packed1(); + Type type = types[bais.packed2()]; + Node cend = new CallEndNode(nIns,(TypeRPC)types[bais.packed2()]); + cend._type = type; + return cend; + } - @Override public String label() { return "CallEnd"; } @Override public boolean blockHead() { return true; } public CallNode call() { return (CallNode)in(0); } + public boolean folding() { return _folding; } @Override public CFGNode idom(Node dep) { // Folding the idom is the one inlining Return @@ -41,182 +76,234 @@ public class CallEndNode extends CFGNode implements MultiNode { @Override public Type compute() { if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL ) - return TypeTuple.RET.dual(); - // Mid-fold, just take the one single callers' return type - if( _folding ) return in(1)._type; + return TypeTuple.STATE.dual(); // Grab the TFP and use the functions declared return type. + // If the call.fptr() is a FRef, return will be BOTTOM. Type ftype = addDep(call.fptr())._type; - Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM; - if( ftype instanceof TypeFunPtr tfp ) { - ret = tfp.ret(); - // Here, if I can figure out I've found *all* callers, then I can meet - // across the linked returns and join with the function return type. - if( 1+tfp.nfcns() == nIns() ) { // A linked function for every concrete function - ret = Type.TOP; - for( int i=1; i<nIns(); i++ ) { - Type tret = in(i)._type instanceof TypeTuple rtt ? rtt.ret() : in(i)._type; - ret = ret.meet(tret); - } - } + if( !(ftype instanceof TypeFunPtr tfp) ) + return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE; + // Mid-fold, just take the one single callers' return type + if( _folding ) { + TypeTuple tt = (TypeTuple)in(1)._type; + return tt.makeFrom(2,tt.ret()); + } + + // Here, if I can figure out I've linked *all* callers, then I can meet + // across the linked returns and join with the function return type. + + // External functions have an exact FIDX but deliberately no local + // FunNode/Return edge. They conservatively crush public memory and + // produce their declared return type. A missing non-external target + // is still an unresolved/error call. + int externs = 0; + if( !XInt.isHigh(tfp.fidxs()) ) + for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) ) + if( CodeGen.CODE.externFunc(fidx)!=null ) + externs++; + boolean hasExtern = externs != 0; + + // If we have an error-call, e.g. wrong args, then we never link. + // Pre-Opto this looks like a missing target fcn, and we assume it will + // appear later - meanwhile, we use a conservative approx of memory + // effects. + if( (nIns()-1)+externs < tfp.nfcns() ) { + // If during Opto, assume call won't be called and thus won't return anything. + boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal(); + Type tmem = opto ? TypeMem.TOP : TypeMem.BOT; + Type ret = opto ? Type.TOP : tfp.ret(); + return TypeTuple.make(Type.CONTROL,tmem,ret); } - return TypeTuple.make(call._type,TypeMem.BOT,ret); + + // A linked function for every concrete function + TypeTuple state = hasExtern + ? TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret()) + : TypeTuple.STATE.dual(); + for( int i=1; i<nIns(); i++ ) + state = (TypeTuple)state.meet(in(i)._type); + // A reachable call whose presently linked exits are all unreachable + // keeps a conservative continuation. In particular, do not let a + // transient no-return result tear down the caller during SCCP. + if( state.ctl()==Type.XCONTROL && + CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) + return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP); + // At least as good as the TFP + return state.makeFrom(2,state.ret().join(tfp.ret())); } @Override public Node idealize() { + return null; + } - // Trivial inlining: call site calls a single function; single function - // is only called by this call site. - if( !_folding && nIns()==2 && in(0) instanceof CallNode 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). - // Disallow if still folding other things, as it makes other - // dependency checks carry long chains of half-folded calls. - // Dead control may end the walk before an enclosing function. - CFGNode idom = call; - while( idom!=null ) { - idom = idom.idom(); - if( idom instanceof FunNode ) break; - if( idom instanceof CallEndNode cend && cend._folding ) break; - } - // Inline? - if( idom instanceof FunNode fun2 && fun2 != fun && !fun2._folding ) { - // 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 - CodeGen.CODE.addAll(fun._outputs); - // Repeat defs 1 layer down, for users of Parm (Phis) - for( Node parm : fun._outputs ) - if( parm instanceof ParmNode ) - CodeGen.CODE.addAll(parm._outputs); - - // Inlining immediately blows all cache idepth fields past the inline point. - // Bump the global version number invalidating them en-masse. - CodeGen.CODE.invalidateIDepthCaches(); - return this; - } else if( idom!=null ) { - addDep(idom); - } - } else { - addDep(fun); - } - } else { // Function ptr has multiple users (so maybe multiple call sites) - addDep(fptr); - } - } + public byte maybeInline() { + byte inline = inlineCandidate(); + if( CodeGen.CODE._midAssert ) + return inline; + _inline = inline; + if( _inline > 0 ) + doInline(); - return null; + return _inline; } - // Clone only tiny, straight-line boilerplate, after ordinary peeps settle. - // Larger bodies still use the existing single-caller, non-cloning inline. - public boolean inlineSmall() { - if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false; - Node fptr = call.fptr(); - if( !(fptr instanceof ConstantNode) || - !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) - { addDep(fptr); return false; } - if( call.err()!=null ) return false; + // Check if candidate right now, sets no flags + private byte inlineCandidate() { + if( _inline < -2 ) return -2; // Never + if( _folding ) return -2; // No, and will collapse anyways + if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death + + if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change + + // Have exactly 1 linked function ReturnNode ret = (ReturnNode)in(1); FunNode fun = ret.fun(); - if( fun._folding ) return false; - int len = smallBody(fun); - if( len==0 ) return false; - // All callers consume the three normal return projections. - for( Node use : _outputs ) - if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) || - (use instanceof CProjNode cp && cp._idx==0)) ) return false; - - // Substitute control and arguments directly; no new function identity, - // parameter Phis, or return address is needed for this straight line. - Node[] copy = new Node[len]; - for( int i=0; i<len; i++ ) { - Node n = TINYBODY[i]; - copy[i] = n==fun ? call.ctrl() : - n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) : - n==ret ? null : n.copyEmpty(); - } - for( int i=0; i<len; i++ ) { - Node n = TINYBODY[i]; - if( n instanceof StoreNode || n instanceof MemMergeNode ) { - for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy)); - CodeGen.CODE.add(copy[i]); - } - } - Node[] result = new Node[3]; - for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep(); - keep(); - call.unlink_all(); - CodeGen.CODE.add(fun); - CodeGen.CODE.addAll(fun._outputs); - // subsume kills each projection; retain CallEnd until all are replaced. - while( nOuts()>1 ) { - Node use = out(0)==null ? out(1) : out(0); - int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx; - CodeGen.CODE.addAll(use._outputs); - use.subsume(result[idx]); - } - unkill(); - for( Node n : result ) n.unkill(); - CodeGen.CODE.invalidateIDepthCaches(); - return true; - } - private static Node mapped(Node n, Node[] copy) { - for( int i=0; i<copy.length; i++ ) - if( TINYBODY[i]==n ) return copy[i]; - return n; // Constants outside the body are shared. + // Heuristic forced inlining off via name + if( fun._name != null && + fun._name.endsWith("_noInline") ) + return -2; // Never, stop asking + + // Disallow self-recursive inlining (loop unrolling by another name). + for( int i=1; i < fun.nIns(); i++ ) + if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun" + { addDep(fun); return -1; } // No, but can lose the recursive edge + + // Things that might improve, so not now a candidate + Node fptr = call.fptr(); + // Constant Function is being called, and its not-null + if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) + { addDep(fptr); return -1; } + + // Arguments are correct + if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) ) + { addDep(fptr); return -1; } + + // Encouraged inlining because small size and constructor. + int maxSize = fun.inlineLimit(); + // A large parse can leave only a tiny initializer. Recognize that + // without repeatedly walking the whole body while it shrinks. + if( fun._approxUIDs >= maxSize ) { + if( !smallBody(fun) ) return -1; + } else if( fun.body().cardinality() >= maxSize ) + return -1; // Try later (if cleanup makes the real body small enough) + + // Can be cloned but not trivial + if( fun.nIns() > 2 ) { addDep(fun); return 2; } + + // A candidate right now + return 1; } - // Bounded recognition of initializer boilerplate. Walk uses from the entry - // and stop at Return, never following callers or constant inputs. This also - // avoids scanning sparse MemMerge input arrays while a large body shrinks. - // Watch this small prefix, including an unsupported node that may fold. + // Bounded, conservative recognition of straight-line initializer boilerplate. + // Walk uses from the entry and stop at Return, never following callers or + // constant inputs. This also avoids scanning sparse MemMerge input arrays. + // Watch only this small prefix, including an unsupported node that may fold. private static final Node[] TINYBODY = new Node[15]; // Tiny worklist - private int smallBody(FunNode fun) { + private boolean smallBody(FunNode fun) { ReturnNode ret = fun.ret(); for( Node in : ret._inputs ) if( in!=null ) addDep(in); - if( ret.ctrl()!=fun ) return 0; + if( ret.ctrl()!=fun ) return false; TINYBODY[0] = fun; int len=1; for( int i=0; i<len; i++ ) { Node n = TINYBODY[i]; addDep(n); if( n==ret ) continue; - if( n instanceof ParmNode parm && parm.region()!=fun ) return 0; + if( n instanceof ParmNode parm && parm.region()!=fun ) return false; if( n!=fun && !(n instanceof ParmNode) && !(n instanceof StoreNode) && !(n instanceof MemMergeNode) ) - return 0; - if( n.nOuts()>TINYBODY.length ) return 0; + return false; // Something unexpected + if( n.nOuts()>TINYBODY.length ) return false; // Too big for( Node use : n._outputs ) { if( use==null ) continue; int j=0; while( j<len && TINYBODY[j]!=use ) j++; - if( j<len ) continue; - if( len==TINYBODY.length ) { addDep(use); return 0; } - TINYBODY[len++] = use; + if( j<len ) continue; // Already in TINYBODY + if( len==TINYBODY.length ) { addDep(use); return false; } // Too big + TINYBODY[len++] = use; // Add to worklist } } - return len; + return true; + } + + public void doInline() { + assert _inline > 0; // Only called when possible to inline + + ReturnNode ret = (ReturnNode)in(1); + FunNode fun = ret.fun(); + CallNode call = call(); + assert fun.sig().isa(call.fptr()._type); + + // Clone first + if( _inline==2 ) { + assert !CodeGen.CODE._midAssert; // Triggered inlining + // Remove the existing function linkage + call.unlink_all(); + // Clone the function body + FunNode fun2 = fun.copyBody(); + // Call uses the unique new function + Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole(); + call.setDef(call.nIns()-1,fptr2); + // Link to the new function + call.link(fun2); + fun = fun2; + } + + // Trivial inlining: call site calls a single function; single function + // is only called by this call site. + doTrivialInlining(fun); + } + + boolean goodArgs( CallNode call, TypeFunPtr tfp ) { + for( int i=0; i<tfp.nargs(); i++ ) { + Node arg = call.arg(i+2); + // The graph's function-pointer type can still contain open forward + // references here. Compare against its recursively upgraded form, + // so deep mutability requirements participate before inlining. + Type formal = tfp.arg(i).upgradeType(Parser.TYPES); + if( !arg._type.isa(formal) || !arg._type.accessISA(formal) ) + { addDep(arg); return false; } + } + return true; + } + + + // Do trivial inlining. Inlining does not need to clone code, merely + // triggers folding the Call/Fun and Return/CallEnd away. + private boolean doTrivialInlining( FunNode fun ) { + assert !CodeGen.CODE._midAssert; // Triggered inlining + // Trivial inline: rewrite + _folding = true; + // Rewrite Fun so the normal RegionNode ideal collapses + fun._folding = true; + fun.setDef(1,call().ctrl());// Bypass the Call; + fun.ret().setDef(3,null); // Return is folding also + + CodeGen.CODE.addAll(fun._outputs); + // Repeat defs 1 layer down, for users of Parm (Phis) + for( Node parm : fun._outputs ) + if( parm instanceof ParmNode ) + CodeGen.CODE.addAll(parm._outputs); + + // Inlining immediately blows all cache idepth fields past the inline point. + // Bump the global version number invalidating them en-masse. + CodeGen.CODE.invalidateIDepthCaches(); + // More peeps + CodeGen.CODE.add(fun); + CodeGen.CODE.add(this); + CodeGen.CODE.addAll(_outputs); + CodeGen.CODE.addAll(_inputs); + return true; // Always return true } + @Override public Node pcopy(int idx) { return _folding ? in(1).in(idx) : null; } + + @Override public Node copy() { + return _folding ? super.copy() : new CallEndNode(_rpc); + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java index e63b3f62..4372e2d5 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -2,12 +2,17 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.TypeRPC; +import com.seaofnodes.simple.type.XInt; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Call @@ -19,8 +24,9 @@ public class CallNode extends CFGNode { public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public CallNode(CallNode call) { super(call); _loc = call._loc; } - - @Override public String label() { return "Call"; } + @Override public Tag serialTag() { return Tag.Call; } + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } + static Node make( BAOS bais ) { return new CallNode(null,new Node[bais.packed1()]); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { String fname = name(); @@ -97,14 +103,11 @@ public class CallNode extends CFGNode { Node progress = null; 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); + int[] fidxs = tfp.fidxs(); + if( !XInt.isHigh(fidxs) ) { + // Walk the bits and link + for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) { + FunNode fun = CodeGen.CODE.link(fidx); if( fun!=null && !fun._folding && !linked(fun) ) progress = link(fun); } @@ -125,35 +128,58 @@ public class CallNode extends CFGNode { // Link so this calls fun public Node link( FunNode fun ) { assert !linked(fun); - fun.addDef(this); + CodeGen.CODE.add(fun).addDef(this); for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) - parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx)); + if( use instanceof ParmNode parm ) { + Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx); + CodeGen.CODE.add(parm).addDef(def); + // Linking can happen during post-SCCP inlining. Keep an RPC + // Parm's cached finite set synchronized with its newly added + // input instead of waiting for the next worklist visit. + if( parm._idx==0 && parm._type instanceof TypeRPC ) + parm._type = parm._type.meet(def._type); + } // Call end points to function return CodeGen.CODE.add(cend()).addDef(fun.ret()); return this; } + // Unlink a single function + public void unlink( FunNode fun, int path ) { + assert linked(fun); + for( Node use : fun._outputs ) + if( use instanceof ParmNode ) + use.delDef(path); + fun.delDef(path); + CodeGen.CODE.add(fun); + CallEndNode cend = cend(); + ReturnNode ret = fun.ret(); + if( cend != null ) { + ret.keep(); + cend.delDef(cend._inputs.find(ret)); + CodeGen.CODE.add(cend); + ret.unkeep(); + } + assert !linked(fun); + } + + // Unlink all linked functions public void unlink_all() { for( int i=0; i<_outputs._len; i++ ) if( out(i) instanceof FunNode fun ) { - assert linked(fun); - int idx = fun._inputs.find(this); - for( Node use : fun._outputs ) - if( use instanceof ParmNode ) - use.delDef(idx); - fun.delDef(idx); - cend().delDef(cend()._inputs.find(fun.ret())); - assert !linked(fun); + int path = fun._inputs.find(this); + unlink(fun,path); i--; } } @Override public Parser.ParseException err() { + if( fptr()._type == Type.BOTTOM ) + return null; // Wrong, but failing earlier if( !(fptr()._type instanceof TypeFunPtr tfp) ) - throw Utils.TODO(); + return Parser.error("Expected a function, but found " + fptr()._type, _loc); if( !tfp.notNull() ) return Parser.error( "Might be null calling "+tfp, _loc); if( nargs() != tfp.nargs() ) @@ -162,11 +188,13 @@ public class CallNode extends CFGNode { // Check for args for( int i=0; i<tfp.nargs(); i++ ) if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) { - cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining - return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc ); + // Constructors return private memory and carry hidden self and + // selfMem arguments ahead of the source-level arguments. + int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0); + return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc ); } - if( tfp.fidxs() < 0 ) + if( tfp.isHigh() ) throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG return null; diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index 4e93f3e6..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.util.Utils; - -// Upcast (join) the input to a t. Used after guard test to lift an input. -// Can also be used to make a type-assertion if ctrl is null. -public class CastNode extends Node { - public Type _t; - public CastNode(Type t, Node ctrl, Node in) { - super(ctrl, in); - _t = t; - setType(compute()); - } - - public CastNode(CastNode c) { - super(c.in(0), c.in(1)); // Call parent constructor - this._t = c._t; // Copy the Type field - setType(compute()); // Ensure type is recomputed - } - - @Override public String label() { return "("+_t.str()+")"; } - - - @Override public boolean isConst() { return true; } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()).n(in(1)); - } - - @Override - public Type compute() { - // Cast array to int - Type t1 = in(1)._type; - if( _t == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - return _t; - // Freeze if the join is high but both inputs are low - Type tj = t1.join(_t); - if( tj.isHigh() && !t1.isHigh() ) - return _type==null ? _t : _type; - - return tj; - } - - @Override - public Node idealize() { - return in(1)._type.isa(_t) && in(1)._type.accessISA(_t) ? in(1) : null; - } - - @Override - public 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 - return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _t.str(), null ); - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java index afdd29be..f94fa484 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -2,11 +2,14 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.type.TypeStruct; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; /** * A field offset. This becomes a constant *after* other optimizations which @@ -15,27 +18,57 @@ import com.seaofnodes.simple.util.SB; */ public class ConFldOffNode extends ConstantNode { - public final String _name; // Struct name + public TypeStruct _ts; // Struct holding field public final String _fname; // Field name - public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; } + public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; } + @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); } @Override public String label() { - return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; + return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); } + @Override public Node idealize() { + // Some array offsets are known early + if( _ts.isAry() ) { + // Length always at offset zero + if( _fname==" len" ) + return CodeGen.CODE.ZERO; + // The shift depends on the element + if( _fname=="<<" ) + return Node.con(_ts.aryScale()); + // The base depends on the layout (alignment for large elements may + // involve padding) and MAY depend on the length if I go for + // smaller array lengths. + } + return null; + } + // Convert field offset to an integer - public Node asOffset(TypeStruct ts) { - return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole(); + public Node asOffset() { + int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname); + if( fldx == -1 ) return null; + // " len" selects the terminal size slot, not a Field. + if( fldx < _ts._fields.length && _ts._fields[fldx]._extern ) + return new ExternOffsetNode(_fname).peephole(); + return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole(); } + // Upgrade the internal type + @Override boolean _upgradeType( HashMap<String,Type> TYPES) { + boolean progress = super._upgradeType(TYPES); + TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES); + if( ts == _ts ) return progress; + _ts = ts; + return true; + } @Override public boolean eq(Node n) { ConFldOffNode off = (ConFldOffNode)n; // Invariant - return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n); + return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n); } - @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); } + @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 7c330175..47a7bbf6 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -5,7 +5,10 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; /** * A Constant node represents a constant value. @@ -17,29 +20,60 @@ import com.seaofnodes.simple.util.SB; * The Constant's value is the value stored in it. */ -public class ConstantNode extends Node { - public final Type _con; +public class ConstantNode extends TypeNode { public ConstantNode( Type type ) { - super(new Node[]{CodeGen.CODE._start}); - _con = _type = type; + this(type,false); + } + protected ConstantNode( Type type, boolean raw ) { + super(type,new Node[]{CodeGen.CODE._start}); + assert raw || !(type instanceof TypeFunPtr tfp && tfp.isConstant()) : + "Unique function identities require a Return-linked FunPtrNode"; + _type = type; } - public ConstantNode( ConstantNode con ) { super(con); _con = con._type; } + public ConstantNode( ConstantNode con ) { super(con); } + @Override public Tag serialTag() { return Tag.Con; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed2(types.get(_con)); // NPE if fails lookup + } + static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); } + // Semantic constant construction through Opto. A unique internal + // function identity carries a lifetime edge to the function's Return. public static Node make( Type type ) { if( type==Type. CONTROL ) return new CtrlNode(); if( type==Type.XCONTROL ) return new XCtrlNode(); - if( type instanceof TypeFunPtr tfp && tfp.isConstant() && tfp.notNull() ) { - FunNode fun = CodeGen.CODE.link(tfp); - if( fun!=null && !fun.isDead() ) return new FunPtrNode(tfp,fun.ret()); + if( CodeGen.CODE._phase != null && + CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() ) + return raw(type); + if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) { + FunNode fun = CodeGen.CODE._link(tfp); + if( fun != null ) + return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init(); + // TODO: A singleton FIDX can also emerge after merging and + // guarding several function pointers. Preserve that identity + // even if its internal Return is no longer available. + return raw(type); } return new ConstantNode(type); } + // Exact construction for deserialization and graph-incomplete machine + // selection. No semantic classification, compute, idealize, or GVN. + public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); } + public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); } + + // Analysis input created from whole cloth, rather than a first-class + // runtime value. In particular, a constructor call descriptor does not + // own the constructor function; the class FunPtr does. + public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); } + @Override protected String repeatName() { return _con==null || _con.toString().length()<=32 ? null : uniqueName(); } @Override public String label() { return "Con"; } + @Override public Node copy() { return raw(this); } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -54,6 +88,4 @@ public class ConstantNode extends Node { @Override public boolean isConst() { return true; } @Override public Type compute() { return _con; } @Override public Node idealize() { return null; } - @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; } - @Override int hash() { return _con.hashCode(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java new file mode 100644 index 00000000..b6c3b0c9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -0,0 +1,101 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; + +import java.util.HashMap; + +// Convert a value to an authoritative destination type. The destination is +// known structurally from a declaration; the source family might sharpen later. +public class ConvertNode extends Node { + private Type _dst; + + public ConvertNode(Type dst, Node val) { + super(null,val); + // Folding this conversion would replace the Return-linked FunPtrNode + // with an unhooked ConstantNode. + assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode); + _dst = dst; + } + + public Type dst() { return _dst; } + public Node val() { return in(1); } + + @Override public String label() { return "Convert_"+_dst.str(); } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()).open().n(val()).close(); + } + + // The declaration fixes the result family during pessimistic parsing. + // Optimistic SCCP must retain stronger facts already inside that family. + @Override public Type compute() { + Type src = val()._type; + if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) { + if( src.isHigh() ) return Type.TOP; + if( (src.isa(_dst) && src.accessISA(_dst)) ) return src; + } + return _dst; + } + + @Override public Node idealize() { + Type src = val()._type; + if( src==Type.BOTTOM || src==Type.TOP ) return null; + if( (src.isa(_dst) && src.accessISA(_dst)) ) return val(); + + // Managed arrays are represented by an object pointer, while C wants + // the address of the first element. The representation cast itself is + // zero-code; the Add skips the array header. + if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() ) + return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]")); + + // Integer (and nil-as-zero) to floating point. + if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat ) + return new ToFloatNode(val()); + + // Convert to i64 first, then apply the destination's integer width. + if( src instanceof TypeFloat && _dst instanceof TypeInteger ) + return new ConvertNode(_dst,new ToIntegerNode(val()).peephole()); + + // Narrow integers produce the declared sign/zero extension. + if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) { + if( dst._min==0 ) + return new AndNode(null,val(),con(dst._max)); + int shift = Long.numberOfLeadingZeros(dst._max)-1; + Node shf = con(shift); + if( shf._type==TypeInteger.ZERO ) return val(); + return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep()); + } + + // Narrow f64 to f32. + if( src instanceof TypeFloat && _dst instanceof TypeFloat ) + return new RoundF32Node(val()); + + // Wait for more source information, or report an error after Opto. + return null; + } + + @Override boolean _upgradeType(HashMap<String,Type> TYPES) { + Type dst = _dst.upgradeType(TYPES); + if( dst==_dst ) return false; + unlock(); + _dst = dst; + return true; + } + + @Override public Parser.ParseException err() { + if( val()._type==Type.BOTTOM || + val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) { + Node out = out(0); + if( out instanceof PhiNode phi ) out = phi.out(0); + if( out instanceof StoreNode st ) + return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null ); + } + return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null); + } + + @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; } + @Override int hash() { return _dst.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java index cf630ad4..62140873 100644 --- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -8,6 +8,7 @@ import com.seaofnodes.simple.type.Type; public class CtrlNode extends CFGNode { public CtrlNode() { super(CodeGen.CODE._start); } + @Override public Tag serialTag() { throw com.seaofnodes.simple.util.Utils.TODO(); } @Override public String label() { return "Ctrl"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); } @Override public boolean isConst() { return true; } diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index 0d037a30..00000000 --- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class DivFNode extends ArithFNode { - public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "DivF"; } - - @Override public String glabel() { return "/"; } - - @Override double doOp( double x, double y ) { return x/y; } - - @Override - public Node idealize() { - // Div of constant - if( in(2)._type instanceof TypeFloat f && f.isConstant() ) - return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole()); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java index 7b5ae16c..f173f0ef 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -2,15 +2,19 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeFloat; import java.util.BitSet; public class DivNode extends ArithNode { + @Override boolean allowFloat() { return true; } public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } + @Override public Tag serialTag() { return Tag.Div; } - @Override public String label() { return "Div"; } - @Override public String op() { return "//"; } + @Override public String op() { return _mode==1 ? "//" : "/"; } - @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override double doOp(double x, double y ) { return x / y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; } @@ -18,11 +22,19 @@ public class DivNode extends ArithNode { @Override public Node idealize() { // Div of 1. - if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger ) + if( in(2)._type == TypeInteger.TRUE && in(1)._type.isa(compute()) ) return in(1); + + // Div of constant + if( _mode==2 ) { + if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) ) + return in(1); + if( in(2)._type instanceof TypeFloat f && f.isConstant() ) + return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode); + } + return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java new file mode 100644 index 00000000..3e95fffb --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java @@ -0,0 +1,116 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.*; +import java.util.HashMap; +import java.util.IdentityHashMap; + +/** + * Merge public and private aliases. + * 0 - control + * 1 - private pointer + * 2 - private alias + * 3 - public alias + */ +public class EscapeNode extends TypeNode { + + public EscapeNode(Field fld, Node self, Node priv, Node pub ) { + super(fld,null,self,priv,pub); + } + public EscapeNode(EscapeNode esc ) { + super(esc); // Copy constructor + // Except we do not want/need self ptr past Opto. + // *NOTE* the weird direct stomp; this is correct because this + // constructor does NOT set the backedges, because it is used to + // copy-construct an entire graph and ALL edges will get rewritten. + _inputs.set(1,null); + _con = esc._con; + } + + // Pointer to some private (unescaped) memory from a NewNode + public Node self() { return in(1); } + public Node priv() { return in(2); } + public Node pub () { return in(3); } + public Field fld() { return (Field)_con; } + + @Override public Tag serialTag() { return Tag.Escape; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed2(types.get(_con)); + } + static Node make( BAOS bais, Type[] types) { + return new EscapeNode((Field)types[bais.packed2()], null,null,null); + } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("Esc#").p(fld()._alias).p(" {"); + if( self()==null ) p.p("---"); + else p.n(self()).p(","); + p.n(priv()).p("}, "); + p.n(pub ()); + return p; + } + + @Override + public Type compute() { + if( priv()._type.isHigh() ) return TypeMem.TOP; + if( pub ()._type.isHigh() ) return TypeMem.TOP; + TypeMem mpriv = (TypeMem)priv()._type; + TypeMem mpub = (TypeMem)pub ()._type; + // Private memory comes from an allocation or other non-aliased source. + // Public memory comes from anywhere, might be bulk memory with a very weak mem type. + Field fld = fld(); + assert mpriv._alias==1 || mpriv._alias==fld._alias; + assert mpub ._alias==1 || mpub ._alias==fld._alias; + Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1 + ? ts.at(ts.findAlias(fld._alias))._t + : mpriv._t; + assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize + Type tpub = mpub._t; + + // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1) + // and we're no worse than the declared field type + Type t = tpriv.meet(tpub).join(fld._t); + int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs); + int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs); + return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t); + } + + @Override public Node idealize() { + // Sharpen private memory input + if( priv() instanceof MemMergeNode mmm ) { + setDef(2,mmm.alias(fld()._alias)); + return this; + } + // Sharpen public memory input + if( pub() instanceof MemMergeNode mmm ) { + setDef(3,mmm.alias(fld()._alias)); + return this; + } + // New allocation is dead + if( self()._type.isHigh() ) + if( pub()._type.isa(_type) ) return pub(); + else addDep(pub()); + return null; + } + +// Upgrade the internal type +@Override boolean _upgradeType( HashMap<String,Type> TYPES) { + Type t = _con.upgradeType(TYPES); + if( t == _con ) return false; + unlock(); // Unlock before changing _con + _con = t; + return true; +} + + @Override public boolean eq(Node n) { + if( fld()._alias!=((EscapeNode)n).fld()._alias ) + return false; + assert _con == ((EscapeNode)n)._con; + return true; + } + + @Override int hash() { return _con.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/src/main/java/com/seaofnodes/simple/node/ExternNode.java index ccbcfc66..978298fc 100644 --- a/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -1,21 +1,38 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** - A constant with external linkage. + A constant with external linkage. */ public class ExternNode extends ConstantNode { public final String _extern; - public ExternNode(Type t, String ex) { super(t); _extern = ex; } + // An exact external function identity has no local FunNode/Return to own. + // Its lifetime and eventual relocation are carried by the extern symbol + // and CodeGen's FIDX-to-extern mapping instead. + public ExternNode(Type t, String ex) { super(t,true); _extern = ex; } + @Override public Tag serialTag() { return Tag.Extern; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed2(types.get(_con )); + baos.packed2( strs.get(_extern)); + } + static Node make( BAOS bais, String[] strs, Type[] types) { + return new ExternNode(types[bais.packed2()], strs[bais.packed2()]); + } @Override public String label() { return "#"+_con+":"+_extern; } @Override public boolean eq(Node n) { return this==n; } + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_extern); + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java new file mode 100644 index 00000000..409ce5d1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.BAOS; +import java.util.HashMap; +import java.util.IdentityHashMap; + +// A field address supplied by the C linker, not a displacement from the base. +// The class base remains available for ideal type/alias analysis. Instruction +// selection replaces it with this symbol's address and a zero displacement. +public class ExternOffsetNode extends ExternNode { + public ExternOffsetNode(String name) { super(TypeInteger.BOT,name); } + @Override public Tag serialTag() { return Tag.ExternOffset; } + @Override public Node copy() { return new ExternOffsetNode(_extern); } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> nodes) { + baos.packed2(strs.get(_extern)); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java index e7333c23..644f1434 100644 --- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java @@ -10,17 +10,32 @@ import com.seaofnodes.simple.type.Type; * A Forward Reference. Its any final constant, including functions. When * the Def finally appears its plugged into the forward reference, which then * peepholes to the Def. + + * FRefs can be external references, to other Simple defined values in ELF + * files, or external FFI/C libs. If external, a file system lookup needs to + * find it in another file, and the name (one time) change to match the found + * location. + * + * Example, a ambiguous name "foo" is looked up externally; it might resolve + * to "A.B.foo" or "libc.foo" or the like; the name is changed to match. */ 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; } + public String _name; + public final Parser.Lexer _loc; // Source location + + public FRefNode( String name, Parser.Lexer loc ) { this(name,loc,FREF_TYPE); } + public FRefNode( String name, Parser.Lexer loc, Type type ) { + super(type); + _name = name; + _loc = loc; + } - @Override public String label() { return "FRef"+_n; } + @Override public String label() { return "FRef"+_name; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("FRef_").p(_n); + return p.p("FRef_").p(_name); } @Override public Node idealize() { @@ -28,8 +43,8 @@ public class FRefNode extends ConstantNode { return nIns()==1 ? null : in(1); } - public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } + public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); } @Override public boolean eq(Node n) { return this==n; } - @Override int hash() { return _n._idx; } + @Override int hash() { return _name.hashCode(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java new file mode 100644 index 00000000..ef894929 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; + +/** + * A field offset. This becomes a constant *after* other optimizations which + * determine field sizes - some fields might be dead, or hold only zeros or + * small values. + */ + +public class FldOffNode extends TypeNode { + public final String _fname; // Field name + public final Parser.Lexer _loc; // + public FldOffNode( Node base, String fname, Parser.Lexer loc ) { super(TypeInteger.BOT,base); _fname = fname; _loc=loc; } + @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); } + + @Override public String label() { + return _fname == " len" ? "sizeof()" : "#"+_fname; + } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()); + } + + @Override public Type compute() { + return Type.BOTTOM; + } + + @Override public Node idealize() { + // When base() gets typed, this xforms into a ConFldOffNode + if( in(0)._type instanceof TypeMemPtr tmp ) + return new ConFldOffNode(tmp._obj,_fname); + return null; + } + + @Override public boolean eq(Node n) { + FldOffNode off = (FldOffNode)n; // Invariant + return _fname.equals(off._fname) && super.eq(n); + } + @Override int hash() { return super.hash() * _fname.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java index 70e1c200..b74f9e6b 100644 --- a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -1,21 +1,21 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.RegMask; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeTuple; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; +import java.util.Arrays; import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; import static com.seaofnodes.simple.codegen.CodeGen.CODE; public class FunNode extends RegionNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - boolean _folding; + public boolean _folding; public boolean folding() { return _folding; } private TypeFunPtr _sig; // Initial signature @@ -23,20 +23,43 @@ public class FunNode extends RegionNode { public String _name; // Debug name - public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; } + public int _approxUIDs; // Approximate function size, used as a inlining heuristic + public CompUnit _compunit; // Defined in source file + public final boolean _extern; // External function; no code, name only + + private FunNode( Parser.Lexer loc, Node[] nodes, TypeFunPtr sig, String name, CompUnit compunit, boolean ext ) { + super(loc,nodes); + _name = name; + _sig = sig; + _extern = ext; + _compunit = compunit; + } + public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); } public FunNode( FunNode fun ) { - super( fun, fun==null ? null : fun._loc ); - if( fun!=null ) { - _sig = fun.sig(); - _name = fun._name; - } else { - _sig = TypeFunPtr.BOT; - _name = ""; - } + super( fun, fun._loc ); + _sig = fun.sig(); + _name = fun._name; + _compunit = fun._compunit; + _extern = fun._extern; + } + @Override public Tag serialTag() { return Tag.Fun; } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) { + assert !_folding; + baos.packed1(nIns()); // Number of linked calls + baos.packed2(types.get(_sig)); // NPE if fails lookup + baos.packed2(_name==null ? 0 : strs.get(_name)); + baos.packed2(_approxUIDs); + } + static Node make( BAOS bais, String[] strs, Type[] types) { + Node[] ins = new Node[bais.packed1()]; + TypeFunPtr sig = (TypeFunPtr)types[bais.packed2()]; + String name = strs[bais.packed2()]; + FunNode fun = new FunNode(null,sig,name,null,ins); + fun._approxUIDs = bais.packed2(); + return fun; } - @Override - public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; } + @Override public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; } // Find the one CFG user from Fun. It's not always the Return, but always // the Return *is* a CFG user of Fun. @@ -48,42 +71,63 @@ public class FunNode extends RegionNode { return null; } - public ParmNode rpc() { - ParmNode rpc = null; + public ParmNode parm(int idx) { + ParmNode pidx = null; for( Node n : _outputs ) - if( n instanceof ParmNode parm && parm._idx==0 ) - { assert rpc==null; rpc=parm; } - return rpc; + if( n instanceof ParmNode parm && parm._idx==idx ) + { assert pidx==null; pidx=parm; } + return pidx; } + public ParmNode rpc() { return parm(0); } // 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; } public ReturnNode ret() { assert _ret!=null; return _ret; } - // Direct calls are known; any other use of an address may have unknown callers. - public boolean hasPtr() { - for( Node use : ret()._outputs ) - if( use instanceof FunPtrNode ) - for( Node user : use._outputs ) - if( !(user instanceof CallNode call) || call.fptr()!=use || - call._inputs.find(use)!=call.nIns()-1 ) return true; - return false; - } - // Signature can improve over time public TypeFunPtr sig() { return _sig; } public void setSig( TypeFunPtr sig ) { - assert sig.isa(_sig); + assert sig.isa(_sig) || resolvesForwardSelf(sig,_sig); if( _sig != sig ) { CODE.add(this); + // Changing the signature can allow more inlining + if( _ret != null ) + for( Node use : ret().outs() ) + if( use instanceof CallEndNode || use instanceof FunPtrNode ) + CODE.add(use); + unlock(); _sig = sig; } } + // A class initializer starts parsing with an open singleton receiver and + // closes that same receiver after discovering its fields. Function + // arguments are contravariant, so ordinary isa does not describe this + // parser-time forward-reference resolution even though every other part + // of the signature sharpens normally. + private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) { + if( !(sig.arg(0) instanceof TypeMemPtr now) || + !(old.arg(0) instanceof TypeMemPtr prior) || + now._obj._name != prior._obj._name || + !prior._obj._open || now._obj._open ) + return false; + return sig.makeFrom(old.arg(0),0).isa(old); + } + + @Override boolean _upgradeType( HashMap<String,Type> TYPES) { + TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES); + if( sig == _sig ) return false; + unlock(); + _sig = sig; + return true; + } + public void setName( String name ) { - if( _name==null ) _name=name; - else _name += "."+name; + if( _name==null ) + _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1 + ? name + : (_compunit._cname+"."+name).intern(); } @Override @@ -92,10 +136,10 @@ public class FunNode extends RegionNode { if( unknownCallers() ) return Type.CONTROL; Type t = Type.XCONTROL; - for (int i = 1; i < nIns(); i++) { - // Since no unknown callers, we are not main and the Start input - // will be a Tuple with XControl, so ignore it. Need to be called - // from someplace other than Start + for( int i = 1; i < nIns(); i++) { + // Since there are no unknown callers, the Start input is only a + // fake hook and will be a Tuple with XControl, so ignore it. Need + // to be called from someplace other than Start. if( !(in(i) instanceof StartNode) ) t = t.meet(in(i)._type); } @@ -105,28 +149,37 @@ public class FunNode extends RegionNode { @Override public Node idealize() { - // Some linked path dies, except main never kills Start + // Some linked path dies, except unknown caller hooks never kill Start. Node progress = deadPath(unknownCallers()); if( progress!=null ) { - if( nIns()==3 && in(2) instanceof CallNode call ) - CODE.add(call.cend()); // If Start and one call, check for inline + if( nIns()==2 && in(1) instanceof CallNode call ) + CODE.add(call.cend()); // If no Start and one call, check for inline return progress; } // Upgrade inferred or user-written return type to actual - if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) { + if( _ret!=null && _ret._type instanceof TypeTuple tt && + tt.ret() != _sig.ret() && tt.ret().isa(_sig.ret()) ) { setSig(_sig.makeFrom(tt.ret())); return this; } + // Attempt to get rid of the unknown caller. + // - Must be past pessimistic Iter, which discovers and links calls + // through class-field loads and other initially imprecise values. + // - FIDX is not in the escape set + // - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start + if( in(1) instanceof StartCUNode start ) { + boolean preOpto = CodeGen.CODE._phase == null || + CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal(); + if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) { + _compunit._stop.delDef(_compunit._stop._inputs.find(ret())); + return removeDeadPath(1); + } else { + addDep(start); + } + } - // 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 @@ -145,22 +198,60 @@ public class FunNode extends RegionNode { } // Bypass Region idom, always assume depth == 1, one more than Start, - // unless folding then just a ID on input#1 + // unless folding @Override public int idepth() { return _folding ? super.idepth() : 1; } + // Bypass Region idom, always assume idom is Start - @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); } + @Override public CFGNode idom(Node dep) { + return _folding ? cfg(1) : null; + } // Always in-progress until we run out of unknown callers public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; } - @Override public boolean inProgress() { return unknownCallers(); } + public boolean isModInit( ) { + // The one top-level <clinit> with no internal dots: + // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not. + return isClz() && _name.indexOf('.')==_name.lastIndexOf('.'); + } + public boolean isClz ( ) { return isClz (_name); } + public boolean isInit( ) { return isInit(_name); } + public boolean isInstance() { return isInstance(_name); } + public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); } + public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); } + public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); } + + // Allocation factories and instance initializers carry extra memory boilerplate. + public int inlineLimit() { + return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100; + } - // Add a new function exit point. - public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); } + // Function is public (callable from Start directly). + public boolean isPublic( ) { + // Never true for anonymous functions + if( _name == null ) return false; + // Only public for <clinit> and <init>, all others + // are found via field loads. The <init> Start hook also supplies its + // conservative pre-Opto construction inputs; StopCU separately keeps + // its private-memory return from escaping as a public result. + if( !isInit() ) return false; + // Private class, has to be found via program text, not linker + if( _name.charAt(0)=='_' ) return false; + + // No instances of "...clz1._clz2..." where the class name path + // includes a private class. + if( _name.indexOf("._") != -1 ) return false; + + // Public class <cl/init> + return true; + } - // Build the function body - public BitSet body() { + @Override public boolean inProgress() { + return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal(); + } + // Build the function body set + public BitSet body() { // Reverse up (stop to start) CFG only, collect bitmap. BitSet cfgs = new BitSet(); cfgs.set(_nid); @@ -170,7 +261,13 @@ public class FunNode extends RegionNode { // 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()); + BitSet visit = new BitSet(); + int old; + do { + old = body.cardinality(); + visit.clear(); + walkDown(this, cfgs, body, visit); + } while( old != body.cardinality() ); return body; } @@ -184,23 +281,147 @@ public class FunNode extends RegionNode { } private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) { + if( n==null ) return false; if( visit.get(n._nid) ) return body.get(n._nid); visit.set(n._nid); - - if( n instanceof CFGNode && !cfgs.get(n._nid) ) + // Visit self as CFG outside the function + if( n instanceof CFGNode && !cfgs.get(n._nid) && unfolded( n ) ) return false; - if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) + // Pretend the NewNode is a CFG, so its projections stay with it in loops + if( n instanceof NewNode nnn ) cfgs.set(n._nid); + // Visit n.cfg() outside of function + if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) && unfolded( n.in( 0 ) ) ) return false; + // Phis inside the function must have their body flag set BEFORE + // recursion walks around a loop and finds them again. + if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) ) + body.set(phi._nid); // Find data cycles boolean in = n.in(0)!=null || n instanceof CFGNode; - for( Node use : n._outputs ) + for( Node use : n._outputs ) { + // Will hit a backedge, so 'n' MUST be in the function, and must be + // set before the cyclic walk finds 'n' again. + if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n ) + body.set(n._nid); // Set before walk in |= walkDown(use,cfgs,body,visit); + } if( in ) body.set(n._nid); return in; } - // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able) + // A CFG is folding, and so is basically Data + private static boolean unfolded( Node cfg) { + if( cfg instanceof CallNode call && call.cend().folding() ) return false; + if( cfg instanceof CallEndNode cend && cend.folding() ) return false; + if( cfg instanceof FunNode fun && fun._folding ) return false; + if( cfg instanceof ReturnNode ret && ret._fun._folding ) return false; + return true; + } + + // Clone function body. Give function a new FIDX. + FunNode copyBody() { + // Build the function body BitSet + BitSet body = body(); + assert body.cardinality() < inlineLimit(); + // Walk the body, cloning + IdentityHashMap<Node,Node> map = new IdentityHashMap<>(); + BitSet visit = CodeGen.CODE.visit(); + bodyCopy( visit, body, map, this ); + visit.clear(); + bodyEdge( visit, body, map, this ); + visit.clear(); + assert map.size()==body.cardinality(); + + // New function/return cross-link each other + FunNode fun2 = (FunNode )map.get(this); + ReturnNode ret2 = (ReturnNode)map.get(_ret); + fun2._ret = ret2; + ret2._fun = fun2; + BitSet body2 = new BitSet(); + for( Node n : map.values() ) + body2.set(n._nid); + + // Remove non-body callers. Body-local call edges were cloned along + // with their CallEnds and must stay linked so their cloned types remain + // monotonic during the post-Opto Iter. + for( int i=1; i<fun2.nIns(); i++ ) + if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) ) + fun2.removeDeadPath(i--); + + // Linking adds graph edges and worklist entries; identity-map order + // would make inlining depend on the host JVM's object hashes. + Node[] olds = map.keySet().toArray(new Node[0]); + Arrays.sort(olds, (x,y) -> Integer.compare(x._nid,y._nid)); + for( Node old : olds ) + if( old instanceof CallNode oldCall ) + relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map); + + // Flip to a new FIDX to avoid confusion with the old one. + // This is a non-monotonic (sideways) type move, only applicable + // because fun2 is new. The fidx is also a purely local fiction, + // since this body will inline and the fidx goes dead. + fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline()); + return fun2; + } + + private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) { + CallEndNode oldCend = oldCall.cend(); + if( oldCend==null || oldCend._folding ) + return; + for( int i=1; i<oldCend.nIns(); i++ ) { + ReturnNode oldRet = (ReturnNode)oldCend.in(i); + FunNode oldFun = oldRet.fun(); + FunNode fun = (FunNode)map.get(oldFun); + if( fun==null ) fun = oldFun; + if( !call.linked(fun) ) + call.link(fun); + } + CallEndNode cend = call.cend(); + if( cend.nIns() > 1 ) + cend._type = cend.compute(); + } + + // Clone small function + private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) { + if( n==null ) return; + if( !body.get(n._nid) ) return; // Not part of the body + if( visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + Node m = n.copy(); + CodeGen.CODE.add(m); + map.put(n,m); + m._type = n._type; + for( Node x : n._outputs ) + bodyCopy(visit,body,map,x); + } + private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) { + if( n==null ) return; + if( !body.get(n._nid) ) return; // Not part of the body + if( visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + Node m = map.get(n); + for( Node e : n._inputs ) { + Node x = e; + if( e != null ) { + if( n instanceof CallEndNode cend && e != cend.call() && + (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) ) + continue; + x = map.get(e); + if( x == null ) { + if( n instanceof CallEndNode cend && e != cend.call() ) + continue; + x = e; + } + } + m.addDef(x); + } + for( Node x : n._outputs ) + bodyEdge(visit,body,map,x); + } + + + // no 2 FunNodes are ever GVN'able @Override public boolean eq( Node n ) { - return _sig == ((FunNode)n)._sig; + return this==n; } @Override public int hash() { @@ -254,4 +475,7 @@ public class FunNode extends RegionNode { return slotRotate*8; } + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_name); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java index 43242c32..82053fd8 100644 --- a/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java @@ -1,12 +1,68 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.print.ExprPrinter; -// A function address keeps its body alive, even without a linked Call. -public class FunPtrNode extends ConstantNode { - public FunPtrNode(TypeFunPtr tfp, ReturnNode ret) { - super(tfp); - addDef(ret); +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; + +// Constant function pointer whose type follows the callee signature. +public class FunPtrNode extends TypeNode { + public FunPtrNode(TypeFunPtr tfp, StartNode start, ReturnNode ret) { + super(tfp,start,ret); + } + public FunPtrNode(FunPtrNode fptr) { super(fptr); } + @Override public Tag serialTag() { return Tag.FunPtr; } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { + baos.packed2(types.get(_con)); + } + static Node make(BAOS bais, Type[] types) { + return new FunPtrNode((TypeFunPtr)types[bais.packed2()],null,null); } public ReturnNode ret() { return (ReturnNode)in(1); } + public FunNode fun() { return ret().fun(); } + + @Override public String label() { return "#"+_con; } + @Override public Node copy() { + FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null); + fptr._type = _type; + return fptr; + } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + FunNode fun = CodeGen.CODE._link(((TypeFunPtr)_con).fidx()); + return fun!=null && fun._name!=null + ? p.p("{ ").p(fun._name).p("}") + : p.p(_con); + } + + @Override public boolean isConst() { return true; } + @Override public Type compute() { + ReturnNode ret = ret(); + if( ret == null ) return _con; + if( !(ret._type instanceof TypeTuple tt) ) return ret._type.oob(); + TypeFunPtr tfp = ret.fun().sig(); + // Both the sig and the return value must be true, so JOIN both + Type tret = tfp._ret.join(tt.ret()); + return tfp.makeFrom(tret); + } + @Override public Node idealize() { + ReturnNode ret = ret(); + if( ret==null ) return null; + // Function died (never executed), but fcn ptr is alive. + // Can be checked for null, or for unequals another FunPtr. + FunNode fun = addDep(ret.fun()); + TypeFunPtr sig = fun.sig(); + if( sig != _con ) + { liftType(sig); return this; } + // TODO: If the function body dies but this value remains live only as + // a null/equality sentinel, preserve the identity without the Return. + if( fun.isDead() ) + { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; } + return null; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/src/main/java/com/seaofnodes/simple/node/GuardMach.java new file mode 100644 index 00000000..202b1ab8 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/GuardMach.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.SB; + +public class GuardMach extends GuardNode implements MachNode { + public GuardMach(GuardNode guard) { super(guard); } + @Override public String op() { return label(); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress() { return in(1)._type==Type.BOTTOM ? 0 : 1; } + @Override public void encoding(Encoding enc) { } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/src/main/java/com/seaofnodes/simple/node/GuardNode.java new file mode 100644 index 00000000..0ce504d9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/GuardNode.java @@ -0,0 +1,68 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeScalar; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; + +import java.util.HashMap; +import java.util.IdentityHashMap; + +// A branch-proven refinement to zero/null or non-zero/non-null. The value +// family is deliberately taken from the input when it becomes known. +public class GuardNode extends Node { + public final boolean _nonZero; + + public GuardNode(boolean nonZero, Node ctrl, Node val) { + super(ctrl,val); + _nonZero = nonZero; + } + + public GuardNode(GuardNode guard) { + super(guard); + _nonZero = guard._nonZero; + } + + @Override public Tag serialTag() { return Tag.Guard; } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { + baos.write(_nonZero ? 1 : 0); + } + static Node make(BAOS bais) { return new GuardNode(bais.read()!=0,null,null); } + + @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()).n(in(1)); + } + + @Override public Type compute() { + // Guard is a pinned, path-local fact. If its control path is not + // executable, the fact is not executable either, regardless of how the + // value input sharpens later. + Type c = in(0)._type; + if( c != Type.CONTROL && c != Type.BOTTOM ) + return Type.TOP; + Type t = in(1)._type; + if( t==Type.BOTTOM || t==Type.TOP ) return t; + // The scalar envelope does not identify a value family. Preserve it + // until SCCP learns whether this is an int, float, pointer, or fptr; + // choosing generic ptr here can be weaker than a later concrete type. + if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t; + Type t2 = _nonZero ? t.nonZero() : t.makeZero(); + return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero + } + + @Override public Node idealize() { + if( in(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM ) + return null; + if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null; + if( _type.isHighOrConst() && in(1)._type.isa(_type) ) + return in(1); + return null; + } + + @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; } + @Override int hash() { return _nonZero ? 1 : 0; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index c96993e5..c83c81d0 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -13,9 +13,7 @@ public class IfNode extends CFGNode implements MultiNode { CodeGen.CODE.add(this); // Because idoms are complex, just add it } public IfNode(IfNode iff) { super(iff); } - - @Override - public String label() { return "If"; } + @Override public Tag serialTag() { return Tag.If; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -46,7 +44,7 @@ public class IfNode extends CFGNode implements MultiNode { // If constant is 0 then false branch is reachable // Else true branch is reachable if( t.isConstant() ) - return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? 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; @@ -59,15 +57,14 @@ public class IfNode extends CFGNode implements MultiNode { // Hunt up the immediate dominator tree. If we find an identical if // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) - for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) { - if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) { + for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) + if( addDep(dom) instanceof IfNode iff && + prior instanceof CProjNode prj && + (addDep(iff.pred())==pred() || + pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) { setDef(1,con( prj._idx==0 ? 1 : 0 )); return this; } - if( dom instanceof RegionNode r ) - for( Node c : r._inputs ) - if( c!= null ) addDep(c); // If region loses input, idom changes - } 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 b3204cff..a557f97c 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,9 +2,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; @@ -22,9 +24,14 @@ 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(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, glb, mem, ptr, off); + public LoadNode(Parser.Lexer loc, String name, int alias, Node ctrl, Node mem, Node ptr, Node off) { + super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off); } + LoadNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { + super(bais,strs,types,fileAliases,aliases,true); + _con = Type.BOTTOM; // Serialized slot retained while MemOps share TypeNode + } + @Override public Tag serialTag() { return Tag.Load; } @Override boolean canDrop(MemOpNode other, Node dep) { return super.canDrop(other,dep) && !clobbered(dep); @@ -40,7 +47,7 @@ public class LoadNode extends MemOpNode { dep.addDepForwards(use); if( use instanceof StoreNode || use instanceof PhiNode || use instanceof MemMergeNode || - use instanceof NewNode || use instanceof CallNode ) return true; + use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ) return true; } return false; } @@ -52,58 +59,102 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - Type tmem = mem()._type; - if( !(tmem instanceof TypeMem mem) ) - return tmem; // No memory yet? Assume TOP/BOT - assert !_declaredType.isFRef(); - // No lifting if ptr might null-check - Type tptr = ptr()._type; - if( err() != null ) - return _declaredType; - if( !(tptr instanceof TypeMemPtr tmp) ) - return tptr; // No pointer yet? Assume TOP/BOT + Type mem0 = mem()._type; + Type ptr0 = ptr()._type; + // Validate argument types + if( ptr0.isHigh() ) + return TypeScalar.TOP; + if( !(mem0 instanceof TypeMem mem) ) + return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; + if( mem._t == Type.TOP ) + return TypeScalar.TOP; + if( ptr0 == Type.NIL ) + return Type.BOTTOM; // Load from nil is error + if( ptr0 == Type.BOTTOM ) + return Type.BOTTOM; // Error in is error out + if( !(ptr0 instanceof TypeMemPtr tmp) ) + return scalar(mem._t); + if( tmp.nullable() ) + return Type.BOTTOM; // Load from nil-able is error + // Load field from object - Field f = tmp._obj.field(_name); + Field pfld = tmp._obj.field(_name); // No field? Open objects might yet get the field when falling; // closed objects with missing field are an error. - if( f == null ) - return tmp.isHigh() ? Type.TOP : _declaredType; - Type t = f._t; + if( pfld == null ) + return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; + + Type t = pfld._t; + boolean readonly = t instanceof TypeMemPtr view && view._ro; // Load member of constant array if( t instanceof TypeConAry ary ) t = ary.elem(); // TODO: if offset is known, can peek the constant - // Lift from declared type and memory input - t = t.join(MemMergeNode.contents(mem(),_alias,this)); - if( ptr()._type.isFinal() || _declaredType.isFinal() ) - t = t.makeRO(); // Deep final applied - // Pinch between declared type - t = t.join(_declaredType); - return t; + if( mem._alias==_alias ) + t = t.join(mem._t); + + // A deeply read-only base produces a deeply read-only value. The + // generic declared field type must not cast this information away. + if( ptr0.isFinal() || readonly ) + t = t.makeRO(); + return scalar(t); + } + + // Loads produce ordinary scalar values. Field/memory joins can still + // expose the enclosing global lattice bounds through an uninitialized + // Type.TOP/BOTTOM field; keep those results inside the scalar envelope. + private static Type scalar(Type t) { + return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t; } @Override public Node idealize() { - if( mem() instanceof CastNode cast ) { - setDef(1,cast.in(1)); + Node mem = mem(); + Node ptr = ptr(); + + // Null checks are handled by types. Array elements retain control for + // range checks, but reading an array's length has no index to check. + if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && + (!tmp._obj.isAry() || _name.equals("#")) ) { + setDef(0,null); return this; } - if( mem() instanceof MemMergeNode merge ) { - setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + + // Forward-ref loads eventually sharpen to a declared type + Field fld; + if( ptr._type instanceof TypeMemPtr tmp && + (fld=tmp._obj.field(_name)) != null && + _alias != fld._alias) { + assert _alias==1 || _alias == fld._alias; + assert !tmp._obj._open && !tmp._obj._fref; + unlock(); // Alias participates in GVN semantics + _alias = fld._alias; return this; } - Node ptr = ptr(); - Node mem = mem(); + // Must sharpen alias first + if( _alias == 1 ) + return null; - Node win = find(mem,ptr,null,null); - if( win!=null ) return folded(win); + if( mem instanceof MemMergeNode merge ) { + setDef(1,merge.alias(_alias)); + // In support of uplifting control to a prior dominating load, if + // we move our memory, recheck other loads to see if they can hoist. + for( Node use : in(1).outs() ) + if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() ) + CodeGen.CODE.add(ld); + + return this; + } // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) // Find a prior load, has same mem,ptr,off but higher ctrl if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() && - cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier + cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() && // Higher control means load is legal earlier + addDep(ld)._type.isa(_type) ) // and not rolling backwards return ld; + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // Push a Load up through a Phi, as long as it collapses on at least @@ -127,13 +178,17 @@ public class LoadNode extends MemOpNode { !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; if( find(memphi.in(2),ptr,stop,new BitSet())!=null || (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { - PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); - phi.setType(_type); // Backedge searches may return this value Phi. + // Integer loop Phis widen to i64. Keep the load's declared + // width at the result, as for ordinary typed loop variables. + if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null; + PhiNode phi = new PhiNode(_name,memphi.region()).keep(); + phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type); for( int i=1; i<memphi.nIns(); i++ ) { Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; phi.addDef(load(memphi.in(i),p,stop,phi)); } - return CodeGen.CODE.add(phi.unkeep()); + CodeGen.CODE.add(phi.unkeep()); + return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi; } } @@ -142,8 +197,7 @@ public class LoadNode extends MemOpNode { // Load a flavored zero from a New private Node zero(NewNode nnn) { - TypeStruct ts = nnn._ptr._obj; - Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero(); + Type zero = declaredType().makeZero(); assert zero.isa(_type); // Catch an uninitialized non-null field return castRO(new ConstantNode(zero).peephole()); } @@ -153,25 +207,39 @@ public class LoadNode extends MemOpNode { // Register dependencies so a later pointer/offset rewrite retries the query. private Node find(Node mem, Node ptr, Node stop, BitSet visit) { if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + Field fld; + boolean external = ptr._type instanceof TypeMemPtr tmp && + (fld=tmp._obj.field(_name))!=null && fld._extern; while( mem!=null ) { addDep(mem); if( mem==stop ) return mem; switch( mem ) { case MemMergeNode merge: mem=merge.alias(_alias); break; - case CastNode cast: mem=cast.in(1); break; case StoreNode st: + // Alias 1 is still unresolved, not a proven different field. + if( st._alias==1 ) return null; addDep(st.ptr()); - if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; - if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && + if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) { + // A full-width load cannot narrow the forwarded value. + if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 && + !addDep(st.val())._type.isa(_type) ) return null; + return st; + } + if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) && !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; mem=st.mem(); break; case ProjNode proj: - if( !(proj.in(0) instanceof NewNode nnn) ) return null; - if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; - if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; - mem=nnn.mem(); // Distinct allocations cannot overlap. - break; + // New's memory is private; public memory is joined at Escape. + if( external || !(proj.in(0) instanceof NewNode nnn) ) return null; + if( declaredType()==Type.BOTTOM ) return null; + return nnn; + case EscapeNode esc: + if( external ) return null; + addDep(esc.self()); + if( esc.self()==ptr ) { mem=esc.priv(); break; } + if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; } + return null; case MemPhiNode phi: addDep(phi.region()); // Only the original loop closes a successful cycle. Another @@ -200,14 +268,14 @@ public class LoadNode extends MemOpNode { if( win!=null ) { if( win==stop ) return value; if( win instanceof MemPhiNode mp ) { - PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); + PhiNode phi=new PhiNode(_name,mp.region()).keep(); for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); - return phi.unkeep().peephole(); + return CodeGen.CODE.add(phi.unkeep().peephole()); } - return folded(win).peephole(); + Node fold=folded(win); + if( fold!=null ) return fold.peephole(); } - LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); - ld.setDef(0,in(0)); + LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off()); return ld.peephole(); } @@ -239,7 +307,7 @@ public class LoadNode extends MemOpNode { // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { - if( (ptr()._type.isFinal() || _declaredType.isFinal()) && !rez._type.isFinal() ) + if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() ) return new ReadOnlyNode(rez).peephole(); return rez; } @@ -247,16 +315,18 @@ public class LoadNode extends MemOpNode { // When a load bypasses a store, the store might truncate bits - and the // load will need to zero/sign-extend. private Node extend(Node val) { - if( !(_declaredType instanceof TypeInteger ti) ) return val; + if( !(declaredType() instanceof TypeInteger ti) ) return val; if( ti._min==0 ) // Unsigned return new AndNode(null,val,con(ti._max)); // Signed extension int shift = Long.numberOfLeadingZeros(ti._max)-1; Node shf = con(shift); - if( shf._type==TypeInteger.ZERO ) + if( shift==0 ) { + if( !val._type.isa(_type) ) + { addDep(val); return null; } return val; + } Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } - } diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 1d493db7..df686bcf 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -3,23 +3,22 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.HashSet; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; public class LoopNode extends RegionNode { - public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); } + public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); } public LoopNode( LoopNode loop ) { super(loop); } + @Override public Tag serialTag() { return Tag.Loop; } + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { } public CFGNode entry() { return cfg(1); } public CFGNode back () { return cfg(2); } @Override - public String label() { return "Loop"; } - - @Override public Type compute() { if( inProgress() ) return Type.CONTROL; return entry()._type; @@ -46,7 +45,7 @@ public class LoopNode extends RegionNode { x = x.idom(); } // Found a no-exit loop. Insert an exit - NeverNode iff = (NeverNode)new NeverNode(back()).peephole(); // Ideal never-branch + NeverNode iff = (NeverNode)new NeverNode(1.2f,back()).peephole(); // Ideal never-branch CProjNode t = new CProjNode(iff,0,"True" ).init(); CProjNode f = new CProjNode(iff,1,"False").init(); setDef(2,t); // True continues loop, False (never) exits loop @@ -56,12 +55,13 @@ public class LoopNode extends RegionNode { // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. - Node top = new ConstantNode(Type.TOP).peephole(); - MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); - memout.setDef(0,f); + Node top = ConstantNode.make(Type.TOP).peephole(); + Node memout = new MemMergeNode(); + memout.addDef(f); // placeholder for control + memout.addDef(top); for( Node u : _outputs ) - if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); - else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) + memout.addDef(phi); memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); @@ -73,7 +73,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = fun._ltree; - mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); + mem = new BulkMemPhiNode(r,mem).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit @@ -88,8 +88,12 @@ public class LoopNode extends RegionNode { ret.setDef(0,ctrl.unkeep()); ret.setDef(1,mem .unkeep()); ret.setDef(2,expr.unkeep()); - ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr] - ret.setType(ret.compute()); + // Force sane types to exit; might locally break monotonicity + ret.init(); + ret.out(0).init(); + stop.init(); + CodeGen.CODE.add(CodeGen.CODE._start); + CodeGen.CODE.add(mem); return fun; } diff --git a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java index 9c0755b9..b25fcaaf 100644 --- a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java @@ -3,7 +3,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; import java.io.ByteArrayOutputStream; // Generic machine-specific class, has a few Node implementations that have to @@ -14,8 +13,8 @@ public abstract class MachConcreteNode extends Node implements MachNode { public MachConcreteNode(Node[]nodes) { super(nodes); } @Override public String label() { return op(); } - @Override public Type compute () { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } + @Override public Type compute () { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } + @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.open().p(op()).p(","); diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java index c0b3af66..025b5dc4 100644 --- a/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MachNode.java @@ -55,6 +55,6 @@ public interface MachNode { // General form: "dst=src+src". default void asm(CodeGen code, SB sb) { throw Utils.TODO(); } - // Human readable opcode; e.g. "ld4" or "call". + // Human-readable opcode; e.g. "ld4" or "call". default String op() { throw Utils.TODO(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 7a329117..dfe8794d 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -4,99 +4,154 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; + import java.util.*; -/** A default slice and explicit alias overrides. A null default means partial - * memory: absent aliases are not covered, rather than having unknown contents. +/** + * 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 { - public MemMergeNode(MemMergeNode mem) { super(mem); } - public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } - public MemMergeNode(Node bulk, int alias, Node precise) { - this(bulk); - alias(alias,precise); - } - public Node alias(int alias) { - Node n = alias < nIns() ? in(alias) : null; - assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); - n = n==null ? in(1) : n; - assert n!=null : "Alias not covered: "+alias; - return n; - } + public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; } + public MemMergeNode(MemMergeNode mem) { super(mem); } + @Override public Tag serialTag() { return Tag.MemMerge; } + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } + static Node make( BAOS bais ) { Node mem = new MemMergeNode(); mem.setDefX(bais.packed1()-1,null); return mem; } - public void alias(int alias, Node mem) { - assert alias > 1; - while( nIns()<=alias ) addDef(null); - setDef(alias,mem); - } + @Override public String label() { return "ALLMEM"; } + @Override public boolean isMem() { return true; } - // Scalar contents of one alias. New zeroes the allocated fields; - // join it with the incoming contents for all previously allocated objects. - // Phis use their cached types, so this query does not recurse around loops. - static Type contents(Node mem, int alias, Node dep) { - // A loop memory Phi can also be a user of the querying Store. - // Its type changing still needs to revisit this query. - dep.addDepForwards(mem); - if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep); - if( mem instanceof MemMergeNode merge ) - return contents(merge.alias(alias),alias,dep); - if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { - assert proj._idx==1 && nnn.field(alias)!=null; - dep.addDep(nnn); - if( dep.addDep(nnn.in(0))._type.isHigh() ) return Type.TOP; - return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero()); + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("MEM[ "); + for( int j=2; j<nIns(); j++ ) { + p.p(j); + p.p(":"); + Node n = in(j); + while( n instanceof ScopeNode loop ) { + p.p("Lazy_"); + n = loop.mem(); + } + if( n==null ) p.p("___ "); + else p.n(n).p(" "); } - // Function parameters and call results may contain arbitrary heap values. - // Their bulk memory types do not imply empty or zero-filled storage. - if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; - if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; - return Type.BOTTOM; + return p.unchar(' ').p("]"); } - @Override public String label() { return "MemMerge"; } - @Override public boolean isMem() { return true; } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("MEM["); - for( int i=1; i<nIns(); i++ ) - if( in(i)!=null ) { - p.p(i==1 ? "default:" : i+":"); - p.n(in(i)).p(" "); + + @Override public Type compute() { + Type tmem = in(1)._type; + if( !(tmem instanceof TypeMem defmem) ) + // A MemMerge is structurally memory even while its default input + // is a weak, not-yet-specialized Phi. + return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT; + // Is this a single private instance memory? + if( defmem._one ) { + if( !(defmem._t instanceof TypeStruct ts) ) + return defmem; + // Perfect singleton memory, so all updates are parallel and + // independent and stack. + for( int i=2; i<nIns(); i++ ) { + Node in = in(i); + if( in !=null && in._type.isHigh() ) + return TypeMem.TOP; + if( in instanceof StoreNode st ) { + Type val = ((TypeMem)st._type)._t; + Field old = ts.field(st._name); + // Lazy add an expected field for open structs. + if( old==null ) { assert ts._open; + old = Field.make(st._name,Type.BOTTOM,st._alias,st._init); + ts = ts.add(old); + } + Field fld = old.makeFrom(val); + // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases + ts = ts.replace(fld); + } } - return p.p("]"); - } + return TypeMem.makePrivate(ts); + } - @Override public Type compute() { - for( Node n : _inputs ) - if( n != null && !n._type.isHigh() ) - return TypeMem.BOT; - return TypeMem.TOP; + // Normal public memory; meet across escaped inputs + TypeMem mem = defmem; + for( int i=2; i<nIns(); i++ ) { + // Has this alias escaped? + if( XInt.bit(mem._escAs,i) && + in(i) != null && !in(i)._type.isHigh() ) { + mem = (TypeMem)mem.meet(in(i)._type); + } + } + return mem; } @Override public Node idealize() { + assert nIns() != 0 && in(1)!=null; // Always have a default. if( nIns()==0 ) return null; + + // Fold defaults into the default boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) { setDef(i,cast.in(1)); progress=true; } - if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,CodeGen.CODE.add(mem.alias(i))); - progress=true; - } - if( in(i)!=null ) allDefault=false; + if( in(1) == in(i) ) { setDef(i,null); progress=true; } + else { allDefault=false; } } - if( allDefault && in(1)!=null ) return in(1); + // If not merging any memory (all memory is just the default) + if( allDefault ) + return in(1); // Become default memory + + // Collapse stacked merged-mem if( in(1) instanceof MemMergeNode mem ) { - for( int i=2; i<mem.nIns(); i++ ) - if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) - alias(i,mem.in(i)); + // A dead memory cycle can lose its last use when we bypass mem. + // Keep the peephole's result alive until the worklist sees it. + keep(); + // Goal is to swap my default mem with mem's default mem + for( int i=2; i<mem.nIns(); i++ ) { + if( mem.in(i) != null ) { + // deeper default mem has a non-default + if( i>=nIns() || in(i)==null ) + setDefX(i,mem.in(i)); + } + } CodeGen.CODE.add(mem.in(1)); setDef(1,mem.in(1)); - return this; + return unkeep(); } - assert BulkMemPhiNode.checkMerge(this); + return progress ? this : null; } + + + public Node in( Var v ) { return in(v._idx); } + + public Node alias( int alias ) { + assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) && + (alias >= nIns() || in(alias)==null)); + return alias < nIns() && in(alias)!=null ? in(alias) : in(1); + } + + public Node alias( int alias, Node st ) { return setDefX(alias,st); } + + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.addAll(phi._outputs); + phi.moveDepsToWorklist(); + if( in != phi ) { + if( !phi.iskeep() ) // Keeping phi around for parser elsewhere + phi.subsume(in); + setDef(i,in); // Set the update back into Scope + } + } + } + } + + @Override public boolean eq( Node n ) { + return this==n; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index e7b2c792..365157d2 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -3,12 +3,16 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.GlobalBits; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; import java.util.BitSet; import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Convenience common base for Load and Store. @@ -19,93 +23,102 @@ import java.util.ArrayList; * 3 - Offset, integer types * 4 - Value, for Stores only */ -public abstract class MemOpNode extends Node { +public abstract class MemOpNode extends TypeNode { // The equivalence alias class - public final int _alias; + public int _alias; // True if load-like, false if store-like. + // Used on CPU-specific combined memory+arithmetic ops. // // Stores produce memory (maybe as part of a tuple with other things), // loads do not. // - // Loads might pick up anti-dependences on prior Stores, and never cause an + // Loads might pick up anti-dependencies on prior Stores, and never cause an // anti-dependence themselves. // // Stores must maximally sink to the least dominator use. Loads can be // opportunistically hoisted. public final boolean _isLoad; - // Declared type; not final because it might be a forward-reference - // which will be lazily improved when the reference is declared. - public Type _declaredType; - - // A debug name, no semantic meaning + // Field name. public final String _name; // Source location for late reported errors public final Parser.Lexer _loc; - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) { - super(null, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off) { + super(decl, ctrl, mem, ptr, off); _name = name; _alias = alias; - _declaredType = glb; _loc = loc; _isLoad = isLoad; } - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) { - this(loc,name, alias, isLoad, glb, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value) { + this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off); addDef(value); } - public MemOpNode( Node mach, MemOpNode mop ) { - super(mach); - _name = mop==null ? null : mop._name; - _alias = mop==null ? 0 : mop._alias; - _loc = mop==null ? null : mop._loc; - _isLoad= mop==null ? true : mop._isLoad; - _declaredType = mop==null ? Type.BOTTOM : mop._declaredType; - if( mop==null ) - throw Utils.TODO("Load or not"); + public MemOpNode( Node ideal, MemOpNode mop ) { + super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con); + _name = mop._name; + _alias = mop._alias; + _loc = mop._loc; + _isLoad= mop._isLoad; } - // Used by M2 when translating graph to Simple - public MemOpNode( boolean isLoad ) { - super((Node)null); - _name = null; - _alias = 0; - _loc = null; - _isLoad = isLoad; - _declaredType = Type.BOTTOM; + MemOpNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases, boolean isLoad ) { + this(null, + strs[bais.packed2()], + mapAlias(bais,fileAliases,aliases),isLoad, + types[bais.packed2()],null,null,null,null); } - static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } - String mlabel() { return mlabel(_name); } + private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) { + int alias = bais.packed2(); + return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias); + } - @Override public boolean isMem() { return !_isLoad; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed2(_name==null ? 0 : strs.get(_name)); + baos.packed2(_alias); + baos.packed2(types.get(_con)); // NPE if fails lookup + assert _isLoad == this instanceof LoadNode; // No machine ops + } + + static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); } + String mlabel() { return mlabel(_name); } public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } + // Current declared field type. This is deliberately a live lookup: + // cyclic/forward types can sharpen by replacing the pointer's struct. + public Type declaredType() { + if( !(ptr()._type instanceof TypeMemPtr tmp) ) + return Type.BOTTOM; + Field fld = tmp._obj.field(_name); + return fld == null || fld._t == null ? Type.BOTTOM : fld._t; + } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); } protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); } - public int log_size() { return _declaredType.log_size(); } + // Extra conditions for factoring matching memory operations through a Phi. // The caller has already checked the opcode, input shape and control. boolean canDrop(MemOpNode other, Node dep) { dep.addDep(ptr()); - return _alias==other._alias && _declaredType==other._declaredType && err()==null; + return _alias>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null; } @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" + return _alias==mem._alias && super.eq(mem); } @Override - int hash() { return _alias; } + int hash() { return _alias ^ super.hash(); } @Override public Parser.ParseException err() { @@ -114,12 +127,20 @@ public abstract class MemOpNode extends Node { 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.notNull() ) - return null; - return Parser.error( "Might be null accessing '" + _name + "'",_loc); + if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) ) + return Parser.error( "Might be null accessing '" + _name + "'",_loc); + if( tmp._obj.field(_name)==null && + CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() ) + return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); + // Sane field + if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() ) + return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); + return null; + } + // Memory writers which must follow this read. A packaging node is not a - // write, but can hide New's input, so follow this alias through aggregates. + // write; follow this alias through aggregates to its writers. public ArrayList<Node> antiDeps() { ArrayList<Node> deps = new ArrayList<>(); antiDeps(mem(),deps,new BitSet()); @@ -136,7 +157,7 @@ public abstract class MemOpNode extends Node { antiDeps(merge,deps,visit); } else if( use instanceof CallNode || (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || - (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof EscapeNode esc && esc.fld()._alias==_alias) || (use instanceof MemPhiNode phi && phi._alias==_alias) || (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { if( !deps.contains(use) ) deps.add(use); diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java index 787333d9..cdfaaf97 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -1,39 +1,64 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.codegen.GlobalBits; +import com.seaofnodes.simple.util.BAOS; -/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ +import java.util.HashMap; +import java.util.IdentityHashMap; + +/** A Phi for one precise memory alias. */ public class MemPhiNode extends PhiNode { + public final int _alias; public MemPhiNode(String label, int alias, Node... inputs) { - super(label,TypeMem.make(alias,Type.BOTTOM),inputs); + super(label, inputs); assert alias > 1; _alias = alias; } - public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + public MemPhiNode(MemPhiNode phi) { + super(phi,phi._label); + _alias = phi._alias; + } + + @Override public Tag serialTag() { return Tag.MemPhi; } + + @Override + public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, + IdentityHashMap<Node,Integer> anodes) { + super.packed(baos,strs,types,anodes); + baos.packed2(_alias); + } - @Override public String label() { return "MemPhi_"+_alias; } + static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) { + Node[] ins = new Node[bais.packed1()]; + String label = strs[bais.packed2()]; + int alias = bais.packed2(); + if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias); + return new MemPhiNode(label,alias,ins); + } - @Override public Type compute() { + @Override + public Type compute() { + Type t = super.compute(); assert BulkMemPhiNode.checkMem(this,_alias); - if( !(region() instanceof RegionNode r) ) - return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type; - if( r.inProgress() ) return r instanceof LoopNode ? _minType : TypeMem.BOT; - Type t = Type.TOP; - for( int i=1; i<nIns(); i++ ) - if( addDep(r.in(i))._type!=Type.XCONTROL && r.in(i)._type!=Type.TOP ) - t = t.meet(MemMergeNode.contents(in(i),_alias,this)); - return TypeMem.make(_alias,t); + if( !(t instanceof TypeMem tmem) ) return t; + assert tmem._alias==1 || tmem._alias==_alias; + // This Phi structurally knows its alias, but not whether the field is + // final. Do not infer slice finality from evolving input types. + return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs); } - @Override public Node idealize() { + @Override + public Node idealize() { + // A precise memory Phi can always bypass a MemMerge and select its + // matching slice. for( int i=1; i<nIns(); i++ ) if( in(i) instanceof MemMergeNode mem ) { - setDef(i,CodeGen.CODE.add(mem.alias(_alias))); + setDef(i,mem.alias(_alias)); return this; } if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() && @@ -44,6 +69,9 @@ public class MemPhiNode extends PhiNode { return super.idealize(); } - @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override public boolean eq(Node n) { + return _alias==((MemPhiNode)n)._alias && super.eq(n); + } + @Override int hash() { return _alias; } } diff --git a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java deleted file mode 100644 index 74439b8c..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.seaofnodes.simple.node; - - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - - -public class MinusFNode extends Node { - public MinusFNode(Node in) { super(null, in); } - - @Override public String label() { return "MinusF"; } - - @Override protected String format() { return "(-%1)"; } - - @Override - public Type compute() { - if( in(1)._type.isHigh() ) return TypeFloat.TOP; - if( in(1)._type instanceof TypeFloat i0) - return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0; - return TypeFloat.F64; - } - - @Override - public Node idealize() { - // -(-x) is x - if( in(1) instanceof MinusFNode minus ) - return minus.in(1); - - return null; - } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 94c69785..e84eb611 100644 --- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -1,33 +1,81 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; + +public class MinusNode extends Node implements ModeNode { + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; -public class MinusNode extends Node { public MinusNode(Node in) { super(null, in); } + public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { + baos.packed1(_mode); + } + @Override public Tag serialTag() { return Tag.Minus; } - @Override public String label() { return "Minus"; } + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } @Override protected String format() { return "(-%1)"; } @Override public Type compute() { - if( in(1)._type.isHigh() ) return TypeInteger.TOP; - if( in(1)._type instanceof TypeInteger i0 && - i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE ) - return TypeInteger.make(-i0._max,-i0._min,i0._widen); - return TypeInteger.BOT; + Type t = in(1)._type; + if( t==null ) + return Type.TOP; + if( t.isHigh() ) + return _mode==0 ? Type.TOP : + _mode==1 ? TypeInteger.TOP : + t instanceof TypeFloat ? t : TypeFloat.F64.dual(); + byte mode = _mode; + if( mode==0 ) { + if( t instanceof TypeFloat ) mode=2; + if( t instanceof TypeInteger ) mode=1; + } + if( mode==0 ) + return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP; + if( mode==1 ) { + if( t instanceof TypeInteger i0 && + i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE ) + return TypeInteger.make(-i0._max,-i0._min,i0._widen); + return TypeInteger.BOT; + } + if( t instanceof TypeFloat f ) + return f.isConstant() ? TypeFloat.constant(-f.value()) : f; + return TypeFloat.F64; } @Override public Node idealize() { // -(-x) is x - if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type instanceof TypeInteger ) + if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type.isa(compute()) ) return minus.in(1); + // Can we decide int vs flt? + if( _mode==0 ) { + byte mode = (byte)( + in(1)._type instanceof TypeInteger ? 1 : + in(1)._type instanceof TypeFloat ? 2 : 0); + if( mode!=0 ) return setMode(mode).init(); + } + return null; } - @Override Node copyF() { return new MinusFNode(null); } + @Override public Parser.ParseException err() { + return ArithNode.nodeErr(this,null,"-",_mode,true); + } + @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; } + @Override public int hash() { return _mode; } } diff --git a/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/src/main/java/com/seaofnodes/simple/node/ModeNode.java new file mode 100644 index 00000000..dc635447 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ModeNode.java @@ -0,0 +1,12 @@ +package com.seaofnodes.simple.node; + +/** + * A node whose numeric input family is selected once: unknown, integer, or + * floating point. Implementors remain ordinary Nodes; this interface only + * centralizes the structural mode contract shared by arithmetic, comparisons, + * and unary minus. + */ +public interface ModeNode { + byte mode(); + Node setMode(byte mode); +} diff --git a/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/src/main/java/com/seaofnodes/simple/node/MulFNode.java deleted file mode 100644 index 5c04af43..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class MulFNode extends ArithFNode { - public MulFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "MulF"; } - - @Override public String glabel() { return "*"; } - - @Override double doOp( double x, double y ) { return x*y; } - - @Override - public Node idealize() { - Node lhs = in(1); - Node rhs = in(2); - Type t1 = lhs._type; - Type t2 = rhs._type; - - // Mul of 1. We do not check for (1*x) because this will already - // canonicalize to (x*1) - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) - return lhs; - - // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) - return swap12(); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index 6e1dc053..bb99cad5 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -1,17 +1,20 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import java.util.BitSet; public class MulNode extends ArithNode { + @Override boolean allowFloat() { return true; } public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public MulNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } + @Override public Tag serialTag() { return Tag.Mul; } - @Override public String label() { return "Mul"; } @Override public String op() { return "*"; } - @Override long doOp( long x, long y ) { return x * y; } + @Override long doOp( long x, long y ) { return x * y; } + @Override double doOp(double x, double y ) { return x * y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; } @@ -32,8 +35,8 @@ public class MulNode extends ArithNode { // Mul of 1. We do not check for (1*x) because this will already // canonicalize to (x*1) long c = i.value(); - if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null; - if( c==0 ) return Parser.ZERO; + if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize(); + if( c==0 ) return CodeGen.CODE.ZERO; // Mul by a power of 2, +/-1. Bit patterns more complex than this // are unlikely to win on an X86 vs the normal "imul", and so // become machine-specific. @@ -58,6 +61,5 @@ public class MulNode extends ArithNode { return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } - @Override Node copyF() { return new MulFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/src/main/java/com/seaofnodes/simple/node/NeverNode.java index 66879b66..ba1dab8c 100644 --- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -11,7 +11,8 @@ import com.seaofnodes.simple.type.TypeTuple; // "Never true" for infinite loop exits public class NeverNode extends IfNode { public NeverNode(NeverNode ctrl) { super(ctrl); } - public NeverNode(Node ctrl) { super(ctrl,null); } + public NeverNode(float ignore, Node ctrl) { super(ctrl,null); } + @Override public Tag serialTag() { return Tag.Never; } @Override public String label() { return "Never"; } diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java index 840d2759..5eedd0cd 100644 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -4,68 +4,65 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; -/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; - * results {ptr, $mem}. - * The memory input and result cover only the aliases in the allocated struct. - */ +/** + * Allocation! Allocate a chunk of memory, no init. + * Takes in ctrl and size, and TypeStruct. + * Produces a ptr and private memory. +*/ public class NewNode extends Node implements MultiNode { + public TypeStruct _ts; + public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; } - public final TypeMemPtr _ptr; + public Node ctrl() { return in(0); } + public Node size() { return in(1); } - public NewNode(TypeMemPtr ptr, Node... nodes) { - super(nodes); - assert !ptr.nullable(); - _ptr = ptr; - assert nodes.length==3; - assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; - assert nodes[1]._type instanceof TypeMem; - assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; + // --- Serialization + public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; } + @Override public Tag serialTag() { return Tag.New; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed2(types.get(_ts)); } - - public Node mem() { return in(1); } - public Node size() { return in(2); } - - public Field field(int alias) { - for( Field f : _ptr._obj._fields ) - if( !f._one && f._alias==alias ) return f; - return null; + static Node make( BAOS bais, Type[] types) { + TypeStruct ts = (TypeStruct)types[bais.packed2()]; + return new NewNode(ts,null,null); } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - + @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); } - @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("new ").p(_ptr._obj.str()); + return p.p("new ").p(_ts.str()); } - - @Override - public TypeTuple compute() { - return in(0)._type.isHigh() - ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP) - : TypeTuple.make(_ptr,TypeMem.BOT); + @Override public TypeTuple compute() { + if( ctrl()._type.isHigh() ) + return TypeTuple.DEAD_NEW; + TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit(); + return TypeTuple.make(TypeMemPtr.make(_ts), + // Arrays get pre-zeroed by constructor. + // Objects start with default-zero memory for + // fields that can be zero-initialized. This is + // still private memory, so no aliases escape yet. + TypeMem.make(1,ts,true,false,false,null,null)); } - @Override - public Node idealize() { - Node progress=null; - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) - progress = setDef(i,cast.in(1)); - } - return progress == null ? null : this; + @Override public Node idealize() { return null; } + + @Override boolean _upgradeType( HashMap<String,Type> TYPES) { + Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts; } - @Override - public boolean eq(Node n) { return this == n; } + // Two NewNodes are never equal; NewNodes always make independent pointers + // so they are not functional - they internally bump-ptr allocate always a + // new pointer. + @Override public boolean eq(Node n) { return this==n; } - @Override - int hash() { return _ptr.hashCode(); } + @Override int hash() { return _ts.hashCode(); } // ------------ // MachNode specifics, shared across all CPUs @@ -74,30 +71,31 @@ public class NewNode extends Node implements MultiNode { private RegMask _retMask; private RegMask _kills; public void cacheRegs(CodeGen code) { - _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg(); - _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0); + _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg(); + _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv); // Return mask depends on TFP (either GPR or FPR) _retMask = code._mach.retMask(TypeFunPtr.CALLOC); // Kill mask is all caller-saves, and any mirror stack slots for args // in registers. - RegMaskRW kills = code._callerSave.copy(); + RegMaskRW kills = code._cCallerSave.copy(); // Start of stack slots int maxReg = code._mach.regs().length; // Incoming function arg slots, all low numbered in the RA int fslot = cfg0().fun()._maxArgSlot; // Killed slots for this calls outgoing args - int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC); + int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv); _xslot = (maxReg+fslot)+xslot; for( int i=0; i<xslot; i++ ) kills.set((maxReg+fslot)+i); _kills = kills; } public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } + public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } public RegMask outregmap() { return null; } public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } public RegMask killmap() { return _kills; } public void asm(CodeGen code, SB sb) { - sb.p("#calloc, ").p(code.reg(size())); + sb.p("#malloc, ").p(code.reg(size())); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index 414530b4..23a7e2c4 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -4,13 +4,11 @@ import com.seaofnodes.print.BaseNode; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.*; import java.util.*; import java.util.function.Function; import static com.seaofnodes.simple.codegen.CodeGen.CODE; @@ -70,8 +68,83 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { _hash = 0; } + // Disk/serialized opcode tags + public enum Tag { + Add,And,EQ,NE,LT,LE,ULT, + CallEnd,Call,ConFldOff,Con,CProj, + Div,Escape,Extern,Fun,FunPtr,If,Load,Loop, + MemMerge,Minus,Mul, + New,Never,Not,Or,Parm,Phi,Proj, + ReadOnly,Return,Region,RoundF32, + Sar,Shl,Shr,Start,Stop,Store,Sub,ToFloat, + XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt,ExternOffset,ToInteger; + public static final Tag[] VALS = values(); + public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { + return switch(this) { + case Add -> new AddNode(null,null,(byte)bais.packed1()); + case And -> { int mode=bais.packed1(); assert mode==1; yield new AndNode(null,null,null); } + case Div -> new DivNode(null,null,(byte)bais.packed1()); + case If -> new IfNode(null,null); + case Loop -> new LoopNode(null,null,null); + case Minus -> new MinusNode(null,(byte)bais.packed1()); + case Mul -> new MulNode(null,null,(byte)bais.packed1()); + case Never -> new NeverNode(1.2f, null); + case Not -> new NotNode(null); + case Or -> { int mode=bais.packed1(); assert mode==1; yield new OrNode(null,null,null); } + case ReadOnly-> new ReadOnlyNode((Node)null); + case Return ->new ReturnNode(null,null,null,null,null); + case RoundF32-> new RoundF32Node(null); + case PtrToInt-> new PtrToIntNode((Node)null); + case Sar -> { int mode=bais.packed1(); assert mode==1; yield new SarNode(null,null,null); } + case Shl -> { int mode=bais.packed1(); assert mode==1; yield new ShlNode(null,null,null); } + case Shr -> { int mode=bais.packed1(); assert mode==1; yield new ShrNode(null,null,null); } + case Start -> new StartNode(null,null,TypeInteger.BOT); + case Sub -> new SubNode(null,null,(byte)bais.packed1()); + case ToFloat-> new ToFloatNode(null); + case ToInteger-> new ToIntegerNode(null); + case XCtrl -> new XCtrlNode(); + case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); } + case Guard -> GuardNode.make(bais); + + case EQ -> new BoolNode.EQ (null,null,(byte)bais.packed1()); + case NE -> new BoolNode.NE (null,null,(byte)bais.packed1()); + case LT -> new BoolNode.LT (null,null,(byte)bais.packed1()); + case LE -> new BoolNode.LE (null,null,(byte)bais.packed1()); + case ULT -> new BoolNode.ULT(null,null,(byte)bais.packed1()); + + case Call -> CallNode.make(bais); + case CallEnd-> CallEndNode.make(bais ,types); + case CProj -> CProjNode.make(bais,strs); + case Con -> ConstantNode.make(bais ,types); + case Escape-> EscapeNode.make(bais ,types); + case Extern-> ExternNode.make(bais,strs,types); + case ExternOffset-> new ExternOffsetNode(strs[bais.packed2()]); + case FunPtr-> FunPtrNode.make(bais, types); + case Fun -> FunNode.make(bais,strs,types); + case Load -> new LoadNode(bais,strs,types,fileAliases,aliases); + case MemMerge->MemMergeNode.make(bais); + case MemPhi-> MemPhiNode.make(bais,strs,types,fileAliases,aliases); + case BulkMemPhi-> BulkMemPhiNode.make(bais,strs,types,fileAliases,aliases); + case New -> NewNode.make(bais ,types); + case Parm -> ParmNode.make(bais,strs,types); + case Phi -> PhiNode.make(bais,strs,types); + case Proj -> ProjNode.make(bais,strs); + case Store -> new StoreNode(bais,strs,types,fileAliases,aliases); + case Stop -> StopNode.make(bais); + case StartCU-> StartCUNode.make(bais,strs,types); + case StopCU -> StopCUNode.make(bais); + case Region-> RegionNode.make(bais); + + default -> throw Utils.TODO("Should not reach here"); + }; + } + }; + public Tag serialTag() { throw Utils.TODO(); } + // Serialize extra data, including input counts + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {} + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public abstract String label(); + public String label() { return serialTag().toString(); } public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } @@ -178,7 +251,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { } // Breaks the edge invariants, used temporarily - protected <N extends Node> void addUse(N n) { _outputs.add(n); } + @SuppressWarnings("unchecked") + protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; } // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. // Return true if the output list is empty afterward. @@ -200,6 +274,12 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { old_def.kill(); // Kill old def } } + public Node removeLast() { + Node old_def = _inputs.pop(); + if( old_def != null ) + old_def.delUse(this); + return old_def; + } /** * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em> @@ -236,11 +316,13 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive - @SuppressWarnings("unchecked") - public <N extends Node> N keep() { addUse(null); return (N)this; } + public <N extends Node> N keep() { return addUse(null); } // Remove bogus null. @SuppressWarnings("unchecked") - public <N extends Node> N unkeep() { delUse(null); return (N)this; } + public <N extends Node> N unkeep() { + delUse(null); + return (N)this; + } // Test "keep" status public boolean iskeep() { return _outputs.find(null) != -1; } public void unkill() { @@ -260,7 +342,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); - n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.add(n); CODE.addAll(n._outputs); @@ -331,13 +412,13 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole() { - var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + public final Node peephole( ) { + var obs = CODE._midAssert ? null : CODE._obs; if( obs != null ) obs.before(this); Node n = peepholeOpt(); - Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); - if( obs != null ) obs.after(this, n == null ? null : rez, false); - return rez; + Node rez = n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n==null ? null : rez, false); + return rez; // Cannot return null for no-progress } /** @@ -363,11 +444,9 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Compute initial or improved Type Type old = setType(compute()); - // 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( !isConst() && _type.isHighOrConst() ) + // Replace constant computations from non-constants with a constant node. + if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) && + _type.isHighOrConst() ) return ConstantNode.make(_type).peephole(); // Global Value Numbering @@ -435,7 +514,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // If changing, add users to worklist. public Type setType(Type type) { Type old = _type; - assert old == null || type.isa(old) : "Monotonicity test failed"; + assert old == null || type.isa(old) : "Monotonicity test failed: "+this+"#"+_nid+" old="+old+" new="+type; + //assert !type.hasClosedBuilder0(); // New types should not have closed builders if( old == type ) return old; _type = type; // Set _type late for easier assert debugging CODE.addAll(_outputs); @@ -500,28 +580,33 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // revisit them if `this` changes. Ary<Node> _deps; public int nDeps() { return _deps == null ? 0 : _deps.size(); } - public Node dep(int idx) { return _deps.get(idx); } + public Node dep(int idx) { return _deps.at(idx); } + + private boolean addDepImpl( Node dep ) { + // Running peepholes during the big assert cannot have side effects + // like adding dependencies. + if( CODE._midAssert ) return false; + if( CODE._obs != null ) CODE._obs.dep(this, dep); + if( dep._deps==null ) dep._deps = new Ary<>(Node.class); + if( dep._deps .find(this) != -1 ) return false; // Already on list + if( dep._outputs.find(this) != -1 ) return false; + return true; + } /** * Add a node to the list of dependencies. Only add it if it's not an input * or output of this node, that is, it is at least one step away. The node * being added must benefit from this node being peepholed. */ - <N extends Node> N addDep( N dep ) { return addDep(dep,false); } - - // Keep an explicit dependency when inspecting an immediate user's shape. - <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } + public <N extends Node> N addDep( N dep ) { + if( !addDepImpl(dep) ) return dep; + if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + dep._deps.add(this); + return dep; + } - private <N extends Node> N addDep(N dep, boolean forwards) { - // Running peepholes during the big assert cannot have side effects - // like adding dependencies. - if( CODE._midAssert ) return dep; - var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; - if( obs != null ) obs.dep(this, dep); - if( dep._deps==null ) dep._deps = new Ary<>(Node.class); - if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( !forwards && dep._outputs.find(this) != -1 ) return dep; + public <N extends Node> N addDepForwards( N dep ) { + if( !addDepImpl(dep) ) return dep; dep._deps.add(this); return dep; } @@ -585,26 +670,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { /** Is this Node Memory related */ public boolean isMem() { return false; } - // Semantic change to the graph (so NOT a peephole), used by the Parser. - // If any input is a float, flip to a float-flavored opcode and widen any - // non-float input. - public final Node widen() { - if( !hasFloatInput() ) return this; - Node flt = copyF(); - if( flt==null ) return this; - for( int i=1; i<nIns(); i++ ) - flt.setDef(i, in(i)._type instanceof TypeFloat ? in(i) : new ToFloatNode(in(i)).peephole()); - kill(); - return flt; - } - private boolean hasFloatInput() { - for( int i=1; i<nIns(); i++ ) - if( in(i)._type instanceof TypeFloat ) - return true; - return false; - } - Node copyF() { return null; } - // ------------------------------------------------------------------------ // Peephole utilities @@ -656,7 +721,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { public Parser.ParseException err() { return null; } // Common integer constants - public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); } + public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(TypeInteger.constant(x)).peephole(); } // Utility to walk the entire graph applying a function; return the first // not-null result. @@ -677,6 +742,20 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { return null; } + public void gather(HashMap<String,Integer> strs ) { } + + // If a Type upgrade happens, put on worklist + boolean _upgradeType( HashMap<String,Type> TYPES ) { return false; } + public final void upgradeType( HashMap<String,Type> TYPES ) { + Type old = _type; _type = _type.upgradeType(TYPES); + boolean progress = _upgradeType(TYPES); + if( progress || old != _type ) { + CODE.add(this); + CODE.addAll(_outputs); + if( _deps!=null ) CODE.addAll(_deps); + } + } + /** * Debugging utility to find a Node by index */ diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java index 775b069b..2328c287 100644 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -6,8 +6,7 @@ import com.seaofnodes.simple.util.Utils; public class NotNode extends Node { public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } + @Override public Tag serialTag() { return Tag.Not; } @Override protected String format() { return "(!%1)"; } diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java index d16dd194..9d50d1a9 100644 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -5,7 +5,8 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class OrNode extends ArithNode { - public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); } + @Override public Tag serialTag() { return Tag.Or; } @Override public String label() { return "Or"; } @Override public String op() { return "|"; } @@ -18,12 +19,13 @@ public class OrNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t1 = lhs._type; Type t2 = rhs._type; // Or of 0. We do not check for (0|x) because this will already // canonicalize to (x|0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && t1.isa(TypeInteger.BOT) && forceInt(lhs) ) return lhs; // Move constants to RHS: con*arg becomes arg*con diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 95934e4b..899d7639 100644 --- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -2,28 +2,54 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; public class ParmNode extends PhiNode { // Argument indices are mapped one-to-one on CallNode inputs public final int _idx; // Argument index + public Type _declaredType; public ParmNode(String label, int idx, Type declaredType, Node... inputs) { - super(label,declaredType,inputs); + super(label, inputs); _idx = idx; + _declaredType = declaredType; + _type = declaredType; + } + public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; } + @Override public Tag serialTag() { return Tag.Parm; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed1(nIns()); // Number of linked calls + baos.packed2(_label==null ? 0 : strs.get(_label)); + baos.packed2(types.get(_declaredType)); // NPE if fails lookup + baos.packed1(_idx); + } + static Node make( BAOS bais, String[] strs, Type[] types) { + Node[] ins = new Node[bais.packed1()]; + String label = strs[bais.packed2()]; + Type minType = types[bais.packed2()]; + return new ParmNode(label, bais.packed1(), minType, ins); } - public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; } @Override public String label() { return MemOpNode.mlabel(_label); } + @Override boolean _upgradeType( HashMap<String,Type> TYPES) { + Type t = _declaredType.upgradeType(TYPES); + if( t == _declaredType ) return false; + unlock(); + _declaredType = t; + return true; + } + public FunNode fun() { return (FunNode)in(0); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( "main".equals(fun()._name) && _label.equals("arg") ) + if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") ) return p.p("arg"); p.p("Parm_").p(_label).open(); for( Node in : _inputs ) p.n(in).p(","); @@ -35,20 +61,36 @@ public class ParmNode extends PhiNode { @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); } @Override + public Type compute() { + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; + // During parsing Phis have to be computed type pessimistically. + if( r.inProgress() || in(nIns()-1)==null ) + return _declaredType; + // Set type to local top of the starting type + Type t = 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. + Type ctrl = addDep(r.in(i))._type; + if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) { + if( in(i)._type==Type.BOTTOM ) + return Type.BOTTOM; + t = t.meet(in(i)._type); + } + } + return t.join(_declaredType); + } + + @Override public Node idealize() { if( !(region() instanceof FunNode) ) - return in(1); // Input has collapse to e.g. starting control. + // A dead function can lose every caller before the Parm itself is + // removed; there is no replacement input in that transient form. + return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control. // If function is folding, do all possible peeps if( fun()._folding ) return super.idealize(); - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2 != _minType ) { - _minType = tmp2; - return this; - } - } // Skip most phi optimizations on parms return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 6cfeecfe..0097c773 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -3,41 +3,47 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.*; +import java.util.HashMap; +import java.util.IdentityHashMap; public class PhiNode extends Node { 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. - Type _minType; - - int lattice_drop; - - public PhiNode(String label, Type minType, Node... inputs) { + public PhiNode( String label, Node... inputs) { super(inputs); _label = label; - assert minType!=null; - _minType = minType; + } + public static PhiNode make(String label, Type minType, Node... inputs) { + return minType instanceof TypeMem mem + ? mem._alias==1 + ? new BulkMemPhiNode(label,inputs) + : new MemPhiNode(label,mem._alias,inputs) + : new PhiNode(label, inputs); } // Used by ParmNode - public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; } + public PhiNode(PhiNode phi, String label) { super(phi); _label = label; } // Used by instruction Selection - public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); } + public PhiNode(PhiNode phi) { this(phi,phi._label); } // Used by the infinite-loop exit breaker public PhiNode(RegionNode r, Node sample) { super(new Node[]{r}); _label = ""; - _minType = sample._type; + _type = sample._type; while( nIns() < r.nIns() ) addDef(sample); } - - public static PhiNode make(String label, Type type, Node... inputs) { - return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs); + @Override public Tag serialTag() { return Tag.Phi; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed1(nIns()); + baos.packed2(_label==null ? 0 : strs.get(_label)); + } + static Node make( BAOS bais, String[] strs, Type[] types) { + Node[] ins = new Node[bais.packed1()]; + return new PhiNode(strs[bais.packed2()], ins); } @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } @@ -52,23 +58,18 @@ public class PhiNode extends Node { } public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMem() { return _minType instanceof TypeMem; } - boolean isRPC() { return false; } + @Override public boolean isMem() { return _type instanceof TypeMem; } @Override public Type compute() { if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; + return Type.TOP; // During parsing Phis have to be computed type pessimistically. if( r.inProgress() || in(nIns()-1)==null ) - // Loop-Phis must lift to the declared type, because that is how - // the Parser keeps precise types until the loop finishes parsing. - // Similar, ParmNodes use precise minType until all calls are - // linked (post opto). - return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : Type.BOTTOM; + return Type.BOTTOM; // Set type to local top of the starting type Type t = Type.TOP; - for (int i = 1; i < nIns(); i++) { + 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. Type ctrl = addDep(r.in(i))._type; @@ -78,91 +79,78 @@ public class PhiNode extends Node { t = t.meet(in(i)._type); } } - Type newt = t.join( _minType ); // Above-center ranges may still fall to unwidened constants. // Widen only below-center ranges to preserve monotonicity. - if( region() instanceof LoopNode && // Only around loops - newt instanceof TypeInteger newi && - // Types changed and are falling (the optimistic case, expected to fall forever) - newi != _type ) { - if( !newi.isHigh() && !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) ) - return newi.same_but_slightly_wider_than(_minType); + if( r instanceof LoopNode && // Only around loops + t instanceof TypeInteger ti && // Only widen integers + !ti.isHigh() && !ti.isConstant() ) { // Only widen live ranges + // Widen, to prevent infinite falling of TypeIntegers + return ti.same_but_slightly_wider_than(); } - return newt; + return t; } @Override public Node idealize() { - if( !(region() instanceof RegionNode r ) ) - return in(1); // Input has collapse to e.g. starting control. - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2._obj != tmp._obj ) { - _minType = tmp.makeFrom(tmp2._obj); - return this; - } - } - if( r.inProgress() || r.nIns()<=1 ) + RegionNode r = (RegionNode)region(); + if( r.inProgress() || nOuts()==0 ) return null; // Input is in-progress // If we have only a single unique input, become it. Node live = singleUniqueInput(); - if( live != null ) { - if( live._type.isa(_type) ) - return live; - // Let the bulk input split this alias before collapsing a precise Phi. - if( live instanceof BulkMemPhiNode ) return null; - // Keep the Phi upcast - return new CastNode(_type,null,live); - } + if( live != null ) + return live; // 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; - // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). - Node down; - if( same_op() && (down=drop_same_op())!=null ) return down; + // Generic "pull down op" + Node progress; + if( same_op() && (progress = drop_same_op()) != null ) + return progress; - // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. - if( nIns()==3 ) { - if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2); - if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1); - } - // If merging a null-checked null and the checked value, just use the value. - // if( val ) ..; phi(Region,False=0/null,True=val); - // then replace with plain val. - if( nIns()==3 ) { - int nullx = -1; - 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); - Node ridom = r.idom(this); - if( ridom instanceof IfNode iff && addDep(iff.pred())==val ) { - // Must walk the idom on the null side to make sure we hit False. - CFGNode idom = (CFGNode)r.in(nullx); - while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); - if( idom instanceof CProjNode proj && proj._idx==1 ) - return val; - } else if( ridom != null ) addDep(ridom); - } - } + // If merging Phi(ZERO, guardNZ(N)) at the matching `if(N)` join, the + // Phi is exactly N. The guard arm proves N is non-zero, and the other + // arm contributes N's zero value. This is only legal if N is already + // available at the Phi region; otherwise a return-scope/live-on-exit + // Phi can lose the zero arm and export a branch-local value upward. + Node unguard; + if( (unguard=matchingGuardMerge(r,1)) != null ) return unguard; + if( (unguard=matchingGuardMerge(r,2)) != null ) return unguard; return null; } + private Node matchingGuardMerge(RegionNode r, int nzIdx) { + Node zero = in(3-nzIdx); + if( !(in(nzIdx) instanceof GuardNode cast && cast._nonZero) || + zero._type.makeZero()!=zero._type || + cast.in(1)==this ) + return null; + if( !(r.in(nzIdx) instanceof CProjNode nz && nz._idx==0 && nz.ctrl() instanceof IfNode iff) ) + return null; + if( !(r.in(3-nzIdx) instanceof CProjNode z && z._idx==1 && z.ctrl()==iff) ) + return null; + // Predicate was guarded? + Node n = cast.in(1), tested = null; + while( true ) { + if( iff.pred()==n ) tested = n; + if( !(n instanceof GuardNode guard && guard._nonZero) ) break; + n = guard.in(1); + } + CFGNode early = CFGNode.earlyCFG(n); + return tested!=null && (early==null || early.dominates(r,this)) ? n : null; + } + // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { Node op = in(1); - if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + if( op instanceof EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; // A bulk Phi must split its aliases before factoring precise Stores. if( this instanceof BulkMemPhiNode ) return false; @@ -214,7 +202,7 @@ public class PhiNode extends Node { ins[i] = in(i).in(j); t = t.meet(ins[i]._type); } - // Not accepts both pointers and integers, but a Phi cannot mix them. + // Do not introduce an operand Phi mixing incompatible type families. if( t==Type.BOTTOM ) { for( int i=1; i<ins.length; i++ ) addDep(ins[i]); cp.kill(); @@ -222,7 +210,7 @@ public class PhiNode extends Node { } PhiNode phi = j==1 && op instanceof MemOpNode mem ? new MemPhiNode(_label,mem._alias,ins) - : PhiNode.make(_label,t.glb(false),ins); + : PhiNode.make(_label,t,ins); x = phi.peephole(); } cp.addDef(x); @@ -269,39 +257,27 @@ public class PhiNode extends Node { } // Never equal if inProgress. - // Also, joins @Override public boolean eq( Node n ) { - if( inProgress() ) return false; - Type min = ((PhiNode)n)._minType; - if( _minType==min ) return true; - Type mt = min.meet(_minType); - if( min!=mt && _minType!=mt ) return false; - //// Theory says these 2 Phis CAN be merged/GVNd, but I need to pick the - //// most general minType. - //_minType = ((PhiNode)n)._minType = mt; - //return true; - return false; + return !inProgress() && super.eq(n); } @Override public Parser.ParseException err() { if( _type != Type.BOTTOM ) return null; - // BOTTOM means we mixed e.g. int and ptr + // Global BOTTOM retains the same role for non-scalar families. 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); + SB sb = new SB().p("No common type amongst "); + for( int i=1; i<nIns(); i++ ) + sb.p(in(i)._type.toString()).p(" and "); + return Parser.error(sb.unchar(5).toString(),null); + } + + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_label); } } diff --git a/src/main/java/com/seaofnodes/simple/node/Proj.java b/src/main/java/com/seaofnodes/simple/node/Proj.java new file mode 100644 index 00000000..c8b9378f --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/Proj.java @@ -0,0 +1,2 @@ +package com.seaofnodes.simple.node; +public interface Proj { int idx(); } diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 92c6c2be..4b29cc3c 100644 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -2,12 +2,15 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; +import com.seaofnodes.simple.util.BAOS; +import java.util.HashMap; +import java.util.IdentityHashMap; - -public class ProjNode extends Node { +public class ProjNode extends Node implements Proj { // Which slice of the incoming multipart value public final int _idx; @@ -21,6 +24,14 @@ public class ProjNode extends Node { _label = label; } public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; } + @Override public Tag serialTag() { return Tag.Proj; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + baos.packed1(_idx); + baos.packed2(_label==null ? 0 : strs.get(_label)); + } + static Node make( BAOS bais, String[] strs ) { + return new ProjNode(null, bais.packed1(), strs[bais.packed2()] ); + } @Override public String label() { return _label; } @@ -51,4 +62,10 @@ public class ProjNode extends Node { @Override int hash() { return _idx; } + + @Override public int idx() { return _idx; } + + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_label); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/CastMach.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java index e45898f5..9a9b94fd 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastMach.java +++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java @@ -5,15 +5,13 @@ import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.util.SB; -/** - * Cast a pointer to read-only - */ -public class CastMach extends CastNode implements MachNode { - public CastMach( CastNode n ) { super(n); } +/** Zero-code machine form of {@link PtrToIntNode}. */ +public class PtrToIntMach extends PtrToIntNode implements MachNode { + public PtrToIntMach(PtrToIntNode ptr) { super(ptr); } @Override public String op() { return label(); } @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } @Override public RegMask outregmap() { return RegMask.FULL; } - @Override public int twoAddress( ) { return 1; } - @Override public void encoding( Encoding enc ) { } + @Override public int twoAddress() { return 1; } + @Override public void encoding(Encoding enc) { } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } } diff --git a/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java new file mode 100644 index 00000000..d3d38060 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; + + +/** + * Reinterpret a non-null managed pointer as its raw integer address. + * + * This node changes representation families but emits no code. No + * control-dependent proof can turn a pointer into an integer; array-to-C-pointer + * conversion adds the array body offset separately. + */ +public class PtrToIntNode extends Node { + public PtrToIntNode(Node ptr) { super(null,ptr); } + public PtrToIntNode(PtrToIntNode ptr) { super(ptr); } + + @Override public Tag serialTag() { return Tag.PtrToInt; } + @Override protected String format() { return "(i64*)%1"; } + + @Override public Type compute() { + return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + + @Override public Parser.ParseException err() { + Type t = in(1)._type; + if( t instanceof TypeMemPtr ptr ) + return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null); + return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java index 94c1b6f6..0a720d5b 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java @@ -10,7 +10,7 @@ import com.seaofnodes.simple.type.TypeMemPtr; public class ReadOnlyNode extends Node { public ReadOnlyNode( Node n ) { super(null,n); } public ReadOnlyNode( ReadOnlyNode n ) { super(n); } - @Override public String label() { return "ReadOnly"; } + @Override public Tag serialTag() { return Tag.ReadOnly; } @Override protected String format() { return "(const)%1"; } @Override public Type compute() { diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 59030dee..8883ba8e 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -2,10 +2,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.*; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.BAOS; import java.util.*; public class RegionNode extends CFGNode { @@ -15,9 +15,9 @@ public class RegionNode extends CFGNode { public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; } public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; } - - @Override - public String label() { return "Region"; } + @Override public Tag serialTag() { return Tag.Region; } + public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } + static Node make( BAOS bais ) { return new RegionNode(null,new Node[bais.packed1()]); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); @@ -47,10 +47,10 @@ public class RegionNode extends CFGNode { // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" if( !hasPhi() && nIns()>3 && // No Phi users, just a control user - in(1) instanceof CProjNode p1 && - in(2) instanceof CProjNode p2 && - addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && - p1.in(0) instanceof IfNode iff ) { + in(1) instanceof CProjNode p1 && + in(2) instanceof CProjNode p2 && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && + p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); // Just delete the path for fat Regions @@ -76,7 +76,7 @@ public class RegionNode extends CFGNode { // Fold Region for( int j=1; j<region.nIns(); j++ ) addDef(region.in(j)); - setDef(i,Parser.XCTRL); + setDef(i,CodeGen.CODE.XCTRL); return this; } } @@ -112,7 +112,8 @@ public class RegionNode extends CFGNode { CodeGen.CODE.addAll(phi.delDef(path)._outputs); } } - return isDead() ? Parser.XCTRL : delDef(path); + if( !isDead() ) delDef(path); + return isDead() ? CodeGen.CODE.XCTRL : this; } private int findDeadInput(int startIdx) { diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 266657be..0431f7d5 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -10,7 +10,15 @@ import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ +/** + * The Return node has two inputs. The first input is a control node and the + * second is the data node that supplies the return value. + * <p> + * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible. + * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements. + * <p> + * The Return's output is the value from the data node. + */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -20,6 +28,7 @@ public class ReturnNode extends CFGNode { _fun = fun; } public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; } + @Override public Tag serialTag() { return Tag.Return; } public Node ctrl() { return in(0); } public Node mem () { return in(1); } @@ -28,9 +37,6 @@ public class ReturnNode extends CFGNode { @Override public FunNode fun() { return _fun; } @Override - public String label() { return "Return"; } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.p("return "); if( expr()==null ) p.p("----"); @@ -43,13 +49,10 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - if( inProgress () ) return TypeTuple.RET; // In progress - // Inlining can delete the entry while callers still use this return. - return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); + return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); } @Override public Node idealize() { - if( inProgress () ) return null; if( _fun.isDead() ) return null; // Upgrade signature based on return type @@ -58,85 +61,47 @@ public class ReturnNode extends CFGNode { if( ret != fcn.ret() && ret.isa(fcn.ret()) ) _fun.setSig(fcn.makeFrom(ret)); - // If dead (cannot be reached; infinite loop), kill the exit values - if( ctrl()._type==Type.XCONTROL && - !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) { - Node top = new ConstantNode(Type.TOP).peephole(); - setDef(1,top); - setDef(2,top); - return this; - } - return null; } - public boolean inProgress() { - return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress(); - } - - // Remember parsed type kinds for diagnostics; the optimized expression decides validity. - private boolean ti=false, tf=false, tp=false, tn=false; - - // Add a return exit to the current parsing function - void addReturn( Node ctrl, Node rmem, Node expr ) { - assert inProgress(); - - // Gather parse-time return types for error reporting - Type t = expr._type; - ti |= t instanceof TypeInteger x; - tf |= t instanceof TypeFloat x; - tp |= t instanceof TypeMemPtr x; - tn |= t==Type.NIL; - - // Merge path into the One True Return - RegionNode r = (RegionNode)ctrl(); - // Assert that the Phis are in particular outputs; not reordered or shuffled - PhiNode mem = (PhiNode)r.out(0); assert mem._minType == TypeMem.BOT; - PhiNode rez = (PhiNode)r.out(1); assert rez._minType == 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() { - if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead - return expr()._type==Type.BOTTOM || expr()._type==Type.TOP ? mixerr(ti,tf,tp,tn,_fun._loc) : null; + if( ctrl()._type == Type.CONTROL && + expr()._type == Type.TOP ) + return Parser.error("No defined return type",null); + if( ctrl()._type == Type.CONTROL && + expr()._type == TypeScalar.BOT ) + return Parser.error("Cannot return generic scalar",null); + // With no user constructor, <init> doubles as the public no-arg + // constructor. Required fields remain TOP in its private memory: a + // typed parser poison which deliberately emitted no Store or code. + if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) { + TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj; + if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem ) + for( Field fld : self._fields ) { + if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue; + Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts + ? ts.field(fld._fname)._t + : mem._alias==fld._alias ? mem._t : Type.TOP; + if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() ) + return Parser.error("'"+self._name+"' is not fully initialized, field '"+ + fld._fname+"' is only partially set in the constructor",null); + } + } + return null; } - static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) { - if( !ti && !tf && !tp && !tn ) - // Hit when the function never returns. e.g. `f = { -> return f() };` - return Parser.error("No defined return type", loc); - // Rather ugly way to print conflicting return types - int cnt = (ti?1:0) + (tf?1:0) + (tp||tn?1:0); - if( cnt==1 ) return null; // Uniform return type, something else errored and will report - 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); + @Override public boolean eq( Node n ) { + return !_fun.isDead() && !((ReturnNode)n)._fun.isDead(); } - // ------------ // MachNode specifics, shared across all CPUs public String op() { - return _fun._frameAdjust > 0 ? "addi" : "ret"; + return _fun._frameAdjust > 0 ? "epilog" : "ret "; } // Correct Nodes outside the normal edges public void postSelect(CodeGen code) { - FunNode fun = (FunNode)rpc().in(0); - _fun = fun; - fun.setRet(this); + _fun.setRet(this); } public RegMask regmap(int i) { return i==2 @@ -148,13 +113,14 @@ public class ReturnNode extends CFGNode { public void asm(CodeGen code, SB sb) { int frameAdjust = fun()._frameAdjust; if( frameAdjust>0 ) - sb.p("rsp += #").p(frameAdjust).p("\nret"); + sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret "); // Post code-gen, just print the "ret" - if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() || + (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) ) // Prints return reg (either RAX or XMM0), RPC and then the // callee-save registers. for( int i=2; i<nIns(); i++ ) - sb.p(code.reg(in(i),fun())).p(" "); + sb.p(code.reg(in(i))).p(" "); // If we did not get the expected rpc, print which one we got else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() ) sb.p("[").p(code.reg(rpc())).p("]"); diff --git a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 10a0e4aa..65f10a79 100644 --- a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -7,8 +7,7 @@ import com.seaofnodes.simple.type.TypeFloat; public class RoundF32Node extends Node { public RoundF32Node(Node lhs) { super(null, lhs); } - - @Override public String label() { return "RoundF32"; } + @Override public Tag serialTag() { return Tag.RoundF32; } @Override protected String format() { return "((f32)%1)"; } diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java index 429be229..737b531f 100644 --- a/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class SarNode extends ArithNode { - public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } + @Override public Tag serialTag() { return Tag.Sar; } - @Override public String label() { return "Sar"; } @Override public String op() { return ">>"; } @Override long doOp( long x, long y ) { return x >> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -22,10 +22,11 @@ public class SarNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t2 = rhs._type; // Sar of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5) diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 307fc0f9..92cad745 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -5,18 +5,18 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Var; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.ParseAll; +import com.seaofnodes.simple.codegen.CompUnit; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.AryInt; import com.seaofnodes.simple.util.Utils; - import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variable records. + * stack of variables. */ -public class ScopeNode extends Node { +public class ScopeNode extends MemMergeNode { /** * The control is a name that binds to the currently active control @@ -26,40 +26,38 @@ public class ScopeNode extends Node { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; - public Node in(Var v) { return in(v._idx); } - - @Override public Type compute() { return Type.BOTTOM; } - @Override public Node idealize() { return null; } - - // All active/live variables in all nested scopes, all run together + // All active/live variables in all nested scopes, all run together. + // Maps 1-to-1 to the _inputs array. public final Ary<Var> _vars; // Lexical scope is typed: public static class Kind { // Basic block scoping - public static class Block extends Kind { } + public static class Block extends Kind { } + // Constructor/initializer block for an allocated struct. + public static class Constructor extends Block { + public Constructor(TypeStruct self) { _self = self; } + public final TypeStruct _self; + } // Function scope - public static class Func extends Kind { } - // Struct definition, mapping types to fields - public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; } - // Struct allocation, mapping values to fields - public static class Alloc extends Kind { public Alloc (TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; } + public static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; } public int _lexSize; // Number of Vars in this scope } public final Ary<Kind> _kinds; // Lexical scope nesting depth public int depth() { return _kinds._len; } + public Kind klast() { return _kinds.last(); } - // Extra guards; tested predicates and casted results + // Extra guards; tested predicates and cast results private final Ary<Node> _guards; // A new ScopeNode public ScopeNode() { - _type = Type.BOTTOM; - _vars = new Ary<>(Var .class); - _kinds = new Ary<>(Kind .class); - _guards = new Ary<>(Node .class); + super(); + _vars = new Ary<>(Var .class); + _kinds = new Ary<>(Kind.class); + _guards = new Ary<>(Node.class); } @Override public String label() { return "Scope"; } @@ -70,7 +68,7 @@ public class ScopeNode extends Node { int j=1; for( int i=0; i<nIns(); i++ ) { if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; } - Var v = _vars.at(i); + Var v = var(i); p.p(v._type()); p.p(" "); if( v._final ) p.p("!"); @@ -89,7 +87,7 @@ public class ScopeNode extends Node { public Node ctrl() { return in(0); } - public Node mem() { return in(lookup(MEM0)); } + public Node mem() { return in(1); } public Var var(int i) { return _vars.at(i); } /** @@ -103,7 +101,7 @@ public class ScopeNode extends Node { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { update(MEM0,n); return n; } + public Node mem(Node n) { return setDef(1,n); } public void push( Kind kind ) { kind._lexSize = _vars.size(); @@ -112,7 +110,11 @@ public class ScopeNode extends Node { // Pop a lexical scope public void pop() { - promote(); // Promote forward references to the next outer scope + promote(null); // Promote forward references to the next outer scope + _pop(); + } + // Pop a scope, no promote FRefs + public void _pop() { int n = _kinds.pop()._lexSize; popUntil(n); // Pop off inputs going out of scope _vars.setLen(n); // Pop off variables going out of scope @@ -121,68 +123,85 @@ public class ScopeNode extends Node { // 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() { - Kind kind = _kinds.last(); + public void promote(CodeGen code) { + Kind kind = klast(); int n = kind._lexSize; for( int i=n; i<nIns(); i++ ) { Var v = var(i); + Node vn = in(i); if( !v.isFRef() ) continue; - if( depth()==1 ) - throw Parser.error("Undefined name '" + v._name + "'",v._loc); - _vars .swap(n,i); - _inputs.swap(n,i); - v._idx = n; + // Shuffle vars and inputs to move the forward reference to + // *before* the start of the using scope ("as if" it was not + // actually a forward reference). + for( int j=n; j<=i; j++ ) { + Var vtmp = var(j); + Node ntmp = in (j); + v._idx = j; + _vars .set(j,v ); + _inputs.set(j,vn); + v = vtmp; + vn = ntmp; + } kind._lexSize = ++n; } } - // Allocation fields live in the scope immediately outside the constructor - // block. Nested blocks retain that permission; surrounding bindings do not. - public boolean canInit(Var v) { - for( int i=_kinds._len-1; i>0; i-- ) - if( _kinds.at(i) instanceof Kind.Alloc ) - return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize; - return false; + public boolean inFunction () { return klast() instanceof Kind.Func; } + // Is any enclosing scope a constructor? + public boolean inConstructor() { return constructorSelf() != null; } + // Nearest constructor self type, if any. + public TypeStruct constructorSelf() { + for( int i=depth()-1; i>=0; i-- ) + if( _kinds.at(i) instanceof Kind.Constructor ctor ) + return ctor._self; + else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) ) + return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj; + return null; } - public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; } - public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; } - public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; } - - public Kind kind( Var v ) { + // Find nearest enclosing function scope + public int enclosingFunction() { for( int i=depth()-1; i>=0; i-- ) - if( v._idx >= _kinds.at(i)._lexSize ) - return _kinds.at(i); - throw Utils.TODO(); + if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) ) + return i; + throw Utils.TODO("Should not reach here"); } - // Return the kind for the defining scope, or a function scope if found - // first. Block scopes means the variable can be r/w directly in the - // scope, Function scopes require final constants (no capture), and - // Constructor scopes mean an instance variable reference. - - // Walk up-lexical scope looking for defining lexical Kind. - // - Skip any amount of nested block scopes - // - Walking out of a function requires v be a final constant (for now) - - // and we can stop walking. - - // - Walking out of a method and (skipping Blocks) into the matching struct - // is OK, and this is a instance var load. + // Find nearest enclosing function or declaration scope + public int enclosingFuncOrDecl() { + for( int i=depth()-1; i>=0; i-- ) + if( _kinds.at(i) instanceof Kind.Func func ) + return i; + throw Utils.TODO("Should not reach here"); + } - // - Walking out of a method and into the wrong struct is an error; - // including nested structs containing (no nested classes yet) + // Which lexical scope contains this Var? + public int kindx( Var v ) { + for( int i=depth()-1; i>=0; i-- ) + if( v._idx >= _kinds.at(i)._lexSize ) + return i; + return -1; + } - // Returns error, forward-ref, ok containing struct, ok containing function/block. + // Which lexical scope contains this Var? + public Kind kind( Var v ) { + int kx = kindx(v); + return kx == -1 ? null : _kinds.at(kx); + } - public String outOfFunction( Var v ) { - //if( v==null ) return null; // Prolly forward reference - //int i; for( i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- ) - // if( _kinds.at(i)=="{->}" ) return "{->}"; - //return _kinds.at(i); - throw Utils.TODO(); + public int kindFcnx( Var v ) { + for( int i=depth()-1; i>=0; i-- ) + if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func ) + return i; + throw Utils.TODO("Should not reach here"); } + // Which function scope contains this Var? + public Kind kindFcn( Var v ) { + int kx = kindFcnx(v); + return kx == -1 ? null : _kinds.at(kx); + } // Find name in reverse, return an index into _vars or -1. Linear scan // instead of hashtable, but probably doesn't matter until the scan @@ -198,9 +217,9 @@ public class ScopeNode extends Node { * Create a new variable name in the current scope */ public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) { - assert _kinds.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory + assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory if( depth() > 0 ) - for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) { + for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) { Var n = var(i); if( n._name.equals(name) ) { if( !n.isFRef() ) return false; // Double define @@ -208,17 +227,45 @@ public class ScopeNode extends Node { 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 + return true; } } - Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL); + Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL); _vars.add(v); // Creating a forward reference - if( init==Parser.XCTRL ) - init = new FRefNode(v).init(); + if( init==CodeGen.CODE.XCTRL ) + //init = new FRefNode(v).init(); + throw Utils.TODO(); addDef(init); return true; } + // Normal lookup was tried and failed. Append a forward ref in the current + // lexical scope. Scope pops promote it outwards "as if" the final constant + // had been declared before the using scope. + + public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) { + Var var = new Var(nIns(),id,t,xfinal,loc,true); + FRefNode fref = new FRefNode(id,loc).init(); + fref._type = fref._con = t; + _vars.add(var); + addDef(fref); + return var; + } + + + // Parser state carries one complete, conservative memory value. Precise + // alias partitions belong to the graph and are introduced by peepholes. + public Node mem( int alias ) { + assert alias == 1; + return in(update(var(1),null)); + } + public void mem( int alias, Node st ) { + assert alias == 1; + update(var(1),st); + } + + /** * Lookup a name in all scopes starting from most deeply nested. * @@ -254,7 +301,7 @@ public class ScopeNode extends Node { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init()); setDef(v._idx,old); } //assert !v._final || st==null; @@ -292,9 +339,15 @@ public class ScopeNode extends Node { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory is one binding, with the same lazy loop Phi as scalar names. - for( int i=1; i<nIns(); i++ ) - dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); + // Memory is one ordinary parser-state value. At loop heads the + // Scope sentinel triggers the same lazy-Phi machinery as variables. + dup.addDef(loop ? this : mem()); + + // Copy of other inputs + for( int i=2; i<nIns(); i++ ) + // For lazy phis on loops we use a sentinel + // that will trigger phi creation on update + dup.addDef(loop && !var(i)._final ? this : in(i)); return dup; } @@ -308,14 +361,17 @@ public class ScopeNode extends Node { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - _merge(that,r); + this._merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } - private void _merge(ScopeNode that, RegionNode r) { - for( int i = 1; i < nIns(); i++) + public void _merge(ScopeNode that, RegionNode r) { + _merge(that,r,nIns()); + } + public void _merge(ScopeNode that, RegionNode r, int max ) { + for( int i = 1; i < max; i++) if( in(i) != that.in(i) ) { // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation @@ -328,18 +384,38 @@ public class ScopeNode extends Node { // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;". // Forward refs are copied to the other side, "as if" they were there all along. + // 'this' scope is the default, 'scope' might have frefs. public void balanceIf( ScopeNode scope ) { - for( int i = nIns(); i < scope.nIns(); i++ ) { + if( nIns()==scope.nIns() ) + return; // Fast-path cutout + int kidx = enclosingFunction(); + assert _kinds.at(kidx) == scope._kinds.at(kidx); + if( kidx==0 ) kidx = 1; + for( int i = _kinds.at(kidx-1)._lexSize; i < scope.nIns(); i++ ) { Var n = scope.var(i); + if( i<nIns() && var(i)==n ) continue; if( n.isFRef() ) { // RHS has forward refs - _vars.add(n); // Copy to LHS - addDef(scope.in(i)); + _vars.insert(n,i); + insertDef(i,scope.in(i)); } else throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); } } + public void balanceLoopFRefs( ScopeNode scope ) { + for( int i = nIns(); i < scope.nIns(); i++ ) { + Var n = scope.var(i); + if( n.isFRef() ) { // Loop body has forward refs + Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true); + v._uninit = n._uninit; + _vars.add(v); + addDef(scope.in(i)); + } + } + } + + // ------------------------------------------------ // 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 ) { @@ -347,49 +423,108 @@ public class ScopeNode extends Node { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - _endLoop(this,back,exit); + this._endLoop( this, back, exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - _useless(); + this._useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { for( int i=1; i<nIns(); i++ ) { - if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop - if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi - if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)in(i); + if( var(i)._final ) continue; // Final vars did not get modified in the loop + Node n = in(i); + if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi + Node con = ConstantNode.make(var(i).type()).init(); + n.subsume(con); + n = con; + } else if( back.in(i) != scope ) { + PhiNode phi = (PhiNode)n; assert phi.region()==scope.ctrl() && phi.in(2)==null; phi.setDef(2,back.in(i)); // Fill backedge } if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also - exit.setDef(i,in(i)); - } - } - - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.addAll(phi._outputs); - phi.moveDepsToWorklist(); - if( in != phi ) { - if( !phi.iskeep() ) // Keeping phi around for parser elsewhere - phi.subsume(in); - setDef(i,in); // Set the update back into Scope - } - } + exit.setDef(i,n); } } + // ------------------------------------------------ // Up-casting: using the results of an If to improve a value. // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null. - public void addGuards( Node ctrl, Node pred, boolean invert ) { - assert ctrl instanceof CFGNode; + // + // This operation is lexical, not a full semantic theorem prover, and thus + // its behavior changes the language spec. Optimizations that hide the + // lexical structure (e.g. constant folding control flow) can hide what is + // guarded or how the guard is applied, making it somewhat fragile. Several + // good use cases are covered here, but not everything you might like. + // + // Simple lexical facts work well: + // + // ``` + // if( ptr ) + // return ptr.field; // `ptr` is guarded non-null here. + // + // if( a && a.ptr ) + // return a.ptr.x; // `a` is guarded while parsing the RHS load, + // // and remains guarded on the true arm. + // ``` + // + // Short-circuit expressions are lowered as nested Phis. We recursively + // decompose the recognized Phi shape, so chains such as: + // + // ``` + // if( x && y && z ) + // use(x,y,z); + // ``` + // + // discover the nested `x && y` and then `x`, `y`, and `z`; this is not + // merely a one-level lookup. Likewise, nested tests carry lexical guards + // inwards: + // + // ``` + // if( x && y ) + // if( z && a ) + // if( x.ptr && z.ptr ) + // ... + // ``` + // + // Here `x` is already guarded while parsing the outer true arm, `z` while + // parsing the inner true arm, and both are available before the matching + // loads. The later post-test decomposition is just a convenience for + // values still schedulable at the post-test control. + // + // The hard limit is that a semantic implication is not always a + // schedulable Node. For `(x && y)`, the true side implies that `y` was + // true, but `y` might be a value computed only on the RHS control arm: + // + // ``` + // val ptr = ...; + // if( ptr && (ptr=fcn() ? 0 : arg ) {} + // ``` + // + // A Guard pinned after the Phi merge and using the branch-local `ptr` + // value would give that value a use above its defining arm. GCM then has + // no legal block between the value's early control and the LCA of its + // uses. Thus `_addGuards` first checks that the predicate's effective + // early CFG dominates the control where the Guard would be pinned. If it + // does not, we skip that guard even if the fact is semantically true. + // + // A more complete answer would need a richer logical/proof node, or would + // need to rematerialize predicates on the guarded path instead of using + // branch-local nodes directly. Plain GuardNodes are intentionally kept to + // facts represented by values already available at the guard control. + // + // TODO: Make this more robust for shapes like: + // ``` + // if( !ary || !ary# ) exit(); // facts about ary are lost, despite the early exit + // for( ...; ary# ) ...; + // ``` + // Here the guard ends up guarding the whole expression (both ary and ary# + // are not-null), but loses that ary is not-null. A better answer might be + // a dedicated logical expression node, or some sort of gather-of-checked + // values to Guard post-test. + + public void addGuards( CFGNode ctrl, Node pred, boolean invert ) { _guards.add(ctrl); // Marker between guard sets // add pred & its cast to the normal input list, with special Vars if( pred==null || pred.isDead() ) @@ -397,10 +532,11 @@ public class ScopeNode extends Node { _addGuards(ctrl,pred,invert); } - private void _addGuards( Node ctrl, Node pred, boolean invert ) { + private void _addGuards( CFGNode ctrl, Node pred, boolean invert ) { if( pred==null || pred.isDead() ) return; - if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) ) + CFGNode early = CFGNode.earlyCFG(pred); + if( early != null && !early.dominates(ctrl,null) ) return; // A single negation is handled below. For !!p (including p != null) // or a negated short-circuit Phi, also discover the underlying facts. @@ -410,8 +546,10 @@ public class ScopeNode extends Node { _addGuards(ctrl,not.in(1),!invert); pred.unkeep(); } - // A false || or true && proves both operands. Only guard values - // available at this control, not ones confined to the RHS branch. + // Short-circuit logic is represented by a Phi. For `a || b` being + // false, or `a && b` being true, both individual operands have the + // same proven value as the whole expression. The "skipped RHS" path + // is the Region input whose Phi value is the controlling If predicate. if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 ) for( int i=1; i<3; i++ ) if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) { @@ -425,38 +563,19 @@ public class ScopeNode extends Node { // Invert the If conditional if( invert ) pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole()); + Node zeroPred = pred instanceof NotNode not ? not.in(1) : null; // This is a zero/null test. // Compute the positive test type. - Type tnz = pred._type.nonZero(); - if( tnz!=null ) - _addGuard(tnz,ctrl,pred); + _addGuard(true,ctrl,pred); // Compute the negative test type. - if( pred instanceof NotNode not ) { - Node npred = not.in(1); - Type tzero = npred._type.makeZero(); - _addGuard(tzero,ctrl,npred); - } - } - - // A Phi's value is available at its Region. Other data nodes require all - // their inputs, including pinned control, to dominate the proposed guard. - private static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) { - if( n==null || n instanceof ConstantNode || visit.get(n._nid) ) return true; - visit.set(n._nid); - if( n instanceof PhiNode phi ) n = phi.region(); - if( n instanceof CFGNode cfg ) { - for( CFGNode dom=ctrl; dom!=null; dom=dom.idom() ) - if( dom==cfg ) return true; - return false; - } - for( Node def : n._inputs ) - if( !availableAt(def,ctrl,visit) ) return false; - return true; + if( zeroPred != null ) + _addGuard(false,ctrl,zeroPred); } private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) { - if( val == null ) return null; + if( val == null ) + return null; if( val == pred ) return (prj._idx==0)==invert ? val : null; if( val instanceof NotNode not && not.in(1)==pred ) @@ -468,19 +587,20 @@ public class ScopeNode extends Node { return null; } - 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(); + private void _addGuard(boolean nonZero, Node ctrl, Node pred) { + Node guard = new GuardNode(nonZero,ctrl,pred).peephole(); + if( guard != pred ) { + pred.keep(); + guard.keep(); _guards.add(pred); - _guards.add(cast); - replace(pred,cast); + _guards.add(guard); + replace(pred,guard,nonZero); } } // Remove matching pred/cast pairs from this guarded region. - public ScopeNode removeGuards( Node ctrl ) { + public void removeGuards( Node ctrl ) { assert ctrl instanceof CFGNode; // Pop the guards up to this region's marker. while( true ) { @@ -490,7 +610,6 @@ public class ScopeNode extends Node { g .unkill(); // Pop/kill cast _guards.pop().unkill(); // Pop/kill pred } - return this; } // If we find a guarded instance of pred, replace with the upcasted version @@ -520,11 +639,65 @@ public class ScopeNode extends Node { } - private void replace( Node old, Node cast ) { + private void replace( Node old, Node cast, boolean nonZero ) { assert old!=null && old!=cast; for( int i=0; i<nIns(); i++ ) - if( in(i)==old ) + if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) ) setDef(i,cast); } + private static boolean nullCheckedPhi( Node n, Node old ) { + if( !(n instanceof PhiNode phi) || phi.nIns()!=3 ) + return false; + if( phi.in(1)==null || phi.in(2)==null ) + return false; + int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 : + phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1; + if( zero == -1 ) + return false; + Node val = phi.in(3-zero); + return val==old || + val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old; + } + + // ------------------------------------------------------------------------ + // Insert a top-level public external symbol, "as-if" some #include/import + // was executed at the top of tile. + public Var insertExtern( ExternNode ext ) { + // Size of top-level scope + int lexSize = _kinds.at(0)._lexSize; + Var var = new Var(lexSize,ext._extern,ext._con,true,null); + insert(var,ext,0); + return var; + } + + // ------------------------------------------------------------------------ + // Insert a Var with Node n in the lex scope just prior to kidx + private void insert( Var var, Node n, int kidx ) { + // Insert just prior to scope kidx + int nidx = _kinds.at(kidx)._lexSize; + _vars.insert(var,nidx); + insertDef(nidx,n); + // All outer Kinds lexical offsets bump by one + for( int i = kidx; i < depth(); i++ ) + _kinds.at(i)._lexSize++; + // All Vars outside the innermost lexical scope bump out one + for( int i=nidx+1; i<_vars._len; i++ ) + var(i)._idx++; + } + + // Save the existing Node state to an array + public Node[] save() { + Node[] olds = new Node[nIns()]; + for( int i=0; i<nIns(); i++ ) + olds[i] = in(i)==null ? null : in(i).keep(); + return olds; + } + + // Restore saved Node state back into scope + public void restore( Node[] olds ) { + assert olds.length<=nIns(); + for( int i=0; i<olds.length; i++ ) + setDef(i,olds[i]==null ? null : olds[i].unkeep()); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java index e2951784..f657cf51 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -1,13 +1,14 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class ShlNode extends ArithNode { - public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } + @Override public Tag serialTag() { return Tag.Shl; } - @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } @Override long doOp( long x, long y ) { return x << y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -30,13 +31,13 @@ public class ShlNode extends ArithNode { if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) { // Shl of 0. - if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger ) + if( (shl.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // (x + c) << i => (x << i) + (c << i) if( lhs instanceof AddNode add && addDep(add).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(_loc,add.in(1),rhs).peephole(), Parser.con(sum) ); + return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) ); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 0023431f..e93ea0e8 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class ShrNode extends ArithNode { - public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } + @Override public Tag serialTag() { return Tag.Shr; } - @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } @Override long doOp( long x, long y ) { return x >>> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -21,7 +21,7 @@ public class ShrNode extends ArithNode { Type t2 = rhs._type; // Shr of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5) diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java index 211489fb..0e104c15 100644 --- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -3,13 +3,16 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.LRG; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.util.SB; public abstract class SplitNode extends MachConcreteNode { + public final LRG _lrg; // Live range for split public final String _kind; // Kind of split - public final byte _round; - public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; } + public final byte _round; // Debugging info for which RegAlloc round + public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; } @Override public String op() { return "mov"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.p("mov("); @@ -17,6 +20,7 @@ public abstract class SplitNode extends MachConcreteNode { else p.n(in(1)); return p.close(); } + public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; } @Override public Type compute() { return in(0)._type; } @Override public Node idealize() { return null; } @Override public void asm(CodeGen code, SB sb) { diff --git a/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java new file mode 100644 index 00000000..cd51f603 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; + +// Start of a single compilation unit, distinct from the outer whole-program Start. +public class StartCUNode extends StartNode { + public final String _fname; + public StartCUNode(StartNode start, StopCUNode stop, Type arg, String fname) { super(start,stop,arg); _fname = fname; } + public StartCUNode(StartCUNode start) { super(start); _fname = start._fname; } + @Override public Tag serialTag() { return Tag.StartCU; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) { + baos.packed2(types.get(_arg)); + baos.packed2(strs.get(_fname)); + } + static Node make( BAOS bais, String[] strs, Type[] types ) { + return new StartCUNode(null,null,types[bais.packed2()],strs[bais.packed2()]); + } + + @Override public CFGNode cfg0() { return (CFGNode)in(0); } + + // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1 + @Override public int idepth() { return 1; } + @Override public CFGNode idom(Node dep) { return cfg0(); } + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_fname); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java index e13d35c0..84c8a3a7 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -5,39 +5,76 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; +import java.util.Arrays; import java.util.HashSet; -import static com.seaofnodes.simple.util.Utils.TODO; -/** Start supplies control, whole memory, and the argument, in that order. */ +/** + * The Start node represents the external world calling, be it calling the + * entry <clinit> or just some linked caller calling. + * <p> + * Start initially has 1 input (arg) from outside and the initial control. + * In ch10 we also add mem aliases as structs get defined; each field in struct + * adds a distinct alias to Start's tuple. + * <p> + * By ch25, Start is the external world (or linker) calling with everything + * that has escaped. Start is NOT the entry <clinit> itself, which can assume + * no memory nor fidxs exist nor escaped. Start gets escaped things from Stop; + * together these close the Grand Cycle between the code we know and the code + * we don't. + */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; - public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); } + public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; } public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } - - @Override public String label() { return "Start"; } + @Override public Tag serialTag() { return Tag.Start; } + public Type arg() { return _arg; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); } - @Override public CFGNode cfg0() { return null; } + @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" + // IfNode or other multi-way branch. For Start, it's the class init for + // this compilation unit. @Override public CFGNode uctrl() { - // Find "main", it's the start. + // Find module.<clinit>, it's the start. CFGNode C = null; for( Node use : _outputs ) - if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) ) + if( use instanceof FunNode fun && fun.isModInit() ) { assert C==null; C = fun; } + if( C==null ) + for( FunNode fun : CodeGen.CODE._linker ) + if( fun!=null && !fun.isDead() && fun.isModInit() ) + { assert C==null; C = fun; } return C; } + boolean escapedFIDX(int fidx) { + // Public class fields are callable by the unknown external world + // regardless of the current SCCP approximation of Start memory. + if( CodeGen.CODE.publicFIDX(fidx) ) return true; + if( !(_type instanceof TypeTuple tt) ) + return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes + TypeMem tmem = (TypeMem)tt._types[1]; + if( tmem == TypeMem.BOT ) return false; + if( tmem == TypeMem.TOP ) return false; + if( XInt.isHigh(tmem._escFs) ) + return false; + return XInt.bit(tmem._escFs,fidx); + } @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); + StopNode stop = (StopNode)in(1); + TypeMem tmem = stop._type instanceof TypeTuple tt + ? tt.mem() + : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT; + assert tmem._alias==1 && !tmem._one; + return TypeTuple.make(Type.CONTROL,tmem,_arg); } @Override public Node idealize() { return null; } @@ -46,4 +83,5 @@ public class StartNode extends LoopNode implements MultiNode { @Override public int idepth() { return 0; } @Override public CFGNode idom(Node dep) { return null; } + @Override public boolean eq( Node n ) { return true; } } diff --git a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java new file mode 100644 index 00000000..ab0f5da6 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java @@ -0,0 +1,122 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; + +// Stop of a single compilation unit, distinct from the outer whole-program Stop. +public class StopCUNode extends StopNode { + public StopCUNode() { super(); } + public StopCUNode(StopNode stop) { super(stop); } + @Override public Tag serialTag() { return Tag.StopCU; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } + static Node make( BAOS bais ) { + StopCUNode stop = new StopCUNode(); + stop.setDefX(bais.packed1()-1,null); + return stop; + } + + public StartCUNode start() { + for( Node use : _outputs ) + if( use instanceof StartCUNode start ) + return start; + return null; + } + + @Override + public TypeTuple compute() { + // During Parsing, new Stops and Returns are being added. This Stop + // cannot know if more are coming, so must assume the worst. + if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse ) + return TypeTuple.STATE; + + StartCUNode start = start(); + // Memory start from all returns + TypeMem xmem = TypeMem.START; + // Escape summaries are monotone discovery facts. In Opto, a Return + // can widen from a precise set to FULL; the public-only normalization + // below must not forget private bits already discovered precisely from + // that Return on an earlier SCCP step. + if( CodeGen.CODE._phase == CodeGen.Phase.Opto && + _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem ) + xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs), + XInt.meet(xmem._escAs,omem._escAs)); + + for( Node def : _inputs ) { + ReturnNode ret = (ReturnNode)def; + if( ret._type == Type.TOP ) continue; + if( !(ret._type instanceof TypeTuple tret) ) + return TypeTuple.STATE; // Must be Type.BOTTOM + + FunNode fun = ret.fun(); + boolean noUserCtor = fun.isInstance() && + !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj); + // Instance <init> is an allocation helper, never an externally + // callable constructor when a user constructor exists. Otherwise + // the same no-arg <init> is the public constructor. + boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor); + TypeFunPtr sig = fun.sig(); + // If public init, the function escapes and the related class escapes + if( pub ) { + xmem = xmem.escapes(sig); + // Classes also escape their public clazz pointer, which then + // escapes all their public fields. Private fields do not + // escape, and are only available from the local class code. + if( fun.isClz() ) + xmem = xmem.escapesAliases(sig._sig[0]); + } + + // The instance <init> Start edge supplies conservative pre-Opto + // inputs, but its private-memory result is consumed only by the + // allocation sequence and is never an externally visible return. + if( fun.isInstance() && !noUserCtor ) continue; + + // Does this function escape? If not, then no need to MEET + // here as its effects are not visible to the outside world. + addDepForwards(start); // Check again if function escapes + if( pub || start.escapedFIDX(sig.fidx()) ) { + if( tret.ctl().isHigh() ) continue; // Return not reachable + // Capture (precisely?) all escaping pointer aliases and fidxs. + // Escaped fidxs means the linked world can call that function; + // escaped aliases means the linked world can R/W those aliases. + xmem = xmem.escapes(tret.ret()); + xmem = publicBottom(xmem); + xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem)); + } + } + assert xmem._alias==1 && !xmem._one; + return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM); + } + + // FULL escape sets arise from global BOTTOM. Across a compilation-unit + // boundary global BOTTOM exposes every public field, while retaining any + // private escapes already discovered precisely. + private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); } + private TypeMem publicBottom(TypeMem mem, TypeMem prior) { + // The pessimistic Iter pass naturally lifts transient FULL inputs to + // precise finite sets. Replacing FULL early there would let a later + // input refinement add bits, reversing Iter's monotonic direction. + // This normalization is needed only for Opto's optimistic grand cycle. + if( CodeGen.CODE._phase != CodeGen.Phase.Opto ) return mem; + int[] escFs = XInt.isHigh(mem._escFs) + ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs()) + : mem._escFs; + int[] escAs = XInt.isHigh(mem._escAs) + ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases()) + : mem._escAs; + return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs); + } + + @Override + public Node idealize() { + int len = nIns(); + for( int i=0; i<nIns(); i++ ) + if( in(i) instanceof ReturnNode ret && addDep(ret.fun()).isDead() ) + delDef(i--); + if( len != nIns() ) return this; + return null; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index 9f7a925b..e52a49a2 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -2,34 +2,45 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; -public class StopNode extends CFGNode { +import java.util.HashMap; +import java.util.IdentityHashMap; - public final String _src; +// A collection of function ReturnNodes for a compilation unit. +public class StopNode extends CFGNode { - public StopNode(String src) { + public StopNode() { super(); - _src = src; _type = compute(); } - public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } - - @Override - public String label() { - return "Stop"; + public StopNode(StopNode stop) { super(stop); } + @Override public Tag serialTag() { return Tag.Stop; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } + static Node make( BAOS bais ) { + StopNode stop = new StopNode(); + stop.setDefX(bais.packed1()-1,null); + return stop; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { // For the sake of many old tests, and single value prints as "return val" - ReturnNode ret1 = ret(); + ReturnNode ret1 = null; + for( Node n : _inputs ) { + if( n instanceof ReturnNode ret ) { + if( ret1 != null ) { ret1=null; break; } + ret1 = ret; + } + } if( ret1!=null ) return p.n(ret1); + p.p("Stop[ "); - for( Node ret : _inputs ) - if( ret!=null ) { - String name = ((ReturnNode)ret).fun()._name; + for( Node n : _inputs ) + if( n instanceof ReturnNode ret ) { + String name = ret.fun()._name; if( name== null || !name.startsWith("sys.") ) p.n(ret).p(" "); } @@ -42,25 +53,36 @@ public class StopNode extends CFGNode { // If a single Return, return it. // Otherwise, null because ambiguous. public ReturnNode ret() { - Node ret1 = this; - for( Node ret : _inputs ) { - String name = ((ReturnNode)ret).fun()._name; - if( name==null || !name.startsWith("sys.") ) - ret1 = ret1==this ? ((ReturnNode)ret) : null; + ReturnNode ret1 = null; + for( Node n : _inputs ) { + // TODO: remove isPublic + if( n instanceof ReturnNode ret && ret.fun().isPublic() ) { + if( ret1 != null ) return null; // Ambiguous + ret1 = ret; + } } - return ret1==this ? null : (ReturnNode)ret1; + return ret1; } @Override - public Type compute() { - return Type.BOTTOM; + public TypeTuple compute() { + // During Parsing, new Stops and Returns are being added. This Stop + // cannot know if more are coming, so must assume the worst. + if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse ) + return TypeTuple.STATE; + + // Just meet-over-inputs. + TypeTuple tt = TypeTuple.STOP_HIGH; + for( Node def : _inputs ) + tt = (TypeTuple)tt.meet(def._type); + assert tt.mem()._alias==1 && !tt.mem()._one; + return tt; } - @Override - public Node idealize() { + @Override public Node idealize() { int len = nIns(); for( int i=0; i<nIns(); i++ ) - if( addDep(((ReturnNode)in(i)).fun()).isDead() ) + if( in(i).nIns()==0 ) delDef(i--); if( len != nIns() ) return this; return null; @@ -70,7 +92,10 @@ public class StopNode extends CFGNode { if( validIDepth() ) return _idepth; int d=0; for( Node ret : _inputs ) - d = Math.max(d,((ReturnNode)ret).idepth()+1); + // All public results are hooked on the Stop, including data + // values, so must check CFG + if( ret instanceof CFGNode cfg ) + d = Math.max(d,cfg.idepth()+1); return cacheIDepth(d); } } diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 4e1d7ce3..4917e890 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -2,9 +2,14 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Store represents setting a value to a memory based object, in chapter 10 @@ -12,7 +17,10 @@ import com.seaofnodes.simple.type.*; */ public class StoreNode extends MemOpNode { - private final boolean _init; // Initializing writes are allowed to write null + final boolean _init; // Initializing writes are allowed to write null + // Semantic store width in bytes. Zero is the sole undecided state; + // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants. + private byte _size; /** * @param name The struct field we are assigning to @@ -21,20 +29,43 @@ public class StoreNode extends MemOpNode { * @param off The offset inside the struct base * @param value Value to be stored */ - 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, false, glb, mem, ptr, off, value); + public StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) { + this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0); + } + private StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) { + super(loc, name, alias, false, decl, ctrl, mem, ptr, off, value); _init = init; + _size = size; + } + StoreNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { + super(bais,strs,types,fileAliases,aliases,false); + addDef(null); + _init = bais.read() != 0; + _size = (byte)bais.read(); + } + @Override public Tag serialTag() { return Tag.Store; } + @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { + super.packed(baos,strs,types, anodes ); + baos.write(_init ? 1 : 0); + baos.write(_size); } - // Debugger label @Override boolean canDrop(MemOpNode other, Node dep) { return super.canDrop(other,dep) && _init==((StoreNode)other)._init; } + // Debugger label @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } + public Node nnptr() { + if( !(ptr() instanceof GuardNode cast) ) return ptr(); + assert cast._nonZero; + return cast.in(1); + } public Node val() { return in(4); } + public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; } + public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); }; @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -43,42 +74,110 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { - Type val = val()._type; - assert !_declaredType.isFRef(); - Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); - return TypeMem.make(_alias,t); + Type val = val()._type; + Type mem0 = mem()._type; + Type ptr0 = ptr()._type; + // Validate argument types + if( ptr0.isHigh() ) + return TypeMem.TOP; + if( !(mem0 instanceof TypeMem mem) || + !(ptr0 instanceof TypeMemPtr ptr) ) + return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT; + + // Sharpen memory value; required for narrowing stores where the parser + // inserts zero/sign masking and somebody reads the TypeMem type. + Type decl = declaredType(); + if( decl != Type.BOTTOM ) + { assert decl != Type.TOP; val = val.join(decl); } + // Allocation uses a private TypeMem and nothing else does. This + // memory is truly private; a temporary singleton until it escapes - + // which is never does in a constructor. + if( !_name.equals("[]") && mem._one ) + // Just track the stored value + return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val); + + // Normal aliasing Store. + assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing + + // Single alias store. If memory is fat, attempt to get a narrow slice. + Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts + ? ts._fields[ts.findAlias(_alias)]._t + : mem._t; + + // Same alias, meet into other fields. + Type t = val.meet(tfld); + // Bulk memory's final bit describes its conservative remainder, not + // this newly precise slice. Slice finality comes from the field. + Field fld = ptr._obj.field(_name); + boolean xfinal = fld != null && fld._final; + return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val); } @Override public Node idealize() { - if( mem() instanceof MemMergeNode merge ) { - setDef(1,CodeGen.CODE.add(merge.alias(_alias))); - return this; + // Freeze the semantic Store variant as soon as the pointer exposes a + // canonical field declaration. Waiting until after structural memory + // peeps can lose the declaration to a flow-sensitive constant field + // state. Imported Stores already carry a serialized non-zero width. + if( _size==0 && (_size= storageType()) != 0 ) { + unlock(); // Width participates in GVN identity. + return init(); } + assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias; - if( mem() instanceof CastNode cast ) { - setDef(1,cast.in(1)); + // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. + Node mem = mem(); + if( _alias != 1 && mem instanceof MemMergeNode merge ) { + setDef(1,merge.alias(_alias)); return this; } + Field fld; + if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null && _size != 0 ) { + // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)). + + // Normally I'd like to just drop in this xform and move on, but + // users of the bulk Store memory might nontheless depend on the + // sharp memory type (e.g. I64) from the store's memory. Using the + // MemMerge means using the bulk memory which e.g. might be BOT. + // Check for store users with the same alias, and use the new store + // directly, preserving the sharper graph. + Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem,ptr(),off(),val(),_init,_size).peephole(); + for( int i=0; i<nOuts(); i++ ) { + Node use = out(i); + if( useAlias(use)==fld._alias ) { + int idx = use._inputs.find(this); + assert idx != -1; + CodeGen.CODE.add(use).setDef(idx,st); + i--; // setDef removed use from this Store's outputs + } + } + MemMergeNode mmm = mem instanceof BulkMemPhiNode bulk + ? bulk.aggregate(mem,fld._alias,st) + : new MemMergeNode(null,mem); + if( !(mem instanceof BulkMemPhiNode) ) + mmm.alias(fld._alias,st); + return mmm.init(); + } + // Simple store-after-store on same address. Should pick up the // required init-store being stomped by a first user store. - if( mem() instanceof StoreNode st && - ptr()==st.ptr() && _alias==st._alias && // Must check same object - off()==st.off() && // And same offset + if( mem instanceof StoreNode st && + ptr()==st.ptr() && // Must check same object + off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers // Must have exactly one use of "this" or you get weird // non-serializable memory effects in the worse case. checkOnlyUse(st) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect + assert _name==st._name; // Equiv class aliasing is perfect setDef(1,st.mem()); return this; } // Value is automatically truncated by narrow store - if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { - int log = _declaredType.log_size(); + if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) { + int log = log_size(); if( log<3 ) { // And-mask vs narrow store long mask = ((TypeInteger)and.in(2)._type).value(); long bits = (1L<<(8<<log))-1; @@ -90,7 +189,7 @@ public class StoreNode extends MemOpNode { } // Store will chop high order bits off; math to change those bits can be dropped. - if( val() instanceof SarNode shr && + if( _size!=0 && val() instanceof SarNode shr && shr.in(1) instanceof ShlNode shl && shr.in(2)._type.isConstant() && shl.in(2)._type.isConstant() ) { @@ -106,15 +205,56 @@ public class StoreNode extends MemOpNode { return this; } } + return null; } + // Flow-sensitive pointer types can carry a current field value (e.g. the + // constant zero in a u32 field). Storage width comes from the canonical + // struct declaration. If the pointer/definition is not available yet, + // leave the Store undecided instead of consulting the value or `_con`. + private byte storageType() { + if( !(ptr()._type instanceof TypeMemPtr tmp) ) + return 0; + TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name); + Field fld = obj.field(_name); + if( fld == null || fld._t == null ) + return 0; + if( fld._t == Type.BOTTOM ) + return 0; // Store into a final field + assert fld._t != Type.TOP; + // Freshly parsed field stores retain the declaring structure on their + // symbolic offset even after the pointer's flow type sharpens. + if( fld._t.isConstant() ) { + if( !(off() instanceof ConFldOffNode coff) ) + return 0; + fld = coff._ts.field(_name); + if( fld == null || fld._t == null ) + return 0; + } + // Compute storage size from field type + byte size = (byte)(1 << fld._t.log_size()); + assert size==1 || size==2 || size==4 || size==8; + return size; + } + + // Alias required by a direct consumer of this Store's memory result. + // Keep in sync with BulkMemPhiNode.outputAlias. + private int useAlias(Node use) { + return switch( use ) { + case MemOpNode mem -> mem._alias; + case MemPhiNode phi -> phi._alias; + case EscapeNode esc -> throw Utils.TODO("Untested"); // esc.pub()==this ? esc.fld()._alias : 0; + default -> 0; + }; + } + // Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)). // With no observers of the backedge store, only the last value matters. // The entry load preserves the field even when the loop takes zero trips. Node sink(MemPhiNode phi) { phi.addDepForwards(this); - if( nOuts()!=1 ) return null; + if( nOuts()!=1 || _alias==1 || _size==0 || _init ) return null; phi.addDep(ptr()); // Start with a fixed field of an allocation dominating the loop. // In particular, do not sink stores through a loop-varying address. @@ -138,9 +278,9 @@ public class StoreNode extends MemOpNode { if( ctrl==null ) return null; Node mem = phi.in(1); - Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); - Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole()); - Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); + Node init = new LoadNode(_loc,_name,_alias,null,mem,ptr(),off()).peephole(); + Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole()); + Node sink = new StoreNode(_loc,_name,_alias,_con,null,mem,ptr(),off(),value,_init,_size); // Break the memory cycle, killing the old store. Keep the Phi alive // even if that store was its last use, until our caller replaces it. phi.keep(); @@ -164,11 +304,16 @@ public class StoreNode extends MemOpNode { public Parser.ParseException err() { Parser.ParseException err = super.err(); if( err != null ) return err; - if( ptr()._type == Type.TOP ) - return null; // This means we have an error input, report elsewhere + assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Opto.ordinal(); + if( ptr()._type == Type.TOP ) // Dead store? + return null; TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( (tmp._ro || tmp._obj.field(_name)._final) && !_init ) + Field f = tmp._obj.field(_name); + if( f!=null && (tmp._ro || f._final) && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); return null; } + + @Override public boolean eq(Node n) { return _size==((StoreNode)n)._size && super.eq(n); } + @Override int hash() { return super.hash() ^ _size; } } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java deleted file mode 100644 index 78b20e9f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; - -/** - * Build a compound object - */ -public class StructNode extends Node { - - public final TypeStruct _ts; - public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; } - - @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( _ts==null ) return p.p("STRUCT?"); - p.p(_ts._name).p(" {"); - for( int i=0; i<nIns(); i++ ) { - p.p(_ts._fields[i]._fname).p(":"); - p.p(in(i)==null ? Type.BOTTOM : in(i)._type); - p.p("; "); - } - return p.unchar("; ").p("}"); - } - - @Override - public TypeStruct compute() { - if( _ts==null ) return TypeStruct.BOT; - Field[] fs = new Field[_ts._fields.length]; - for( int i=0; i<fs.length; i++ ) - fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type); - return TypeStruct.make(_ts._name,false,fs); - } - - @Override - public Node idealize() { return null; } - - @Override - public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } - - @Override - int hash() { return _ts==null ? 0 : _ts.hashCode(); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/src/main/java/com/seaofnodes/simple/node/SubFNode.java deleted file mode 100644 index 3fc4f7ca..00000000 --- a/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class SubFNode extends ArithFNode { - public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); } - - @Override public String label() { return "SubF"; } - - @Override public String glabel() { return "-"; } - - @Override double doOp( double x, double y ) { return x-y; } - - @Override - public Node idealize() { - // Sub of 0. - Type t2 = in(2)._type; - if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) - return in(1); - - return null; - } - @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); } -} diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java index 2c4fed39..8eb1f4cc 100644 --- a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -6,12 +6,15 @@ import com.seaofnodes.simple.type.TypeInteger; import java.util.BitSet; public class SubNode extends ArithNode { + @Override boolean allowFloat() { return true; } public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); } + @Override public Tag serialTag() { return Tag.Sub; } - @Override public String label() { return "Sub"; } @Override public String op() { return "-"; } - @Override long doOp( long x, long y ) { return x - y; } + @Override long doOp( long x, long y ) { return x - y; } + @Override double doOp(double x, double y ) { return x - y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sub of same is 0 if( in(1)==in(2) ) @@ -32,8 +35,8 @@ public class SubNode extends ArithNode { Type t1 = lhs._type; Type t2 = rhs._type; - // Keep invalid operands for type checking. - if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { + // Keep invalid operands and unresolved numeric modes for type checking. + if( _mode==1 && t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { if( y.isConstant() && y.value()==0 ) return lhs; if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); } @@ -49,6 +52,5 @@ public class SubNode extends ArithNode { return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } - @Override Node copyF() { return new SubFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 840a95ec..151a326e 100644 --- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -8,8 +8,7 @@ import com.seaofnodes.simple.type.TypeInteger; public class ToFloatNode extends Node { public ToFloatNode(Node lhs) { super(null, lhs); } - - @Override public String label() { return "ToFloat"; } + @Override public Tag serialTag() { return Tag.ToFloat; } @Override protected String format() { return "(flt)%1"; } diff --git a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java index df740202..33806300 100644 --- a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -7,6 +7,7 @@ import com.seaofnodes.simple.type.TypeInteger; // Truncate toward zero, saturating overflow; NaN converts to zero. public class ToIntegerNode extends Node { public ToIntegerNode(Node lhs) { super(null,lhs); } + @Override public Tag serialTag() { return Tag.ToInteger; } @Override public String label() { return "ToInteger"; } @Override protected String format() { return "(int)%1"; } diff --git a/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/src/main/java/com/seaofnodes/simple/node/TypeNode.java new file mode 100644 index 00000000..3ab2d6f9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/TypeNode.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; +import java.util.BitSet; +import java.util.HashMap; + +// Node carries an extra internal type +public abstract class TypeNode extends Node { + public Type _con; + public TypeNode(Type t, Node... nodes) { super(nodes); assert t!=null; _con = t; } + public TypeNode(TypeNode tn) { super(tn); _con = tn._con; } + // Copy edges from ideal, but 'this' is a Type-Mach-Node (e.g. MemOpX86) and needs a _con + public TypeNode(Node ideal, Type con) { super(ideal); _con = con; } + + // Upgrade the internal type + @Override boolean _upgradeType( HashMap<String,Type> TYPES) { + return liftType(_con.upgradeType(TYPES)); + } + + // Lift type + public boolean liftType( Type t ) { + if( t == _con ) return false; + unlock(); // Unlock before changing _con + _con = t; + return true; + } + + @Override public boolean eq(Node n) { return _con==((TypeNode)n)._con; } + @Override int hash() { return _con.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java index 477b9af8..32e1deb9 100644 --- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.type.Type; public class XCtrlNode extends CFGNode { public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); } - @Override public String label() { return "Xctrl"; } + @Override public Tag serialTag() { return Tag.XCtrl; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); } @Override public boolean isConst() { return true; } @Override public Type compute() { return Type.XCONTROL; } diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java index 75cea98e..c825f396 100644 --- a/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class XorNode extends ArithNode { - public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } + @Override public Tag serialTag() { return Tag.Xor; } - @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } @Override long doOp( long x, long y ) { return x ^ y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -18,12 +18,13 @@ public class XorNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t1 = lhs._type; Type t2 = rhs._type; // Xor of 0. We do not check for (0^x) because this will already // canonicalize to (x^0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // Move constants to RHS: con*arg becomes arg*con diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java index 127f17ff..31bbde86 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -9,9 +9,11 @@ import com.seaofnodes.simple.util.Utils; // Jump on flags, uses flags public class BranchARM extends IfNode implements MachNode, RIPRelSize { String _bop; - BranchARM(IfNode iff, String bop ) { + final boolean _fp; + BranchARM(IfNode iff, String bop, boolean fp ) { super(iff); _bop = bop; + _fp = fp; } @Override public String op() { return "j"+_bop; } @Override public String label() { return op(); } @@ -36,7 +38,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize { // by comparison (or sub). No need for regs because it uses flags if( in(1)!=null ) { // B.cond - enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) ); + enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop, _fp)) ); } else { if( _bop=="!=" ) return; // Inverted, no code enc.add4(Arm64.b(Arm64.OP_UJMP, 0)); @@ -56,7 +58,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch if( opLen==4 ) { - enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop))); + enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop, _fp))); } else { throw Utils.TODO(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java index e1717cb3..4de28f3a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -14,8 +14,7 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize { _inputs.pop(); // Pop constant target assert tfp.isConstant(); _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = CodeGen.CODE.funcName(tfp.fidx()); } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java index dc21e075..db2540f7 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java @@ -4,21 +4,22 @@ import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; //FMOV (scalar, immediate) //Floating-point move immediate. -public class FloatARM extends ConstantNode implements MachNode, RIPRelSize { - FloatARM(ConstantNode con) { super(con); } +public class FltARM extends ConstantNode implements MachNode, RIPRelSize { + final String _ext; + FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.DMASK; } @Override public boolean isClone() { return true; } - @Override public FloatARM copy() { return new FloatARM(this); } + @Override public FltARM copy() { return new FltARM(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/); short dst = (short)(enc.reg(this) - arm.D_OFFSET); enc.add4(Arm64.load_pc(Arm64.OPF_ARM, 0, dst)); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java index 7a9e74c6..72b76edd 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -7,19 +7,22 @@ import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; // Integer constants public class IntARM extends ConstantNode implements MachNode { - IntARM( ConstantNode con ) { super(con); } + final String _ext; + IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ldi"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } - @Override public IntARM copy() { return new IntARM(this); } + @Override public IntARM copy() { return new IntARM(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); short self = enc.reg(this); long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value(); Arm64.constant(enc,self,x); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java index 8fc22a67..53494092 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -25,7 +25,7 @@ public class LoadARM extends MemOpARM { // ldr(immediate - unsigned offset) | ldr(register) @Override public void encoding( Encoding enc ) { - ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size()); + ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_con.log_size()], this, size()); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java index dfbe5e06..14eaf510 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -7,13 +7,13 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; public abstract class MemOpARM extends MemOpNode implements MachNode { final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned final int _imm; // Limit ? bits - final char _sz = (char)('0'+(1<<_declaredType.log_size())); + final int _bytes; + final char _sz; MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) { super(mop,mop); assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode); @@ -22,6 +22,8 @@ public abstract class MemOpARM extends MemOpNode implements MachNode { _inputs.setX(3, idx); _off = off; _imm = imm; + _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); + _sz = (char)('0'+_bytes); } // Store-based flavors have a value edge @@ -35,10 +37,10 @@ public abstract class MemOpARM extends MemOpNode implements MachNode { @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } + @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } + @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } - int size() { return 1<<_declaredType.log_size(); } + int size() { return _bytes; } // Wider mask to store both GPRs and FPRs @Override public RegMask regmap(int i) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java index 7110df62..8bb8983e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -32,7 +32,8 @@ public class NewARM extends NewNode implements MachNode, RIPRelSize { } // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi x0=#1\n"); + sb.p("\n"); // Multi-op trigger + sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); sb.p("call #calloc"); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java index afbbd3bf..e5b06c39 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -9,12 +9,14 @@ import com.seaofnodes.simple.util.SB; // e.g CSET, stores flag info into GPRS after compare public class SetARM extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== - SetARM(Node cmp, String bop) { + final boolean _fp; // FP compare (unsigned encoding) + SetARM(Node cmp, String bop, boolean fp) { super(cmp); _inputs.setLen(1); // Pop the cmp inputs // Replace with the matched cmp _inputs.push(cmp); _bop = bop; + _fp = fp; } @Override public String op() { return "set"+_bop; } @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } @@ -22,7 +24,7 @@ public class SetARM extends MachConcreteNode implements MachNode { @Override public void encoding( Encoding enc ) { // Using RM==RN==31 implies a *zero* value, not R31. - int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this)); + int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop, _fp), 0b011111, enc.reg(this)); enc.add4(body); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java index f93f0f24..dcfeb5d8 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -9,7 +9,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitARM extends SplitNode { - SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} + SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);} @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } @Override public RegMask outregmap() { return arm.SPLIT_MASK; } @@ -25,7 +25,7 @@ public class SplitARM extends SplitNode { if(src >= arm.MAX_REG) { throw Utils.TODO(); } - int off = enc._fun.computeStackOffset(enc._code,dst); + int off = fun(enc).computeStackOffset(enc._code,dst); if( srcX ) src -= arm.D_OFFSET; enc.add4(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8)); return; @@ -33,7 +33,7 @@ public class SplitARM extends SplitNode { if(src >= arm.MAX_REG) { // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); + int off = fun(enc).computeStackOffset(enc._code,src); if( dstX ) dst -= arm.D_OFFSET; enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); return; @@ -57,10 +57,4 @@ public class SplitARM extends SplitNode { enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst)); } } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java index 2cc54f76..0b90ac8b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -28,7 +28,7 @@ public class StoreARM extends MemOpARM { private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, }; @Override public void encoding( Encoding enc ) { - ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size()); + ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size()); } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java index 98a56089..a053ba46 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -2,28 +2,30 @@ package com.seaofnodes.simple.node.cpus.arm; import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { - TFPARM( ConstantNode con ) { super(con); } +public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize { + final String _ext; + // Pointer to a Simple function + TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; } + // Pointer to an Extern "C" function + TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } @Override public String op() { return "ldx"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } - @Override public TFPARM copy() { return new TFPARM(this); } + @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); } @Override public void encoding( Encoding enc ) { - enc.relo(this); - short self = enc.reg(this); + if( _ext==null ) enc.relo(this); // Internal patch + else enc.external(this,_ext); // ELF-file external patch + short dst = enc.reg(this); // adrp x0, 0 - int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self); + enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst)); // add x0, x0, 0 - enc.add4(adrp); - arm.imm_inst(enc,Arm64.OPI_ADD,0, 0); + arm.imm_inst(enc,Arm64.OPI_ADD,0, dst); } @Override public byte encSize(int delta) { @@ -34,15 +36,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short rpc = enc.reg(this); if(opLen == 8 ) { - // ARM encoding delta is from PC & 0xFFF - int target = opStart+delta; - int base = opStart & ~0xFFF; - delta = target-base; - int adrp_delta = delta >> 12; - // patch upper 20 bits via adrp - enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc)); - // low 12 bits via add - enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, rpc)); + arm.patch_adrp_add(enc, opStart, delta, rpc); } else { // should not happen as one instruction is 4 byte, and TFP arm encodes 2. throw Utils.TODO(); @@ -51,7 +45,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); - _con.print(sb.p(reg).p(" #")); + _type.print(sb.p(reg).p(" #")); } @Override public boolean eq(Node n) { return this==n; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java index d1cafa96..b2531f97 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java @@ -10,14 +10,16 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { - TMPARM( ConstantNode con ) { super(con); } + final String _ext; + TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ldp"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } - @Override public TMPARM copy() { return new TMPARM(this); } + @Override public TMPARM copy() { return new TMPARM(this,_ext); } @Override public void encoding( Encoding enc ) { - enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); + if( _ext!=null ) enc.externalData(this,_ext); + else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // adrp x0, 0 enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst)); @@ -33,15 +35,7 @@ public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); if(opLen == 8 ) { - // ARM encoding delta is from PC & 0xFFF - int target = opStart+delta; - int base = opStart & ~0xFFF; - delta = target-base; - int adrp_delta = delta >> 12; - // patch upper 20 bits via adrp - enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst)); - // low 12 bits via add - enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, dst)); + arm.patch_adrp_add(enc, opStart, delta, dst); } else { throw Utils.TODO(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 32c1e2fc..660f1559 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -212,6 +212,14 @@ public class arm extends Machine { enc.add4(body); } + public static void patch_adrp_add(Encoding enc, int opStart, int delta, int rd) { + int target = opStart + delta; + int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12; + int imm21 = pageDelta & 0x1FFFFF; + enc.patch4(opStart , Arm64.adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd)); + enc.patch4(opStart+4, Arm64.imm_inst_l(OPI_ADD, target & 0xFFF, rd)); + } + public static void f_cmp(Encoding enc, Node n) { short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); @@ -225,10 +233,16 @@ public class arm extends Machine { if( idx==1 ) return null; // Count floats in signature up to index if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence + boolean hidden = hiddenSelf(tfp); + int sigidx = idx-2; + if( hidden ) { + if( sigidx==0 ) return null; + sigidx--; + } // Count floats in signature up to index int fcnt=0; - for( int i=2; i<idx; i++ ) - if( tfp.arg(i-2) instanceof TypeFloat) + for( int i=hidden ? 1 : 0; i<idx-2; i++ ) + if( tfp.arg(i) instanceof TypeFloat) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -236,24 +250,27 @@ public class arm extends Machine { return XMMS[fcnt]; } else { RegMask[] cargs = CALLINMASK; - if( idx-2-fcnt < cargs.length ) - return cargs[idx-2-fcnt]; + if( sigidx-fcnt < cargs.length ) + return cargs[sigidx-fcnt]; } // Pass on stack slot (8 and higher) if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + (idx - 2)); + return new RegMask(MAX_REG + 1 + sigidx); } // Return the max stack slot used by this signature, or 0 @Override public short maxArgSlot( TypeFunPtr tfp ) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { + for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) { if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); return (short)nstk; } + private static boolean hiddenSelf(TypeFunPtr tfp) { + return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; + } private static final long CALLEE_SAVE = 1L<<X19 | @@ -271,7 +288,7 @@ public class arm extends Machine { @Override public int rpc() { return X30; } // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); } + @Override public SplitNode split(LRG lrg, String kind, byte round) { return new SplitARM(lrg,kind,round); } // Return a MachNode unconditional branch @Override public CFGNode jump() { @@ -281,30 +298,33 @@ public class arm extends Machine { // Instruction selection @Override public Node instSelect(Node n ) { return switch( n ) { - case AddFNode addf -> new AddFARM(addf); case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); + case PtrToIntNode ptr -> new PtrToIntMach(ptr); + case GuardNode guard -> new GuardMach(guard); case CallEndNode cend-> new CallEndMach(cend); case CProjNode c -> new CProjNode(c); case ConstantNode con-> con(con); - case DivFNode divf -> new DivFARM(divf); - case DivNode div -> new DivARM(div); + case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div); + case EscapeNode esc -> new EscapeNode(esc); case FunNode fun -> new FunARM(fun); + case FunPtrNode fptr -> fptr(fptr); case IfNode iff -> jmp(iff); case LoadNode ld -> ld(ld); case MemMergeNode mem-> new MemMergeNode(mem); - case MinusNode neg -> new NegARM(neg); - case MulFNode mulf -> new MulFARM(mulf); - case MulNode mul -> new MulARM(mul); + case MinusNode neg -> { + if( neg.mode()==2 ) throw Utils.TODO(); + yield new NegARM(neg); + } + case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul); case NewNode nnn -> new NewARM(nnn); case NotNode not -> new NotARM(not); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyMach(read); @@ -312,11 +332,12 @@ public class arm extends Machine { case SarNode sar -> asr(sar); case ShlNode shl -> lsl(shl); case ShrNode shr -> lsr(shr); + case StartCUNode start -> new StartCUNode(start); + case StopCUNode stop -> new StopCUNode(stop); case StartNode start -> new StartNode(start); case StopNode stop -> new StopNode(stop); case StoreNode st -> st(st); - case SubFNode subf -> new SubFARM(subf); - case SubNode sub -> sub(sub); + case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub); case ToFloatNode tfn -> new I2F8ARM(tfn); case ToIntegerNode cvt -> new F8ToIARM(cvt); case XorNode xor -> xor(xor); @@ -329,7 +350,7 @@ public class arm extends Machine { private Node cmp(BoolNode bool){ Node cmp = _cmp(bool); - return new SetARM(cmp, bool.op()); + return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM); } private Node _cmp(BoolNode bool) { if( bool.isFloat() ) @@ -348,13 +369,18 @@ public class arm extends Machine { // Most general arith ops will also set flags, which the Jmp needs directly. // Loads do not set the flags, and will need an explicit TEST String op = "!="; - if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); + boolean fp = false; + if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); } else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); - return new BranchARM(iff, op); + else iff.setDef(1, new BoolNode.NE(iff.in(1), + ConstantNode.raw(TypeInteger.ZERO), + (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1))); + return new BranchARM(iff, op, fp); } private Node add(AddNode add) { + if( add.mode()==2 ) + return new AddFARM(add); return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) ? new AddIARM(add, (int)ti.value()) : new AddARM(add); @@ -367,22 +393,30 @@ public class arm extends Machine { } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + if( !con._con.isConstant() ) + return ConstantNode.raw(con); // Default unknown caller inputs + String ext = con instanceof ExternNode ext0 ? ext0._extern : null; return switch( con._con ) { - case TypeInteger ti -> new IntARM(con); - case TypeFloat tf -> new FloatARM(con); - case TypeFunPtr tfp -> new TFPARM(con); - case TypeMemPtr tmp -> new TMPARM(con); + case TypeInteger ti -> new IntARM(con,ext); + case TypeFloat tf -> new FltARM(con,ext); + case TypeMemPtr tmp -> new TMPARM(con,ext); + case TypeFunPtr tfp -> new TFPARM(tfp,ext); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con); }; } + private Node fptr( FunPtrNode con ) { + return new TFPARM(con); + } + private Node call(CallNode call){ - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallARM(call, tfp) - : new CallRRARM(call); + return call.fptr() instanceof FunPtrNode fptr + ? new CallARM(call, (TypeFunPtr)fptr._type) + : call.fptr() instanceof ConstantNode con + ? new CallARM(call, (TypeFunPtr)con._type) + : new CallRRARM(call); } private Node or(OrNode or) { @@ -424,6 +458,12 @@ public class arm extends Machine { : new LsrARM(lsr); } + private Node prj(ProjNode prj) { + TypeInteger ret = cReturn(prj); + if( ret!=null ) return new ExtendARM(prj,ret); + return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj); + } + private static int off; private static Node idx; private Node st(StoreNode st) { @@ -449,9 +489,4 @@ public class arm extends Machine { return mop; } - private Node prj(ProjNode prj) { - TypeInteger ret = cReturn(prj); - return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret); - } - } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java index 14581111..59bad64e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java @@ -2,19 +2,19 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.FunPtrNode; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; // Add upper 20bits to PC. Immediate comes from the relocation info. -public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { - AUIPC( Type tfp ) { super(tfp); } +public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize { + AUIPC( FunPtrNode fptr ) { super(fptr); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public AUIPC copy() { return new AUIPC(_con); } + @Override public AUIPC copy() { return new AUIPC(this); } @Override public String op() { return "auipc"; } @Override public void encoding( Encoding enc ) { enc.relo(this); @@ -37,7 +37,6 @@ public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); sb.p(reg).p(" = PC+#"); - if( _con == null ) sb.p("---"); - else _con.print(sb); + _type.print(sb); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index f0ad8e88..b1426a3f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -15,8 +15,7 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize { assert tfp.isConstant(); _inputs.pop(); // Pop constant target _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = CodeGen.CODE.funcName(tfp.fidx()); } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java index 5eee596d..cda4bc79 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -14,7 +14,7 @@ public class CallRRISC extends CallNode implements MachNode { ? riscv.RMASK // Function call target : riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument } - @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap() { return riscv.RPC_MASK; } @Override public void encoding( Encoding enc ) { short rpc = enc.reg(this); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java index 51ddc9db..a110d156 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java @@ -9,12 +9,13 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class FltRISC extends ConstantNode implements MachNode, RIPRelSize { - FltRISC(ConstantNode con) { super(con); } + final String _ext; + FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.FMASK; } @Override public boolean isClone() { return true; } - @Override public FltRISC copy() { return new FltRISC(this); } + @Override public FltRISC copy() { return new FltRISC(this,_ext); } @Override public void encoding( Encoding enc ) { enc.largeConstant(this,_con, 0, -1); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java index 23ac8481..dd4a02a3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java @@ -5,21 +5,23 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; // Special instruction for loading 8 byte constants from the constant pool. public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize { - public Int8RISC(ConstantNode con) { super(con); } - + final String _ext; + Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public Int8RISC copy() { return new Int8RISC(this); } + @Override public Int8RISC copy() { return new Int8RISC(this,_ext); } @Override public byte encSize(int delta) { return 8; } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); short dst = enc.reg(this); short tmp = (short)riscv.T6; enc.largeConstant(this,_con, 0, -1); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java index 9088abbd..35d2bc65 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -11,7 +11,7 @@ import com.seaofnodes.simple.util.SB; // 12-bit integer constant. Larger constants are made up in the instruction // selection by adding with a LUI. public class IntRISC extends ConstantNode implements MachNode { - public IntRISC(ConstantNode con) { super(con); } + IntRISC(ConstantNode con) { super(con); } @Override public String op() { return "ldi"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java index 2e3dfd8d..e9f3ec02 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -3,7 +3,9 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; // Load memory addressing on RISC // Support imm, reg(direct), or reg+off(indirect) addressing @@ -22,6 +24,39 @@ public class LoadRISC extends MemOpRISC { if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET; enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off)); } + @Override int func3() { + int func3 = -1; + // no unsigned flavour for store, so both signed and unsigned trigger the same + Type declType = _con; + if( declType == TypeInteger. I8 ) func3=0; // LB + if( declType == TypeInteger.I16 ) func3=1; // LH + if( declType == TypeInteger.I32 ) func3=2; // LW + if( declType == TypeInteger.BOT ) func3=3; // LD + if( declType == TypeInteger. U8 ) func3=4; // LBU + if( declType == TypeInteger.BOOL) func3=4; // LBU + if( declType == TypeInteger.U16 ) func3=5; // LHU + if( declType == TypeInteger.U32 ) func3=6; // LWU + if( declType instanceof TypeInteger ti ) { + if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB + } + + // float + if( declType == TypeFloat.F32) func3 = 2; // fLW fSW + if( declType == TypeFloat.F64) func3 = 3; // fLD fSD + + if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld) + if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers + if( declType instanceof TypeConAry ary ) + func3 = switch( ary.log_size() ) { + case 0 -> 4; // LBU + case 1 -> 5; // LHU + case 2 -> 2; // LW + case 3 -> 3; // LD + default -> throw Utils.TODO("RISC-V constant-array load for "+declType); + }; + if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType); + return func3; + } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index adf21848..e0bbc219 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -11,6 +11,7 @@ import com.seaofnodes.simple.type.*; public abstract class MemOpRISC extends MemOpNode implements MachNode { final int _off; // Limit 12 bits final char _sz; + final int _bytes; MemOpRISC(MemOpNode mop, Node base, int off, Node val) { super(mop,mop); assert base._type instanceof TypeMemPtr; @@ -18,7 +19,8 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode { _inputs.setX(3, null); // Never an index _inputs.setX(4, val ); _off = off; - _sz = (char)('0'+(1<<_declaredType.log_size())); + _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); + _sz = (char)('0'+_bytes); } @Override public String label() { return op(); } @@ -26,42 +28,11 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode { @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } + @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } + @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } // func3 is based on load/store size and extend - int func3() { - int func3 = -1; - // no unsigned flavour for store, so both signed and unsigned trigger the same - if(this instanceof StoreRISC) { - if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB - if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH - if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 ) func3=2; // SW - if( _declaredType instanceof TypeMemPtr) func3=3; // SD - if( _declaredType == TypeInteger.BOT ) func3=3; // SD - if( func3 == -1 ) throw Utils.TODO(); - return func3; - } - if( _declaredType == TypeInteger. I8 ) func3=0; // LB - if( _declaredType == TypeInteger.I16 ) func3=1; // LH - if( _declaredType == TypeInteger.I32 ) func3=2; // LW - if( _declaredType == TypeInteger.BOT ) func3=3; // LD - if( _declaredType == TypeInteger. U8 ) func3=4; // LBU - if( _declaredType == TypeInteger.BOOL) func3=4; // LBU - if( _declaredType == TypeInteger.U16 ) func3=5; // LHU - if( _declaredType == TypeInteger.U32 ) func3=6; // LWU - if( _declaredType instanceof TypeInteger ti ) { - if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB - } - - // float - if( _declaredType == TypeFloat.F32) func3 = 2; // fLW fSW - if( _declaredType == TypeFloat.F64) func3 = 3; // fLD fSD - - if( _declaredType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld) - if( func3 == -1 ) throw Utils.TODO(); - return func3; - } + abstract int func3(); // Register mask allowed on input i. @Override public RegMask regmap(int i) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index 965a3c7c..40d0359c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -41,8 +41,9 @@ public class NewRISC extends NewNode implements MachNode, RIPRelSize { // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi a1=#1\n"); + sb.p("\n"); // Multi-op trigger + sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); sb.p("auipc a2=#calloc\n"); - sb.p("call a2+#calloc, a0"); + sb.p("call a2+#calloc"); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java index 8a16f6b7..d455337b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -9,7 +9,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitRISC extends SplitNode { - SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); } + SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); } @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; } @Override public RegMask outregmap() { return riscv.SPLIT_MASK; } @Override public void encoding( Encoding enc ) { @@ -25,7 +25,7 @@ public class SplitRISC extends SplitNode { if( src >= riscv.MAX_REG ) { throw Utils.TODO(); // Very rare stack-stack move } - int off = enc._fun.computeStackOffset(enc._code,dst); + int off = fun(enc).computeStackOffset(enc._code,dst); int op = srcX ? RiscV.OP_STOREFP : RiscV.OP_STORE; if( srcX ) src -= riscv.F_OFFSET; enc.add4(RiscV.s_type(op, 0b011, riscv.RSP, src, off)); @@ -33,7 +33,7 @@ public class SplitRISC extends SplitNode { } if( src >= riscv.MAX_REG ) { // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); + int off = fun(enc).computeStackOffset(enc._code,src); int op = dstX ? RiscV.OP_LOADFP : RiscV.OP_LOAD; if( dstX ) dst -= riscv.F_OFFSET; enc.add4(RiscV.i_type(op, dst, 0b011, riscv.RSP, off)); @@ -58,10 +58,4 @@ public class SplitRISC extends SplitNode { enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001)); } } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index 48cb7c78..c3fcf4d3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -4,6 +4,7 @@ import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; @@ -27,7 +28,7 @@ public class StoreRISC extends MemOpRISC { if( i==1 ) return null; // mem if( i==2 ) return riscv.RMASK; // ptr // 2 - index - if( i==4 ) return _sz >= '4' + if( i==4 ) return _bytes >= 4 ? riscv.MEM_MASK // Word/dword value in GPR or FPR : riscv.RMASK; // Byte/short stores require a GPR return null; // Anti-dependence @@ -42,6 +43,11 @@ public class StoreRISC extends MemOpRISC { enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off)); } + // func3 is based on load/store size and extend + @Override int func3() { + return Integer.numberOfTrailingZeros(_bytes); + } + @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); if( val()==null ) sb.p("#").p("0"); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java index e57f6e09..44ef1a95 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -2,25 +2,27 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { - TFPRISC(ConstantNode con) { super(con); } +public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize { + final String _ext; + // Pointer to a Simple function + TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; } + // Pointer to an Extern "C" function + TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } @Override public String op() { return "ldx"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public TFPRISC copy() { return new TFPRISC(this); } + @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); } @Override public void encoding( Encoding enc ) { - enc.relo(this); + if( _ext==null ) enc.relo(this); // Internal patch + else enc.external(this,_ext); // ELF-file external patch // TODO: 1 op encoding, plus a TODO if it does not fit short dst = enc.reg(this); - TypeFunPtr tfp = (TypeFunPtr)_con; // auipc t0,0 enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); // addi t1,t0 + #0 @@ -33,9 +35,14 @@ public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short rpc = enc.reg(this); if( opLen==8 ) { - // ADDI sign-extends its low 12 bits, so round the AUIPC high part. - enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12)); - enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); + int hi20 = (delta + 0x800) >> 12; + int lo12 = delta - (hi20 << 12); + // AUIPC (upper 20 bits) + // opstart of add + enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, hi20)); + // addi(low 12 bits) + enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF)); + // addi } else { // should not happen as one instruction is 4 byte, and TFP arm encodes 2. throw Utils.TODO(); @@ -43,7 +50,7 @@ public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { } @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" #")); + _type.print(sb.p(code.reg(this)).p(" #")); } @Override public boolean eq(Node n) { return this==n; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java index 7bc2db7e..a5ff3753 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -9,14 +9,16 @@ import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { - TMPRISC(ConstantNode con) { super(con); } + final String _ext; + TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ldp"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public TMPRISC copy() { return new TMPRISC(this); } + @Override public TMPRISC copy() { return new TMPRISC(this,_ext); } @Override public void encoding( Encoding enc ) { - enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); + if( _ext!=null ) enc.externalData(this,_ext); + else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // AUIPC dst,#hi20_constant_pool enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); @@ -29,9 +31,9 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); - // Round the high part because ADDI's low 12 bits are signed. + // AUIPC dst,#hi20_constant_pool enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12)); - // ADDI dst,dst,#low12_constant_pool + // ADDI's low 12 bits are signed; round the AUIPC page accordingly. enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF)); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 7c994624..19b91866 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -15,7 +15,6 @@ public class riscv extends Machine { } @Override public String name() {return "riscv";} - //@Override public int maxReg() { return MAX_REG; } // Using ABI names instead of register names public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7; @@ -34,24 +33,24 @@ public class riscv extends Machine { public static final int F_OFFSET = 32; static final String[] REGS = new String[] { - "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" , - "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , - "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , - "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , - "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , - "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , - "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , - "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" - // Register-based naming hella easier to debug than "official" register - // names when single-stepping in debugger. - //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , - //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15", - //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23", - //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31", + //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" , + //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , + //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , + //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" + // Register-based naming hella easier to debug than "official" register + // names when single-stepping in debugger. + "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , + "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15", + "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23", + "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31", + "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , + "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , + "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , + "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" }; @Override public String[] regs() { return REGS; } @@ -92,7 +91,7 @@ public class riscv extends Machine { public static RegMask FA6_MASK = new RegMask(FA6); public static RegMask FA7_MASK = new RegMask(FA7); - // Int arguments calling conv + // Int/ptr arguments calling convention static RegMask[] CALLINMASK = new RegMask[] { A0_MASK, A1_MASK, @@ -138,10 +137,16 @@ public class riscv extends Machine { if( idx==0 ) return RPC_MASK; if( idx==1 ) return null; if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence + boolean hidden = hiddenSelf(tfp); + int sigidx = idx-2; + if( hidden ) { + if( sigidx==0 ) return null; + sigidx--; + } // Count floats in signature up to index int fcnt=0; - for( int i=2; i<idx; i++ ) - if( tfp.arg(i-2) instanceof TypeFloat ) + for( int i=hidden ? 1 : 0; i<idx-2; i++ ) + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -149,23 +154,26 @@ public class riscv extends Machine { return XMMS[fcnt]; } else { RegMask[] cargs = CALLINMASK; - if( idx-2-fcnt < cargs.length ) - return cargs[idx-2-fcnt]; + if( sigidx-fcnt < cargs.length ) + return cargs[sigidx-fcnt]; } // Pass on stack slot(8 and higher) if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + (idx - 2)); + return new RegMask(MAX_REG + 1 + sigidx); } @Override public short maxArgSlot( TypeFunPtr tfp ) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { + for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) { if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); return (short)nstk; } + private static boolean hiddenSelf(TypeFunPtr tfp) { + return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; + } static final long CALLER_SAVE = (1L<<RPC) | @@ -186,7 +194,7 @@ public class riscv extends Machine { @Override public CFGNode jump() { return new UJmpRISC(); } // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); } + @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); } // True if signed 12-bit immediate public static boolean imm12(TypeInteger ti) { @@ -203,30 +211,33 @@ public class riscv extends Machine { @Override public Node instSelect( Node n ) { return switch (n) { - case AddFNode addf -> addf(addf); case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); + case PtrToIntNode ptr -> new PtrToIntMach(ptr); + case GuardNode guard -> new GuardMach(guard); case CallEndNode cend -> new CallEndMach(cend); case CProjNode c -> new CProjNode(c); case ConstantNode con -> con(con); - case DivFNode divf -> new DivFRISC(divf); - case DivNode div -> new DivRISC(div); + case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div); + case EscapeNode esc -> new EscapeNode(esc); case FunNode fun -> new FunRISC(fun); + case FunPtrNode fptr -> fptr(fptr); case IfNode iff -> jmp(iff); case LoadNode ld -> ld(ld); case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> new NegRISC(neg); - case MulFNode mulf -> new MulFRISC(mulf); - case MulNode mul -> new MulRISC(mul); + case MinusNode neg -> { + if( neg.mode()==2 ) throw Utils.TODO(); + yield new NegRISC(neg); + } + case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul); case NewNode nnn -> nnn(nnn); case NotNode not -> new NotRISC(not); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); - case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); + case BulkMemPhiNode phi-> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -234,11 +245,12 @@ public class riscv extends Machine { case SarNode sar -> sra(sar); case ShlNode shl -> sll(shl); case ShrNode shr -> srl(shr); + case StartCUNode start -> new StartCUNode(start); + case StopCUNode stop -> new StopCUNode(stop); case StartNode start -> new StartNode(start); case StopNode stop -> new StopNode(stop); case StoreNode st -> st(st); - case SubFNode subf -> new SubFRISC(subf); - case SubNode sub -> sub(sub); + case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub); case ToFloatNode tfn -> i2f8(tfn); case ToIntegerNode cvt -> new F8ToIRISC(cvt); case XorNode xor -> xor(xor); @@ -249,11 +261,9 @@ public class riscv extends Machine { }; } - private Node addf(AddFNode addf) { - return new AddFRISC(addf); - } - private Node add(AddNode add) { + if( add.mode()==2 ) + return new AddFRISC(add); if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) ) return new AddIRISC(add, (int)ti.value(),true); return new AddRISC(add); @@ -271,8 +281,10 @@ public class riscv extends Machine { } private Node call(CallNode call) { - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallRISC(call, tfp) + return call.fptr() instanceof FunPtrNode fptr + ? new CallRISC(call, (TypeFunPtr)fptr._type) + : call.fptr() instanceof ConstantNode con + ? new CallRISC(call, (TypeFunPtr)con._type) : new CallRRISC(call); } private Node nnn(NewNode nnn) { @@ -311,9 +323,13 @@ public class riscv extends Machine { } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + if( !con._con.isConstant() ) + return ConstantNode.raw(con); // Default unknown caller inputs + String ext = con instanceof ExternNode ext0 ? ext0._extern : null; return switch( con._con ) { case TypeInteger ti -> { + // External; without knowing the size assume we have to load something + if( ext!=null ) yield new Int8RISC(con,ext); if( imm12(ti) ) yield new IntRISC(con); long x = ti.value(); if( imm20Exact(ti) ) yield new LUI((int)x); @@ -326,18 +342,22 @@ public class riscv extends Machine { } // Need more complex sequence for larger constants... or a load // from a constant pool, which does not need an extra register - yield new Int8RISC(con); + yield new Int8RISC(con,null); } // Load from constant pool - case TypeFloat tf -> new FltRISC(con); - case TypeFunPtr tfp -> new TFPRISC(con); - case TypeMemPtr tmp -> new TMPRISC(con); + case TypeFloat tf -> new FltRISC(con,ext); + case TypeMemPtr tmp -> new TMPRISC(con,ext); + case TypeFunPtr tfp -> new TFPRISC(tfp,ext); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con); }; } + private Node fptr( FunPtrNode con ) { + return new TFPRISC(con); + } + private Node jmp( IfNode iff ) { if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) { // if less than or equal switch inputs @@ -394,7 +414,7 @@ public class riscv extends Machine { private Node prj(ProjNode prj) { TypeInteger ret = cReturn(prj); if( ret!=null ) return new ExtendRISC(prj,ret); - return new ProjRISC(prj); + return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj); } private Node ld(LoadNode ld) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java index 974f690f..74af9266 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class AddFMemX86 extends MemOpX86 { - AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { + AddFMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { super(add,ld, base, idx, off, scale, 0, val ); } @Override public String op() { return "addf"+_sz; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index d1a43b83..6bafbf37 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -9,21 +9,27 @@ import com.seaofnodes.simple.util.SB; public class CallX86 extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; + final boolean _extern; CallX86( CallNode call, TypeFunPtr tfp ) { super(call); _inputs.pop(); // Pop constant target assert tfp.isConstant(); _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = CodeGen.CODE.funcName(tfp.fidx()); + _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null; } @Override public String op() { return "call"; } @Override public String label() { return op(); } @Override public String name() { return _name; } @Override public TypeFunPtr tfp() { return _tfp; } - @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); } + @Override public RegMask regmap(int i) { + return _extern + ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv) + : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); + } @Override public RegMask outregmap() { return null; } @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr + @Override public boolean external() { return _extern; } @Override public void encoding( Encoding enc ) { FunNode fun = CodeGen.CODE.link(_tfp); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 7b7d98d5..4014045b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -20,8 +20,8 @@ public class CmpMemX86 extends MemOpX86 { short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); if( src==-1 ) { assert !_swap; - X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); - } else encVal(enc,_declaredType,ptr,idx,src,_off,_scale,_swap); + X86.cmpImm(enc,_con.log_size(),_imm,ptr,idx,_off,_scale); + } else encVal(enc,_con,ptr,idx,src,_off,_scale,_swap); } static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index da9bdce7..59f06341 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -4,17 +4,20 @@ import com.seaofnodes.isa.X86; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ private byte _encSize; - FltX86(ConstantNode con ) { super(con); } + final String _ext; + FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.XMASK; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new FltX86(this); } + @Override public Node copy() { return new FltX86(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET); enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index c7a20e4d..0e44aee7 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -11,6 +11,7 @@ public class FunX86 extends FunNode implements MachNode { FunX86( FunNode fun ) { super(fun); } @Override public void postSelect(CodeGen code) { super.postSelect(code); + _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv); // One more for RPC pre-pushed; X86 special _maxArgSlot++; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java index 656b1543..2ffaa3d1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -6,15 +6,17 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; // Integer constants public class IntX86 extends ConstantNode implements MachNode { - IntX86( ConstantNode con ) { super(con); } + final String _ext; + IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi"; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new IntX86(this); } + @Override public Node copy() { return new IntX86(this,_ext); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Zero-set uses XOR kills flags @@ -23,6 +25,7 @@ public class IntX86 extends ConstantNode implements MachNode { } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value()); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index d4f1413e..5c472dbe 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -13,7 +13,7 @@ public class LoadX86 extends MemOpX86 { super(ld,ld, base, idx, off, scale, 0); } @Override public String op() { return "ld"+_sz; } - @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; } + @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; } @Override public void encoding( Encoding enc ) { // REX.W + 8B /r MOV r64, r/m64 // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32 @@ -21,7 +21,7 @@ public class LoadX86 extends MemOpX86 { short dst = enc.reg(this ); short ptr = enc.reg(ptr()); short idx = enc.reg(idx()); - enc(enc, _declaredType, dst, ptr, idx, _off, _scale); + enc(enc, _con, dst, ptr, idx, _off, _scale); } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index 309e62d9..fcca7f8f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; // Generic X86 memory operand base. @@ -42,7 +41,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { _off = off; _scale = scale; _imm = imm; - _sz = (char)('0'+(1<<_declaredType.log_size())); + int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); + _sz = (char)('0'+size); } // Store-based flavors have a value edge @@ -59,8 +59,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { } @Override public String label() { return op(); } - @Override public Type compute() { throw Utils.TODO(); } - @Override public Node idealize() { throw Utils.TODO(); } + @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } + @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } // Register mask allowed on input i. @Override public RegMask regmap(int i) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index 1cfc1c42..d6a06c2f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -16,7 +16,8 @@ public class NewX86 extends NewNode implements MachNode { } // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi rcx = #1\n"); + sb.p("\n"); // Multi-op trigger + sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); sb.p("call #calloc"); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java index e8281975..b2b643d3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -12,8 +12,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitX86 extends SplitNode { - SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } - @Override public String op() { return "mov"; } + SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); } @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } @@ -26,10 +25,10 @@ public class SplitX86 extends SplitNode { boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET; // Stack spills if( dst >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,dst); + int off = fun(enc).computeStackOffset(enc._code,dst); if( src >= x86_64_v2.MAX_REG ) { // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] - int soff = enc._fun.computeStackOffset(enc._code,src); + int soff = fun(enc).computeStackOffset(enc._code,src); X86.stackCopy(enc,soff,off); return; } @@ -37,7 +36,7 @@ public class SplitX86 extends SplitNode { return; } if( src >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,src); + int off = fun(enc).computeStackOffset(enc._code,src); LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); return; } @@ -45,10 +44,4 @@ public class SplitX86 extends SplitNode { X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst, srcX ? src-x86_64_v2.XMM_OFFSET : src); } - - // General form: "mov dst = src" - @Override public void asm(CodeGen code, SB sb) { - FunNode fun = code._encoding==null ? null : code._encoding._fun; - sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); - } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index c83dfd44..9ffa25fc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -27,8 +27,8 @@ public class StoreX86 extends MemOpX86 { @Override public RegMask outregmap() { return null; } @Override public void encoding( Encoding enc ) { short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); - if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); - else encVal(enc,_declaredType,ptr,idx,src,_off,_scale); + if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale); + else encVal(enc,_con,ptr,idx,src,_off,_scale); } // Non-immediate encoding diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java index fff2fb04..e3563713 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -3,39 +3,39 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.isa.X86; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; // Function constants -public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { - TFPX86( ConstantNode con ) { super(con); } - @Override public String op() { - return _con == Type.NIL ? "xor" : "ldx"; - } +public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize { + private byte _opLen; + final String _ext; // External name + // Pointer to a Simple function + TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); } + // Spill clone: copy inputs without registering their reverse edges; + // insertBefore will place and register the clone in its use block. + private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; } + // Pointer to an Extern "C" function + TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } + @Override public String op() { return "ldx"; } + @Override public String label() { return op(); } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new TFPX86(this); } + @Override public TFPX86 copy() { return new TFPX86(this); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } - // Zero-set uses XOR kills flags - @Override public RegMask killmap() { - return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null; - } - @Override public void encoding( Encoding enc ) { - if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; } - enc.relo(this); - X86.rip(enc,0,0x8D,true,enc.reg(this)); + if( _ext==null ) enc.relo(this); + else enc.external(this,_ext); + _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this)); } - @Override public byte encSize(int delta) { - return 7; - } + @Override public byte encSize(int delta) { return _opLen; } - // Delta is from opcode start + // Delta is from opcode start, X86 expects it from opcode end @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart + 3, delta - 7); + enc.patch4(opStart + opLen - 4, delta - opLen); } // Human-readable form appended to the SB. Things like the encoding, @@ -43,11 +43,9 @@ public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { // Just something like "ld4\tR17=[R18+12] // Load array base". // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - String reg = code.reg(this); - if( _con == Type.NIL ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" = #")); + sb.p(code.reg(this)); + if( _ext == null ) _type.print(sb.p(" #")); + else sb.p(_ext); } @Override public boolean eq(Node n) { return this==n; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java index c78ac6de..682597f4 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java @@ -7,30 +7,29 @@ import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ - TMPX86(ConstantNode con ) { super(con); } + private byte _opLen; + final String _ext; + TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; } @Override public String op() { return "ldp"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new TMPX86(this); } + @Override public Node copy() { return new TMPX86(this,_ext); } @Override public void encoding( Encoding enc ) { short dst = enc.reg(this); - X86.rexF(dst, 0, 0, true, enc); - enc.add1(0x8D).add1(X86.modrm(X86.MOD.INDIRECT, dst, 0b101)).add4(0); - int opLen = 1/*rexF*/+1/*opcode 0x8D*/+1/*modrm*/+4/*pc rel offset*/; - // Base of constant array - TypeMemPtr tmp = (TypeMemPtr)_con; - int baseOff = tmp._obj.aryBase(); - enc.largeConstant(this,tmp._obj,opLen-baseOff,2/*ELF encoding PC32*/); + // 0 or 1 for REX depending on the dst. + _opLen = X86.rip(enc,0,0x8D,true,dst); + if( _ext!=null ) enc.externalData(this,_ext); + else enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/); } // Delta is from opcode start - @Override public byte encSize(int delta) { return (byte)7; } + @Override public byte encSize(int delta) { return _opLen; } // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart+opLen-4,delta-opLen); + enc.patch4(opStart + opLen - 4, delta - opLen); } @Override public void asm(CodeGen code, SB sb) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index e283bc8f..ff15fdad 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -97,22 +97,29 @@ public class x86_64_v2 extends Machine { // Map from function signature and argument index to register. // Used to set input registers to CallNodes, and ParmNode outputs. @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); } static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { + return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv); + } + static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { if( idx==0 ) return RPC_MASK; if( idx==1 ) return null; if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - return switch( CodeGen.CODE._callingConv ) { + return switch( conv ) { case "SystemV" -> callSys5 (tfp,idx,maxArgSlot); - case "Win64" -> callWin64(tfp,idx,maxArgSlot); + case "win64" -> callWin64(tfp,idx,maxArgSlot); default -> throw Utils.TODO(); }; } // Maximum stack args used by this signature @Override public short maxArgSlot( TypeFunPtr tfp ) { - return switch( CodeGen.CODE._callingConv ) { + return maxArgSlot(tfp,CodeGen.CODE._callingConv); + } + @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) { + return switch( conv ) { case "SystemV" -> maxArgSlotSys5 (tfp); - case "Win64" -> maxArgSlotWin64(tfp); + case "win64" -> maxArgSlotWin64(tfp); default -> throw Utils.TODO(); }; } @@ -145,14 +152,22 @@ public class x86_64_v2 extends Machine { static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) { // idx 2,3,4,5 passed in registers, with stack slot mirrors. // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC). - if( idx >= 6 ) - return new RegMask(MAX_REG+maxArgSlot+(idx-2)); + int sigidx = idx-2; + if( hiddenSelf(tfp) ) { + if( sigidx==0 ) return null; + sigidx--; + } + if( sigidx >= 4 ) + return new RegMask(MAX_REG+maxArgSlot+sigidx); return tfp.arg(idx-2) instanceof TypeFloat - ? XMMS8 [idx-2] - : WIN64_CALL[idx-2]; + ? XMMS8 [sigidx] + : WIN64_CALL[sigidx]; } static short maxArgSlotWin64(TypeFunPtr tfp) { - return (short)tfp.nargs(); + return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0)); + } + static short maxIncomingArgSlotWin64(TypeFunPtr tfp) { + return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4); } // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing @@ -186,9 +201,14 @@ public class x86_64_v2 extends Machine { static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) { // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09 // First 8 floats passed in registers: xmm0-xmm7 + int sigidx = idx-2; + if( hiddenSelf(tfp) ) { + if( sigidx==0 ) return null; + sigidx--; + } int icnt=0, fcnt=0; // Count of ints, floats - for( int i=2; i<idx; i++ ) { - if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; + for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) { + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); @@ -198,13 +218,16 @@ public class x86_64_v2 extends Machine { } static short maxArgSlotSys5(TypeFunPtr tfp) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { + for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) { if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); return (short)nstk; } + private static boolean hiddenSelf(TypeFunPtr tfp) { + return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; + } static final long SYSTEM5_CALLER_SAVE = (1L << RAX) | (1L << RCX) | (1L << RDX) | @@ -223,12 +246,18 @@ public class x86_64_v2 extends Machine { (1L<<XMM4) | (1L<<XMM5) ; @Override public long callerSave() { - return switch (CodeGen.CODE._callingConv) { + return callerSave(CodeGen.CODE._callingConv); + } + @Override public long callerSave(String conv) { + return switch (conv) { case "SystemV" -> SYSTEM5_CALLER_SAVE; - case "Win64" -> WIN64_CALLER_SAVE; - default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); + case "win64" -> WIN64_CALLER_SAVE; + default -> throw new IllegalArgumentException("Unknown calling convention: " + conv); }; } + @Override public String cCallingConv(String callingConv) { + return callingConv; + } @Override public long neverSave() { return 1L<<RSP; } @Override public RegMask retMask( TypeFunPtr tfp ) { return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; @@ -237,8 +266,8 @@ public class x86_64_v2 extends Machine { // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { - return new SplitX86(kind, round); + @Override public SplitNode split(LRG lrg, String kind, byte round ) { + return new SplitX86(lrg,kind, round); } // Return a MachNode unconditional branch @@ -250,30 +279,33 @@ public class x86_64_v2 extends Machine { @Override public Node instSelect(Node n) { return switch (n) { - case AddFNode addf -> addf(addf); case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); case CProjNode c -> new CProjNode(c); case CallEndNode cend -> new CallEndMach(cend); case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); + case PtrToIntNode ptr -> new PtrToIntMach(ptr); + case GuardNode guard -> new GuardMach(guard); case ConstantNode con -> con(con); - case DivFNode divf -> new DivFX86(divf); - case DivNode div -> new DivX86(div); + case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div); + case EscapeNode esc -> new EscapeNode(esc); case FunNode fun -> new FunX86(fun); + case FunPtrNode fptr -> fptr(fptr); case IfNode iff -> jmp(iff); case LoadNode ld -> ld(ld); case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> new NegX86(neg); - case MulFNode mulf -> new MulFX86(mulf); - case MulNode mul -> mul(mul); + case MinusNode neg -> { + if( neg.mode()==2 ) throw Utils.TODO(); + yield new NegX86(neg); + } + case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul); case NewNode nnn -> new NewX86(nnn); case NotNode not -> new NotX86(not); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); - case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); + case BulkMemPhiNode phi-> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -281,14 +313,15 @@ public class x86_64_v2 extends Machine { case SarNode sar -> sar(sar); case ShlNode shl -> shl(shl); case ShrNode shr -> shr(shr); + case StartCUNode start -> new StartCUNode(start); + case StopCUNode stop -> new StopCUNode(stop); case StartNode start -> new StartNode(start); case StopNode stop -> new StopNode(stop); case StoreNode st -> st(st); - case SubFNode subf -> new SubFX86(subf); - case SubNode sub -> sub(sub); + case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub); case ToFloatNode tfn -> i2f8(tfn); case ToIntegerNode cvt -> new F8ToIX86(cvt); - case XCtrlNode x -> new ConstantNode(Type.XCONTROL); + case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); @@ -303,14 +336,12 @@ public class x86_64_v2 extends Machine { // Attempt a full LEA-style break down. private Node add(AddNode add) { + if( add.mode()==2 ) + return new AddFX86(add); Node lhs = add.in(1); Node rhs = add.in(2); - if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld.declaredType().log_size() >= 3) return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); - - if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) - return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); - // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) { @@ -333,17 +364,6 @@ public class x86_64_v2 extends Machine { } - private Node addf(AddFNode addf) { - if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1) - return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2)); - -// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1) -// throw Utils.TODO(); // Swap load sides - - return new AddFX86(addf); - } - - private Node _lea(Node add, Node base, Node idx, int off) { int scale = 0; if( base instanceof ShlNode && !(idx instanceof ShlNode) ) @@ -367,8 +387,10 @@ public class x86_64_v2 extends Machine { } private Node call(CallNode call) { - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallX86(call, tfp) + return call.fptr() instanceof FunPtrNode con + ? new CallX86(call, (TypeFunPtr)con._type) + : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr + ? new CallX86(call, (TypeFunPtr)con._type) : new CallRX86(call); } @@ -393,12 +415,13 @@ public class x86_64_v2 extends Machine { // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped. // Vs memory - if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) ) + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld.declaredType()) ) return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false); // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop` - if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) && - (val!=null || bool.op()=="==" || bool.op()=="!=") ) + if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) && + (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) || + bool.op()=="==" || bool.op()=="!=") ) return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true ); // Vs immediate @@ -415,18 +438,23 @@ public class x86_64_v2 extends Machine { private Node con( ConstantNode con ) { if( !con._con.isConstant() ) - return new ConstantNode(con); // Default unknown caller inputs + return ConstantNode.raw(con); // Default unknown caller inputs + String ext = con instanceof ExternNode ext0 ? ext0._extern : null; return switch (con._con) { - case TypeInteger ti -> new IntX86(con); - case TypeFloat tf -> new FltX86(con); - case TypeFunPtr tfp -> new TFPX86(con); - case TypeMemPtr tmp -> new TMPX86(con); + case TypeInteger ti -> new IntX86(con,ext); + case TypeFloat tf -> new FltX86(con,ext); + case TypeMemPtr tmp -> new TMPX86(con,ext); + case TypeFunPtr tfp -> new TFPX86(tfp,ext); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con); + case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con); }; } + private Node fptr( FunPtrNode con ) { + return new TFPX86(con); + } + private Node i2f8(ToFloatNode tfn) { assert tfn.in(1)._type instanceof TypeInteger ti; return new I2f8X86(tfn); @@ -439,7 +467,9 @@ public class x86_64_v2 extends Machine { String op = "!="; if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op(); else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + else iff.setDef(1, new BoolNode.NE(iff.in(1), + ConstantNode.raw(TypeInteger.ZERO), + (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1))); return new JmpX86(iff, op); } @@ -462,7 +492,7 @@ public class x86_64_v2 extends Machine { private Node prj( ProjNode prj ) { TypeInteger ret = cReturn(prj); if( ret!=null ) return new ExtendX86(prj,ret); - return new ProjX86(prj); + return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj); } private Node sar(SarNode sar) { @@ -549,7 +579,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range |
