diff options
Diffstat (limited to 'chapter-sccp/src/main/java/com/seaofnodes/simple/node')
224 files changed, 12183 insertions, 0 deletions
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java new file mode 100644 index 00000000..4ad02dbb --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -0,0 +1,31 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java new file mode 100644 index 00000000..2bbe1053 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -0,0 +1,169 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import static com.seaofnodes.simple.Parser.con; + +public class AddNode extends ArithNode { + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Add"; } + @Override public String op() { return "+"; } + + @Override long doOp( long x, long 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) && + !overflow(x._max,y._max) ) + return TypeInteger.make(x._min+y._min,x._max+y._max); + return TypeInteger.BOT; + } + + @Override + public Node idealize () { + Node lhs = in(1); + Node rhs = in(2); + if( rhs instanceof AddNode add && add.err()!=null ) + return null; + Type t2 = rhs._type; + + // Add of 0. We do not check for (0+x) because this will already + // canonicalize to (x+0) + if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger ) + return lhs; + + // Add of same to a multiply by 2 + if( lhs==rhs ) + return new ShlNode(null,lhs,con(1)); + + // Goal: a left-spine set of adds, with constants on the rhs (which then fold). + + // Move non-adds to RHS + if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) + return swap12(); + + // x+(-y) becomes x-y + if( rhs instanceof MinusNode minus ) + return new SubNode(lhs,minus.in(1)); + + // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) + + // Do we have x + (y + z) ? + // 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)); + + // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) + if( !(lhs instanceof AddNode) ) + // Rotate; look for (add (phi cons) con/(phi cons)) + return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); + + // 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 ) + return null; + + // Do we have (x + con1) + con2? + // Replace with (x + (con1+con2) which then fold the constants + // lhs.in(2) is con1 here + // If lhs.in(2) is not a constant, we add ourselves as a dependency + // 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()); + + + // Do we have ((x + (phi cons)) + con) ? + // Do we have ((x + (phi cons)) + (phi cons)) ? + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + Node phicon = phiCon(this,true); + if( phicon!=null ) return phicon; + + // Now we sort along the spine via rotates, to gather similar things together. + + // 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 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)) ? + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + static Node phiCon(Node op, boolean rotate) { + Node lhs = op.in(1); + Node rhs = op.in(2); + if( rhs._type==TypeInteger.TOP ) return null; + // LHS is either a Phi of constants, or another op with Phi of constants + PhiNode lphi = pcon(lhs,op); + if( rotate && lphi==null && lhs.nIns() > 2 ) { + // Only valid to rotate constants if both are same associative ops + if( lhs.getClass() != op.getClass() ) return null; + lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push + } + if( lphi==null ) return null; + if( lphi.region().nIns() <=2 ) return null; // Phi is collapsing + + // RHS is a constant or a Phi of constants + if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null ) + return null; + + // If both are Phis, must be same Region + if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) ) + return null; + + // Note that this is the exact reverse of Phi pulling a common op down + // to reduce total op-count. We don't get in an endless push-up + // push-down peephole cycle because the constants all fold first. + Node[] ns = new Node[lphi.nIns()]; + ns[0] = lphi.in(0); + // Push constant up through the phi: x + (phi con0+con0 con1+con1...) + 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(); + // Rotate needs another op, otherwise just the phi + return lhs==lphi ? phi : op.copy(lhs.in(1),phi); + } + + /** + * Tests if the op is a phi and has all constant inputs. + * If not, returns null. + * If op is a phi, but its inputs are not all constants, then dep is added as + * a dependency to the phi's non-const input, because if later the input turn into a constant + * dep can make progress. + */ + static PhiNode pcon(Node op, Node dep) { + return op instanceof PhiNode phi && !(phi instanceof ParmNode) && phi.allCons(dep) ? phi : null; + } + + // Compare two off-spine nodes and decide what order they should be in. + // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? + // Generally constants always go right, then Phi-of-constants, then muls, then others. + // Ties with in a category sort by node ID. + // TRUE if swapping hi and lo. + static boolean spine_cmp( Node hi, Node lo, Node dep ) { + if( lo._type.isConstant() || lo instanceof ConFldOffNode ) return false; + if( hi._type.isConstant() || hi instanceof ConFldOffNode ) return true ; + + if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false; + if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false; + + if( lo instanceof PhiNode && lo.allCons(dep) ) return false; + if( hi instanceof PhiNode && hi.allCons(dep) ) return true ; + + if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true; + if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false; + + // Same category of "others" + 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); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java new file mode 100644 index 00000000..9957472b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +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); } + + @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) { + // Sharpen allowed bits if either value is narrowed + long mask = x.mask() & y.mask(); + return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask); + } + + @Override + 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) ) + return null; // Malformed, e.g. (17 & 3.14) + + // 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 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Do we have ((x & (phi cons)) & con) ? + // Do we have ((x & (phi cons)) & (phi cons)) ? + // Push constant up through the phi: x & (phi con0&con0 con1&con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) + return phicon; + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new AndNode(_loc,lhs,rhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java new file mode 100644 index 00000000..dafba626 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java @@ -0,0 +1,30 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java new file mode 100644 index 00000000..54290330 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -0,0 +1,98 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; + + +public abstract class ArithNode extends Node { + // Source location for late reported errors + Parser.Lexer _loc; + + public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } + abstract String op(); + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.open().n(in(1)).p(op()).n(in(2)).close(); + } + + abstract long doOp(long x, long y); + abstract TypeInteger doOp(TypeInteger x, TypeInteger y); + + // Generic airthmetic 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; + } + + private TypeInteger con( Type t1, Type t2 ) { + return TypeInteger.constant( doOp( ((TypeInteger)t1).value(), ((TypeInteger)t2).value()) ); + } + + // Check for "op(Phi(con,x),Phi(con,y))" and push-up through the Phi. + // Note that this is the exact reverse of Phi pulling a common op down + // to reduce total op-count. We don't get in an endless push-up + // 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() { + 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(); + } + String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label; + return new PhiNode(label,lhs._minType,ns).peephole(); + } + } + return null; + } + + static boolean isCon( PhiNode lhs, PhiNode rhs, int i ) { + return lhs.in(i)._type.isConstant() && rhs.in(i)._type.isConstant(); + } + + + static boolean overflow( long x, long y ) { + 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; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java new file mode 100644 index 00000000..0d82c5b9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -0,0 +1,156 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; +import static com.seaofnodes.simple.type.TypeInteger.*; + +abstract public class BoolNode extends Node { + + public BoolNode(Node lhs, Node rhs) { + super(null, lhs, rhs); + } + + 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(); + } + + @Override + public TypeInteger compute() { + Type t1 = in(1)._type; + Type t2 = in(2)._type; + // 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); + if( t1 instanceof TypeFloat f1 && + t2 instanceof TypeFloat f2 && + f1.isConstant() && f2.isConstant() ) + return doOp(f1.value(), f2.value()) ? TRUE : FALSE; + return BOOL; + } + + 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; } + + @Override + public Node idealize() { + // Compare of same + if( in(1)==in(2) ) + return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE); + + // 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 X==0 becomes a !X + if( (in(2)._type == ZERO || in(2)._type == Type.NIL) ) + return new NotNode(in(1)); + } + + // Do we have ((phi cons) cmp con) ? + // Do we have ((phi cons) cmp (phi cons)) ? + // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) + Node phicon = AddNode.phiCon(this,this instanceof EQ); + if( phicon!=null ) return phicon; + + return null; + } + + public static class EQ extends BoolNode { + public EQ(Node lhs, Node rhs) { super(lhs,rhs); } + 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); } + } + + public static class NE extends BoolNode { + public NE(Node lhs, Node rhs) { super(lhs,rhs); } + 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(); } + } + + public static class LT extends BoolNode { + public LT(Node lhs, Node rhs) { super(lhs,rhs); } + 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); } + } + + public static class LE extends BoolNode { + public LE(Node lhs, Node rhs) { super(lhs,rhs); } + 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); } + } + + // 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 String op() { return "u<" ; } + TypeInteger doOp(TypeInteger i1, TypeInteger i2) { + if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; + if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; + return BOOL; + } + Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); } + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..7d4f1fb8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import java.util.BitSet; + +/** + * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; + * those aliases must be represented by parallel precise MemPhis at the same + * program slice. + */ +public class BulkMemPhiNode extends PhiNode { + + private static final BitSet EMPTY = new BitSet(); + + // Excluded Aliases; empty means NO exclusions. + // Each split clones the exclusions; published sets are never mutated. + public final BitSet _aliases; + + public BulkMemPhiNode(String label, Node... inputs) { + this(label,EMPTY,inputs); + } + + private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { + super(label,TypeMem.BOT,inputs); + _aliases = aliases; + } + + public boolean isSplit(int alias) { return _aliases.get(alias); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @Override public String label() { return "BulkPhi"+_aliases; } + + @Override + public Type compute() { + Type t = super.compute(); + return t instanceof TypeMem mem ? mem : + t==Type.BOTTOM ? TypeMem.BOT : + TypeMem.TOP; + } + + @Override + public Node idealize() { + Node progress = super.idealize(); + if( progress != null ) return progress; + if( ((RegionNode)region()).inProgress() || region().nIns()<=1 ) + return null; // Input is in-progress + + // "Peek through" a MemMerge that covers this alias set on its default + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) { + setDef(i,CodeGen.CODE.add(mmm.in(1))); + return this; + } + + // If any input or output uses or defines a specific alias we cover, + // make a private MemPhi for it, and add it to the excluded list. + for( int i=1; i<nIns(); i++ ) { + int alias = inputAlias(in(i)); + if( alias!=0 ) + return slice(alias); + } + + for( int i=0; i<_outputs.size(); i++ ) { + int alias = outputAlias(_outputs.get(i)); + if( alias!=0 ) + return slice(alias); + } + + return null; + } + + static public boolean checkMem(Node n, int alias) { + for( Node m : n._inputs ) + // Bulk memory supplies this alias + if( m instanceof BulkMemPhiNode bulk ) { + if( bulk._aliases.get(alias) ) + return false; + } else if( m instanceof MemMergeNode mmm && + mmm.in(1) instanceof BulkMemPhiNode bulk && + bulk._aliases.get(alias) && + (alias >= mmm.nIns() || mmm.in(alias)==null) ) + return false; + return true; + } + + + // True if the MemMerge has no precise slice still covered by this Phi. + private boolean canPeek(MemMergeNode mmm) { + return mmm.in(1)!=null && missingAlias(mmm)==0; + } + + // Return an alias required by an input but still covered by this Phi, or 0. + private int inputAlias(Node n) { + return switch(n) { + case ProjNode proj -> 0; + case ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + case MemOpNode mem -> unsplit(mem._alias); + case BulkMemPhiNode bulk -> missingAlias(bulk); + default -> throw new AssertionError("Unexpected memory input "+n); + }; + } + + // Return an alias required by a user but still covered by this Phi, or 0. + private int outputAlias(Node use) { + // This decision depends backwards on an immediate user's alias/set. + // Normal graph-neighbor enqueueing flows from defs to uses, so retain + // an explicit forward dependency to revisit this Phi when the user + // sharpens. + addDepForwards(use); + return switch(use) { + case ScopeNode scope -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "Unexpected bulk-memory user "+use; + yield 0; + } + }; + } + + // Alias zero/one is bulk, and an already excluded precise alias cannot + // trigger another split of this Phi. + private int unsplit(int alias) { + if( alias <= 1 ) return 0; + assert !_aliases.get(alias); + return alias; + } + + // First alias excluded by the neighboring bulk Phi but not by this one. + private int missingAlias(BulkMemPhiNode bulk) { + for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) + if( !_aliases.get(alias) ) + return alias; + return 0; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + return alias; + return 0; + } + + // Slice out given alias + private MemMergeNode slice( int alias ) { + assert !_aliases.get(alias); + // Inputs are the same; MemPhi will sharpen his own inputs. If a + // parallel precise Phi already exists, reuse it instead of building a + // duplicate for the same Region/alias pair. + Node mem = precisePhi(alias); + + BitSet aliases = ((BitSet)_aliases.clone()); + aliases.set(alias); + BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases); + for( int i=0; i<nIns(); i++ ) + bphi.addDef(in(i)); + // Installing bphi creates a new bulk user with a different exclusion + // set. Queue the original memory inputs before bphi itself can peep + // away and hide those neighbor relationships. + for( int i=1; i<bphi.nIns(); i++ ) + CodeGen.CODE.add(bphi.in(i)); + // 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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.add(mem); + return aggregate(bulk,alias,mem); + } + + // Build aggregate memory over a bulk Phi, preserving every precise slice + // 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); + 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); + return mmm; + } + + // Find or make the precise Phi parallel to this bulk Phi at the same merge. + private Node precisePhi(int alias) { + MemPhiNode mphi = _findPhi(alias); + if( mphi!=null ) return mphi; + + mphi = new MemPhiNode("$"+alias,alias); + mphi.addDef(region()); + // Due to cycles, must set before calling peephole + mphi.setType(TypeMem.make(alias,Type.BOTTOM)); + 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 ) + if( use instanceof MemPhiNode mphi && mphi._alias==alias ) + return mphi; + return null; + } + + // Select an alias already split out of a predecessor's bulk memory. + private Node preciseInput(Node mem, int alias) { + if( mem instanceof MemMergeNode mmm ) + return mmm.alias(alias); + if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) ) + return bulk.precisePhi(alias); + return mem; + } + + // Every alias excluded by a bulk default must be supplied explicitly. + static boolean checkMerge(MemMergeNode mmm) { + if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true; + for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) + if( alias >= mmm.nIns() || mmm.in(alias)==null ) + return false; + return true; + } + + @Override public boolean eq(Node n) { + return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); + } + + @Override int hash() { return _aliases.hashCode(); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java new file mode 100644 index 00000000..6805c1ef --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -0,0 +1,222 @@ +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; + +/** Control Flow Graph Nodes + * <p> + * CFG nodes have a immediate dominator depth (idepth) and a loop nesting + * depth(loop_depth). + * <p> + * idepth is computed lazily upon first request, and is valid even in the + * Parser, and is used by peepholes during parsing and afterward. + * <p> + * loop_depth is computed after optimization as part of scheduling. + * + * Start - Block head; has constants and trailing Funs + * Region - Block head; has Phis. Includes Fun and Parms. + * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms + * CProj - Block head + * + * If - Block tail; followed by CProj same block + * Call - Block tail; followed by CallEnd and linked Funs + * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj + * + */ +public abstract class CFGNode extends Node { + + public CFGNode(Node... nodes) { super(nodes); } + public CFGNode(CFGNode cfg) { + super(cfg); + if( cfg != null ) { + _idepth = cfg._idepth; + _idepthVersion = cfg._idepthVersion; + _ltree = cfg._ltree; + _pre = cfg._pre; + } + } + + public CFGNode cfg(int idx) { return (CFGNode)in(idx); } + + // Block head is Start, Region, CProj, but not e.g. If, Return, Stop + public boolean blockHead() { return false; } + + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. + public CFGNode uctrl() { + CFGNode c = null; + for( Node n : _outputs ) + if( n instanceof CFGNode cfg ) + { assert c==null; c = cfg; } + return c; + } + + // Used by the encoding / final BB layout + public CFGNode uctrlSkipEmpty() { + CFGNode x = this, y; + while( x.nOuts() == 1 && (y=x.uctrl())!=null && !(y instanceof CallNode) ) // Skip empty blocks + x = y; + return x; + } + + // ------------------------------------------------------------------------ + /** + * Immediate dominator tree depth, used to approximate a real IDOM during + * parsing where we do not have the whole program, and also peepholes + * change the CFG incrementally. + * <p> + * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>} + */ + public char _idepth; + public char _idepthVersion; + public int idepth() { return validIDepth() ? _idepth : cacheIDepth(idom().idepth()+1); } + // Zero depth means uncached. Check before narrowing so overflow cannot wrap. + final int cacheIDepth(int depth) { + assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; + _idepthVersion = CodeGen.CODE.iDepthVersion(); + return _idepth = (char)depth; + } + + final boolean validIDepth() { return _idepth!=0 && _idepthVersion==CodeGen.CODE.iDepthVersion(); } + + + // Return the immediate dominator of this Node and compute dom tree depth. + public CFGNode idom(Node dep) { return cfg(0); } + public final CFGNode idom() { return idom(null); } + + // Return the LCA of two idoms + public CFGNode domLCA(CFGNode rhs, Node dep) { + if( rhs==null ) return this; + CFGNode lhs = this; + while( lhs != rhs ) { + if( lhs==null || rhs==null ) return null; + int comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom(); + if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom(); + } + return lhs; + } + + // Find nearest enclosing FunNode + public FunNode fun() { + CFGNode cfg = this; + while( !(cfg instanceof FunNode fun) ) + cfg = cfg.idom(); + return fun; + } + + // ------------------------------------------------------------------------ + // Loop nesting + public LoopNode loop() { return _ltree._head; } + public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); } + + public LoopTree _ltree; + public int _pre; // Pre-order numbers for loop tree finding + static class LoopTree { + LoopTree _par; + final LoopNode _head; + int _depth; + LoopTree(LoopNode head) { _head = head; } + @Override public String toString() { return "LOOP"+_head._nid; } + int depth() { + return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth; + } + } + + // ------------------------------------------------------------------------ + // 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; } + } + + _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); + _bltWalk(2,null,stop, new BitSet()); + } + int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { + // Pre-walked? + if( _pre!=0 ) return pre; + _pre = pre++; + // Seed the function's outer tree before walking its body. Synthetic + // exits can be added before the post-order walk reaches the entry. + if( this instanceof FunNode ) _ltree = stop._ltree; + if( this instanceof ReturnNode ) { + _ltree = fun._ltree; + post.set(_nid); + return pre; // Stop/Start links describe the outside world, not a loop. + } + // Pre-walk + for( Node use : _outputs ) + if( use instanceof CFGNode usecfg && !skip( usecfg ) ) + pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post ); + + // Post-order work: find innermost loop + LoopTree inner = null, ltree; + for( Node use : _outputs ) { + if( !(use instanceof CFGNode usecfg) ) continue; + if( skip(usecfg) ) continue; + if( usecfg._type == Type.XCONTROL || // Do not walk dead control + usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs + continue; + // Child visited but not post-visited? + if( !post.get(usecfg._nid) ) { + // Must be a backedge to a LoopNode then + ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg); + } else { + // Take child's loop choice, which must exist + ltree = usecfg._ltree; + // If falling into a loop, use the target loop's parent instead + if( ltree._head == usecfg ) { + if( ltree._par == null ) + // This loop never had an If test choose to take its + // exit, i.e. it is a no-exit infinite loop. + ltree._par = ltree._head.forceExit(fun,stop)._ltree; + ltree = ltree._par; + } + } + // Sort inner loops. The decision point is some branch far removed + // from either loop head OR either backedge so requires pre-order + // numbers to figure out innermost. + if( inner == null ) { inner = ltree; continue; } + if( inner == ltree ) continue; // No change + LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree; + inner = ltree._head._pre > inner._head._pre ? ltree : inner; + inner._par = outer; + } + // Set selected loop + if( inner!=null ) + _ltree = inner; + // Tag as post-walked + post.set(_nid); + return pre; + } + + private boolean skip(CFGNode usecfg) { + // Only walk control users that are alive. + // Do not walk from a Call to linked Fun's. + return usecfg instanceof XCtrlNode || + (this instanceof CallNode && usecfg instanceof FunNode) || + (this instanceof ReturnNode && usecfg instanceof CallEndNode); + } + + + public String label( CFGNode target ) { + return (target instanceof LoopNode ? "LOOP" : "L")+target._nid; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java new file mode 100644 index 00000000..c62de840 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -0,0 +1,69 @@ +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.TypeMem; +import com.seaofnodes.simple.type.TypeTuple; +import com.seaofnodes.simple.util.Utils; + +public class CProjNode extends CFGNode { + + // Which slice of the incoming multipart value + public int _idx; + + // Debugging label + public String _label; + + public CProjNode(Node ctrl, int idx, String label) { + super(ctrl); + _idx = idx; + _label = label; + } + public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; } + + @Override public String label() { return _label; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); } + + @Override public boolean blockHead() { return true; } + + public CFGNode ctrl() { return cfg(0); } + + @Override + public Type compute() { + Type t = ctrl()._type; + return t instanceof TypeTuple tt ? tt._types[_idx] : (t.isHigh() ? Type.TOP : Type.BOTTOM); + } + + @Override + public Node idealize() { + if( ctrl()._type instanceof TypeTuple tt ) { + if( tt._types[_idx]==Type.XCONTROL ) + return Parser.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 ) + return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); + + // Copy of some other input + return ((MultiNode)ctrl()).pcopy(_idx); + } + + // Only called during basic-block layout, inverts a T/F CProj + public void invert() { + _label = _idx == 0 ? "False" : "True"; + _idx = 1-_idx; + } + + @Override + public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } + + @Override + int hash() { return _idx; } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java new file mode 100644 index 00000000..d26ea33b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.util.SB; + +public class CallEndMach extends CallEndNode implements MachNode { + public CallEndMach( CallEndNode n ) { super(n); } + // MachNode specifics, shared across all CPUs + public int _xslot; + private RegMask _retMask; + private RegMask _kills; + public void cacheRegs(CodeGen code) { + // Return mask depends on TFP (either GPR or FPR) + _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(); + // 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()); + _xslot = (maxReg+fslot)+xslot; + for( int i=0; i<xslot; i++ ) + kills.set((maxReg+fslot)+i); + _kills = kills; + } + public String op() { return "cend"; } + public RegMask regmap(int i) { return null; } + public RegMask outregmap() { return null; } + public RegMask outregmap(int idx) { return idx==2 ? _retMask : null; } + public RegMask killmap() { return _kills; } + public void encoding( Encoding enc ) { } + public void asm(CodeGen code, SB sb) { } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java new file mode 100644 index 00000000..3321301b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -0,0 +1,235 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.type.*; + +/** + * CallEnd + */ +public class CallEndNode extends CFGNode implements MultiNode { + + // When set true, this Call/CallEnd/Fun/Return is being trivially inlined + private boolean _folding; + public boolean folding() { return _folding; } + public final TypeRPC _rpc; + // Pessimistic analysis may know more than the written signature. Preserve + // that summary when local SCCP disconnects the callee's return edges. + private Type _retType = Type.BOTTOM; + + public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); } + public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; _retType = cend._retType; } + + public void freezeResult() { + if( _type instanceof TypeTuple tt ) { + unlock(); + _retType = tt.ret(); + } + } + + @Override public boolean eq(Node n) { return _retType==((CallEndNode)n)._retType; } + @Override int hash() { return _retType.hashCode(); } + + @Override public String label() { return "CallEnd"; } + @Override public boolean blockHead() { return true; } + + public CallNode call() { return (CallNode)in(0); } + + @Override public CFGNode idom(Node dep) { + // Folding the idom is the one inlining Return + return _folding ? cfg(1) : super.idom(dep); + } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("cend( "); + p.p( in(0) instanceof CallNode ? "Call, " : "----, "); + for( int i=1; i<nIns()-1; i++ ) + p.n(in(i)).p(","); + return p.unchar(',').unchar(' ').close(); + } + + @Override + public Type compute() { + if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL ) + return TypeTuple.RET.dual(); + // Mid-fold, just take the one single callers' return type + if( _folding ) return in(1)._type; + // Grab the TFP and use the functions declared return type. + Type ftype = addDep(call.fptr())._type; + Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM; + if( ftype instanceof TypeFunPtr tfp ) { + ret = tfp.ret().join(_retType); + // 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); + } + } + } + return TypeTuple.make(call._type,TypeMem.BOT,ret); + } + + @Override + public Node idealize() { + + // Trivial inlining: call site calls a single function; single function + // is only called by this call site. + if( !_folding && nIns()==2 && 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); + } + } + + return null; + } + + // 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; + 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. + } + + // 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. + private static final Node[] TINYBODY = new Node[15]; // Tiny worklist + private int smallBody(FunNode fun) { + ReturnNode ret = fun.ret(); + for( Node in : ret._inputs ) + if( in!=null ) addDep(in); + if( ret.ctrl()!=fun ) return 0; + 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!=fun && !(n instanceof ParmNode) && + !(n instanceof StoreNode) && !(n instanceof MemMergeNode) ) + return 0; + if( n.nOuts()>TINYBODY.length ) return 0; + 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; + } + } + return len; + } + + @Override public Node pcopy(int idx) { + return _folding ? in(1).in(idx) : null; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java new file mode 100644 index 00000000..e63b3f62 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -0,0 +1,175 @@ +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.util.Utils; + +/** + * Call + */ +public class CallNode extends CFGNode { + + // Source location for late reported errors + public final Parser.Lexer _loc; + + public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } + public CallNode(CallNode call) { super(call); _loc = call._loc; } + + @Override public String label() { return "Call"; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + String fname = name(); + if( fname == null ) p.n(fptr()); + else p.p(fname); + p.p("( "); + for( int i=2; i<nIns()-1; i++ ) + p.n(in(i)).p(","); + return p.unchar(',').unchar(' ').close(); + } + public String name() { + if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) { + FunNode fun = CodeGen.CODE._link(tfp); + if( fun !=null ) return fun._name; + if( fptr() instanceof ExternNode ex ) return ex._extern; + } + return null; + } + + + + Node ctrl() { return in(0); } + Node mem () { return in(1); } + // Args mapped 1-to-1 on inputs, so conceptually start at 2 + public Node arg(int idx) { return in(idx); } + // Same arg accounting as TFPs, although the numbering starts at 2 + public int nargs() { return nIns()-3; } // Minus control, memory, fptr + // args from input 2 to last; last is function input + public Node fptr() { return _inputs.last(); } + // Error if not a TFP + public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; } + + // Call is to an externally supplied code + public boolean external() { return false; } + + + // Find the Call End from the Call + public CallEndNode cend() { + // Always in use slot 0 + if( nOuts()>0 && out(0) instanceof CallEndNode cend ) { + assert _cend()==cend; + return cend; + } else { + assert _cend()==null; + return null; + } + } + private CallEndNode _cend() { + CallEndNode cend=null; + for( Node n : _outputs ) + if( n instanceof CallEndNode cend0 ) + { assert cend == null; cend = cend0; } + return cend; + } + + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. + @Override public CFGNode uctrl() { return cend(); } + + + @Override + public Type compute() { + return ctrl()._type; + } + + @Override + public Node idealize() { + CallEndNode cend = cend(); + if( cend==null ) return null; // Still building + + // Link: call calls target function. Linking makes the target FunNode + // point to this Call, and all his Parms point to the call arguments; + // also the CallEnd points to the Return. + 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); + if( fun!=null && !fun._folding && !linked(fun) ) + progress = link(fun); + } + } + } + + return progress; + } + + // True if Fun is linked to this Call + public boolean linked( FunNode fun ) { + for( Node n : fun._inputs ) + if( n == this ) + return true; + return false; + } + + // Link so this calls fun + public Node link( FunNode fun ) { + assert !linked(fun); + 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)); + // Call end points to function return + CodeGen.CODE.add(cend()).addDef(fun.ret()); + return this; + } + + // 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); + i--; + } + } + + @Override + public Parser.ParseException err() { + if( !(fptr()._type instanceof TypeFunPtr tfp) ) + throw Utils.TODO(); + if( !tfp.notNull() ) + return Parser.error( "Might be null calling "+tfp, _loc); + if( nargs() != tfp.nargs() ) + return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc); + + // Check for args + for( int i=0; i<tfp.nargs(); i++ ) + if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) { + 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 ); + } + + if( tfp.fidxs() < 0 ) + throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG + + return null; + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java new file mode 100644 index 00000000..0afc5827 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.type.Type; + +public class CalleeSaveNode extends ProjNode implements MachNode { + final RegMask _mask; + public CalleeSaveNode(FunNode fun, int reg, String label) { + super(fun,reg,label); + fun._outputs.pop(); + int i=0; + while( fun.out(i) instanceof PhiNode ) i++; + fun._outputs.insert(this,i); + _mask = new RegMask(reg); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return _mask; } + @Override public void encoding( Encoding enc ) { } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java new file mode 100644 index 00000000..e45898f5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java @@ -0,0 +1,19 @@ +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.util.SB; + +/** + * Cast a pointer to read-only + */ +public class CastMach extends CastNode implements MachNode { + public CastMach( CastNode n ) { super(n); } + @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 void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java new file mode 100644 index 00000000..4e93f3e6 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -0,0 +1,71 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java new file mode 100644 index 00000000..afdd29be --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +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; + +/** + * 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 ConFldOffNode extends ConstantNode { + public final String _name; // Struct name + public final String _fname; // Field name + public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; } + + @Override public String label() { + return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; + } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()); + } + + // 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(); + } + + + @Override public boolean eq(Node n) { + ConFldOffNode off = (ConFldOffNode)n; // Invariant + return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n); + } + @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java new file mode 100644 index 00000000..7c330175 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -0,0 +1,59 @@ +package com.seaofnodes.simple.node; + +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; + +/** + * A Constant node represents a constant value. + * <p> + * Constants have no semantic inputs. However, we set Start as an input to + * Constants to enable a forward graph walk. This edge carries no semantic + * meaning, and it is present <em>solely</em> to allow visitation. + * <p> + * The Constant's value is the value stored in it. + */ + +public class ConstantNode extends Node { + public final Type _con; + public ConstantNode( Type type ) { + super(new Node[]{CodeGen.CODE._start}); + _con = _type = type; + } + public ConstantNode( ConstantNode con ) { super(con); _con = con._type; } + + 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()); + } + return new ConstantNode(type); + } + + @Override protected String repeatName() { + return _con==null || _con.toString().length()<=32 ? null : uniqueName(); + } + + @Override public String label() { return "Con"; } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( _con instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) { + FunNode fun = CodeGen.CODE._link(tfp); + if( fun!=null && fun._name != null ) + return p.p("{ ").p(fun._name).p("}"); + } + return p.p(_con==null ? "---" : _con.toString()); + } + + @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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java new file mode 100644 index 00000000..cf630ad4 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -0,0 +1,16 @@ +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.type.Type; + +public class CtrlNode extends CFGNode { + public CtrlNode() { super(CodeGen.CODE._start); } + @Override public String label() { return "Ctrl"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); } + @Override public boolean isConst() { return true; } + @Override public Type compute() { return Type.CONTROL; } + @Override public Node idealize() { return null; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java new file mode 100644 index 00000000..0d037a30 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -0,0 +1,26 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java new file mode 100644 index 00000000..7b5ae16c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; + +public class DivNode extends ArithNode { + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + @Override public String op() { return "//"; } + + @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + // Div of 1. + if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger ) + return in(1); + return super.idealize(); + } + + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } + @Override Node copyF() { return new DivFNode(null,null); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java new file mode 100644 index 00000000..ccbcfc66 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -0,0 +1,21 @@ +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.TypeFunPtr; +import com.seaofnodes.simple.util.SB; +import java.util.BitSet; + +/** + A constant with external linkage. + */ + +public class ExternNode extends ConstantNode { + public final String _extern; + public ExternNode(Type t, String ex) { super(t); _extern = ex; } + + @Override public String label() { return "#"+_con+":"+_extern; } + + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java new file mode 100644 index 00000000..e7333c23 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Var; +import com.seaofnodes.simple.type.Type; + +/** + * A Forward Reference. Its any final constant, including functions. When + * the Def finally appears its plugged into the forward reference, which then + * peepholes to the Def. + */ +public class FRefNode extends ConstantNode { + public static final Type FREF_TYPE = Type.BOTTOM; + public final Var _n; + public FRefNode( Var n ) { super(FREF_TYPE); _n = n; } + + @Override public String label() { return "FRef"+_n; } + + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p("FRef_").p(_n); + } + + @Override public Node idealize() { + // When FRef finds its definition, idealize to it + return nIns()==1 ? null : in(1); + } + + public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } + + @Override public boolean eq(Node n) { return this==n; } + @Override int hash() { return _n._idx; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java new file mode 100644 index 00000000..70e1c200 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -0,0 +1,257 @@ +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.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.util.BitSet; +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() { return _folding; } + + private TypeFunPtr _sig; // Initial signature + private ReturnNode _ret; // Return pointer + + public String _name; // Debug name + + public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; } + 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 = ""; + } + } + + @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. + @Override public CFGNode uctrl() { + for( Node n : _outputs ) + if( n instanceof CFGNode cfg && + (cfg instanceof RegionNode || cfg.cfg0()==this) ) + return cfg; + return null; + } + + public ParmNode rpc() { + ParmNode rpc = null; + for( Node n : _outputs ) + if( n instanceof ParmNode parm && parm._idx==0 ) + { assert rpc==null; rpc=parm; } + return rpc; + } + + // Cannot create the Return and Fun at the same time; one has to be first. + // So setting the return requires a second step. + public void setRet(ReturnNode ret) { _ret=ret; } + 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); + if( _sig != sig ) { + CODE.add(this); + _sig = sig; + } + } + + public void setName( String name ) { + if( _name==null ) _name=name; + else _name += "."+name; + } + + @Override + public Type compute() { + // Only dead if no callers after SCCP + 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 + if( !(in(i) instanceof StartNode) ) + t = t.meet(in(i)._type); + } + return t; + } + + @Override + public Node idealize() { + + // Some linked path dies, except main never kills 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 + return progress; + } + + // Upgrade inferred or user-written return type to actual + if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) { + setSig(_sig.makeFrom(tt.ret())); + return this; + } + + + // When can we assume no callers? Or no other callers (except main)? + // In a partial compilation, we assume Start gets access to any/all + // top-level public structures and recursively what they point to. + // This in turn is valid arguments to every callable function. + // + // In a total compilation, we can start from Start and keep things + // more contained. + + // If no default/unknown caller, use the normal RegionNode ideal rules + // to collapse + if( unknownCallers() ) return null; + + // If folding (already inlined) down to a single input, become that input. + // Cannot collapse if not-inlining, and might not inline if call site + // calls multiple targets. + if( _folding && nIns()==2 && !hasPhi() ) { + CODE.add( CODE._stop ); // Stop will remove dead path + CODE.add( _ret ); // Return will compute to TOP control + return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own + } + + return null; + } + + // Bypass Region idom, always assume depth == 1, one more than Start, + // unless folding then just a ID on input#1 + @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); } + + // 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(); } + + // Add a new function exit point. + public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); } + + // Build the function body + public BitSet body() { + + // Reverse up (stop to start) CFG only, collect bitmap. + BitSet cfgs = new BitSet(); + cfgs.set(_nid); + walkUp(ret(),cfgs ); + + // Top down (start to stop) all flavors. CFG limit to bitmap. + // If data use bottoms out in wrong CFG, returns false - but tries all outputs. + // If any output hits an in-CFG use (e.g. phi), then keep node. + BitSet body = new BitSet(); + walkDown(this, cfgs, body, new BitSet()); + return body; + } + + private static void walkUp(CFGNode n, BitSet cfgs) { + if( cfgs.get(n._nid) ) return; + cfgs.set(n._nid); + if( n instanceof RegionNode r ) + for( int i=1; i<n.nIns(); i++ ) + walkUp(n.cfg(i),cfgs); + else walkUp(n.cfg0(),cfgs); + } + + private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) { + if( visit.get(n._nid) ) return body.get(n._nid); + visit.set(n._nid); + + if( n instanceof CFGNode && !cfgs.get(n._nid) ) + return false; + if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) + return false; + boolean in = n.in(0)!=null || n instanceof CFGNode; + for( Node use : n._outputs ) + in |= walkDown(use,cfgs,body,visit); + if( in ) body.set(n._nid); + return in; + } + + // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able) + @Override public boolean eq( Node n ) { + return _sig == ((FunNode)n)._sig; + } + + @Override public int hash() { + return _sig.hashCode(); + } + + // ------------ + // MachNode specifics, shared across all CPUs + + // Function may call other functions? X86 requires 16b aligned SP on + // outbound calls, but not leaf. Set during an unrelated RA scan. + public boolean _hasCalls; + + // Maximum incoming argument slot, based on function type + public int _maxArgSlot; + + // Frame adjust, including padding, set during early Encoding + public int _frameAdjust; + + public String op() { return _frameAdjust==0 ? "entry" : "subi"; } + public void postSelect(CodeGen code) { + code.link(this); + // Max slot seen past space reserved for incoming arguments + _maxArgSlot = code._mach.maxArgSlot(sig()); + } + public RegMask regmap(int i) { return null; } + public RegMask outregmap() { return null; } + public void encoding( Encoding enc ) { throw Utils.TODO(); } + public void asm(CodeGen code, SB sb) { + if( _frameAdjust!=0 ) + sb.p("rsp -= #").p(_frameAdjust); + } + + public void computeFrameAdjust(CodeGen code, int maxReg) { + // Max slot seen, or 0. + int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot); + // Frame adjust before rounding, can be negative for no spills + int size = maxSlot - _maxArgSlot; + _frameAdjust = size*8; + } + + // Given a high register number and during encoding compute the stack + // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a + // FunNode. + public int computeStackOffset(CodeGen code, int reg) { + String[] regs = code._mach.regs(); + int stackSlot = reg - regs.length; + int slotRotate = stackSlot < _maxArgSlot + ? stackSlot + (_frameAdjust>>3) + : stackSlot - _maxArgSlot; + return slotRotate*8; + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java new file mode 100644 index 00000000..43242c32 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java @@ -0,0 +1,12 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.TypeFunPtr; + +// 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); + } + public ReturnNode ret() { return (ReturnNode)in(1); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java new file mode 100644 index 00000000..c96993e5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -0,0 +1,103 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; + +public class IfNode extends CFGNode implements MultiNode { + + public IfNode(Node ctrl, Node pred) { + super(ctrl, pred); + CodeGen.CODE.add(this); // Because idoms are complex, just add it + } + public IfNode(IfNode iff) { super(iff); } + + @Override + public String label() { return "If"; } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("if( "); + if( in(1)==null ) p.p("never"); + else p.n(in(1)); + return p.p(" )"); + } + + public Node ctrl() { return in(0); } + public Node pred() { return in(1); } + + // No one unique control follows + @Override public CFGNode uctrl() { return null; } + + @Override + public Type compute() { + // If the If node is not reachable then neither is any following Proj + assert !_inputs.isEmpty(); + if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM ) + return TypeTuple.IF_NEITHER; + Node pred = pred(); + Type t = pred._type; + // High types mean NEITHER side is reachable. + // Wait until the type falls to decide which way to go. + if( t.isHigh() ) + return TypeTuple.IF_NEITHER; + // 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; + // If adding a zero makes a difference, the predicate must not have a zero/null + if( !t.makeZero().isa(t) ) + return TypeTuple.IF_TRUE; + + return TypeTuple.IF_BOTH; + } + + @Override + public Node idealize() { + // 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 ) { + 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; + } + + // MachNode variants need to support this and negate the conditional test. + // The following CProjs will be inverted by the caller. + public void negate() { throw Utils.TODO(); } + + // Negate the sense of a test + public static String negate( String bop ) { + return switch( bop ) { + case "<" -> ">="; + case "<=" -> ">" ; + case "==" -> "!="; + case "!=" -> "=="; + case ">" -> "<="; + case ">=" -> "<" ; + default -> throw Utils.TODO(); + }; + } + + // Swap compare operands + public static String swap( String bop ) { + return switch( bop ) { + case "<" -> ">" ; + case "<=" -> ">="; + case "==" -> "=="; + case "!=" -> "!="; + case ">" -> "<" ; + case ">=" -> "<="; + default -> throw Utils.TODO(); + }; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java new file mode 100644 index 00000000..e057ef1c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -0,0 +1,263 @@ +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.type.*; +import com.seaofnodes.simple.util.Utils; +import java.util.BitSet; + +/** + * Load represents extracting a value from inside a memory object, + * in chapter 10 this means Struct fields. + */ +public class LoadNode extends MemOpNode { + + /** + * Load a value from a ptr.field. + * + * @param name The field we are loading + * @param mem The memory alias node - this is updated after a Store + * @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); + } + + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + dep.addDepForwards(mem); + for( Node use : mem._outputs ) { + if( use==null ) continue; + dep.addDepForwards(use); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + + // Debugger label + @Override public String label() { return "ld_"+mlabel(); } + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(".").p(_name); } + + @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 + // Load field from object + Field f = 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; + // 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; + } + + @Override + public Node idealize() { + if( mem() instanceof CastNode cast ) { + setDef(1,cast.in(1)); + return this; + } + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } + Node ptr = ptr(); + Node mem = mem(); + + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); + + // 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 + return ld; + + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + + // Push a Load up through a Phi, as long as it collapses on at least + // one arm. If at a Loop, the backedge MUST collapse - else we risk + // spinning the same transform around the loop indefinitely. + // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): + // if( pred ) ptr.x = e0; val = pred ? e0 + // else ptr.x = e1; : e1; + // val = ptr.x; ptr.x = val; + if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && + // Retain control on both copies only if it dominates both paths. + // The Region itself, or a later range check, cannot move to its inputs. + (in(0)==null || cfg0().domLCA(memphi.region().idom(this),this)==cfg0()) && + // Offset can be hoisted + off() instanceof ConstantNode && + // Pointer can be hoisted + hoistPtr(ptr,memphi) ) { + + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(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. + 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()); + } + } + + return null; + } + + // 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(); + assert zero.isa(_type); // Catch an uninitialized non-null field + return castRO(new ConstantNode(zero).peephole()); + } + + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // 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); + 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: + addDep(st.ptr()); + if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; + if( _alias==st._alias && !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; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,_declaredType,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 folded(win).peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); + } + + private static boolean neverAlias( Node ptr1, Node ptr2 ) { + return ptr1.in(0) != ptr2.in(0) && + // Unrelated allocations + ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode && + ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode; + } + + private static boolean hoistPtr(Node ptr, PhiNode memphi ) { + // Can I hoist ptr above the Region? + if( !(memphi.region() instanceof RegionNode r) ) + return false; // Dead or dying Region/Phi + // If ptr from same Region, then yes, just use hoisted split pointers + if( ptr instanceof PhiNode pphi && pphi.region() == r ) + return true; + + // No, so can we lift this ptr? + CFGNode cptr = ptr.cfg0(); + if( cptr != null ) + // Pointer is controlled high + // TODO: Really needs to be the LCA of all inputs is high + return cptr.idepth() <= r.idepth(); + + // Dunno without a longer walk + return false; + } + + // 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() ) + return new ReadOnlyNode(rez).peephole(); + return rez; + } + + // 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( 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 ) + return val; + Node shl = new ShlNode(null,val,shf.keep()).peephole(); + return new SarNode(null,shl,shf.unkeep()); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java new file mode 100644 index 00000000..1d493db7 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -0,0 +1,104 @@ +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; + +public class LoopNode extends RegionNode { + public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); } + public LoopNode( LoopNode loop ) { super(loop); } + + 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; + } + + // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) + @Override public int idepth() { return validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); } + // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) + @Override public CFGNode idom(Node dep) { return entry(); } + + // If this is an unreachable loop, it may not have an exit. If it does not + // (i.e., infinite loop), force an exit to make it reachable. + public FunNode forceExit( FunNode fun, StopNode stop ) { + // Walk the backedge, then immediate dominator tree util we hit this + // Loop again. If we ever hit a CProj from an If (as opposed to + // directly on the If) we found our exit. + CFGNode x = back(); + while( x != this ) { + if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) { + CFGNode other = iff.cproj(1-exit._idx); + if( other!=null && other._ltree != _ltree && nested(_ltree,other._ltree) ) + return fun; // Found an exit, not an infinite loop + } + x = x.idom(); + } + // Found a no-exit loop. Insert an exit + NeverNode iff = (NeverNode)new NeverNode(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 + ReturnNode ret = fun.ret(); + iff._ltree = t._ltree = _ltree; + ret._ltree = f._ltree = fun._ltree; + + // 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); + for( Node u : _outputs ) + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + memout.init(); + + Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); + if( ctrl!=null && ctrl._type != Type.XCONTROL ) { + // Perfect aligned exit? + if( !(ctrl instanceof RegionNode r && + mem instanceof PhiNode pmem && pmem.region()==r && + expr instanceof PhiNode prez && prez.region()==r ) ) { + // Nope, insert an aligned exit layer + RegionNode r = new RegionNode(_loc,null,ctrl).init(); + ctrl = r; r._ltree = fun._ltree; + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); + expr = new PhiNode(r,expr).init(); + } + // Append new Never exit + ctrl.keep().addDef(f ); + mem .keep().addDef(memout); + expr.keep().addDef(top); + } else { + ctrl = f .keep(); + mem = memout.keep(); + expr = top .keep(); + } + 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()); + + return fun; + } + + private static boolean nested(LoopTree inner, LoopTree outer) { + for( ; inner!=null; inner = inner._par ) + if( inner == outer ) + return true; + return false; + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java new file mode 100644 index 00000000..9c0755b9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java @@ -0,0 +1,27 @@ +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 +// exist (abstract) but are not useful past the optimizer. +public abstract class MachConcreteNode extends Node implements MachNode { + + public MachConcreteNode(Node node) { super(node); } + 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.open().p(op()).p(","); + for( int i=1; i<nIns(); i++ ) + (in(i)==null ? p.p(" ---") : p.n(in(i))).p(","); + return p.unchar(',').close(); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java new file mode 100644 index 00000000..c0b3af66 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public interface MachNode { + + // Easy access to the abstract Machine + default Machine machine() { return CodeGen.CODE._mach; } + + // Run a post-instruction-selection action + default void postSelect( CodeGen code ) { } + + // Register mask allowed on input i. 0 for no register. + RegMask regmap( int i ); + // Register mask allowed as a result. 0 for no register. + RegMask outregmap(); + // Multi-reg-defining machine op; idx comes from Proj. + // Sometimes these Projs have no uses, and just exist to kill a register. + default RegMask outregmap(int idx) { throw Utils.TODO(); } + + // Set of killed registers + default RegMask killmap() { return null; } + + // If this op updates the same register as input 'idx', e.g. many X86 "R1 += + // R2" style ops. This forces the output live range to equal the input live + // range, so they both get the same register. + + // This can hold true for projections, if the instruction updates many + // registers that it also takes in. Note that ops also use the same + // registers for both inputs and outputs but these are pinned and without any + // choices (e.g. always RCX for both an input and output) and are better + // served with just declaring as a fixed register. + + // Returns either 0 for not-two-adr or the updated input; for projections + // this is their Multi's updated input. + default int twoAddress( ) { return 0; } + // Ok to switch arguments + default boolean commutes() { return false; } + + // Instructions cheaper to recreate than to spill, such as loading small constants + default boolean isClone() { return false; } + + // Make a clone of a cheap instruction + default Node copy() { return null; } + + // Encoding is appended, relocations gathered. + void encoding( Encoding enc ); + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "R17=[R18+12] // Load array base". + // General form: "dst=src+src". + default void asm(CodeGen code, SB sb) { throw Utils.TODO(); } + + // Human readable opcode; e.g. "ld4" or "call". + default String op() { throw Utils.TODO(); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..7a329117 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -0,0 +1,102 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +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. + */ +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 void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } + + // 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()); + } + // 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; + } + + @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(" "); + } + return p.p("]"); + } + + + @Override public Type compute() { + for( Node n : _inputs ) + if( n != null && !n._type.isHigh() ) + return TypeMem.BOT; + return TypeMem.TOP; + } + + @Override public Node idealize() { + 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( allDefault && in(1)!=null ) return in(1); + + 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)); + CodeGen.CODE.add(mem.in(1)); + setDef(1,mem.in(1)); + return this; + } + assert BulkMemPhiNode.checkMerge(this); + return progress ? this : null; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java new file mode 100644 index 00000000..e7b2c792 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -0,0 +1,146 @@ +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.util.Utils; +import java.lang.StringBuilder; +import java.util.BitSet; +import java.util.ArrayList; + +/** + * Convenience common base for Load and Store. + * + * 0 - Control + * 1 - Memory + * 2 - Base of object, OOP + * 3 - Offset, integer types + * 4 - Value, for Stores only + */ +public abstract class MemOpNode extends Node { + + // The equivalence alias class + public final int _alias; + + // True if load-like, false if store-like. + // + // 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 + // 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 + 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); + _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); + 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"); + } + + // 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; + } + + static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } + String mlabel() { return mlabel(_name); } + + @Override public boolean isMem() { return !_isLoad; } + + public Node mem() { return in(1); } + public Node ptr() { return in(2); } + public Node off() { return in(3); } + + @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; + } + + @Override + public boolean eq(Node n) { + MemOpNode mem = (MemOpNode)n; // Invariant + return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" + } + + @Override + int hash() { return _alias; } + + @Override + public Parser.ParseException err() { + Type ptr = ptr()._type; + // Already an error, but better error messages come from elsewhere + if( ptr == Type.BOTTOM ) return null; + if( ptr.isHigh() ) return null; // Assume it will fall to not-null + // Better be a not-nil TMP + if( ptr instanceof TypeMemPtr tmp && tmp.notNull() ) + return null; + return Parser.error( "Might be null accessing '" + _name + "'",_loc); + } + // 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. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..787333d9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,49 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; + +/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ +public class MemPhiNode extends PhiNode { + public final int _alias; + + public MemPhiNode(String label, int alias, Node... inputs) { + super(label,TypeMem.make(alias,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type 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); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(_alias))); + return this; + } + if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() && + in(2) instanceof StoreNode st && st.mem()==this ) { + Node sink = st.sink(this); + if( sink!=null ) return sink; + } + return super.idealize(); + } + + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override int hash() { return _alias; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java new file mode 100644 index 00000000..74439b8c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -0,0 +1,31 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java new file mode 100644 index 00000000..94c69785 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.Utils; + +public class MinusNode extends Node { + public MinusNode(Node in) { super(null, in); } + + @Override public String label() { return "Minus"; } + + @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; + } + + @Override + public Node idealize() { + // -(-x) is x + if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type instanceof TypeInteger ) + return minus.in(1); + + return null; + } + @Override Node copyF() { return new MinusFNode(null); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java new file mode 100644 index 00000000..5c04af43 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -0,0 +1,36 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java new file mode 100644 index 00000000..6e1dc053 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -0,0 +1,63 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; + +public class MulNode extends ArithNode { + public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Mul"; } + @Override public String op() { return "*"; } + + @Override long doOp( long x, long y ) { return x * y; } + @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Multiply by constant + if ( t2.isConstant() && t2 instanceof TypeInteger i ) { + // 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; + // 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. + if( (c & (c-1)) == 0 ) + return new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(c))); + // 2**n + 1, e.g. 9 + long d = c-1; + if( (d & (d-1)) == 0 ) + return new AddNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(d))).peephole(),lhs); + // 2**n - 1, e.g. 31 + long e = c+1; + if( (e & (e-1)) == 0 ) + return new SubNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(e))).peephole(),lhs); + + } + + // Do we have ((x * (phi cons)) * con) ? + // Do we have ((x * (phi cons)) * (phi cons)) ? + // Push constant up through the phi: x * (phi con0*con0 con1*con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) return phicon; + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } + @Override Node copyF() { return new MulFNode(null,null); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java new file mode 100644 index 00000000..b4d6d78e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.util.Ary; + + +public interface MultiNode { + + abstract Ary<Node> outs(); + + // Find a projection by index + default ProjNode proj( int idx ) { + for( Node out : outs() ) + if( out instanceof ProjNode prj && prj._idx==idx ) + return prj; + return null; + } + + // Find a projection by index + default CProjNode cproj( int idx ) { + for( Node out : outs() ) + if( out instanceof CProjNode prj && prj._idx==idx ) + return prj; + return null; + } + + // Return not-null if this projection index is a ideal copy. + // Called by ProjNode ideal and used to collapse Multis. + default Node pcopy(int idx) { return null; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java new file mode 100644 index 00000000..66879b66 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.Type; +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); } + + @Override public String label() { return "Never"; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Never"); } + + @Override public Type compute() { return TypeTuple.IF_BOTH; } + + @Override public Node idealize() { return null; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java new file mode 100644 index 00000000..840d2759 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -0,0 +1,103 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + + +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. + */ +public class NewNode extends Node implements MultiNode { + + public final TypeMemPtr _ptr; + + 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; + } + + 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; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } + + + @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()); + } + + + @Override + public TypeTuple compute() { + return in(0)._type.isHigh() + ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP) + : TypeTuple.make(_ptr,TypeMem.BOT); + } + + @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 boolean eq(Node n) { return this == n; } + + @Override + int hash() { return _ptr.hashCode(); } + + // ------------ + // MachNode specifics, shared across all CPUs + public int _arg2Reg, _xslot; + private RegMask _arg3Mask; + 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); + // 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(); + // 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); + _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 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())); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java new file mode 100644 index 00000000..414530b4 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java @@ -0,0 +1,693 @@ +package com.seaofnodes.simple.node; + +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.print.IRPrinter; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; +import java.util.*; +import java.util.function.Function; +import static com.seaofnodes.simple.codegen.CodeGen.CODE; + +/** + * All Nodes in the Sea of Nodes IR inherit from the Node class. + * The Node class provides common functionality used by all subtypes. + * Subtypes of Node specialize by overriding methods. + */ +public abstract class Node extends BaseNode<Node> implements Cloneable { + + /** + * Inputs to the node. These are use-def references to Nodes. + * <p> + * Generally fixed length, ordered, nulls allowed, no unused trailing space. + * Ordering is required because e.g. "a/b" is different from "b/a". + * The first input (offset 0) is often a {@link CFGNode} node. + */ + public Ary<Node> _inputs; + + /** + * Outputs reference Nodes that are not null and have this Node as an + * input. These nodes are users of this node, thus these are def-use + * references to Nodes. + * <p> + * Outputs directly match inputs, making a directed graph that can be + * walked in either direction. These outputs are typically used for + * efficient optimizations but otherwise have no semantics meaning. + */ + public Ary<Node> _outputs; + + /** + * Current computed type for this Node. This value changes as the graph + * changes and more knowledge is gained about the program. + */ + public Type _type; + + Node(Node... inputs) { + super(CODE.getUID()); // allocate unique dense ID + _inputs = new Ary<>(Node.class); + Collections.addAll(_inputs,inputs); + _outputs = new Ary<>(Node.class); + for( Node n : _inputs ) + if( n != null ) + n.addUse( this ); + } + + // Make a Node using the existing arrays of nodes. + // Used by any pass rewriting all Node classes but not the edges. + Node( Node n ) { + super(CODE.getUID()); // allocate unique dense ID + assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Select.ordinal(); + _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry()); + _outputs = new Ary<>(Node.class); + _type = n==null ? Type.BOTTOM : n._type; + _deps = null; + _hash = 0; + } + + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" + public abstract String label(); + + public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } + + public boolean isConst() { return false; } + + // ------------------------------------------------------------------------ + // Graph Node & Edge manipulation + + @Override public String typeName() { return _type==null ? null : _type.toString(); } + + /** + * Gets the ith input node + * @param i Offset of the input node + * @return Input node or null + */ + public Node in(int i) { return _inputs.get(i); } + public Node out(int i) { return _outputs.get(i); } + public final Ary<Node> outs() { return _outputs; } + + public int nIns() { return _inputs.size(); } + + public int nOuts() { return _outputs.size(); } + + public boolean isUnused() { return nOuts() == 0; } + + public CFGNode cfg0() { return (CFGNode)in(0); } + + /** + * Change a <em>def</em> into a Node. Keeps the edges correct, by removing + * the corresponding <em>use->def</em> edge. This may make the original + * <em>def</em> go dead. This function is co-recursive with {@link #kill}. + * <p> + + * This method is the normal path for altering a Node, because it does the + * proper default edge maintenance. It also <em>immediately</em> kills + * Nodes that lose their last use; at times care must be taken to avoid + * killing Nodes that are being used without having an output Node. This + * definitely happens in the middle of recursive {@link #peephole} calls. + * + * @param idx which def to set + * @param new_def the new definition + * @return new_def for flow coding + */ + public <N extends Node> N setDef(int idx, N new_def ) { + unlock(); + Node old_def = in(idx); + if( old_def == new_def ) return new_def; // No change + // If new def is not null, add the corresponding def->use edge + // This needs to happen before removing the old node's def->use edge as + // the new_def might get killed if the old node kills it recursively. + if( new_def != null ) + new_def.addUse(this); + // Set the new_def over the old (killed) edge + _inputs.set(idx,new_def); + if( old_def != null ) { // If the old def exists, remove a def->use edge + if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + else CODE.add(old_def); // Else old lost a use, so onto worklist + } + moveDepsToWorklist(); + // Return new_def for easy flow-coding + return new_def; + } + public <N extends Node> N setDefX(int idx, N new_def ) { + while( nIns() <= idx ) addDef(null); + return setDef(idx,new_def); + } + + // Remove the numbered input, compressing the inputs in-place. This + // shuffles the order deterministically - which is suitable for Region and + // Phi, but not for every Node. If the def goes dead, it is recursively + // killed, which may include 'this' Node. + public Node delDef(int idx) { + unlock(); + Node old_def = in(idx); + _inputs.del(idx); + if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + old_def.moveDepsToWorklist(); + return this; + } + + // Insert the numbered input, sliding other inputs to the right + public void insertDef(int idx, Node new_def) { + _inputs.insert(null,idx); + setDef(idx,new_def); + } + + /** + * Add a new def to an existing Node. Keep the edges correct by + * adding the corresponding <em>def->use</em> edge. + * + * @param new_def the new definition, appended to the end of existing definitions + * @return new_def for flow coding + */ + public Node addDef(Node new_def) { + unlock(); + // Add use->def edge + _inputs.add(new_def); + // If new def is not null, add the corresponding def->use edge + if( new_def != null ) + new_def.addUse(this); + return new_def; + } + + // Breaks the edge invariants, used temporarily + protected <N extends Node> void addUse(N n) { _outputs.add(n); } + + // 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. + // Error is 'use' does not exist; ok for 'use' to be null. + public boolean delUse( Node use ) { + _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. + return _outputs.isEmpty(); + } + + // Shortcut for "popping" until n nodes. A "pop" is basically a + // setDef(last,null) followed by lowering the nIns() count. + public void popUntil(int n) { + unlock(); + while( nIns() > n ) { + Node old_def = _inputs.pop(); + if( old_def != null && // If it exists and + old_def.delUse(this) ) // If we removed the last use, the old def is now dead + old_def.kill(); // Kill old def + } + } + + /** + * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em> + * to null. This may recursively kill more Nodes, and is basically dead + * code elimination. + */ + public void kill( ) { + assert isUnused(); // Has no uses, so it is dead + unlock(); + moveDepsToWorklist(); + _type=null; // Flag as dead + while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes + Node old_def = _inputs.removeLast(); + // Revisit neighbor because removed use + if( old_def != null ) { + if( CODE.add(old_def).delUse(this) ) + old_def.kill(); // If we removed the last use, the old def is now dead + old_def.moveDepsToWorklist(); // Use-count changes can enable distant rewrites. + } + } + assert isDead(); // Really dead now + } + + // Preserve CFG use-ordering when killing + public void killOrdered() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + kill(); + } + + // Mostly used for asserts and printing. + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + + // 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; } + // Remove bogus null. + @SuppressWarnings("unchecked") + 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() { + if( unkeep().isUnused() ) + kill(); + } + public void isKill() { + if( isUnused() ) + kill(); + } + + // Replace self with nnn in the graph, making 'this' go dead + public void subsume( Node nnn ) { + assert nnn!=this; + while( nOuts() > 0 ) { + Node n = _outputs.removeLast(); + 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); + } + kill(); + } + + // insert `this` immediately after `def` in the same basic block. + public void insertAfter( Node def ) { + CFGNode cfg = def.cfg0(); + int i = cfg._outputs.find(def)+1; + if( cfg instanceof CallEndNode ) { + cfg = cfg.uctrl(); i=0; + } else if( def.in(0) instanceof MultiNode ) { + assert i==0; + i = cfg._outputs.find(def.in(0))+1; + } + + while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++; + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + } + + // Insert this in front of use.in(uidx) with this, and insert this + // immediately before use in the basic block. + public void insertBefore( Node use, int uidx ) { + CFGNode cfg = use.cfg0(); + int i; + if( use instanceof PhiNode phi ) { + cfg = phi.region().cfg(uidx); + if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr ) + cfg = nvr.cfg0(); + i = cfg.nOuts()-1; + } else { + i = cfg._outputs.find(use); + } + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + if( _inputs._len > 1 && this instanceof SplitNode ) + setDefOrdered(1,use.in(uidx)); + use.setDefOrdered(uidx,this); + } + + public void setDefOrdered( int idx, Node def) { + // If old is dying, remove from CFG ordered + Node old = in(idx); + if( old!=null && old.nOuts()==1 ) { + CFGNode cfg = old.cfg0(); + if( cfg!=null ) { + cfg._outputs.remove(cfg._outputs.find(old)); + old._inputs.set(0,null); + } + } + setDef(idx,def); + } + + public void removeSplit() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + if( _inputs._len > 1 ) subsume(in(1)); + } + + // ------------------------------------------------------------------------ + // Graph-based optimizations + + /** + * 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; + 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; + } + + /** + * Try to peephole at this node and return a better replacement Node if + * possible. We compute a {@link Type} and then check and replace: + * <ul> + * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li> + * <li>in a future chapter we will look for a + * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a> + * to eliminate.</li> + * <li>we ask the Node for a better replacement. The "better replacement" + * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes + * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing + * math constants, remove algebraic identities and generally simplify the + * code. </li> + * + * Unlike peephole above, this explicitly returns null for no-change, or not-null + * for a better replacement (which can be this). + * </ul> + */ + public final Node peepholeOpt( ) { + CODE.iterCnt(); + // 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() ) + return ConstantNode.make(_type).peephole(); + + // Global Value Numbering + if( _hash==0 ) { + Node n = CODE._gvn.get(this); // Will set _hash as a side effect + if( n==null ) + CODE._gvn.put(this,this); // Put in table now + else { + // Because of random worklist ordering, the two equal nodes + // might have different types. Because of monotonicity, both + // types are valid. To preserve monotonicity, the resulting + // shared Node has to have the best of both types. + n.setType(n._type.join(_type)); + _hash = 0; // Clear, since it never went in the table + return deadCodeElim(n);// Return previous; does Common Subexpression Elimination + } + } + + // Ask each node for a better replacement + Node n = idealize(); + if( n != null ) // Something changed + return n; // Report progress + + if( old!=_type ) return this; // Report progress; + CODE.iterNop(); + return null; // No progress + } + + // m is the new Node, self is the old. + // Return 'm', which may have zero uses but is alive nonetheless. + // If self has zero uses (and is not 'm'), {@link #kill} self. + private Node deadCodeElim(Node m) { + // If self is going dead and not being returned here (Nodes returned + // from peephole commonly have no uses (yet)), then kill self. + if( m != this && isUnused() && !isDead() ) { + // Killing self - and since self recursively kills self's inputs we + // might end up killing 'm', which we are returning as a live Node. + // So we add a bogus extra null output edge to stop kill(). + m.keep(); // Keep m alive + kill(); // Kill self because replacing with 'm' + m.unkeep(); // Okay to peephole m + } + return m; + } + + /** + * This function needs to be + * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a> + * as it is part of a Monotone Analysis Framework. + * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>. + * <p> + * For Chapter 2, all our Types are really integer constants, and so all + * the needed properties are trivially true, and we can ignore the high + * theory. Much later on, this will become important and allow us to do + * many fancy complex optimizations trivially... because theory. + * <p> + * {@link #compute()} needs to be stand-alone, and cannot recursively call compute + * on its inputs, because programs are cyclic (have loops!) and this will just + * infinitely recurse until stack overflow. Instead, compute typically + * computes a new type from the {@link #_type} field of its inputs. + */ + public abstract Type compute(); + + // Set the type. Assert monotonic progress. + // If changing, add users to worklist. + public Type setType(Type type) { + Type old = _type; + assert old == null || type.isa(old) : "Monotonicity test failed"; + if( old == type ) return old; + _type = type; // Set _type late for easier assert debugging + CODE.addAll(_outputs); + moveDepsToWorklist(); + return old; + } + + @SuppressWarnings("unchecked") + public <N extends Node> N init() { _type = compute(); return (N)this; } + + /** + * This function rewrites the current Node into a more "idealized" form. + * This is the bulk of our peephole rewrite rules, and we use this to + * e.g. turn arbitrary collections of adds and multiplies with mixed + * constants into a normal form that's easy for hardware to implement. + * Example: An array addressing expression: + * <pre> ary[idx+1]</pre> + * might turn into Sea-of-Nodes IR: + * <pre> (ary+12)+((idx+1) * 4)</pre> + * This expression can then be idealized into: + * <pre> ary + ((idx*4) + (12 + (1*4)))</pre> + * And more folding: + * <pre> ary + ((idx<<2) + 16)</pre> + * And during code-gen: + * <pre> MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation </pre> + * <p> + * {@link #idealize} has a very specific calling convention: + * <ul> + * <li>If NO change is made, return {@code null} + * <li>If ANY change is made, return not-null; this can be {@code this} + * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. + * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}. + * </ul> + * <p> + * Examples: + * <table border="3"> + * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr> + * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr> + * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr> + * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr> + * </table> + * + * The last entry deserves more discussion. The new Node {@code (1+2)} + * created in {@link #idealize} calls {@link #peephole} (which then folds + * into a constant). The other new Node {@code (x+3)} does not call + * peephole, because it is returned and peephole itself will recursively + * call peephole. + * <p> + * Since idealize calls peephole and peephole calls idealize, you must be + * careful that all idealizations are <em>monotonic</em>: all transforms + * remove some feature, so that the set of available transforms always + * shrinks. If you don't, you risk an infinite peephole loop! + * + * @return Either a new or changed node, or null for no changes. + */ + public abstract Node idealize(); + + // Some of the peephole rules get complex, and search further afield than + // just the nearest neighbor. These peepholes can fail the pattern match + // on a node some distance away, and if that node ever changes we should + // retry the peephole. Track a set of Nodes dependent on `this`, and + // 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); } + + /** + * 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); } + + 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; + dep._deps.add(this); + return dep; + } + + // Move the dependents onto a worklist, and clear for future dependents. + public void moveDepsToWorklist( ) { moveDepsToWorklist(CODE._iter); } + public void moveDepsToWorklist( IterPeeps iter ) { + if( _deps == null ) return; + iter.addAll(_deps); + _deps.clear(); + } + + // Two nodes are equal if they have the same inputs and the same "opcode" + // which means the same Java class, plus same internal parts. + @Override public final boolean equals( Object o ) { + if( o==this ) return true; + if( o.getClass() != getClass() ) return false; + Node n = (Node)o; + int len = _inputs.size(); + if( len != n._inputs.size() ) return false; + for( int i=0; i<len; i++ ) + if( in(i) != n.in(i) ) + return false; + return eq(n); + } + // Subclasses add extra checks (such as ConstantNodes have same constant), + // and can assume "this!=n" and has the same Java class. + public boolean eq( Node n ) { return true; } + + // Cached hash. If zero, then not computed AND this Node is NOT in the GVN + // table - and can have its edges hacked (which will change his hash + // anyway). If Non-Zero then this Node is IN the GVN table, or is being + // probed to see if it can be inserted. His edges are "locked", because + // hacking his edges will change his hash. + int _hash; + + // If the _hash is set, then the Node is in the GVN table; remove it. + void unlock() { + if( _hash==0 ) return; + Node old = CODE._gvn.remove(this); // Pull from table + assert old==this; + _hash=0; // Out of table now + } + + // Hash of opcode and inputs + @Override public final int hashCode() { + if( _hash != 0 ) return _hash; + int hash = hash(); + for( Node n : _inputs ) + if( n != null ) + hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid; + if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing + return (_hash=hash); + } + // Subclasses add extra hash info (such as ConstantNodes constant) + int hash() { return 0; } + + // ------------------------------------------------------------------------ + // + + /** 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 + + // Swap inputs without letting either input go dead during the swap. + public Node swap12() { + unlock(); // Hash is order dependent + _inputs.swap(1,2); + return this; + } + + /** + * Does this node contain all constants? Ignores in(0), as is usually + * control. In an input is not a constant, we add dep as a dependency to + * it because dep can make progress if the input becomes a constant later. + * It is sufficient for one of the non-const inputs to have the dependency, + * so we don't bother checking the rest. + */ + boolean allCons(Node dep) { + for( int i=1; i<nIns(); i++ ) + if( !(in(i)._type.isConstant()) ) { + dep.addDep(in(i)); // If in(i) becomes a constant later, will trigger some peephole + return false; + } + return true; + } + + // Make a shallow copy (same class) of this Node, with given inputs and + // empty outputs and a new Node ID. The original inputs are ignored. + // Does not need to be implemented in isCFG() nodes. + Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + + public Node copy() { return copyEmpty(); } + + // Exact-class copy with fresh identity and no edges. Unlike machine + // rematerialization copies, this never registers or copies input edges. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( Exception e ) { throw new RuntimeException(e); } + n._nid = CODE.getUID(); // allocate unique dense ID + n._inputs = new Ary<>(Node.class); + n._outputs = new Ary<>(Node.class); + n._deps = null; + n._hash = 0; + return n; + } + + // Report any post-optimize errors + public Parser.ParseException err() { return null; } + + // Common integer constants + public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); } + + // Utility to walk the entire graph applying a function; return the first + // not-null result. + final public <E> E walk( Function<Node,E> pred ) { + assert CODE._visit.isEmpty(); + E rez = _walk(pred); + CODE._visit.clear(); + return rez; + } + + private <E> E _walk( Function<Node,E> pred ) { + if( CODE._visit.get(_nid) ) return null; // Been there, done that + CODE._visit.set(_nid); + E x = pred.apply(this); + if( x != null ) return x; + for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x; + for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x; + return null; + } + + /** + * Debugging utility to find a Node by index + */ + public Node find(int nid) { return _find(nid, new BitSet()); } + private Node _find(int nid, BitSet bs) { + if( bs.get(_nid) ) return null; // Been there, done that + bs.set(_nid); + if( _nid==nid ) return this; + Node x; + for( Node def : _inputs ) if( def != null && (x = def._find(nid,bs)) != null ) return x; + for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x; + return null; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java new file mode 100644 index 00000000..775b069b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.*; +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 protected String format() { return "(!%1)"; } + + @Override + public TypeInteger compute() { + Type t0 = in(1)._type; + if( t0.isHigh() ) return (TypeInteger)TypeInteger.BOOL.dual(); + if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE; + if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE; + if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE; + return TypeInteger.BOOL; + } + + @Override + public Node idealize() { return null; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java new file mode 100644 index 00000000..d16dd194 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +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); } + + @Override public String label() { return "Or"; } + @Override public String op() { return "|"; } + @Override long doOp( long x, long y ) { return x | y; } + @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { + return TypeInteger.BOT; + } + + @Override + 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 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Do we have ((x | (phi cons)) | con) ? + // Do we have ((x | (phi cons)) | (phi cons)) ? + // Push constant up through the phi: x | (phi con0|con0 con1|con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) return phicon; + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new OrNode(_loc,lhs,rhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java new file mode 100644 index 00000000..95934e4b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -0,0 +1,59 @@ +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; + +public class ParmNode extends PhiNode { + + // Argument indices are mapped one-to-one on CallNode inputs + public final int _idx; // Argument index + + public ParmNode(String label, int idx, Type declaredType, Node... inputs) { + super(label,declaredType,inputs); + _idx = idx; + } + public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; } + + @Override public String label() { return MemOpNode.mlabel(_label); } + + public FunNode fun() { return (FunNode)in(0); } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( "main".equals(fun()._name) && _label.equals("arg") ) + return p.p("arg"); + p.p("Parm_").p(_label).open(); + for( Node in : _inputs ) p.n(in).p(","); + return p.unchar(',').close(); + } + + + // Always in-progress until we run out of unknown callers + @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); } + + @Override + public Node idealize() { + if( !(region() instanceof FunNode) ) + return 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; + } + + @Override public boolean eq( Node n ) { + return ((ParmNode)n)._idx==_idx && super.eq(n); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java new file mode 100644 index 00000000..953e009e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -0,0 +1,310 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; + +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) { + super(inputs); + _label = label; + assert minType!=null; + _minType = minType; + } + // Used by ParmNode + public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; } + // Used by instruction Selection + public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); } + // Used by the infinite-loop exit breaker + public PhiNode(RegionNode r, Node sample) { + super(new Node[]{r}); + _label = ""; + _minType = 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 String label() { return "Phi_"+MemOpNode.mlabel(_label); } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( !(region() instanceof RegionNode r) || r.inProgress() ) + p.p("Z"); + p.p("Phi("); + for( Node in : _inputs ) p.n(in).p(","); + return p.unchar(',').close(); + } + + public CFGNode region() { return (CFGNode)in(0); } + @Override public boolean isMem() { return _minType instanceof TypeMem; } + boolean isRPC() { return false; } + + @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 ) + // 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; + // 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); + } + } + Type newt = t.join( _minType ); + + // Widen loop Phis so integer ranges cannot grow one step per trip forever. + // Calls are opaque during SCCP here. The next chapter also widens at + // ParmNode and CallEndNode when solving through recursive calls. + // 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); + } + + return newt; + } + + @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 ) + 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); + } + + // 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; + + // 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); + } + } + + return 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 || + 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; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + addDepForwards(use); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( addDep(region().in(i))._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + addDepForwards(n); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) addDepForwards(use); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } + return true; + } + + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + // Not accepts both pointers and integers, but a Phi cannot mix them. + if( t==Type.BOTTOM ) { + for( int i=1; i<ins.length; i++ ) addDep(ins[i]); + cp.kill(); + return null; + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(false),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) addDep(in(i).in(j)); + cp.kill(); + return null; + } + + /** + * If only single unique input, return it + */ + private Node singleUniqueInput() { + if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL ) + return null; // Dead entry loops just ignore and let the loop collapse + Node live = null; + 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. + if( addDep(region().in(i))._type != Type.XCONTROL && in(i) != this ) + if( live == null || live == in(i) ) live = in(i); + else return null; + } + return live; + } + + @Override + boolean allCons(Node dep) { + if( !(region() instanceof RegionNode r) ) return false; + // When the region completes (is no longer in progress) the Phi can + // become a "all constants" Phi, and the "dep" might make progress. + dep.addDep(this); + if( r.inProgress() ) return false; + return super.allCons(dep); + } + + // True if last input is null + public boolean inProgress() { + return in(nIns()-1) == null; + } + + // 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; + } + + @Override + public Parser.ParseException err() { + if( _type != Type.BOTTOM ) return null; + + // BOTTOM means we mixed e.g. int and ptr + for( int i=1; i<nIns(); i++ ) + // Already an error, but better error messages come from elsewhere + if( in(i)._type == Type.BOTTOM ) + return null; + + // Gather a minimal set of types that "cover" all the rest + boolean ti=false, tf=false, tp=false, tn=false; + for( int i=1; i<nIns(); i++ ) { + Type t = in(i)._type; + ti |= t instanceof TypeInteger x; + tf |= t instanceof TypeFloat x; + tp |= t instanceof TypeMemPtr x; + tn |= t==Type.NIL; + } + return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java new file mode 100644 index 00000000..92c6c2be --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -0,0 +1,54 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.TypeTuple; + + +public class ProjNode extends Node { + + // Which slice of the incoming multipart value + public final int _idx; + + // Debugging label + public final String _label; + + public ProjNode(Node ctrl, int idx, String label) { + super(new Node[]{ctrl}); + _idx = idx; + _label = label; + } + public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; } + + @Override public String label() { return _label; } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( _label != null ) return p.p(_label); + if( in(0) instanceof CallEndNode cend && cend.call()!=null ) + return p.n(cend.call()); + return p.p("LONELY PROJ"); + } + + @Override public CFGNode cfg0() { + return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0(); + } + + @Override public boolean isMem() { return _type instanceof TypeMem; } + + @Override + public Type compute() { + Type t = in(0)._type; + return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob(); + } + + @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); } + + @Override + public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } + + @Override + int hash() { return _idx; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java new file mode 100644 index 00000000..a35f5a1d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java @@ -0,0 +1,19 @@ +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.util.SB; + +/** + * Cast a pointer to read-only + */ +public class ReadOnlyMach extends ReadOnlyNode implements MachNode { + public ReadOnlyMach( ReadOnlyNode n ) { super(n); } + @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 void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java new file mode 100644 index 00000000..94c1b6f6 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + +/** + * Cast a pointer to read-only + */ +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 protected String format() { return "(const)%1"; } + @Override public Type compute() { + Type t = in(1)._type; + return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t; + } + + @Override public Node idealize() { + if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() ) + return in(1); + return null; + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java new file mode 100644 index 00000000..59030dee --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -0,0 +1,174 @@ +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 java.util.*; + +public class RegionNode extends CFGNode { + // Source location for late discovered errors + public Parser.Lexer _loc; + + 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()); + } + + @Override public boolean blockHead() { return true; } + + @Override + public Type compute() { + if( inProgress() ) return Type.CONTROL; + Type t = Type.XCONTROL; + for (int i = 1; i < nIns(); i++) + t = t.meet(in(i)._type); + return t; + } + + @Override + public Node idealize() { + if( inProgress() ) return null; + + Node progress = deadPath(false); + if( progress!=null ) return progress; + + // If down to a single input, become that input + if( nIns()==2 && !hasPhi() ) + return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own + + // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" + if( !hasPhi() && 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 ) { + // Replace with the iff.ctrl directly + if( nIns()==3 ) return iff.ctrl(); + // Just delete the path for fat Regions + setDef(1,iff.ctrl()); + return delDef(2); + } + + // Flatten stacked regions (no loops involved) + if( getClass() == RegionNode.class && nIns()>2 ) { + for( int i=1; i<nIns(); i++ ) + if( cfg(i) instanceof RegionNode region && region.getClass() == RegionNode.class && region.nIns()>2 && !hasMidUser(region) ) { + assert !region.inProgress(); + // Fold Phis + for( Node use : _outputs ) { + if( use instanceof PhiNode phi ) { + PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null; + for( int j=1; j<region.nIns(); j++ ) + phi.addDef(phi2==null ? phi.in(i) : phi2.in(j)); + CodeGen.CODE.add(phi); + } + } + + // Fold Region + for( int j=1; j<region.nIns(); j++ ) + addDef(region.in(j)); + setDef(i,Parser.XCTRL); + return this; + } + } + + return null; + } + + Node deadPath(boolean skipStart) { + // Delete dead paths into a Region + int path = findDeadInput(skipStart ? 2 :1); + if( path==0 ) return null; + + // Do not delete the entry path of a loop (ok to remove the back edge + // and make the loop a single-entry Region which folds away the Loop). + // Folding the entry path confused the loop structure, moving the + // backedge to the entry point. + if( this instanceof LoopNode loop && loop.entry()==in(path) ) + return null; + return removeDeadPath(path); + } + + public Node removeDeadPath(int path) { + // Cannot use the obvious output iterator here, because a Phi deleting + // an input might recursively delete *itself*. This shuffles the + // output array, and we might miss iterating an unrelated Phi. So on + // rare occasions we repeat the loop to get all the Phis. + int nouts = 0; + while( nouts != nOuts() ) { + nouts = nOuts(); + for( int i=0; i<nOuts(); i++ ) + if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) { + CodeGen.CODE.addAll(phi.in(path)._outputs); + CodeGen.CODE.addAll(phi.delDef(path)._outputs); + } + } + return isDead() ? Parser.XCTRL : delDef(path); + } + + private int findDeadInput(int startIdx) { + for( int i=startIdx; i<nIns(); i++ ) + if( in(i)._type==Type.XCONTROL ) + return i; + return 0; // All inputs alive + } + + boolean hasPhi() { + for( Node phi : _outputs ) + if( phi instanceof PhiNode ) + return true; + return false; + } + + // Is there a value in-between 2 Regions? Difficult to correctly fuse + // without extensive testing, so just ignore. + boolean hasMidUser(RegionNode r) { + for( Node use : r._outputs ) { + if( use==this ) continue; + if( use instanceof PhiNode ) { + for( Node data : use._outputs ) { + if( !(data instanceof PhiNode phi2) || phi2.region()!=this ) + { addDep(use); if( data!=null ) addDep(data); return true; } + } + } else + { addDep(use); return true; } // Control user + } + return false; + } + + + // Immediate dominator of Region is a little more complicated. + @Override public int idepth() { + if( validIDepth() ) return _idepth; + int depth=0; + for( Node n : _inputs ) + if( n!=null ) + depth = Math.max(depth,((CFGNode)n).idepth()+1); + return cacheIDepth(depth); + } + + @Override public CFGNode idom(Node dep) { + CFGNode lca = null; + // Recompute from predecessors: CFG edits can change the dominator. + for( int i=1; i<nIns(); i++ ) + if( !cfg(i)._type.isHigh() ) + lca = cfg(i).domLCA(lca,dep); + return lca; + } + + // True if last input is null + public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; } + + // Never equal if inProgress + @Override public boolean eq( Node n ) { return !inProgress(); } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java new file mode 100644 index 00000000..266657be --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -0,0 +1,163 @@ +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.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +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}. */ +public class ReturnNode extends CFGNode { + + public FunNode _fun; + + public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) { + super(ctrl, mem, data, rpc); + _fun = fun; + } + public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; } + + public Node ctrl() { return in(0); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } + public Node rpc () { return in(3); } + @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("----"); + else p.n(expr()); + return p.p(";"); + } + + // No one unique control follows; can be many call end sites + @Override public CFGNode uctrl() { return null; } + + @Override + public Type compute() { + if( inProgress () ) return TypeTuple.RET; // In progress + // Inlining can delete the entry while callers still use this return. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); + } + + @Override public Node idealize() { + if( inProgress () ) return null; + if( _fun.isDead() ) return null; + + // Upgrade signature based on return type + Type ret = expr()._type; + TypeFunPtr fcn = _fun.sig(); + 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; + } + + 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); + } + + + // ------------ + // MachNode specifics, shared across all CPUs + public String op() { + return _fun._frameAdjust > 0 ? "addi" : "ret"; + } + // Correct Nodes outside the normal edges + public void postSelect(CodeGen code) { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + public RegMask regmap(int i) { + return i==2 + ? CodeGen.CODE._mach.retMask(_fun.sig()) + : CodeGen.CODE._retMasks[i]; + } + public RegMask outregmap() { return null; } + public void encoding( Encoding enc ) { throw Utils.TODO(); } + public void asm(CodeGen code, SB sb) { + int frameAdjust = fun()._frameAdjust; + if( frameAdjust>0 ) + sb.p("rsp += #").p(frameAdjust).p("\nret"); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // 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(" "); + // 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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java new file mode 100644 index 00000000..10a0e4aa --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +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 protected String format() { return "((f32)%1)"; } + + @Override + public Type compute() { + if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() ) + return TypeFloat.constant((float)i0.value()); + if( in(1)._type==TypeFloat.F64 ) return TypeFloat.F32; + return in(1)._type; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Type t1 = lhs._type; + + // RoundF32 of float + if( t1 instanceof TypeFloat tf && tf._sz==32 ) + return lhs; + + return null; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java new file mode 100644 index 00000000..429be229 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.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; + +public class SarNode extends ArithNode { + public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + + @Override public String label() { return "Sar"; } + @Override public String op() { return ">>"; } + @Override long doOp( long x, long y ) { return x >> y; } + @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { + if( y.isConstant() ) { + int log = (int)y.value(); + return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1); + } + return TypeInteger.BOT; + } + + @Override + 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 ) + return lhs; + + // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5) + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new SarNode(_loc,lhs,rhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java new file mode 100644 index 00000000..307fc0f9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -0,0 +1,530 @@ +package com.seaofnodes.simple.node; + +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.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. + */ +public class ScopeNode extends Node { + + /** + * The control is a name that binds to the currently active control + * node in the graph + */ + public static final String CTRL = "$ctrl"; + 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 + public final Ary<Var> _vars; + + // Lexical scope is typed: + public static class Kind { + // Basic block scoping + public static class Block extends Kind { } + // 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 int _lexSize; // Number of Vars in this scope + } + public final Ary<Kind> _kinds; + // Lexical scope nesting depth + public int depth() { return _kinds._len; } + + // Extra guards; tested predicates and casted 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); + } + + @Override public String label() { return "Scope"; } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("Scope[ "); + 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); + p.p(v._type()); + p.p(" "); + if( v._final ) p.p("!"); + p.p(v._name); + p.p("="); + Node n = in(i); + while( n instanceof ScopeNode loop ) { + p.p("Lazy_"); + n = loop.in(i); + } + p.n(n); + p.p(", "); + } + return p.unchar(", ").p("]"); + } + + + public Node ctrl() { return in(0); } + public Node mem() { return in(lookup(MEM0)); } + public Var var(int i) { return _vars.at(i); } + + /** + * The ctrl of a ScopeNode is always bound to the currently active + * control node in the graph, via a special name '$ctrl' that is not + * a valid identifier in the language grammar and hence cannot be + * referenced in Simple code. + * + * @param n The node to be bound to '$ctrl' + * + * @return Node that was bound + */ + public <N extends Node> N ctrl(N n) { return setDef(0,n); } + public Node mem(Node n) { update(MEM0,n); return n; } + + public void push( Kind kind ) { + kind._lexSize = _vars.size(); + _kinds.push(kind); + } + + // Pop a lexical scope + public void pop() { + promote(); // Promote forward references to the next outer scope + int n = _kinds.pop()._lexSize; + popUntil(n); // Pop off inputs going out of scope + _vars.setLen(n); // Pop off variables going out of scope + } + + + // 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(); + int n = kind._lexSize; + for( int i=n; i<nIns(); i++ ) { + Var v = var(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; + 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 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 ) { + for( int i=depth()-1; i>=0; i-- ) + if( v._idx >= _kinds.at(i)._lexSize ) + return _kinds.at(i); + throw Utils.TODO(); + } + + // 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. + + // - Walking out of a method and into the wrong struct is an error; + // including nested structs containing (no nested classes yet) + + // Returns error, forward-ref, ok containing struct, ok containing function/block. + + 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(); + } + + + // Find name in reverse, return an index into _vars or -1. Linear scan + // instead of hashtable, but probably doesn't matter until the scan + // typically hits many dozens of variables. + public int find( String name ) { + for( int i=_vars.size()-1; i>=0; i-- ) + if( var(i)._name.equals(name) ) + return i; + return -1; + } + + /** + * 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 + if( depth() > 0 ) + for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) { + Var n = var(i); + if( n._name.equals(name) ) { + if( !n.isFRef() ) return false; // Double define + FRefNode fref = (FRefNode)in(n._idx); // Get forward ref + if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant + n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location + setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also + } + } + Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL); + _vars.add(v); + // Creating a forward reference + if( init==Parser.XCTRL ) + init = new FRefNode(v).init(); + addDef(init); + return true; + } + + /** + * Lookup a name in all scopes starting from most deeply nested. + * + * @param name Name to be looked up + * @return null if not found, or the implementing Node + */ + public Var lookup( String name ) { + int idx = find(name); + // -1 is missed in all scopes, not found + return idx == -1 ? null : update(var(idx),null); + } + + /** + * Redefine an existing name + * + * @param name Name being redefined + * @param n The node to bind to the name + */ + public void update( String name, Node n ) { + int idx = find(name); + assert idx>=0; + update(var(idx),n); + } + + public Var update( Var v, Node st ) { + Node old = in(v._idx); + if( old instanceof ScopeNode loop ) { + // Lazy Phi! + Node def = loop.in(v._idx); + old = def instanceof PhiNode phi && loop.ctrl()==phi.region() + // Loop already has a real Phi, use it + ? def + // 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()); + setDef(v._idx,old); + } + //assert !v._final || st==null; + if( st!=null ) setDef(v._idx,st); // Set new value + return v; + } + + /** + * Duplicate a ScopeNode; including all levels, up to Nodes. So this is + * neither shallow (would dup the Scope but not the internal HashMap + * tables), nor deep (would dup the Scope, the HashMap tables, but then + * also the program Nodes). + * <p> + * If the {@code loop} flag is set, the edges are filled in as the original + * Scope, as an indication of Lazy Phis at loop heads. The goal here is to + * not make Phis at loop heads for variables which are never touched in the + * loop body. + * <p> + * The new Scope is a full-fledged Node with proper use<->def edges. + */ + public ScopeNode dup() { return dup(false); } + public ScopeNode dup(boolean loop) { + ScopeNode dup = new ScopeNode(); + // Our goals are: + // 1) duplicate the name bindings of the ScopeNode across all stack levels + // 2) Make the new ScopeNode a user of all the nodes bound + // 3) Ensure that the order of defs is the same to allow easy merging + dup._vars .addAll(_vars ); + dup._kinds .addAll(_kinds ); + dup._guards .addAll(_guards ); + // The dup'd guards all need dup'd keepers, to keep proper accounting + // when later removing all guards + for( Node n : _guards ) + if( !(n instanceof CFGNode) ) + 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)); + return dup; + } + + /** + * Merges the names whose node bindings differ, by creating Phi node for such names + * The names could occur at all stack levels, but a given name can only differ in the + * innermost stack level where the name is bound. + * + * @param that The ScopeNode to be merged into this + * @return A new node representing the merge point + */ + public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { + RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); + _merge(that,r); + that.kill(); // Kill merged scope + CodeGen.CODE.add(r); + return r.unkeep(); + } + + private void _merge(ScopeNode that, RegionNode r) { + for( int i = 1; i < nIns(); 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 + Var v = var(i); + Node lhs = this.in(this.update(v,null)); + Node rhs = that.in(that.update(v,null)); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); + } + } + + // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;". + // Forward refs are copied to the other side, "as if" they were there all along. + public void balanceIf( ScopeNode scope ) { + for( int i = nIns(); i < scope.nIns(); i++ ) { + Var n = scope.var(i); + if( n.isFRef() ) { // RHS has forward refs + _vars.add(n); // Copy to LHS + addDef(scope.in(i)); + } else + throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); + } + } + + + // peephole the backedge scope into this loop head scope + // We set the second input to the phi from the back edge (i.e. loop body) + public void endLoop(ScopeNode back, ScopeNode exit ) { + Node ctrl = ctrl(); + assert ctrl instanceof LoopNode loop && loop.inProgress(); + ctrl.setDef(2,back.ctrl()); + + _endLoop(this,back,exit); + back.kill(); // Loop backedge is dead + // Now one-time do a useless-phi removal + _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); + 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 + } + } + } + } + + // 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; + _guards.add(ctrl); // Marker between guard sets + // add pred & its cast to the normal input list, with special Vars + if( pred==null || pred.isDead() ) + return; // Dead, do not add any guards + _addGuards(ctrl,pred,invert); + } + + private void _addGuards( Node ctrl, Node pred, boolean invert ) { + if( pred==null || pred.isDead() ) + return; + if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) ) + return; + // A single negation is handled below. For !!p (including p != null) + // or a negated short-circuit Phi, also discover the underlying facts. + if( pred instanceof NotNode not && + (not.in(1) instanceof NotNode || not.in(1) instanceof PhiNode) ) { + pred.keep(); // Recursive peepholes may rediscover this Not. + _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. + 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 ) { + Node skip = shortCircuitSkippedPred(phi.in(i),iff.pred(),prj,invert); + if( skip != null ) { + _addGuards(ctrl,skip,invert); + _addGuards(ctrl,phi.in(3-i),invert); + break; + } + } + // Invert the If conditional + if( invert ) + pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole()); + // This is a zero/null test. + // Compute the positive test type. + Type tnz = pred._type.nonZero(); + if( tnz!=null ) + _addGuard(tnz,ctrl,pred); + + // Compute the negative test type. + if( pred instanceof NotNode not ) { + Node npred = not.in(1); + Type tzero = npred._type.makeZero(); + _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; + } + + private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) { + if( val == null ) return null; + if( val == pred ) + return (prj._idx==0)==invert ? val : null; + if( val instanceof NotNode not && not.in(1)==pred ) + return (prj._idx==1)==invert ? val : null; + if( val._type == TypeInteger.TRUE && invert ) + return prj._idx==0 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole()); + if( val._type == TypeInteger.FALSE && !invert ) + return prj._idx==1 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole()); + 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(); + _guards.add(pred); + _guards.add(cast); + replace(pred,cast); + } + } + + + // Remove matching pred/cast pairs from this guarded region. + public ScopeNode removeGuards( Node ctrl ) { + assert ctrl instanceof CFGNode; + // Pop the guards up to this region's marker. + while( true ) { + Node g = _guards.pop(); + if( g == ctrl ) break; + if( g instanceof CFGNode ) continue; + 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 + public Node upcastGuard( Node pred ) { + // If finding an instanceof pred, replace with cast. + // Otherwise, just pred itself. + int i = _guards._len; + while( i > 0 ) { + Node cast = _guards.at(--i); + if( cast instanceof CFGNode ) continue; // Marker between guard sets + Node xpred = _guards.at(--i); + if( xpred == pred ) + return cast; + } + return pred; + } + + // Kill guards also + @Override public void kill() { + for( Node n : _guards ) + if( !(n instanceof CFGNode) ) + n.unkill(); + _guards.clear(); + // Can have lazy uses remaining + if( isUnused() ) + super.kill(); + } + + + private void replace( Node old, Node cast ) { + assert old!=null && old!=cast; + for( int i=0; i<nIns(); i++ ) + if( in(i)==old ) + setDef(i,cast); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java new file mode 100644 index 00000000..e2951784 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +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); } + + @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 ) { + if( x == TypeInteger.ZERO ) + return TypeInteger.ZERO; + if( y.isConstant() ) { + int shf = (int)y.value(); + // If no overflow, shift endpoints + if( !(((x._min<<shf)>>shf) != x._min || + ((x._max<<shf)>>shf) != x._max ) ) + return TypeInteger.make(x._min<<shf,x._max<<shf); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + + if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) { + // Shl of 0. + if( (shl.value()&63)==0 && 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) ); + } + } + + // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5) + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new ShlNode(_loc,lhs,rhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java new file mode 100644 index 00000000..0023431f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +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); } + + @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 ) { + return x == TypeInteger.ZERO ? x : TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t2 = rhs._type; + + // Shr of 0. + if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger ) + return lhs; + + // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5) + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new ShrNode(_loc,lhs,rhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java new file mode 100644 index 00000000..a7fe6c5f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.SB; + +public abstract class SplitNode extends MachConcreteNode { + 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; } + // A register-only transfer may need another split to reach stack storage. + public boolean isNonStackSplit() { return false; } + @Override public String op() { return "mov"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("mov("); + if( in(1) == null ) p.p("---"); + else p.n(in(1)); + return p.close(); + } + @Override public Type compute() { return in(0)._type; } + @Override public Node idealize() { return null; } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } + @Override public String comment() { return _kind + " #"+ _round; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java new file mode 100644 index 00000000..e13d35c0 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -0,0 +1,49 @@ +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.type.*; +import java.util.HashSet; +import static com.seaofnodes.simple.util.Utils.TODO; + +/** Start supplies control, whole memory, and the argument, in that order. */ +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) { super(start); _arg = start==null ? null : start._arg; } + + @Override public String label() { return "Start"; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()); + } + + @Override public CFGNode cfg0() { return null; } + + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. For Start, its "main" + @Override public CFGNode uctrl() { + // Find "main", it's the start. + CFGNode C = null; + for( Node use : _outputs ) + if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) ) + { assert C==null; C = fun; } + return C; + } + + + @Override public TypeTuple compute() { + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); + } + + @Override public Node idealize() { return null; } + + // No immediate dominator, and idepth==0 + @Override public int idepth() { return 0; } + @Override public CFGNode idom(Node dep) { return null; } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java new file mode 100644 index 00000000..9f7a925b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -0,0 +1,76 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.Utils; + +public class StopNode extends CFGNode { + + public final String _src; + + public StopNode(String src) { + super(); + _src = src; + _type = compute(); + } + public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } + + @Override + public String label() { + 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(); + if( ret1!=null ) return p.n(ret1); + p.p("Stop[ "); + for( Node ret : _inputs ) + if( ret!=null ) { + String name = ((ReturnNode)ret).fun()._name; + if( name== null || !name.startsWith("sys.") ) + p.n(ret).p(" "); + } + return p.p("]"); + } + + @Override public boolean blockHead() { return true; } + + + // 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; + } + return ret1==this ? null : (ReturnNode)ret1; + } + + @Override + public Type compute() { + return Type.BOTTOM; + } + + @Override + public Node idealize() { + int len = nIns(); + for( int i=0; i<nIns(); i++ ) + if( addDep(((ReturnNode)in(i)).fun()).isDead() ) + delDef(i--); + if( len != nIns() ) return this; + return null; + } + + @Override public int idepth() { + if( validIDepth() ) return _idepth; + int d=0; + for( Node ret : _inputs ) + d = Math.max(d,((ReturnNode)ret).idepth()+1); + return cacheIDepth(d); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java new file mode 100644 index 00000000..47f12d82 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -0,0 +1,183 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.type.*; + +/** + * Store represents setting a value to a memory based object, in chapter 10 + * this means a field inside a struct. + */ +public class StoreNode extends MemOpNode { + + private final boolean _init; // Initializing writes are allowed to write null + + /** + * @param name The struct field we are assigning to + * @param mem The memory alias node - this is updated after a Store + * @param ptr The ptr to the struct base where we will store a value + * @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); + _init = init; + } + + // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + + @Override public String label() { return "st_"+mlabel(); } + @Override public boolean isMem() { return true; } + + public Node val() { return in(4); } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(".").p(_name).p("=").p( val()).p(";"); + } + + @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); + } + + @Override + public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } + + + if( mem() instanceof CastNode cast ) { + setDef(1,cast.in(1)); + return this; + } + + // Simple store-after-store on same address. Should pick up the + // required init-store being stomped by a first user store. + if( mem() instanceof StoreNode st && + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset + 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 + 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( log<3 ) { // And-mask vs narrow store + long mask = ((TypeInteger)and.in(2)._type).value(); + long bits = (1L<<(8<<log))-1; + // Mask does not mask any of the stored bits + if( (bits&mask)==bits ) + // So and-mask is already covered by the store + { setDef(4,and.in(1)); return this; } + } + } + + // Store will chop high order bits off; math to change those bits can be dropped. + if( val() instanceof SarNode shr && + shr.in(1) instanceof ShlNode shl && + shr.in(2)._type.isConstant() && + shl.in(2)._type.isConstant() ) { + TypeInteger shrC = (TypeInteger) shr.in(2)._type; + + // size of the thing that sign-extends + int base_size = (1 << shr.in(1)._type.log_size()) << 3; + int not_affected_bits = base_size - (int) shrC.value(); + int store_size = (1 << log_size()) << 3; + // if the store is unrelated to the shift amount, then get rid of the shift + if( shl.in(2)._type == shr.in(2)._type && shrC.value() >= store_size && not_affected_bits >= store_size) { + setDef(4, shl.in(1)); + return this; + } + } + return null; + } + + // 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; + 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. + if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) || + !(ptr()._type instanceof TypeMemPtr tmp) || tmp._obj.isAry() || + !(off() instanceof ConstantNode) || err()!=null ) return null; + LoopNode loop = (LoopNode)phi.region(); + // Bulk splitting identifies precise slices by Region/alias. Finish it + // before replacing this memory point with a Store. + for( Node use : loop._outputs ) + if( use instanceof BulkMemPhiNode ) { + phi.addDepForwards(use); + return null; + } + phi.addDep(obj); + CFGNode ctrl = loop.entry(); + while( ctrl!=null && ctrl!=obj.cfg0() ) { + phi.addDep(ctrl); + ctrl=ctrl.idom(phi); + } + 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); + // 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(); + phi.setDef(2,mem); + phi.unkeep(); + return sink; + } + + // Check that "mem" has no uses except "this" + private boolean checkOnlyUse(Node mem) { + if( mem.nOuts()==1 ) return true; + // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that + // when the other uses go away we can retry. + for( Node use : mem._outputs ) + if( use != this ) + addDep(use); + return false; + } + + @Override + 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 + TypeMemPtr tmp = (TypeMemPtr)ptr()._type; + if( (tmp._ro || tmp._obj.field(_name)._final) && !_init ) + return Parser.error("Cannot modify final field '"+_name+"'",_loc); + return null; + } + + // Do not GVN Stores. Two equal Stores on two of three paths are the easy + // failure: sharing them makes GCM place the Store on all three paths. + // Sharing is safe only if every exit path goes through an original Store, + // or GCM splits the shared Store to keep it on those paths. Stores must + // sink, not run on extra paths. GVN does not check this; GCM does not split. + // Identity also disables generic Phi factoring of Stores. + // The closed-loop Store sink is a separate peep and still applies. + @Override public boolean eq(Node n) { return this == n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java new file mode 100644 index 00000000..78b20e9f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -0,0 +1,47 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java new file mode 100644 index 00000000..3fc4f7ca --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -0,0 +1,27 @@ +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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java new file mode 100644 index 00000000..2c4fed39 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -0,0 +1,54 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import java.util.BitSet; + +public class SubNode extends ArithNode { + public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Sub"; } + @Override public String op() { return "-"; } + + @Override long doOp( long x, long y ) { return x - y; } + @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { + // Sub of same is 0 + if( in(1)==in(2) ) + return TypeInteger.ZERO; + // Fold ranges like {2-3} - {0-1} into {1-3}. + if( !AddNode.overflow(x._min,-y._max) && + !AddNode.overflow(x._max,-y._min) && + y._min != Long.MIN_VALUE ) + return TypeInteger.make(x._min-y._max,x._max-y._min); + + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Keep invalid operands for type checking. + if( 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); + } + + // x - (-y) is x+y + if( rhs instanceof MinusNode minus ) + return new AddNode(lhs,minus.in(1)); + + // (-x) - y is -(x+y) + if( lhs instanceof MinusNode minus ) + return new MinusNode(new AddNode(minus.in(1),rhs).peephole()); + + return super.idealize(); + } + + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } + @Override Node copyF() { return new SubFNode(null,null); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java new file mode 100644 index 00000000..840a95ec --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 protected String format() { return "(flt)%1"; } + + @Override + public Type compute() { + if( in(1)._type.isHigh() ) return TypeFloat.F64.dual(); + if( in(1)._type == Type.NIL ) return TypeFloat.FZERO; + if( in(1)._type instanceof TypeInteger i0 && i0.isConstant() ) + return TypeFloat.constant(i0.value()); + return TypeFloat.F64; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java new file mode 100644 index 00000000..477b9af8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -0,0 +1,16 @@ +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.type.Type; + +public class XCtrlNode extends CFGNode { + public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); } + @Override public String label() { return "Xctrl"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); } + @Override public boolean isConst() { return true; } + @Override public Type compute() { return Type.XCONTROL; } + @Override public Node idealize() { return null; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java new file mode 100644 index 00000000..75cea98e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +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); } + + @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 ) { + return TypeInteger.BOT; + } + + @Override + 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 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Do we have ((x ^ (phi cons)) ^ con) ? + // Do we have ((x ^ (phi cons)) ^ (phi cons)) ? + // Push constant up through the phi: x ^ (phi con0^con0 con1^con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) return phicon; + + return super.idealize(); + } + @Override Node copy(Node lhs, Node rhs) { return new XorNode(_loc,lhs,rhs); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java new file mode 100644 index 00000000..76e47ca2 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class AddARM extends MachConcreteNode implements MachNode { + AddARM( Node add) { super(add); } + @Override public String op() { return "add"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + // ADD (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_ADD); } + // General form: "rd = rs1 + rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java new file mode 100644 index 00000000..67a10b48 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class AddFARM extends MachConcreteNode implements MachNode { + AddFARM(Node addf) { super(addf); } + @Override public String op() { return "addf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + + //FADD (scalar) + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_OP_ADD);} + + // Default on double precision for now(64 bits) + // General form: "addf rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java new file mode 100644 index 00000000..4c635d07 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class AddIARM extends MachConcreteNode implements MachNode { + final int _imm; + AddIARM(Node add, int imm) { + super(add); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { + return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); + } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + //ADD (immediate) + @Override public void encoding( Encoding enc ) { + arm.imm_inst(enc,this, in(1), Arm64.OPI_ADD,_imm); + } + // General form: "addi rd = rs1 + imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java new file mode 100644 index 00000000..1a0365ad --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.SB; + +public class AndARM extends MachConcreteNode implements MachNode { + AndARM(Node and) { super(and); } + @Override public String op() { return "and"; } + @Override public String glabel() { return "&"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + // AND (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_AND); } + // General form: #rd = rs1 & rs2 + @Override public void asm(CodeGen code, SB sb){ + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java new file mode 100644 index 00000000..6292e98e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class AndIARM extends MachConcreteNode implements MachNode { + final int _imm; + AndIARM(Node and, int imm) { + super(and); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { return "andi"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + arm.imm_inst_n(enc,this, in(1), Arm64.OPI_AND,_imm); + } + // General form: "andi rd = rs1 & imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(Arm64.decodeImm12(_imm)); + } +}
\ No newline at end of file diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java new file mode 100644 index 00000000..77f14d4c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Arithmetic Shift Right (register) +public class AsrARM extends MachConcreteNode implements MachNode { + AsrARM(Node asr) {super(asr);} + @Override public String op() { return "sar"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_ASR); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java new file mode 100644 index 00000000..7cb42eec --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.SB; + +public class AsrIARM extends MachConcreteNode implements MachNode { + final int _imm; + AsrIARM(Node asr, int imm) { + super(asr); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { return "asri"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd)); + } + // General form: "asri rd = rs1 >> imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm); + } +}
\ No newline at end of file diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java new file mode 100644 index 00000000..127f17ff --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -0,0 +1,78 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; +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 ) { + super(iff); + _bop = bop; + } + @Override public String op() { return "j"+_bop; } + @Override public String label() { return op(); } + + @Override public void postSelect(CodeGen code) { + Node set = in(1); + if( set==null ) return; // Never-node cutout + Node cmp = set.in(1); + // Bypass an expected Set and just reference the cmp directly + if( set instanceof SetARM) + _inputs.set(1,cmp); + else + throw Utils.TODO(); + } + + @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } + @Override public RegMask outregmap() { return null; } + @Override public void negate() { _bop = negate(_bop); } + + @Override public void encoding( Encoding enc ) { + // Assuming that condition flags are already set. These flags are set + // 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)) ); + } else { + if( _bop=="!=" ) return; // Inverted, no code + enc.add4(Arm64.b(Arm64.OP_UJMP, 0)); + } + enc.jump(this,cproj(0)); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code + if( -(1<<19) <= delta && delta < (1<<19) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @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))); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + if( in(1)!=null ) { // Never-node + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + } else if( _bop=="!=" ) { + sb.p("never"); + return; + } + CFGNode prj = cproj(0).uctrlSkipEmpty(); + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); + } + @Override public String comment() { return "L"+cproj(1)._nid; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java new file mode 100644 index 00000000..e1717cb3 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -0,0 +1,51 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.util.SB; + +public class CallARM extends CallNode implements MachNode, RIPRelSize { + final TypeFunPtr _tfp; + final String _name; + CallARM(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 + } + + @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 arm.callInMask(_tfp,i,fun()._maxArgSlot); } + @Override public RegMask outregmap() { return null; } + @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr + + @Override public void encoding( Encoding enc ) { + FunNode fun = CodeGen.CODE.link(_tfp); + if( fun==null ) enc.external(this,_name); + else enc.relo(this); + // BL + enc.add4(Arm64.b(Arm64.OP_CALL,0)); // Target patched at link time + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart,Arm64.b(Arm64.OP_CALL,delta)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i+2))).p(", "); + sb.unchar(2); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java new file mode 100644 index 00000000..1949ae46 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java @@ -0,0 +1,33 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class CallRRARM extends CallNode implements MachNode { + CallRRARM(CallNode call) { super(call); } + @Override public String op() { return "callr"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + return i==_inputs._len + ? arm.RMASK // Function call target + : arm.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + @Override public void encoding( Encoding enc ) { + // Needs a register, typically a jump-and-link-register opcode + // blr + short self = enc.reg(this); + enc.add4(Arm64.blr(Arm64.OP_CALLRARM, self)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(", "); + sb.unchar(2); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java new file mode 100644 index 00000000..1bd9c372 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class CmpARM extends MachConcreteNode implements MachNode { + CmpARM( Node cmp ) { super(cmp); } + @Override public String op() { return "cmp"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + + // SUBS (shifted register) + @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,Arm64.OP_CMP); } + + // General form: "cmp rs1, rs2" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java new file mode 100644 index 00000000..67ffc27b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// compare instruction on float regs input(D-registers) +public class CmpFARM extends MachConcreteNode implements MachNode{ + CmpFARM(Node cmp) {super(cmp);} + @Override public String op() { return "cmpf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + + // FCMP + @Override public void encoding( Encoding enc ) { arm.f_cmp(enc,this); } + // General form: "cmp d0, d1" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java new file mode 100644 index 00000000..cd4dd9cf --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java @@ -0,0 +1,41 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Compare with immediate. +// CMP (immediate) - SUBS +public class CmpIARM extends MachConcreteNode implements MachNode { + final int _imm; + final String _bop; + CmpIARM(BoolNode bool, int imm) { + super(bool); + _inputs.pop(); + _bop = bool.op(); + _imm = imm; + } + CmpIARM( CmpIARM cmp ) { + super((Node)null); + addDef(null); + addDef(cmp.in(1)); + _bop = cmp._bop; + _imm = cmp._imm; + } + @Override public String op() { return _imm==0 ? "test" : "cmp"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.FLAGS_MASK; } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new CmpIARM(this); } + + @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), Arm64.OPI_CMP,_imm); } + + // General form: "cmp rs1, 1" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))); + if( _imm != 0 ) sb.p(", #").p(_imm); + } + } diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java new file mode 100644 index 00000000..e94b6491 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class DivARM extends MachConcreteNode implements MachNode { + DivARM( Node div ) { super(div); } + @Override public String op() { return "div"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + // SDIV + @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_DIV,3); } + // General form: "div dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java new file mode 100644 index 00000000..68ad6047 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class DivFARM extends MachConcreteNode implements MachNode { + DivFARM( Node divf) { super(divf); } + @Override public String op() { return "divf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + + // FDIV (scalar) + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_DIV); } + // General form: "VDIF = dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java new file mode 100644 index 00000000..814a82c2 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +// Convert a narrow C return register to Simple's full-width integer value. +public class ExtendARM extends MachConcreteNode { + final int _bits; + final boolean _signed; + ExtendARM(ProjNode prj, TypeInteger ret) { + super(prj); + _inputs.set(0,null); + _inputs.add(new ProjARM(prj)); + _bits = 8 << ret.log_size(); + _signed = ret._min < 0; + } + @Override public String op() { return (_signed ? "sext" : "zext")+_bits; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding(Encoding enc) { + short dst = enc.reg(this), src = enc.reg(in(1)); + // SBFM/UBFM aliases: sign/zero extend the low byte, short or int. + enc.add4(Arm64.imm_shift(_signed ? Arm64.OPI_ASR : Arm64.OPI_LSR,0,_bits-1,src,dst)); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..31dcfc36 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java new file mode 100644 index 00000000..dc21e075 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java @@ -0,0 +1,44 @@ +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.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); } + @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 void encoding( Encoding enc ) { + 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)); + } + + // Delta is from opcode start. + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short dst = (short)(enc.reg(this) - arm.D_OFFSET); + enc.patch4(opStart, Arm64.load_pc(Arm64.OPF_ARM, delta, dst)); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" = #")); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java new file mode 100644 index 00000000..17cafcde --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class FunARM extends FunNode implements MachNode { + FunARM(FunNode fun) { super(fun); } + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + _frameAdjust = (_frameAdjust+15) & -16; // Keep SP aligned, including leaf spills. + } + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + if( sz >= 1L<<12 ) throw Utils.TODO(); + enc.add4(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java new file mode 100644 index 00000000..b1c30afc --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java @@ -0,0 +1,26 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class I2F8ARM extends MachConcreteNode implements MachNode { + I2F8ARM(Node i2f8) { super(i2f8); } + @Override public String op() { return "cvt"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + @Override public void encoding( Encoding enc ) { + // SCVTF + short self = (short)(enc.reg(this )-arm.D_OFFSET); + short reg1 = enc.reg(in(1)); + int body = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self); + enc.add4(body); + } + + // General form: "i2f8 (flt)int_value" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java new file mode 100644 index 00000000..7a9e74c6 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -0,0 +1,36 @@ +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.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +// Integer constants +public class IntARM extends ConstantNode implements MachNode { + IntARM( ConstantNode con ) { super(con); } + + @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 void encoding( Encoding enc ) { + short self = enc.reg(this); + long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value(); + Arm64.constant(enc,self,x); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // 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); + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java new file mode 100644 index 00000000..8fc22a67 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.SB; + +// Load memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base + +public class LoadARM extends MemOpARM { + LoadARM(LoadNode ld, Node base, Node idx, int off) { + super(ld, base, idx, off, 0); + } + @Override public String op() { return "ld"+_sz; } + @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; } + + private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, }; + + private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, }; + + // 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()); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java new file mode 100644 index 00000000..954cfac2 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Logical Shift Left (register) +public class LslARM extends MachConcreteNode implements MachNode { + LslARM(Node asr) {super(asr);} + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSL); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java new file mode 100644 index 00000000..b32153ab --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.SB; + +public class LslIARM extends MachConcreteNode implements MachNode { + final int _imm; + LslIARM(Node lsl, int imm) { + super(lsl); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { return "lsli"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>) + // immr must be (-<shift> MOD 64) = 64 - shift + enc.add4(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd)); + } + // General form: "lsli rd = rs1 << imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm); + } +}
\ No newline at end of file diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java new file mode 100644 index 00000000..91be34e3 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Logical Shift Right (register) +public class LsrARM extends MachConcreteNode implements MachNode { + LsrARM(Node asr) {super(asr);} + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSR); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java new file mode 100644 index 00000000..243ce968 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// logical right shift immediate +public class LsrIARM extends MachConcreteNode implements MachNode { + final int _imm; + LsrIARM(Node lsr, int imm) { + super(lsr); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { return "lsri"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd)); + } + // General form: "lsri rd = rs1 >>> imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm); + } +}
\ No newline at end of file diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java new file mode 100644 index 00000000..dfbe5e06 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -0,0 +1,79 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.print.ExprPrinter; + +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())); + MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) { + super(mop,mop); + assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode); + assert ptr() == ptr; + _inputs.setX(2, ptr); + _inputs.setX(3, idx); + _off = off; + _imm = imm; + } + + // Store-based flavors have a value edge + MemOpARM( MemOpNode mop, Node ptr, Node idx, int off, int imm, Node val ) { + this(mop,ptr,idx,off,imm); + _inputs.setX(4,val); + } + + @Override public String label() { return op();} + Node val() { return in(4); } // Only for stores + + @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(); } + + int size() { return 1<<_declaredType.log_size(); } + + // Wider mask to store both GPRs and FPRs + @Override public RegMask regmap(int i) { + // 0 - ctrl + if( i==1 ) return null; // memory + if( i==2 ) return arm.RMASK; // ptr/base + if( i==3 ) return arm.RMASK; // off/index + if( i==4 ) return size() >= 4 ? arm.MEM_MASK : arm.RMASK; // value + return null; // Anti-dependence + } + + // Shared encoding for loads and stores(int and float) + public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) { + short ptr = enc.reg(ptr()); + short off = enc.reg(off()); + short val = enc.reg(xval); + // The allocated register selects integer vs SIMD/FP encoding, even for + // bitwise copies. ARM numbers each register bank from zero in the opcode. + boolean fp = val >= arm.D0; + assert !fp || size >= 4; + if( fp ) { + opcode_imm |= 1<<4; + opcode_reg |= 1<<5; + val -= arm.D0; + } + int body = off() == null + ? Arm64.load_str_imm(opcode_imm, _off, ptr, val, size) + : Arm64.indr_adr(opcode_reg, off, Arm64.STORE_LOAD_OPTION.SXTX, 0, ptr, val); + enc.add4(body); + } + + SB asm_address(CodeGen code, SB sb) { + sb.p("[").p(code.reg(ptr())).p("+"); + if( off() != null ) sb.p(code.reg(off())); + else sb.p(_off); + return sb.p("]"); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java new file mode 100644 index 00000000..f598cefb --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// mulh signed multiply instruction(no-imm form) +public class MulARM extends MachConcreteNode implements MachNode { + MulARM(Node mul) {super(mul);} + @Override public String op() { return "mul"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_MUL,31); } + // General form: "rd = rs1 * rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java new file mode 100644 index 00000000..a4f81fd4 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class MulFARM extends MachConcreteNode implements MachNode{ + MulFARM(Node mulf) {super(mulf);} + @Override public String op() { return "mulf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_MUL); } + // Default on double precision for now(64 bits) + // General form: "VMUL.f32 rd = src1 * src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java new file mode 100644 index 00000000..881b41f9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +public class NegARM extends MachConcreteNode implements MachNode { + NegARM(Node sub) { + super(sub); + //_inputs.pop(); + } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + // reverse subtract with immediate 0 + arm.imm_inst(enc,this, in(1), 0b00100110, 0); + } + // General form: "subi rd = rs1 - imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = -").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java new file mode 100644 index 00000000..7110df62 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class NewARM extends NewNode implements MachNode, RIPRelSize { + // A pre-zeroed chunk of memory. + NewARM(NewNode nnn) { super(nnn); } + @Override public void encoding( Encoding enc ) { + // BL(branch with link) + enc.external(this,"calloc"). + add4(Arm64.mov(Arm64.OP_MOVZ, 0, 1, 0)). // movz x0,#1 + add4(Arm64.b(Arm64.OP_CALL, 0)); + } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public byte encSize(int delta) { return 4*2; } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) { + // Negative patches are JIT emulator targets. + if( opStart+delta < 0 ) { + enc.patch4(opStart+4, Arm64.b_calloc(Arm64.OP_CALL, delta-4)); + } else { + throw Utils.TODO(); + } + } + // General form: "alloc #bytes PC" + @Override public void asm(CodeGen code, SB sb) { + sb.p("ldi x0=#1\n"); + sb.p("call #calloc"); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java new file mode 100644 index 00000000..f8e450f6 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.util.SB; + +public class NotARM extends MachConcreteNode implements MachNode{ + NotARM(NotNode not) {super(not);} + @Override public String op() { return "not"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public RegMask killmap() { return arm.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + // subs xzr, rs, #0 + // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0 + // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset + short self = enc.reg(this ); + short reg1 = enc.reg(in(1)); + int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31); + enc.add4(subs); + int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self); + enc.add4(cset); + } + + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java new file mode 100644 index 00000000..5b7264ec --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Not using the ORRS variant. +public class OrARM extends MachConcreteNode implements MachNode { + OrARM(Node and) { super(and); } + @Override public String op() { return "or"; } + @Override public String glabel() { return "|"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_OR); } + // General form: #rd = rs1 & rs2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java new file mode 100644 index 00000000..86d07bee --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class OrIARM extends MachConcreteNode implements MachNode { + final int _imm; + OrIARM(Node or, int imm) { + super(or); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { return "ori"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + arm.imm_inst_n(enc, this, in(1), Arm64.OPI_OR, _imm); + } + // General form: "ori rd = rs1 | imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java new file mode 100644 index 00000000..b419fe0a --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; + +public class ParmARM extends ParmNode implements MachNode { + final RegMask _rmask; + ParmARM(ParmNode parm) { + super(parm); + _rmask = arm.callInMask(fun().sig(),_idx,0); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return _rmask; } + @Override public void encoding( Encoding enc ) { } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java new file mode 100644 index 00000000..4b7d6f13 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java @@ -0,0 +1,15 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; + +public class ProjARM extends ProjNode implements MachNode { + ProjARM( ProjNode p ) { super(p); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } + @Override public void encoding( Encoding enc ) { } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java new file mode 100644 index 00000000..a7ed5073 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java @@ -0,0 +1,16 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class RetARM extends ReturnNode implements MachNode { + RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + @Override public void encoding( Encoding enc ) { + int frameAdjust = ((FunARM)fun())._frameAdjust; + if( frameAdjust > 0 ) + enc.add4(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP)); + enc.add4(Arm64.ret(Arm64.OP_RET)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java new file mode 100644 index 00000000..afbbd3bf --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// conditional set? +// 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) { + super(cmp); + _inputs.setLen(1); // Pop the cmp inputs + // Replace with the matched cmp + _inputs.push(cmp); + _bop = bop; + } + @Override public String op() { return "set"+_bop; } + @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + @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)); + enc.add4(body); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)); + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(" = ").p(src); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java new file mode 100644 index 00000000..f93f0f24 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -0,0 +1,66 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +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]);} + @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } + @Override public RegMask outregmap() { return arm.SPLIT_MASK; } + + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + boolean dstX = dst >= arm.D_OFFSET; + boolean srcX = src >= arm.D_OFFSET; + + if (dst >= arm.MAX_REG) { + // store to sp + if(src >= arm.MAX_REG) { + throw Utils.TODO(); + } + int off = enc._fun.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; + } + + if(src >= arm.MAX_REG) { + // Load from SP + int off = enc._fun.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; + } + + // pick opcode based on regs + if(!dstX && !srcX) { + // GPR->GPR + enc.add4(Arm64.mov_reg(Arm64.OP_MOV, src, dst)); + } else if(dstX && srcX) { + // FPR->FPR + // fmov reg + enc.add4(Arm64.f_mov_reg(Arm64.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET)); + } else if(!srcX && dstX) { + // GPR->FPR + // FMOV(general) 64 bits to DOUBLE-PRECISION + enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET)); + } else if( srcX && !dstX) { + //FPF->GPR + // FMOV(general) DOUBLE-PRECISION to 64 bits + 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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java new file mode 100644 index 00000000..3e045a6f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; + +// NZCV transfers require a GPR. Further splits can spill that GPR to the stack. +public class SplitFlagsARM extends SplitARM { + private static final RegMask MASK = new RegMask(arm.WR_BITS,1L); + SplitFlagsARM(String kind,byte round) { super(kind,round); } + @Override public boolean isNonStackSplit() { return true; } + @Override public RegMask regmap(int i) { return MASK; } + @Override public RegMask outregmap() { return MASK; } + + @Override public void encoding(Encoding enc) { + short dst=enc.reg(this), src=enc.reg(in(1)); + if( dst==src ) return; + if( src==arm.FLAGS ) enc.add4(Arm64.flags(dst,false)); + else if( dst==arm.FLAGS ) enc.add4(Arm64.flags(src,true)); + else super.encoding(enc); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java new file mode 100644 index 00000000..46eff4ca --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.codegen.*; + +// Keep a saved NZCV value in GPRs and stack slots through subsequent spills. +public class SplitGPRARM extends SplitARM { + private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L); + SplitGPRARM(String kind,byte round) { super(kind,round); } + @Override public RegMask regmap(int i) { return MASK; } + @Override public RegMask outregmap() { return MASK; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java new file mode 100644 index 00000000..2cc54f76 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -0,0 +1,38 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.util.SB; + +// Store memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// base - base pointer, offset is added to base +// null - index never allowed (no [reg+reg] mode) +// off - offset added to base +// imm - immediate value to store, only if val is null +// val - value to store or null + +//e.g s.cs[0] = 67; // C +// base = s.cs, off = 4, imm = 67, val = null +public class StoreARM extends MemOpARM { + StoreARM(StoreNode st, Node base, Node idx, int off, Node val) { + super(st, base, idx, off, 0, val); + } + @Override public String op() { return "st"+_sz; } + @Override public RegMask outregmap() { return null; } + + private static final int[] OP_STORES = new int[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, }; + + 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()); + } + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p(_imm); + else sb.p(code.reg(val())); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java new file mode 100644 index 00000000..fd5cd4de --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class SubARM extends MachConcreteNode implements MachNode { + SubARM(Node sub) { super(sub); } + @Override public String op() { return "sub"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_SUB); } + + // General form: "sub # rd = rs1 - rs2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java new file mode 100644 index 00000000..1a751434 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class SubFARM extends MachConcreteNode implements MachNode { + SubFARM(Node subf) {super(subf);} + @Override public String op() { return "subf"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.DMASK; } + @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_SUB); } + // Default on double precision for now(64 bits) + // General form: "vsub.f32 rd = src1 + src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java new file mode 100644 index 00000000..b2c0b2ed --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java @@ -0,0 +1,29 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +public class SubIARM extends MachConcreteNode implements MachNode { + final int _imm; + SubIARM(Node sub, int imm) { + super(sub); + _inputs.pop(); + _imm = imm; + } + @Override public String op() { return _imm == -1 ? "dec" : "subi"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + @Override public void encoding( Encoding enc ) { + arm.imm_inst(enc,this, in(1), Arm64.OPI_SUB,_imm); + } + + // General form: "subi rd = rs1 - imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java new file mode 100644 index 00000000..98a56089 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -0,0 +1,57 @@ +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.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); } + @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 void encoding( Encoding enc ) { + enc.relo(this); + short self = enc.reg(this); + // adrp x0, 0 + int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self); + // add x0, x0, 0 + enc.add4(adrp); + arm.imm_inst(enc,Arm64.OPI_ADD,0, 0); + } + + @Override public byte encSize(int delta) { + return 8; + } + + // Delta is from opcode start + @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)); + } else { + // should not happen as one instruction is 4 byte, and TFP arm encodes 2. + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" #")); + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java new file mode 100644 index 00000000..d1cafa96 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java @@ -0,0 +1,55 @@ +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.type.TypeMemPtr; +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); } + @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 void encoding( Encoding enc ) { + 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)); + // add x0, x0, 0 + arm.imm_inst(enc,Arm64.OPI_ADD,0, dst); + } + + @Override public byte encSize(int delta) { + return 8; + } + + // Delta is from opcode start + @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)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" #")); + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java new file mode 100644 index 00000000..aecd02bb --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +// unconditional jump +public class UJmpARM extends CFGNode implements MachNode, RIPRelSize { + UJmpARM() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected String format() { return "jmp "; } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + int body = Arm64.b(Arm64.OP_UJMP, 0); + enc.add4(body); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1<<26) <= delta && delta < (1<<26) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java new file mode 100644 index 00000000..d56397a3 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class XorARM extends MachConcreteNode implements MachNode{ + XorARM(Node xor) {super(xor);} + @Override public String op() { return "xor"; } + @Override public String glabel() { return "^"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_XOR); } + // General form: "rd = x1 ^ x2" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java new file mode 100644 index 00000000..12143ccd --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.util.SB; + +public class XorIARM extends MachConcreteNode implements MachNode { + final int _imm; + XorIARM(Node xor, int imm) { + super(xor); + _inputs.pop(); + _imm = imm; + } + + @Override public String op() { return "xori"; } + @Override public RegMask regmap(int i) { return arm.RMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + + // General form: "xori rd = rs1 ^ imm" + @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), Arm64.OPI_XOR,_imm); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java new file mode 100644 index 00000000..fae98a99 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -0,0 +1,466 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import static com.seaofnodes.isa.Arm64.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; + +public class arm extends Machine { + public arm( CodeGen code ) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } + + // ARM64 + @Override public String name() { return "arm"; } + + // GPR(S) + public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; + public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; + public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; + public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31; + + // Floating point registers + public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; + public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; + public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; + public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63; + + static final int FLAGS = 64; + static final int MAX_REG = 65; + public static final int D_OFFSET = 32; + + static final String[] REGS = new String[] { + "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", + "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15", + "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23", + "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", + "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15", + "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23", + "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31", + "flags" + }; + @Override public String[] regs() { return REGS; } + + // from (x0-x30) + // General purpose register mask: pointers and ints, not floats + static final long RD_BITS = 0xFFFFFFFFL; + static final RegMask RMASK = new RegMask(RD_BITS); + static final long WR_BITS = 0x7FFFFFFFL; // All the GPRs, not RSP + static final RegMask WMASK = new RegMask(WR_BITS); + + // Float mask from(d0–d31) + static final long FP_BITS = 0xFFFFFFFFL<<D0; + static final RegMask DMASK = new RegMask(FP_BITS); + + // Load/store mask; both GPR and FPR + static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); + + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/); + + static final RegMask FLAGS_MASK = new RegMask(FLAGS); + + // Arguments masks + static final RegMask X0_MASK = new RegMask(X0); + static final RegMask X1_MASK = new RegMask(X1); + static final RegMask X2_MASK = new RegMask(X2); + static final RegMask X3_MASK = new RegMask(X3); + static final RegMask X4_MASK = new RegMask(X4); + static final RegMask X5_MASK = new RegMask(X5); + static final RegMask X6_MASK = new RegMask(X6); + static final RegMask X7_MASK = new RegMask(X7); + + // Arguments(float) masks + static final RegMask D0_MASK = new RegMask(D0); + static final RegMask D1_MASK = new RegMask(D1); + static final RegMask D2_MASK = new RegMask(D2); + static final RegMask D3_MASK = new RegMask(D3); + static final RegMask D4_MASK = new RegMask(D4); + static final RegMask D5_MASK = new RegMask(D5); + static final RegMask D6_MASK = new RegMask(D6); + static final RegMask D7_MASK = new RegMask(D7); + + // Calling convention; returns a machine-specific register + // for incoming argument idx. + // index 0 for control, 1 for memory, real args start at index 2 + static final RegMask[] CALLINMASK = new RegMask[] { + X0_MASK, + X1_MASK, + X2_MASK, + X3_MASK, + X4_MASK, + X5_MASK, + X6_MASK, + X7_MASK, + }; + static final RegMask[] XMMS = new RegMask[] { + D0_MASK, + D1_MASK, + D2_MASK, + D3_MASK, + D4_MASK, + D5_MASK, + D6_MASK, + D7_MASK, + }; + + // ARM ENCODING + + // True if signed 9-bit immediate + private static boolean imm9(TypeInteger ti) { + // 55 = 64-9 + return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value(); + } + // True if signed 12-bit immediate + private static boolean imm12(TypeInteger ti) { + // 52 = 64-12 + return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); + } + + // Can we encode this in ARM's 12-bit LOGICAL immediate form? + // Some combination of shifted bit-masks. + private static int imm12Logical(TypeInteger ti) { + return ti.isConstant() ? Arm64.logicalImmediate(ti.value()) : -1; + } + + public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n2); + + // ADD/SUB encode an unsigned magnitude; switch operation for a negative addend. + if( imm12<0 && (opcode==OPI_ADD || opcode==OPI_SUB) ) { + opcode ^= OPI_ADD ^ OPI_SUB; + imm12 = -imm12; + } + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); + enc.add4(body); + } + + public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) { + short reg1 = enc.reg(n2); + // 31 = 11111 + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, 31); + enc.add4(body); + } + + // for cases where rs1 and dst are the same, eg add x0, x0, 1 + public static void imm_inst(Encoding enc, int opcode, int imm12, int self) { + int body = Arm64.imm_inst(opcode, imm12&0xFFF, self, self); + enc.add4(body); + } + + public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) { + short self = enc.reg(n); + short reg1 = enc.reg(n2); + int body = Arm64.imm_inst_n(opcode, imm13, reg1, self); + enc.add4(body); + } + + // for cases where rs1 and dst are the same, eg add x0, x0, 1 + public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self); + return body; + } + + // for normal add, reg1, reg2 cases (reg-to-reg) + // using shifted-reg form + + public static void r_reg(Encoding enc, Node n, int opcode) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self); + enc.add4(body); + } + + public static void r_reg_subs(Encoding enc, Node n, int opcode) { + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + // 31 = 11111 + int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, 31); + enc.add4(body); + } + + public static void shift_reg(Encoding enc, Node n, int op2) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.shift_reg(0b10011010110, reg2, op2, reg1, self); + enc.add4(body); + } + + // MUL can be considered an alias for MADD with the third operand Ra being set to 0 + + public static void madd(Encoding enc, Node n, int opcode, int ra) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = Arm64.madd(opcode, reg2, ra, reg1, self); + enc.add4(body); + } + + public static void f_scalar(Encoding enc, Node n, int op ) { + short self = (short)(enc.reg(n) -D_OFFSET); + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = Arm64.f_scalar(0b00011110, 1, reg2, op, reg1, self); + enc.add4(body); + } + + 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); + int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1); + enc.add4(body); + } + + @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { + if( idx==0 ) return CodeGen.CODE._rpcMask; + if( idx==1 ) return null; + // Count floats in signature up to index + if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat) + fcnt++; + // Floats up to XMMS in XMM registers + if( tfp.arg(idx-2) instanceof TypeFloat ) { + if( fcnt < XMMS.length ) + return XMMS[fcnt]; + } else { + RegMask[] cargs = CALLINMASK; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + // Pass on stack slot (8 and higher) + if( maxArgSlot>0 ) throw Utils.TODO(); + return new RegMask(MAX_REG + 1 + (idx - 2)); + } + + // 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++ ) { + 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 final long CALLEE_SAVE = + 1L<<X19 | + 1L<<X20 | 1L<<X21 | 1L<<X22 | 1L<<X23 | + 1L<<X24 | 1L<<X25 | 1L<<X26 | 1L<<X27 | + 1L<<X28 | + 1L<<D9 | 1L<<D10 | 1L<<D11 | + 1L<<D12 | 1L<<D13 | 1L<<D14 | 1L<<D15; + static final long CALLER_SAVE = ~CALLEE_SAVE & ~(1L<<RSP); + @Override public long callerSave() { return CALLER_SAVE; } + @Override public long neverSave() { return 1L<<RSP; } + @Override public RegMask retMask( TypeFunPtr tfp ) { + return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK; + } + @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) { + RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask(); + if( (def!=null && def.size1() && def.test(FLAGS)) || + (use!=null && use.size1() && use.test(FLAGS)) ) + return new SplitFlagsARM(kind,round); + // Spilling the saved flags requires a GPR between NZCV and the stack. + if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM ) + return new SplitGPRARM(kind,round); + return new SplitARM(kind,round); + } + + // Return a MachNode unconditional branch + @Override public CFGNode jump() { + return new UJmpARM(); + } + + // 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 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 FunNode fun -> new FunARM(fun); + 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 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyMach(read); + case ReturnNode ret -> new RetARM(ret,ret.fun()); + case SarNode sar -> asr(sar); + case ShlNode shl -> lsl(shl); + case ShrNode shr -> lsr(shr); + 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 ToFloatNode tfn -> new I2F8ARM(tfn); + case ToIntegerNode cvt -> new F8ToIARM(cvt); + case XorNode xor -> xor(xor); + + case LoopNode loop -> new LoopNode(loop); + case RegionNode region-> new RegionNode(region); + default -> throw Utils.TODO(); + }; + } + + private Node cmp(BoolNode bool){ + Node cmp = _cmp(bool); + return new SetARM(cmp, bool.op()); + } + private Node _cmp(BoolNode bool) { + if( bool.isFloat() ) + return new CmpFARM(bool); + return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new CmpIARM(bool, (int)ti.value()) + : new CmpARM(bool); + } + + private Node ld(LoadNode ld) { + return new LoadARM(address(ld), ld.ptr(), idx, off); + } + + private Node jmp(IfNode iff) { + // If/Bool combos will match to a Cmp/Set which sets flags. + // 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(); + 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); + } + + private Node add(AddNode add) { + return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) + ? new AddIARM(add, (int)ti.value()) + : new AddARM(add); + } + + private Node sub(SubNode sub) { + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new SubIARM(sub, (int)(ti.value())) + : new SubARM(sub); + } + + private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + 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 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); + }; + } + + private Node call(CallNode call){ + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallARM(call, tfp) + : new CallRRARM(call); + } + + private Node or(OrNode or) { + int imm12; + return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new OrIARM(or, imm12) + : new OrARM(or); + } + + private Node xor(XorNode xor) { + int imm12; + return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new XorIARM(xor, imm12) + : new XorARM(xor); + } + + private Node and(AndNode and) { + int imm12; + return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1 + ? new AndIARM(and, imm12) + : new AndARM(and); + } + + private Node asr(SarNode asr) { + return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new AsrIARM(asr, (int)ti.value()) + : new AsrARM(asr); + } + + private Node lsl(ShlNode lsl) { + return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new LslIARM(lsl, (int)ti.value()) + : new LslARM(lsl); + } + + private Node lsr(ShrNode lsr) { + return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63 + ? new LsrIARM(lsr, (int)ti.value()) + : new LsrARM(lsr); + } + + private static int off; + private static Node idx; + private Node st(StoreNode st) { + return new StoreARM(address(st),st.ptr(),idx,off,st.val()); + } + + // Gather addressing mode bits prior to constructing. This is a builder + // pattern, but saving the bits in a *local* *global* here to keep mess + // contained. + private <N extends MemOpNode> N address(N mop ) { + off = 0; // Reset + idx = null; + Node base = mop.ptr(); + // Skip/throw-away a ReadOnly, only used to typecheck + if( base instanceof ReadOnlyNode read ) base = read.in(1); + assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived + if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) { + off = (int)ti.value(); + assert off == ti.value(); // In 32-bit range + } else { + idx = mop.off(); + } + return mop; + } + + private Node prj(ProjNode prj) { + TypeInteger ret = cReturn(prj); + return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java new file mode 100644 index 00000000..14581111 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java @@ -0,0 +1,43 @@ +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.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); } + @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 String op() { return "auipc"; } + @Override public void encoding( Encoding enc ) { + enc.relo(this); + short dst = enc.reg(this); + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + //short rpc = enc.reg(this); + //// High half is where the TFP constant used to be, the last input + //short auipc = enc.reg(in(_inputs._len-1)); + //enc.patch4(opStart,RiscV.i_type(0x67, rpc, 0, auipc, delta)); + throw Utils.TODO(); + } + + @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); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java new file mode 100644 index 00000000..d87e49b9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java @@ -0,0 +1,18 @@ +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.util.SB; + +// fadd.d +public class AddFRISC extends MachConcreteNode implements MachNode { + AddFRISC(Node addf) {super(addf);} + @Override public String op() { return "addf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.FMASK; } + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,1); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java new file mode 100644 index 00000000..8012b61e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class AddIRISC extends ImmRISC { + // Used to inst-selection as a direct match against an ideal Add/Sub + public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); } + @Override public String op() { return "addi"; } + @Override public String glabel() { return "+"; } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() {return 0;} + @Override public AddIRISC copy() { + // Clone the AddI, using the same inputs-only code used during inst select. + // Output edges are missing. + AddIRISC add = new AddIRISC(in(1),_imm12,false); + // Copys happen when output edges should be valid, so correct missing output edges. + in(1)._outputs.add(add); + return add; + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java new file mode 100644 index 00000000..7010916f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class AddRISC extends MachConcreteNode implements MachNode { + public AddRISC( Node add ) { super(add); } + AddRISC( Node base, Node off ) { + super(new Node[3]); + _inputs.set(1,base); + _inputs.set(2,off ); + } + @Override public String op() { return "add"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java new file mode 100644 index 00000000..9fd65e7d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java @@ -0,0 +1,13 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class AndIRISC extends ImmRISC { + public AndIRISC( Node and, int imm) { super(and,imm); } + @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() {return 7;} +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java new file mode 100644 index 00000000..1845985e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java @@ -0,0 +1,16 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class AndRISC extends MachConcreteNode implements MachNode { + public AndRISC(Node and) { super(and); } + @Override public String op() { return "and"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java new file mode 100644 index 00000000..a9d03da5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java @@ -0,0 +1,95 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +// Conditional branch such as: BEQ +public class BranchRISC extends IfNode implements MachNode, RIPRelSize { + String _bop; + // label is obtained implicitly + public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { + super(iff); + _bop = bop; + _inputs.setX(1,n1); + _inputs.setX(2,n2); + } + + @Override public String op() { return "b"+_bop; } + @Override public String label() { return op(); } + @Override public String comment() { return "L"+cproj(1)._nid; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return null; } + @Override public void negate() { + _bop = negate(_bop); + // Cannot encode ">" or "<=", so flip these + if( _bop.equals(">") || _bop.equals("<=") ) { + swap12(); // Cannot negate the test, so swap the operands + _bop = swap(_bop); // Swap test to match + } + } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("if( "); + if( in(1)==null ) p.p("0"); + else p.n(in(1)); + p.p(_bop); + if( in(2)==null ) p.p("0"); + else p.n(in(2)); + return p.p(" )"); + } + + @Override public void encoding( Encoding enc ) { + if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code + enc.jump(this,cproj(0)); + if( in(1)==null ) // Never node + enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); + else { + // Todo: relocs (for offset - immf) + short src1 = enc.reg(in(1)); + short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); + enc.add4(RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, 0)); + } + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code + if( -4*1024 <= delta && delta < 4*1024 ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @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( in(1)==null ) { // Never node + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); + } else { + short src1 = enc.reg(in(1)); + short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2)); + if( opLen==4 ) { + enc.patch4(opStart,RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, delta)); + } else { + throw Utils.TODO(); + } + } + } + + @Override public void asm(CodeGen code, SB sb) { + if( in(1)==null && _bop=="!=" ) { + sb.p("never"); + return; + } + String src1 = in(1)==null ? "#0" : code.reg(in(1)); + String src2 = in(2)==null ? "#0" : code.reg(in(2)); + sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" "); + CFGNode prj = cproj(0).uctrlSkipEmpty(); + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java new file mode 100644 index 00000000..f0ad8e88 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -0,0 +1,63 @@ +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.TypeFunPtr; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class CallRISC extends CallNode implements MachNode, RIPRelSize { + final TypeFunPtr _tfp; + final String _name; + CallRISC( CallNode call, TypeFunPtr tfp ) { + super(call); + 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 + } + + @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 riscv.callInMask(_tfp,i,fun()._maxArgSlot); } + @Override public RegMask outregmap() { return riscv.RPC_MASK; } + @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr + + @Override public void encoding( Encoding enc ) { + // Short form +/-4K: beq r0,r0,imm12 + // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] + FunNode fun = CodeGen.CODE.link(_tfp); + if( fun==null ) enc.external(this,_name); + else enc.relo(this); + short rpc = enc.reg(this); + enc.add4(RiscV.j_type(RiscV.OP_JAL, rpc, 0)); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short rpc = enc.reg(this); + if( opLen==4 ) { + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, rpc, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i+2))).p(", "); + sb.unchar(2); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java new file mode 100644 index 00000000..5eee596d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -0,0 +1,32 @@ +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.util.SB; + +public class CallRRISC extends CallNode implements MachNode { + CallRRISC( CallNode call ) { super(call); } + @Override public String op() { return "callr"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + return i==_inputs._len-1 + ? riscv.RMASK // Function call target + : riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument + } + @Override public RegMask outregmap() { return null; } + + @Override public void encoding( Encoding enc ) { + short rpc = enc.reg(this); + short src = enc.reg(in(_inputs._len-1)); + int body = RiscV.i_type(RiscV.OP_JALR, rpc, 0, src, 0); + enc.add4(body); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(", "); + sb.unchar(2); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java new file mode 100644 index 00000000..8eb4fb3b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java @@ -0,0 +1,20 @@ +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.util.SB; + +// fdiv.d +public class DivFRISC extends MachConcreteNode implements MachNode{ + DivFRISC(Node divf) {super(divf);} + @Override public String op() { return "divf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.FMASK; } + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RUP,13); } + // Default on double precision for now(64 bits) + // General form: "fdiv.d rd = src1 / src2 + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java new file mode 100644 index 00000000..515ffa47 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class DivRISC extends MachConcreteNode implements MachNode{ + public DivRISC(Node div) {super(div);} + @Override public String op() { return "div"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,1); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java new file mode 100644 index 00000000..5bd3ec1c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java @@ -0,0 +1,32 @@ +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.TypeInteger; +import com.seaofnodes.simple.util.SB; + +// Convert a narrow C return register to Simple's full-width integer value. +public class ExtendRISC extends MachConcreteNode { + final int _bits; + final boolean _signed; + ExtendRISC(ProjNode prj, TypeInteger ret) { + super(prj); + _inputs.set(0,null); + _inputs.add(new ProjRISC(prj)); + _bits = 8 << ret.log_size(); + _signed = ret._min < 0; + } + @Override public String op() { return (_signed ? "sext" : "zext")+_bits; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding(Encoding enc) { + short dst = enc.reg(this), src = enc.reg(in(1)); + int shift = 64-_bits; + enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,1,src,shift)); + enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0))); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..a56ad216 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,19 @@ +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.util.SB; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java new file mode 100644 index 00000000..51ddc9db --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java @@ -0,0 +1,47 @@ +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.type.TypeFloat; +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); } + @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 void encoding( Encoding enc ) { + enc.largeConstant(this,_con, 0, -1); + short dst = (short)(enc.reg(this) - riscv.F_OFFSET); + short tmp = (short)riscv.T6; + // AUIPC dst,#hi20_constant_pool + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0)); + // Load dst,[dst+#low12_constant_pool] + enc.add4(RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, 0)); + } + @Override public RegMask killmap() { return new RegMask(riscv.T6); } + + // Delta is from opcode start. + @Override public byte encSize(int delta) { return 8; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short dst = (short)(enc.reg(this) - riscv.F_OFFSET); + short tmp = (short)riscv.T6; + // AUIPC dst,#hi20_constant_pool + enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12)); + // Load dst,[dst+#low12_constant_pool] + enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF)); + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" = #")); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java new file mode 100644 index 00000000..d3d64b06 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class FunRISC extends FunNode implements MachNode { + FunRISC(FunNode fun) {super(fun);} + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + if( _hasCalls ) // If non-leaf, pad to 16b + _frameAdjust = ((_frameAdjust+8) & -16); + } + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + if( sz >= 1L<<12 ) throw Utils.TODO(); + enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java new file mode 100644 index 00000000..1b53ab0f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java @@ -0,0 +1,24 @@ +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.util.SB; + +// fcvt.d.w +// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd. +public class I2F8RISC extends MachConcreteNode implements MachNode { + I2F8RISC(Node i2f8) {super(i2f8);} + @Override public String op() { return "cvtf"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.FMASK; } + @Override public void encoding( Encoding enc ) { + short dst = (short)(enc.reg(this )-riscv.F_OFFSET); + short src1 = enc.reg(in(1)); + int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.RM.RNE.ordinal(),src1,0,0x69); + enc.add4(body); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java new file mode 100644 index 00000000..ee247c3e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java @@ -0,0 +1,55 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +abstract public class ImmRISC extends MachConcreteNode implements MachNode { + public final int _imm12; + ImmRISC( Node add, int imm12, boolean pop ) { + super(add); + _imm12 = imm12; + // If pop, we are moving from an ideal Node to MachNode; the ideal Node + // inputs got copied here, but we are absorbing the immediate into the + // MachNode and so do not want the constant input. + if( pop ) _inputs.pop(); + // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the + // input edges to no-control + else { _inputs.set(0,null); _inputs.setX(1,add); } + } + ImmRISC( Node add, int imm12 ) { this(add,imm12,true); } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { + // Allow cloning of the 2nd half of a 2-part constant; this might + // trigger cloning the first part also. + return in(1) instanceof LUI || in(1) instanceof AUIPC; + } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits + p.p("( ").n(in(1)); + return p.p(String.format(" %s #%d )",glabel(),imm12)); + } + + abstract int opcode(); + abstract int func3(); + + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + enc.add4(RiscV.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF)); + } + + // General form: "addi rd = rs1 + imm" + @Override public void asm(CodeGen code, SB sb) { + int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12); + if( in(1) instanceof LUI lui ) + sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java new file mode 100644 index 00000000..23ac8481 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java @@ -0,0 +1,47 @@ +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.util.SB; + +// 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); } + + @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 byte encSize(int delta) { return 8; } + + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this); + short tmp = (short)riscv.T6; + enc.largeConstant(this,_con, 0, -1); + // AUIPC dst,#hi20_constant_pool + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0)); + // Load dst,[dst+#low12_constant_pool] + enc.add4(RiscV.i_type(RiscV.OP_LOAD, dst, 0b11, tmp, 0)); + } + @Override public RegMask killmap() { return new RegMask(riscv.T6); } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + short dst = enc.reg(this); + short tmp = (short)riscv.T6; + // AUIPC dst,#hi20_constant_pool + enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12)); + // Load dst,[dst+#low12_constant_pool] + enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOAD, dst, 0b11, tmp, delta & 0xFFF)); + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" = #")); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java new file mode 100644 index 00000000..9088abbd --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -0,0 +1,31 @@ +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.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +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); } + @Override public String op() { return "ldi"; } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { return true; } + @Override public IntRISC copy() { return new IntRISC(this); } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this); + int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF); + // Explicit truncation of larger immediates; this will sign-extend on + // load and this is handled during instruction selection. + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, riscv.ZERO, val)); + } + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java new file mode 100644 index 00000000..aedd8b47 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java @@ -0,0 +1,33 @@ +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.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +// Load upper 20bits. +public class LUI extends ConstantNode implements MachNode { + public LUI( int imm20 ) { + super(TypeInteger.constant(imm20)); + assert (imm20 & 0xFFF)==0; + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public boolean isClone() { return true; } + @Override public LUI copy() { return new LUI((int)((TypeInteger)_con).value()); } + @Override public String op() { return "lui"; } + @Override public void encoding( Encoding enc ) { + long x = ((TypeInteger)_con).value(); + int imm20 = (int)(x>>12) & 0xFFFFF; + short dst = enc.reg(this); + int lui = RiscV.u_type(RiscV.OP_LUI, dst, imm20); + enc.add4(lui); + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value())); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java new file mode 100644 index 00000000..2e3dfd8d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -0,0 +1,30 @@ +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.util.SB; + +// Load memory addressing on RISC +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - imm12 added to base +public class LoadRISC extends MemOpRISC { + public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); } + @Override public String op() { return "ld" +_sz; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.MEM_MASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + int op = dst >= riscv.F_OFFSET ? RiscV.OP_LOADFP : RiscV.OP_LOAD; + if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET; + enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java new file mode 100644 index 00000000..adf21848 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -0,0 +1,80 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; + +public abstract class MemOpRISC extends MemOpNode implements MachNode { + final int _off; // Limit 12 bits + final char _sz; + MemOpRISC(MemOpNode mop, Node base, int off, Node val) { + super(mop,mop); + assert base._type instanceof TypeMemPtr; + _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base + _inputs.setX(3, null); // Never an index + _inputs.setX(4, val ); + _off = off; + _sz = (char)('0'+(1<<_declaredType.log_size())); + } + + @Override public String label() { return op(); } + Node val() { return in(4); } // Only for stores + + @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(); } + + // 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; + } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + // 0 - ctrl + // 1 - memory + if( i==2 ) return riscv.RMASK; // base + // 2 - index + if( i==4 ) return riscv.MEM_MASK; // value + throw Utils.TODO(); + } + + SB asm_address(CodeGen code, SB sb) { + sb.p("[").p(code.reg(ptr())).p("+"); + return sb.p(_off).p("]"); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java new file mode 100644 index 00000000..dacec2b5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java @@ -0,0 +1,18 @@ +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.util.SB; + +// fmul.d +public class MulFRISC extends MachConcreteNode implements MachNode { + MulFRISC(Node mulf) {super(mulf);} + @Override public String op() { return "mulf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.FMASK; } + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,9); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java new file mode 100644 index 00000000..9cb9ace7 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// mulh signed multiply instruction(no-imm form) +public class MulRISC extends MachConcreteNode implements MachNode{ + public MulRISC(Node mul) {super(mul);} + @Override public String op() { return "mul"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,1); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java new file mode 100644 index 00000000..3070c56c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java @@ -0,0 +1,21 @@ +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.util.SB; + +public class NegRISC extends MachConcreteNode implements MachNode { + public NegRISC( Node neg ) { super(neg); } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + RiscV.r_type(RiscV.OP,dst,0,0,src,0x20); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = -").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java new file mode 100644 index 00000000..965a3c7c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -0,0 +1,48 @@ +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.print.ASMPrinter; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class NewRISC extends NewNode implements MachNode, RIPRelSize { + // A pre-zeroed chunk of memory. + NewRISC( NewNode nnn ) { super(nnn); } + @Override public void encoding( Encoding enc ) { + // Generic external encoding; 2 ops. + enc.external(this,"calloc"); + // A1 is a caller-save, allowed to crush building external address + // auipc + enc.add4(RiscV.i_type(RiscV.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1)); + enc.add4(RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , 0)); + enc.add4(RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, 0)); + } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public byte encSize(int delta) { return 4*3; } + + // Patch is for running "new" in a JIT. + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + // Negative patches are JIT emulator targets. + if( opStart+delta < 0 ) { + delta -= 4; // Offset from the AUIPC start + int imm20 = delta>>12; + if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension + int imm12 = delta&0xFFF; + enc.patch4(opStart+4,RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , imm20)); + enc.patch4(opStart+8,RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, imm12)); + } else + throw Utils.TODO(); + } + + // General form: "alloc #bytes PC" + @Override public void asm(CodeGen code, SB sb) { + sb.p("ldi a1=#1\n"); + sb.p("auipc a2=#calloc\n"); + sb.p("call a2+#calloc, a0"); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java new file mode 100644 index 00000000..6f499db1 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.NotNode; +import com.seaofnodes.simple.util.SB; + +public class NotRISC extends MachConcreteNode implements MachNode { + public NotRISC(NotNode not) { super(not); } + @Override public String op() { return "not"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + // SEQZ is unsigned: negative nonzero inputs must also produce false. + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1)); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java new file mode 100644 index 00000000..937595a3 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java @@ -0,0 +1,13 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class OrIRISC extends ImmRISC { + public OrIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 6; } + @Override public String glabel() { return "|"; } + @Override public String op() { return "ori"; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java new file mode 100644 index 00000000..a310a1f4 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java @@ -0,0 +1,16 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class OrRISC extends MachConcreteNode implements MachNode { + public OrRISC(Node or) { super(or); } + @Override public String op() { return "or"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java new file mode 100644 index 00000000..0f8db4e5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; + +public class ParmRISC extends ParmNode implements MachNode { + final RegMask _rmask; + ParmRISC(ParmNode parm) { + super(parm); + _rmask = riscv.callInMask(fun().sig(),_idx,0); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return _rmask; } + @Override public void encoding( Encoding enc ) { } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java new file mode 100644 index 00000000..2f9ac469 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java @@ -0,0 +1,15 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; + +public class ProjRISC extends ProjNode implements MachNode { + ProjRISC(ProjNode p) { super(p); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } + @Override public void encoding( Encoding enc ) { } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java new file mode 100644 index 00000000..33143c6b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ReturnNode; +import com.seaofnodes.simple.util.Utils; + +public class RetRISC extends ReturnNode implements MachNode { + public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); } + @Override public void encoding( Encoding enc ) { + int sz = fun()._frameAdjust; + if( sz >= 1L<<12 ) throw Utils.TODO(); + if( sz != 0 ) + enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF)); + short rpc = enc.reg(rpc()); + enc.add4(RiscV.i_type(RiscV.OP_JALR, riscv.ZERO, 0, rpc, 0)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java new file mode 100644 index 00000000..d037dd5c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java @@ -0,0 +1,34 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.BoolNode; +import com.seaofnodes.simple.node.MachConcreteNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.util.SB; + +// corresponds to feq.d = equal +// fle.d = less than or equal +// flt.d = less than +public class SetFRISC extends MachConcreteNode implements MachNode { + final String _bop; // One of <,<=,== + SetFRISC( BoolNode bool ) { + super(bool); + assert bool.isFloat(); + _bop = bool.op(); + } + @Override public String op() { return "set"+_bop; } + @Override public RegMask regmap(int i) { return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET); + short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET); + int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.fsetop(_bop),src1,src2,0b1010001/*FCMP*/); + enc.add4(body); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java new file mode 100644 index 00000000..9bc86565 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.node.Node; + +// corresponds to slti,sltiu +public class SetIRISC extends ImmRISC { + final boolean _unsigned; // slti vs sltiu + public SetIRISC( Node src, int imm12, boolean unsigned ) { + this(src,imm12,unsigned,true); + } + public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) { + super(src,imm12,pop); + if( !pop ) _inputs.setLen(2); + _unsigned = unsigned; + } + @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; } + @Override public String glabel() { return "<"+ (_unsigned ? "u":""); } + @Override int opcode() { return 0b0010011; } + @Override int func3() { return _unsigned ? 0b011 : 0b010; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java new file mode 100644 index 00000000..848cc59f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// corresponds to slt sltu +public class SetRISC extends MachConcreteNode implements MachNode { + final boolean _unsigned; + public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; } + @Override public String op() { return "slt" + (_unsigned ? "u":""); } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" < ").p(code.reg(in(2))); + } +}
\ No newline at end of file diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java new file mode 100644 index 00000000..1425caa2 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java @@ -0,0 +1,13 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class SllIRISC extends ImmRISC { + public SllIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 1; } + @Override public String glabel() { return "<<"; } + @Override public String op() { return "slli"; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java new file mode 100644 index 00000000..5a4d4cc6 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Shift left logical +public class SllRISC extends MachConcreteNode implements MachNode { + public SllRISC(Node sll) { super(sll); } + @Override public String op() { return "shl"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,1,0); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java new file mode 100644 index 00000000..8a16f6b7 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -0,0 +1,67 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +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]); } + @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; } + @Override public RegMask outregmap() { return riscv.SPLIT_MASK; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + // Flag for being either XMM or stack + boolean dstX = dst >= riscv.F_OFFSET; + boolean srcX = src >= riscv.F_OFFSET; + + if( dst >= riscv.MAX_REG ) { + // Store to SP + if( src >= riscv.MAX_REG ) { + throw Utils.TODO(); // Very rare stack-stack move + } + int off = enc._fun.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)); + return; + } + if( src >= riscv.MAX_REG ) { + // Load from SP + int off = enc._fun.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)); + return; + } + // pick opcode based on regs + if( !dstX && !srcX ) { + // GPR->GPR + enc.add4(RiscV.r_type(RiscV.OP,dst,0,src,riscv.ZERO,0)); + } else if( dstX && srcX ) { + // FPR->FPR + src -= riscv.F_OFFSET; + dst -= riscv.F_OFFSET; + enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src) + } else if(!srcX && dstX) { + //GPR->FPR + // fmv.d.x + enc.add4(RiscV.r_type(RiscV.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001)); + } else if(srcX && !dstX) { + //FPR->GPR + //fmv.x.d + 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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java new file mode 100644 index 00000000..b316b303 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java @@ -0,0 +1,15 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class SraIRISC extends ImmRISC { + public SraIRISC( Node and, int imm) { + super(and,imm | (0x20 << 5)); + } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 5;} + @Override public String glabel() { return ">>"; } + @Override public String op() { return "srai"; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java new file mode 100644 index 00000000..e2e34415 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Right Shift Arithmetic +public class SraRISC extends MachConcreteNode implements MachNode { + public SraRISC(Node sra) { super(sra); } + @Override public String op() { return "sar"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java new file mode 100644 index 00000000..d0ea6b18 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java @@ -0,0 +1,13 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class SrlIRISC extends ImmRISC { + public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 5; } + @Override public String glabel() { return ">>>"; } + @Override public String op() { return "srli"; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java new file mode 100644 index 00000000..1eccc1c8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +// Right Shift Logical +public class SrlRISC extends MachConcreteNode implements MachNode { + public SrlRISC(Node srli) { super(srli); } + @Override public String op() { return "shr"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,7,0); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java new file mode 100644 index 00000000..48cb7c78 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +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.util.SB; +import com.seaofnodes.simple.util.Utils; + +// Store memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base) +// imm = imm - immediate value to store +// val = Node of immediate value to store(null if its a constant immediate) + +//e.g s.cs[0] = 67; // C +// base = s.cs, off = 4, imm = 67, val = null + +// sw rs2,offset(rs1) +public class StoreRISC extends MemOpRISC { + StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); } + @Override public String op() { return "st"+_sz; } + @Override public RegMask regmap(int i) { + // 0 - ctrl + if( i==1 ) return null; // mem + if( i==2 ) return riscv.RMASK; // ptr + // 2 - index + if( i==4 ) return _sz >= '4' + ? riscv.MEM_MASK // Word/dword value in GPR or FPR + : riscv.RMASK; // Byte/short stores require a GPR + return null; // Anti-dependence + } + @Override public RegMask outregmap() { return null; } + + @Override public void encoding( Encoding enc ) { + short val = enc.reg(val()); + short ptr = enc.reg(ptr()); + int op = val >= riscv.F_OFFSET ? RiscV.OP_STOREFP : RiscV.OP_STORE; + if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET; + enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off)); + } + + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p("0"); + else sb.p(code.reg(val())); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java new file mode 100644 index 00000000..044820cf --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java @@ -0,0 +1,18 @@ +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.util.SB; + +// fsub.d +public class SubFRISC extends MachConcreteNode implements MachNode { + SubFRISC(Node subf) {super(subf);} + @Override public String op() { return "subf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.FMASK; } + @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,5); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java new file mode 100644 index 00000000..2adfce23 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java @@ -0,0 +1,16 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class SubRISC extends MachConcreteNode implements MachNode { + public SubRISC( Node sub ) { super(sub); } + @Override public String op() { return "sub"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0x20); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java new file mode 100644 index 00000000..e57f6e09 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -0,0 +1,49 @@ +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.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); } + @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 void encoding( Encoding enc ) { + enc.relo(this); + // 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 + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0)); + } + + @Override public byte encSize(int delta) { return 8; } + + // Delta is from opcode start + @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)); + } else { + // should not happen as one instruction is 4 byte, and TFP arm encodes 2. + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" #")); + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java new file mode 100644 index 00000000..7bc2db7e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -0,0 +1,42 @@ +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.type.TypeMemPtr; +import com.seaofnodes.simple.util.SB; + +public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { + TMPRISC(ConstantNode con) { super(con); } + @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 void encoding( Encoding enc ) { + 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)); + // addi dst,[dst+#low12_constant_pool] + enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0)); + } + + @Override public byte encSize(int delta) { return 8; } + + // 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. + enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12)); + // ADDI dst,dst,#low12_constant_pool + enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF)); + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" #")); + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java new file mode 100644 index 00000000..448d9ec3 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java @@ -0,0 +1,49 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +// unconditional jump +public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize { + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("jmp "); } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + // Short form +/-4K: beq r0,r0,imm12 + // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32] + enc.jump(this,uctrl()); + enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0)); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java new file mode 100644 index 00000000..f4323cae --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java @@ -0,0 +1,14 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class XorIRISC extends ImmRISC { + public XorIRISC( Node and, int imm) { super(and,imm); } + public XorIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } + @Override int opcode() { return RiscV.OP_IMM; } + @Override int func3() { return 4; } + @Override public String glabel() { return "^"; } + @Override public String op() { return "xori"; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java new file mode 100644 index 00000000..413a7c3b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java @@ -0,0 +1,16 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.util.SB; + +public class XorRISC extends MachConcreteNode implements MachNode { + public XorRISC(Node or) { super(or); } + @Override public String op() { return "xor"; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,0); } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java new file mode 100644 index 00000000..7c994624 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -0,0 +1,428 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import static com.seaofnodes.isa.RiscV.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; + +public class riscv extends Machine { + + public riscv(CodeGen code) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } + + @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; + public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15; + public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23; + public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31; + + // FP registers + public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; + public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; + public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; + public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + + static final int MAX_REG = 64; + + 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", + //"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; } + + // General purpose register mask: pointers and ints, not floats + static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs + static final RegMask RMASK = new RegMask(RD_BITS); + + static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP + static final RegMask WMASK = new RegMask(WR_BITS); + // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask + static final long FP_BITS = 0b11111111111111111111111111111111L<<F0; + static final RegMask FMASK = new RegMask(FP_BITS); + + // Load/store mask; both GPR and FPR + static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); + + static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L ); + + // Arguments masks + public static RegMask A0_MASK = new RegMask(A0); + public static RegMask A1_MASK = new RegMask(A1); + public static RegMask A2_MASK = new RegMask(A2); + public static RegMask A3_MASK = new RegMask(A3); + public static RegMask A4_MASK = new RegMask(A4); + public static RegMask A5_MASK = new RegMask(A5); + public static RegMask A6_MASK = new RegMask(A6); + public static RegMask A7_MASK = new RegMask(A7); + + public static final RegMask RPC_MASK = new RegMask(RPC); + + // Float arguments masks + public static RegMask FA0_MASK = new RegMask(FA0); + public static RegMask FA1_MASK = new RegMask(FA1); + public static RegMask FA2_MASK = new RegMask(FA2); + public static RegMask FA3_MASK = new RegMask(FA3); + public static RegMask FA4_MASK = new RegMask(FA4); + public static RegMask FA5_MASK = new RegMask(FA5); + public static RegMask FA6_MASK = new RegMask(FA6); + public static RegMask FA7_MASK = new RegMask(FA7); + + // Int arguments calling conv + static RegMask[] CALLINMASK = new RegMask[] { + A0_MASK, + A1_MASK, + A2_MASK, + A3_MASK, + A4_MASK, + A5_MASK, + A6_MASK, + A7_MASK + }; + + static RegMask[] XMMS = new RegMask[] { + FA0_MASK, + FA1_MASK, + FA2_MASK, + FA3_MASK, + FA4_MASK, + FA5_MASK, + FA6_MASK, + FA7_MASK + }; + + // Since riscv instructions are fixed we can just or them together + + public static void r_type(Encoding enc, Node n, int func3, int func7) { + short dst = enc.reg(n); + short src1 = enc.reg(n.in(1)); + short src2 = enc.reg(n.in(2)); + int body = RiscV.r_type(OP,dst,func3,src1,src2,func7); + enc.add4(body); + } + + public static void rf_type(Encoding enc, Node n, RM func3, int func7) { + short dst = (short)(enc.reg(n )-F_OFFSET); + short src1 = (short)(enc.reg(n.in(1))-F_OFFSET); + short src2 = (short)(enc.reg(n.in(2))-F_OFFSET); + int body = RiscV.r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7); + enc.add4(body); + } + + @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } + static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat ) + fcnt++; + // Floats up to XMMS in XMM registers + if( tfp.arg(idx-2) instanceof TypeFloat ) { + if( fcnt < XMMS.length ) + return XMMS[fcnt]; + } else { + RegMask[] cargs = CALLINMASK; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + // Pass on stack slot(8 and higher) + if( maxArgSlot>0 ) throw Utils.TODO(); + return new RegMask(MAX_REG + 1 + (idx - 2)); + } + + @Override public short maxArgSlot( TypeFunPtr tfp ) { + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=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; + } + + static final long CALLER_SAVE = + (1L<<RPC) | + (1L<<A0 ) | (1L<<A1 ) | (1L<<A2 ) | (1L<<A3 ) | (1L<<A4 ) | (1L<<A5 ) | (1L<<A6 ) | (1L<<A7 ) | + (1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) | + (1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) | + (1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) | + (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11); + + @Override public long callerSave() { return CALLER_SAVE; } + @Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); } + @Override public RegMask retMask( TypeFunPtr tfp ) { + return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK; + } + @Override public int rpc() { return RPC; } + + // Return a MachNode unconditional branch + @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); } + + // True if signed 12-bit immediate + public static boolean imm12(TypeInteger ti) { + // 52 = 64-12 + return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value(); + } + // True if HIGH 20-bit signed immediate, with all zeros low. + public static boolean imm20Exact(TypeInteger ti) { + // shift left 32 to clear out the upper 32 bits. + // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits. + // shift left 12 to re-center the bits. + return ti.isConstant() && (((ti.value()<<32)>>44)<<12) == ti.value(); + } + + @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 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 FunNode fun -> new FunRISC(fun); + 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 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read-> new ReadOnlyMach(read); + case ReturnNode ret -> new RetRISC(ret, ret.fun()); + case SarNode sar -> sra(sar); + case ShlNode shl -> sll(shl); + case ShrNode shr -> srl(shr); + 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 ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIRISC(cvt); + case XorNode xor -> xor(xor); + + case LoopNode loop -> new LoopNode(loop); + case RegionNode region -> new RegionNode(region); + default -> throw Utils.TODO(); + }; + } + + private Node addf(AddFNode addf) { + return new AddFRISC(addf); + } + + private Node add(AddNode 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); + } + + private Node and(AndNode and) { + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) { + if( imm12(ti) ) + return new AndIRISC(and, (int)ti.value()); + // Could be any size low bit mask + if( ti.value() == 0xFFFFFFFFL ) + return new SrlIRISC(new SllIRISC(and,32),32,false); + } + return new AndRISC(and); + } + + private Node call(CallNode call) { + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallRISC(call, tfp) + : new CallRRISC(call); + } + private Node nnn(NewNode nnn) { + // TODO: pass in the TFP for alloc + return new NewRISC(nnn); + } + + private Node cmp(BoolNode bool) { + // Float variant directly implemented in hardware + if( bool.isFloat() ) + return new SetFRISC(bool); + + // Only < and <u are implemented in hardware. + // x < y - as-is + // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` + // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` + + // x > y - swap; y < x + // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT. + // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT. + + // The ">", ">=" and "!=" in Simple include a NotNode, which can be + // implemented with a XOR. If one of the above is followed by a NOT + // we can remove the double XOR in the encodings. + + return switch( bool.op() ) { + case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new SetIRISC(bool, (int)ti.value(),false) + : new SetRISC(bool, false); + // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` + case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false); + // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` + case "==" -> new SetIRISC(new SubRISC(bool),1,true,false); + default -> throw Utils.TODO(); + }; + } + + private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + return switch( con._con ) { + case TypeInteger ti -> { + if( imm12(ti) ) yield new IntRISC(con); + long x = ti.value(); + if( imm20Exact(ti) ) yield new LUI((int)x); + if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate + // Here, the low 12 bits get sign-extended, which means if + // bit11 is set, the value is negative and lowers the LUI + // value. Add a bit 12 to compensate + if( ((x>>11)&1)==1 ) x += 0x1000; + yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false); + } + // 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); + } + // Load from constant pool + case TypeFloat tf -> new FltRISC(con); + case TypeFunPtr tfp -> new TFPRISC(con); + case TypeMemPtr tmp -> new TMPRISC(con); + 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); + }; + } + + private Node jmp( IfNode iff ) { + if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) { + // if less than or equal switch inputs + String bop = bool.op(); + if( bop.equals("<=") || bop.equals(">") ) + return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1)); + return new BranchRISC(iff, bop, bool.in(1), bool.in(2)); + } + // Vs zero + return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null); + } + + private Node or(OrNode or) { + if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new OrIRISC(or, (int)ti.value()); + return new OrRISC(or); + } + + private Node xor(XorNode xor) { + if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new XorIRISC(xor, (int)ti.value()); + return new XorRISC(xor); + } + + private Node sra(SarNode sar) { + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new SraIRISC(sar, (int)ti.value()); + return new SraRISC(sar); + } + + private Node srl(ShrNode shr) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new SrlIRISC(shr, (int)ti.value(),true); + return new SrlRISC(shr); + } + + private Node sll(ShlNode sll) { + if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)) + return new SllIRISC(sll, (int)ti.value()); + return new SllRISC(sll); + } + + private Node sub(SubNode sub) { + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) + ? new AddIRISC(sub, (int)(-ti.value()),true) + : new SubRISC(sub); + } + + private Node i2f8(ToFloatNode tfn) { + assert tfn.in(1)._type instanceof TypeInteger ti; + return new I2F8RISC(tfn); + } + + private Node prj(ProjNode prj) { + TypeInteger ret = cReturn(prj); + if( ret!=null ) return new ExtendRISC(prj,ret); + return new ProjRISC(prj); + } + + private Node ld(LoadNode ld) { + return new LoadRISC(ld,address(ld),off); + } + + private Node st(StoreNode st) { + Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val(); + return new StoreRISC(st,address(st), off, xval); + } + + // Gather addressing mode bits prior to constructing. This is a builder + // pattern, but saving the bits in a *local* *global* here to keep mess + // contained. + private static int off; + private Node address( MemOpNode mop ) { + off = 0; // Reset + Node base = mop.ptr(); + // Skip/throw-away a ReadOnly, only used to typecheck + if( base instanceof ReadOnlyNode read ) base = read.in(1); + assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived + if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) { + off = (int)ti.value(); + } else { + base = new AddRISC(base,mop.off()); + base._type = mop.ptr()._type; + } + return base; + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java new file mode 100644 index 00000000..974f690f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -0,0 +1,33 @@ +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.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 ) { + super(add,ld, base, idx, off, scale, 0, val ); + } + @Override public String op() { return "addf"+_sz; } + @Override public RegMask regmap(int i) { + if( i==1 ) return null; // Memory + if( i==2 ) return x86_64_v2.RMASK; // base + if( i==3 ) return x86_64_v2.RMASK; // index + if( i==4 ) return x86_64_v2.XMASK; // value + throw Utils.TODO(); + } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 4; } + @Override public void encoding( Encoding enc ) { + X86.memory(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } + + // General form: "add dst = src + [base + idx<<2 + 12]" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = "); + sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); + asm_address(code,sb); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java new file mode 100644 index 00000000..c7d0cf9c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java @@ -0,0 +1,24 @@ +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.util.SB; + +public class AddFX86 extends MachConcreteNode implements MachNode { + AddFX86( Node addf ) { super(addf); } + @Override public String op() { return "addf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + @Override public boolean commutes() { return true; } + + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); + } + + // General form: "addf dst += src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" += ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java new file mode 100644 index 00000000..0e652a93 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class AddIX86 extends ImmX86 { + AddIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { + return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi"); + } + @Override public String glabel() { return "+"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 0; } + @Override public final void encoding( Encoding enc ) { + if( _imm== 1 || _imm== -1 ) { + short dst = enc.reg(this); // Also src1 + X86.unary(enc,0xFF,_imm==1 ? 0 : 1,dst); + } else super.encoding(enc); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java new file mode 100644 index 00000000..ed65b0a8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java @@ -0,0 +1,24 @@ +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.util.SB; + +public class AddMemX86 extends MemOpX86 { + AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) { + super(add,ld, base, idx, off, scale, imm, val ); + } + @Override public String op() { return "add"+_sz; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 4; } + @Override public void encoding( Encoding enc ) { + X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } + + // General form: "add dst += [base + idx<<2 + 12]" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" += "); + asm_address(code,sb); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java new file mode 100644 index 00000000..84543e8f --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class AddX86 extends RegX86 { + AddX86( Node add ) { super(add); } + @Override public String op() { return "add"; } + @Override public String glabel() { return "+"; } + @Override int opcode() { return 0x03; } + @Override public boolean commutes() { return true; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java new file mode 100644 index 00000000..3c1b567c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class AndIX86 extends ImmX86 { + AndIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 4; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java new file mode 100644 index 00000000..dedbce45 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class AndX86 extends RegX86 { + AndX86( Node add ) { super(add); } + @Override public String op() { return "and"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return 0x23; } + @Override public boolean commutes() { return true; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java new file mode 100644 index 00000000..f97163eb --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.CallNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.util.SB; + +public class CallRX86 extends CallNode implements MachNode { + CallRX86( CallNode call ) { super(call); } + @Override public String op() { return "callr"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + return i==_inputs._len + ? x86_64_v2.WMASK // Function call target + : x86_64_v2.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument + } + @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + X86.callRegister(enc,enc.reg(fptr())); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(arg(i))).p(" "); + sb.unchar(2); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java new file mode 100644 index 00000000..d1a43b83 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -0,0 +1,49 @@ +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.type.TypeFunPtr; +import com.seaofnodes.simple.util.SB; + +public class CallX86 extends CallNode implements MachNode, RIPRelSize { + final TypeFunPtr _tfp; + final String _name; + 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 + } + @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 outregmap() { return null; } + @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr + + @Override public void encoding( Encoding enc ) { + FunNode fun = CodeGen.CODE.link(_tfp); + if( fun==null ) enc.external(this,_name); + else enc.relo(this); + X86.call(enc); + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 5; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+1, delta-5); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name).p(" "); + for( int i=0; i<nargs(); i++ ) + sb.p(code.reg(in(i+2))).p(", "); + sb.unchar(2); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java new file mode 100644 index 00000000..5ef7a476 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java @@ -0,0 +1,24 @@ +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.util.SB; + +public class CmpFX86 extends MachConcreteNode implements MachNode { + CmpFX86( Node cmp ) { super(cmp); } + @Override public String op() { return "cmpf"; } + @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); + } + + // General form: "cmp src1,src2" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java new file mode 100644 index 00000000..151f8ca8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java @@ -0,0 +1,38 @@ +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.util.SB; + +// Compare immediate. Sets flags. +public class CmpIX86 extends MachConcreteNode implements MachNode { + final int _imm; + CmpIX86( Node cmp, int imm, boolean swap ) { + super(cmp); + _inputs.del(swap ? 1 : 2); + _imm = imm; + } + CmpIX86( CmpIX86 cmp ) { + super((Node)null); + addDef(null); + addDef(cmp.in(1)); + _imm = cmp._imm; + } + + @Override public String op() { return _imm==0 ? "test" : "cmp"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + @Override public boolean isClone() { return true; } + @Override public Node copy() { return new CmpIX86(this); } + + @Override public final void encoding( Encoding enc ) { + X86.imm(enc,0x81,7,enc.reg(in(1)),_imm); + } + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))); + if( _imm != 0 ) sb.p(", #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java new file mode 100644 index 00000000..7b7d98d5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -0,0 +1,44 @@ +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.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.util.SB; + +public class CmpMemX86 extends MemOpX86 { + final boolean _swap; // Op switched LHS, RHS + CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) { + super(bool,ld, base, idx, off, scale, imm, val ); + _swap = swap; + assert !_swap || val() != null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate + } + @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; } + @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + 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); + } + + static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { + assert !(decl instanceof TypeFloat); // Selected separately as CmpFX86. + X86.cmpMem(enc,decl.log_size(),src,ptr,idx,off,scale,_swap); + } + + // General form: "cmp dst = src, [base + idx<<2 + 12]" + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + if( _swap ) asm_address(code,sb).p(","); + if( val()==null ) { + if( _imm!=0 ) sb.p("#").p(_imm); + } else { + sb.p(code.reg(val())); + } + if( !_swap ) asm_address(code,sb.p(", ")); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java new file mode 100644 index 00000000..7faea17b --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java @@ -0,0 +1,28 @@ +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.util.SB; + +public class CmpX86 extends MachConcreteNode implements MachNode { + CmpX86( Node add ) { super(add); } + @Override public String op() { return "cmp"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; } + // This one is marginal: cloning a Cmp[reg,reg] means stretching the + // lifetimes of 2 normal registers in exchange for shortening a flags + // register lifetime. Spilling flags is (probably) relatively expensive + // compared to some normal registers - and those normal registers might not + // be spilling! + @Override public boolean isClone() { return true; } + + @Override public void encoding( Encoding enc ) { + X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2))); + } + @Override public void asm(CodeGen code, SB sb) { + String dst = code.reg(this); + if( dst!="flags" ) sb.p(dst).p(" = "); + sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java new file mode 100644 index 00000000..7bbb5102 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java @@ -0,0 +1,24 @@ +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.util.SB; + +public class DivFX86 extends MachConcreteNode implements MachNode { + DivFX86( Node divf) { super(divf); } + @Override public String op() { return "divf"; } + @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); + } + + + // General form: "divf dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java new file mode 100644 index 00000000..9f375a80 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java @@ -0,0 +1,25 @@ +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.util.SB; + +public class DivX86 extends MachConcreteNode implements MachNode { + DivX86( Node div ) { super(div); } + @Override public String op() { return "div"; } + @Override public RegMask regmap(int i) { + return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.DIV_MASK; + } + @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; } + @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; } + + @Override public void encoding( Encoding enc ) { + X86.divide(enc,enc.reg(in(2))); + } + // General form: "div dst /= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2))); + } + @Override public String comment() { return "kill rdx"; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java new file mode 100644 index 00000000..1a6ae123 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java @@ -0,0 +1,29 @@ +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.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +// Convert a narrow C return register to Simple's full-width integer value. +public class ExtendX86 extends MachConcreteNode { + final int _bits; + final boolean _signed; + ExtendX86(ProjNode prj, TypeInteger ret) { + super(prj); + _inputs.set(0,null); + _inputs.add(new ProjX86(prj)); + _bits = 8 << ret.log_size(); + _signed = ret._min < 0; + } + @Override public String op() { return (_signed ? "sext" : "zext")+_bits; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public void encoding(Encoding enc) { + X86.extend(enc,_bits,_signed,enc.reg(this),enc.reg(in(1))); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..a79a1017 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,22 @@ +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.util.SB; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java new file mode 100644 index 00000000..da9bdce7 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -0,0 +1,36 @@ +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.util.SB; + +public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ + private byte _encSize; + FltX86(ConstantNode con ) { super(con); } + @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 void encoding( Encoding enc ) { + _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET); + enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + // TODO: 1-byte RIP offset? + return (byte)_encSize; + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+opLen-4,delta-opLen); + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" = #")); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java new file mode 100644 index 00000000..c7a20e4d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.util.SB; + +public class FunX86 extends FunNode implements MachNode { + FunX86( FunNode fun ) { super(fun); } + @Override public void postSelect(CodeGen code) { + super.postSelect(code); + // One more for RPC pre-pushed; X86 special + _maxArgSlot++; + } + + @Override public void computeFrameAdjust(CodeGen code, int maxReg) { + super.computeFrameAdjust(code,maxReg); + // Alignment, but off by RPC + if( _hasCalls ) // If non-leaf, pad to 16b + _frameAdjust = ((_frameAdjust+8+8) & -16)-8; + } + + @Override public void encoding( Encoding enc ) { + int sz = _frameAdjust; + if( sz == 0 ) return; // Skip if no frame adjust + X86.imm(enc,0x81,5,x86_64_v2.RSP,sz); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java new file mode 100644 index 00000000..ee357901 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java @@ -0,0 +1,21 @@ +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.util.SB; + +public class I2f8X86 extends MachConcreteNode implements MachNode { + I2f8X86(Node i2f8 ) { super(i2f8); } + @Override public String op() { return "cvtf"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java new file mode 100644 index 00000000..5a4ff4b2 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java @@ -0,0 +1,32 @@ +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.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +public abstract class ImmX86 extends MachConcreteNode implements MachNode { + final int _imm; + ImmX86( Node add, int imm ) { + super(add); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + + abstract int opcode(); + abstract int mod(); + + @Override public void encoding( Encoding enc ) { + X86.imm(enc,opcode(),mod(),enc.reg(this),_imm); + } + + // General form: "addi dst += #imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java new file mode 100644 index 00000000..656b1543 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -0,0 +1,40 @@ +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.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.SB; + +// Integer constants +public class IntX86 extends ConstantNode implements MachNode { + IntX86( ConstantNode con ) { super(con); } + @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 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 || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null; + } + + @Override public void encoding( Encoding enc ) { + X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value()); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // 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 || _con == TypeInteger.ZERO ) + sb.p(reg).p(",").p(reg); + else + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java new file mode 100644 index 00000000..10cee059 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java @@ -0,0 +1,65 @@ +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.util.SB; +import com.seaofnodes.simple.util.Utils; + +// Jump on flags, uses flags +public class JmpX86 extends IfNode implements MachNode, RIPRelSize { + String _bop; + JmpX86( IfNode iff, String bop ) { + super(iff); + _bop = bop; + } + @Override public String op() { return "j"+_bop; } + @Override public String label() { return op(); } + @Override public void postSelect(CodeGen code) { + Node set = in(1); + if( set==null ) return; // Never-node cutout + Node cmp = set.in(1); + // Bypass an expected Set and just reference the cmp directly + if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) { + _inputs.set( 1, cmp ); + _bop = setx._bop; + } else + throw Utils.TODO(); + } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return null; } + @Override public void negate() { _bop = negate(_bop); } + + @Override public void encoding( Encoding enc ) { + if( in(1)==null && _bop=="!=" ) return; + X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + enc.jump(this,cproj(0)); + } + + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { + if( in(1)==null && _bop=="!=" ) return 0; + return X86.branchSize(delta,in(1)!=null); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + assert !(in(1)==null && _bop=="!="); + X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15); + } + + @Override public void asm(CodeGen code, SB sb) { + if( in(1)!=null ) { // Never-node + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + } else if( _bop=="!=" ) { + sb.p("never"); + return; + } + CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target + if( !prj.blockHead() ) prj = prj.cfg0(); + sb.p(label(prj)); + } + + @Override public String comment() { return "L"+cproj(1)._nid; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java new file mode 100644 index 00000000..4b00e4dd --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java @@ -0,0 +1,38 @@ +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.util.SB; + +public class LeaX86 extends MachConcreteNode implements MachNode { + final int _scale; + final int _offset; + LeaX86( Node add, Node base, Node idx, int scale, int offset ) { + super(add); + assert scale==0 || scale==1 || scale==2 || scale==3; + _inputs.pop(); + _inputs.pop(); + _inputs.push(base); + _inputs.push(idx); + _scale = scale; + _offset = offset; + } + + @Override public String op() { return "lea"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale); + } + + // General form: "lea dst = base + 4*idx + 12" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = "); + if( in(1) != null ) + sb.p(code.reg(in(1))).p(" + "); + sb.p(code.reg(in(2))).p("<<").p(_scale); + if( _offset!=0 ) sb.p(" + #").p(_offset); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java new file mode 100644 index 00000000..d4f1413e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +public class LoadX86 extends MemOpX86 { + LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) { + 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 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 + // Use movsx and movzx + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + enc(enc, _declaredType, dst, ptr, idx, _off, _scale); + } + + static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // The chapter chooses the register bank and extension from its own type facts. + boolean xmm=dst>=x86_64_v2.XMM_OFFSET; + if( xmm && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( !xmm && decl instanceof TypeFloat ) + decl=decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; + boolean signed=decl.isa(TypeInteger.I8) || decl==TypeInteger.I16 || decl==TypeInteger.I32; + X86.load(enc,decl.log_size(),signed,xmm, + xmm ? dst-x86_64_v2.XMM_OFFSET : dst,ptr,idx,off,scale); + } + + + // General form: "ldN dst,[base + idx<<2 + 12]" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java new file mode 100644 index 00000000..2e18a058 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java @@ -0,0 +1,28 @@ +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.util.SB; + +public class MemAddX86 extends MemOpX86 { + MemAddX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { + super(st, st, base, idx, off, scale, imm, val ); + } + @Override public String op() { + return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz; + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public void encoding( Encoding enc ) { + X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale); + } + // General form: "add [base + idx<<2 + 12] += src" + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb); + if( val()==null ) { + if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm); + } else sb.p(" += ").p(code.reg(val())); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java new file mode 100644 index 00000000..309e62d9 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -0,0 +1,89 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.print.ExprPrinter; + +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. +// inputs: +// ctrl - will appear for possibly RCE (i.e. array ops) +// mem - memory dependence edge +// base - Basic object pointer +// idx/null - Scaled index offset, or null if none +// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used +// Constants: +// offset - offset added to base. Can be zero. +// scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null +// imm - immediate value to store or op-to-mem, and only when val is null +public abstract class MemOpX86 extends MemOpNode implements MachNode { + final int _off; // Limit 32 bits + final int _scale; // Limit 0,1,2,3 + final int _imm; // Limit 32 bits + final char _sz; // Handy print name + MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) { + super(op,mop); + assert base._type instanceof TypeMemPtr && !(base instanceof AddNode); + assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3); + + // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add + if( op != mop ) { + _inputs.set(0,mop.in(0)); // Control from mem op + _inputs.set(1,mop.in(1)); // Memory from mem op + _inputs.set(2,base); // Base handed in + } + + assert ptr() == base; + _inputs.setX(3,idx); + _off = off; + _scale = scale; + _imm = imm; + _sz = (char)('0'+(1<<_declaredType.log_size())); + } + + // Store-based flavors have a value edge + MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) { + this(op,mop,base,idx,off,scale,imm); + _inputs.setX(4,val); + } + + Node idx() { return in(3); } + Node val() { return in(4); } // Only for stores, including op-to-memory + + @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { + return p.p(".").p(_name); + } + + @Override public String label() { return op(); } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { + if( i==1 ) return null; // Memory + if( i==2 ) return x86_64_v2.RMASK; // base in GPR + if( i==3 ) return x86_64_v2.RMASK; // index in GPR + if( i==4 ) return _sz >= '4' + ? x86_64_v2.MEM_MASK // value in GPR or XMM + : x86_64_v2.RMASK; // Bytes and shorts in GPR only + return null; // Anti-dependence + } + + // "[base + idx<<2 + 12]" + SB asm_address(CodeGen code, SB sb) { + sb.p("[").p(code.reg(ptr())); + if( idx() != null ) { + sb.p("+").p(code.reg(idx())); + if( _scale != 0 ) + sb.p("*").p((1<<_scale)); + } + if( _off != 0 ) + sb.p("+").p(_off); + return sb.p("]"); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java new file mode 100644 index 00000000..74616db7 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java @@ -0,0 +1,24 @@ +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.util.SB; + +public class MulFX86 extends MachConcreteNode implements MachNode { + MulFX86( Node mulf) { super(mulf); } + @Override public String op() { return "mulf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + @Override public boolean commutes() { return true; } + + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); + } + + // General form: "mulf dst *= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java new file mode 100644 index 00000000..5dcb253d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java @@ -0,0 +1,27 @@ +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.util.SB; + +public class MulIX86 extends MachConcreteNode implements MachNode { + final int _imm; + MulIX86( Node add, int imm ) { + super(add); + _inputs.pop(); // Pop the constant input + _imm = imm; // Record the constant + } + @Override public String op() { return "muli"; } + @Override public String glabel() { return "*"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + // Rare 3-address form + @Override public final void encoding( Encoding enc ) { + X86.imul(enc,enc.reg(this),enc.reg(in(1)),_imm); + } + // General form: "addi dst += #imm" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java new file mode 100644 index 00000000..6f3fcdeb --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java @@ -0,0 +1,24 @@ +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.util.SB; + +public class MulX86 extends MachConcreteNode implements MachNode { + MulX86( Node mul ) { super(mul); } + @Override public String op() { return "mul"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + @Override public boolean commutes() { return true; } + + @Override public void encoding( Encoding enc ) { + X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2))); + } + + // General form: "mul dst *= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java new file mode 100644 index 00000000..86610f6d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java @@ -0,0 +1,20 @@ +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.util.SB; + +public class NegX86 extends MachConcreteNode implements MachNode { + NegX86(MinusNode not) { super(not); } + @Override public String op() { return "neg"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public int twoAddress() { return 1; } + + @Override public void encoding( Encoding enc ) { + X86.unary(enc,0xF7,3,enc.reg(this)); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java new file mode 100644 index 00000000..1cfc1c42 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -0,0 +1,23 @@ +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.type.TypeFunPtr; +import com.seaofnodes.simple.util.SB; + +public class NewX86 extends NewNode implements MachNode { + // A pre-zeroed chunk of memory. + NewX86( NewNode nnn ) { super(nnn); } + @Override public void encoding( Encoding enc ) { + enc.external(this,"calloc"); + X86.constant(enc,_arg2Reg,1); + X86.call(enc); + } + // General form: "alloc #bytes PC" + @Override public void asm(CodeGen code, SB sb) { + sb.p("ldi rcx = #1\n"); + sb.p("call #calloc"); + } + +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java new file mode 100644 index 00000000..1e4e7c26 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java @@ -0,0 +1,21 @@ +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.util.SB; + +public class NotX86 extends MachConcreteNode implements MachNode { + NotX86(NotNode not) { super(not); } + @Override public String op() { return "not"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + + @Override public void encoding( Encoding enc ) { + assert !(in(1) instanceof NotNode); // Cleared out by peeps + assert !(in(1) instanceof BoolNode); + X86.not(enc,enc.reg(this),enc.reg(in(1))); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java new file mode 100644 index 00000000..15f4824d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class OrIX86 extends ImmX86 { + OrIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { return "ori"; } + @Override public String glabel() { return "|"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 1; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java new file mode 100644 index 00000000..bc8f0ea8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class OrX86 extends RegX86 { + OrX86( Node add ) { super(add); } + @Override public String op() { return "or"; } + @Override public String glabel() { return "|"; } + @Override int opcode() { return 0x0B; } + @Override public boolean commutes() { return true; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java new file mode 100644 index 00000000..5cfdd804 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ParmNode; + +public class ParmX86 extends ParmNode implements MachNode { + final RegMask _rmask; + ParmX86( ParmNode parm ) { + super(parm); + _rmask = x86_64_v2.callInMask(fun().sig(),_idx,1/*RPC*/); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return _rmask; } + @Override public void encoding( Encoding enc ) { } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java new file mode 100644 index 00000000..046c7b4c --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java @@ -0,0 +1,15 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.ProjNode; + +public class ProjX86 extends ProjNode implements MachNode { + ProjX86( ProjNode p ) { super(p); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { + return ((MachNode)in(0)).outregmap(_idx); + } + @Override public void encoding( Encoding enc ) {} +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java new file mode 100644 index 00000000..06755143 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java @@ -0,0 +1,22 @@ +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.util.SB; + +public abstract class RegX86 extends MachConcreteNode { + RegX86( Node add ) { super(add); } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public int twoAddress() { return 1; } + abstract int opcode(); + @Override public void encoding( Encoding enc ) { + X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2))); + } + // General form: "add dst += src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java new file mode 100644 index 00000000..e4467bb4 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java @@ -0,0 +1,15 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class RetX86 extends ReturnNode implements MachNode { + RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); } + @Override public void encoding( Encoding enc ) { + int sz = fun()._frameAdjust; + if( sz!=0 ) X86.imm(enc,0x81,0,x86_64_v2.RSP,sz); + X86.ret(enc); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java new file mode 100644 index 00000000..cf6275dc --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.SB; + +// Arithmetic Right Shift +public class SarIX86 extends MachConcreteNode implements MachNode { + final int _imm; + SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } + @Override public String op() { return "sari"; } + @Override public String glabel() { return ">>"; } + int opcode() { return 0xC1; } + int mod() { return 7; } + + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java new file mode 100644 index 00000000..92493712 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class SarX86 extends RegX86 { + SarX86( Node add ) { super(add); } + @Override public String op() { return "sar"; } + @Override public String glabel() { return ">>"; } + @Override public RegMask regmap(int i) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + @Override int opcode() { return 0xD3; } + @Override public void encoding( Encoding enc ) { + X86.unary(enc,opcode(),7,enc.reg(this)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java new file mode 100644 index 00000000..864c1b55 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java @@ -0,0 +1,44 @@ +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.util.SB; +import com.seaofnodes.simple.util.Utils; + +// Corresponds to the x86 instruction "sete && setne". +// Use result of comparison without jump. +public class SetX86 extends MachConcreteNode implements MachNode { + final String _bop; // One of <,<=,== + final boolean _unsigned; + // Constructor expects input is an X86 and not an Ideal node. + SetX86( Node cmp, String bop, boolean unsigned ) { + super(cmp); + _inputs.setLen(1); // Pop the cmp inputs + // Replace with the matched cmp + _inputs.push(cmp); + _bop = bop; + _unsigned = unsigned; + } + @Override public String op() { return "set"+_bop; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + int op=switch (_bop) { + case "==" -> 0x94; // SETE + case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL + case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE + case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE + case ">" -> _unsigned ? 0x97 : 0x9F; // SETA /SETG + default -> throw Utils.TODO(); + }; + X86.set(enc,op&15,enc.reg(this)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)); + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(" = ").p(src); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java new file mode 100644 index 00000000..1e62243e --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java @@ -0,0 +1,32 @@ +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.Node; +import com.seaofnodes.simple.util.SB; + +public class ShlIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShlIX86( Node shli, int imm ) { + super(shli); + assert x86_64_v2.imm8(imm); + _inputs.pop(); // Pop constant input off + _imm = imm; + } + public int opcode() { return 0xC1; } + public int mod() { return 4; } + + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + + @Override public String op() { return "shli"; } + @Override public String glabel() { return "<<"; } + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java new file mode 100644 index 00000000..768d97e7 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class ShlX86 extends RegX86 { + ShlX86( Node add ) { super(add); } + @Override public String op() { return "shl"; } + @Override public String glabel() { return "<<"; } + @Override public RegMask regmap(int i) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + X86.unary(enc,opcode(),4,enc.reg(this)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java new file mode 100644 index 00000000..8a0d1b58 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java @@ -0,0 +1,27 @@ +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.Node; +import com.seaofnodes.simple.util.SB; + +public class ShrIX86 extends MachConcreteNode implements MachNode { + final int _imm; + ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; } + @Override public String op() { return "shri"; } + @Override public String glabel() { return ">>>"; } + int opcode() { return 0xC1; } + int mod() { return 5; } + + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + + @Override public void encoding(Encoding enc) { + X86.shift(enc,mod(),enc.reg(this),_imm); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java new file mode 100644 index 00000000..1ba20fee --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.util.Utils; + +public class ShrX86 extends RegX86 { + ShrX86( Node add ) { super(add); } + @Override public String op() { return "shr"; } + @Override public String glabel() { return ">>>"; } + @Override public RegMask regmap(int i) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + X86.unary(enc,opcode(),5,enc.reg(this)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java new file mode 100644 index 00000000..4650b535 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java @@ -0,0 +1,25 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; + +// Flag snapshots can use GPRs or stack slots, but never XMM registers. +public class SplitFlagsX86 extends SplitX86 { + private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L); + SplitFlagsX86(String kind,byte round) { super(kind,round); } + @Override public RegMask regmap(int i) { return MASK; } + @Override public RegMask outregmap() { return MASK; } + + @Override public void encoding(Encoding enc) { + short dst=enc.reg(this), src=enc.reg(in(1)); + if( dst==src ) return; + if( dst==x86_64_v2.FLAGS || src==x86_64_v2.FLAGS ) { + boolean toFlags = dst==x86_64_v2.FLAGS; + int reg = toFlags ? src : dst; + if( reg>=x86_64_v2.MAX_REG ) + X86.flagsStack(enc,enc._fun.computeStackOffset(enc._code,reg),toFlags); + else + X86.flags(enc,reg,toFlags); + } else super.encoding(enc); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java new file mode 100644 index 00000000..4e9eda7d --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -0,0 +1,52 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.FunNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.SplitNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; +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"; } + @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } + + // Ordinary GPR, XMM, and stack moves. Flags use SplitFlagsX86. + @Override public void encoding( Encoding enc ) { + short dst=enc.reg(this), src=enc.reg(in(1)); + assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS; + 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); + 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); + X86.stackCopy(enc,soff,off); + return; + } + StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0); + return; + } + if( src >= x86_64_v2.MAX_REG ) { + int off = enc._fun.computeStackOffset(enc._code,src); + LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); + return; + } + + 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/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java new file mode 100644 index 00000000..c83dfd44 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -0,0 +1,48 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.util.SB; + +public class StoreX86 extends MemOpX86 { + StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { + super(st,st, base, idx, off, scale, imm, val); + } + @Override public String op() { return "st"+_sz; } + @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { + Node val = val(); + p.p(".").p(_name).p("="); + if( val==null ) p.p(_imm); + else p.n(val); + return p.p(";"); + } + // Register mask allowed as a result. 0 for no register. + @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); + } + + // Non-immediate encoding + static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) { + boolean xmm=src>=x86_64_v2.XMM_OFFSET; + X86.store(enc,decl.log_size(),xmm, + xmm ? src-x86_64_v2.XMM_OFFSET : src,ptr,idx,off,scale); + } + + + // General form: "stN [base + idx<<2 + 12],val" + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb).p(","); + if( val()==null ) sb.p("#").p(_imm); + else sb.p(code.reg(val())); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java new file mode 100644 index 00000000..29f0033a --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java @@ -0,0 +1,23 @@ +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.util.SB; + +public class SubFX86 extends MachConcreteNode implements MachNode { + SubFX86( Node subf) { super(subf); } + @Override public String op() { return "subf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + + @Override public void encoding( Encoding enc ) { + X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET); + } + + // General form: "subf dst -= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2))); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java new file mode 100644 index 00000000..8da34bf5 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java @@ -0,0 +1,10 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class SubX86 extends RegX86 { + SubX86( Node add ) { super(add); } + @Override public String op() { return "sub"; } + @Override public String glabel() { return "-"; } + @Override int opcode() { return 0x2B; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java new file mode 100644 index 00000000..fff2fb04 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -0,0 +1,53 @@ +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; + +// 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"; + } + @Override public boolean isClone() { return true; } + @Override public Node 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)); + } + + @Override public byte encSize(int delta) { + return 7; + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart + 3, delta - 7); + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // 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(" = #")); + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java new file mode 100644 index 00000000..c78ac6de --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java @@ -0,0 +1,40 @@ +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.type.TypeMemPtr; +import com.seaofnodes.simple.util.SB; + +public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ + TMPX86(ConstantNode con ) { super(con); } + @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 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*/); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { return (byte)7; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart+opLen-4,delta-opLen); + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" = #")); + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java new file mode 100644 index 00000000..591a51c3 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java @@ -0,0 +1,41 @@ +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.type.Type; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; + +// unconditional jump +public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize { + UJmpX86() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override protected String format() { return "jmp "; } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + X86.branch(enc,-1); + } + + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { + return X86.branchSize(delta,false); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + X86.patchBranch(enc.bits(),opStart,opLen,delta,-1); + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + //assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java new file mode 100644 index 00000000..2d989a50 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class XorIX86 extends ImmX86 { + XorIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { return "xori"; } + @Override public String glabel() { return "^"; } + @Override int opcode() { return 0x81; } + @Override int mod() { return 6; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java new file mode 100644 index 00000000..86703903 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java @@ -0,0 +1,11 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.node.Node; + +public class XorX86 extends RegX86 { + XorX86( Node add ) { super(add); } + @Override public String op() { return "xor"; } + @Override public String glabel() { return "^"; } + @Override int opcode() { return 0x33; } + @Override public boolean commutes() { return true; } +} diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java new file mode 100644 index 00000000..fcae17f8 --- /dev/null +++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -0,0 +1,564 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; + +public class x86_64_v2 extends Machine { + public x86_64_v2( CodeGen code ) {} + // X86-64 V2. Includes e.g. SSE4.2 and POPCNT. + @Override public String name() { return "x86_64_v2"; } + @Override public int defaultOpSize() { return 5; } + + public static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7; + public static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15; + + public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23; + public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31; + public static final int FLAGS = 32; + public static final int MAX_REG = 33; + public static final int RPC = 33; + + // Human-readable name for a register number, e.g. "RAX". + public static final String[] REGS = new String[]{ + "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", + "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", + "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" , + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", + "flags", + }; + // Hard crash for bad register number, fix yer bugs! + @Override public String[] regs() { return REGS; } + + public static int XMM_OFFSET = 16; + // General purpose register mask: pointers and ints, not floats + static final long RD_BITS = 0b1111111111111111L; // All the GPRs + static RegMask RMASK = new RegMask(RD_BITS); + // No RSP in the *write* general set. + static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP + static RegMask WMASK = new RegMask(WR_BITS); + // Xmm register mask + static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs + static RegMask XMASK = new RegMask(FP_BITS); + static RegMask FLAGS_MASK = new RegMask(FLAGS); + static RegMask RPC_MASK = new RegMask(RPC); + // Divide mask: exclude RDX:RAX + static RegMask DIV_MASK = new RegMask(RD_BITS & ~(1L<<RAX) & ~(1L<<RDX)); + + static final long SPILLS = -(1L << MAX_REG); + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | SPILLS, -1L ); + + // Load/store mask; both GPR and FPR + static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS); + + public static RegMask RAX_MASK = new RegMask(RAX); + public static RegMask RCX_MASK = new RegMask(RCX); + public static RegMask RDX_MASK = new RegMask(RDX); + public static RegMask RDI_MASK = new RegMask(RDI); + public static RegMask R08_MASK = new RegMask(R08); + public static RegMask R09_MASK = new RegMask(R09); + public static RegMask RSI_MASK = new RegMask(RSI); + + public static RegMask XMM0_MASK = new RegMask(XMM0); + + // Encoding + public static int REX = 0x40; + public static int REX_W = 0x48; + public static int REX_WR = 0x4C; + public static int REX_WRB= 0x4D; + public static int REX_WB = 0x49; + + // opcode included here + // 0F 84 cd JE rel32 + // 0F 85 cd JNE rel32 + // 0F 8F cd JG rel32 + // 0F 8C cd JL rel32 + // 0F 8E cd JLE rel32 + // 0F 8D cd JGE rel32 + static public int jumpop(String op) { + return switch (op) { + case "==" -> 0x84; + case "!=" -> 0x85; + case ">" -> 0x8F; + case "<" -> 0x8C; + case "<=" -> 0x8E; + case ">=" -> 0x8D; + default -> throw new IllegalArgumentException("Too many arguments"); + }; + } + + // Limit of float args passed in registers + static RegMask[] XMMS8 = new RegMask[]{ + new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), + new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7), + }; + + // 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); } + static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { + 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 ) { + case "SystemV" -> callSys5 (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 ) { + case "SystemV" -> maxArgSlotSys5 (tfp); + case "Win64" -> maxArgSlotWin64(tfp); + default -> throw Utils.TODO(); + }; + } + + // Win64 - max *4* args in registers of all kinds; after that on stack. + // Stack args land in increasing memory order with empty/mirror slots for + // every register. + // + // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) + // + // -- Prior Frame -- + // i6 + // f5 + // i4 + // empty mirror, XMM3= f3 + // empty mirror, R08 = i2 + // empty mirror, XMM1= f1 + // empty mirror, RCX = i0 + // -- Caller Frame; 16b align -- + // RPC + // PAD/ALIGN + // -- Callee Frame; 16b align -- + static RegMask[] WIN64_CALL = new RegMask[] { + RCX_MASK, + RDX_MASK, + R08_MASK, + R09_MASK, + }; + + 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)); + return tfp.arg(idx-2) instanceof TypeFloat + ? XMMS8 [idx-2] + : WIN64_CALL[idx-2]; + } + static short maxArgSlotWin64(TypeFunPtr tfp) { + return (short)tfp.nargs(); + } + + // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing + // memory order as needed - no mirror space. + // + // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... ) + // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10 + // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15 + // + // -- Prior Frame -- + // f18 + // i18 + // f17 + // i16 + // i14 + // i12 + // -- Caller Frame; 16b align -- + // RPC + // PAD/ALIGN + // -- Callee Frame; 16b align -- + // SystemV(param passing) + static RegMask[] SYS5_CALL = new RegMask[] { + RDI_MASK, + RSI_MASK, + RDX_MASK, + RCX_MASK, + R08_MASK, + R09_MASK, + }; + + 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 icnt=0, fcnt=0; // Count of ints, floats + for( int i=2; i<idx; i++ ) { + if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; + else icnt++; + } + int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); + return tfp.arg(idx-2) instanceof TypeFloat + ? fcnt<8 ? XMMS8 [fcnt] : new RegMask(MAX_REG+maxArgSlot+nstk) + : icnt<6 ? SYS5_CALL[icnt] : new RegMask(MAX_REG+maxArgSlot+nstk); + } + static short maxArgSlotSys5(TypeFunPtr tfp) { + int icnt=0, fcnt=0; // Count of ints, floats + for( int i=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; + } + + static final long SYSTEM5_CALLER_SAVE = + (1L << RAX) | (1L << RCX) | (1L << RDX) | + (1L << RDI) | (1L << RSI) | + (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11) | + (1L << FLAGS) | // Flags are killed + // All FP regs are killed + FP_BITS; + + static final long WIN64_CALLER_SAVE = + (1L<< RAX) | (1L<< RCX) | (1L<< RDX) | + (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) | + (1L<<FLAGS)| // Flags are killed + // Only XMM0-XMM5 are killed; XMM6-XMM15 are preserved + (1L<<XMM0) | (1L<<XMM1) | (1L<<XMM2) | (1L<<XMM3) | + (1L<<XMM4) | (1L<<XMM5) ; + + @Override public long callerSave() { + return switch (CodeGen.CODE._callingConv) { + case "SystemV" -> SYSTEM5_CALLER_SAVE; + case "Win64" -> WIN64_CALLER_SAVE; + default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); + }; + } + @Override public long neverSave() { return 1L<<RSP; } + @Override public RegMask retMask( TypeFunPtr tfp ) { + return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; + } + @Override public int rpc() { return RPC; } + + + // Create a split op; any register to any register, including stack slots + @Override public SplitNode split(String kind, byte round, LRG lrg) { + return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS) + ? new SplitFlagsX86(kind,round) + : new SplitX86(kind,round); + } + + // Return a MachNode unconditional branch + @Override public CFGNode jump() { + return new UJmpX86(); + } + + // Instruction selection + @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 ConstantNode con -> con(con); + case DivFNode divf -> new DivFX86(divf); + case DivNode div -> new DivX86(div); + case FunNode fun -> new FunX86(fun); + 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 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read-> new ReadOnlyMach(read); + case ReturnNode ret -> new RetX86(ret, ret.fun()); + case SarNode sar -> sar(sar); + case ShlNode shl -> shl(shl); + case ShrNode shr -> shr(shr); + 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 ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIX86(cvt); + case XCtrlNode x -> new ConstantNode(Type.XCONTROL); + case XorNode xor -> xor(xor); + + case LoopNode loop -> new LoopNode(loop); + case RegionNode region -> new RegionNode(region); + default -> throw Utils.TODO(); + }; + } + + public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; } + + public static boolean imm32( long imm ) { return (int)imm==imm; } + + // Attempt a full LEA-style break down. + private Node add(AddNode add) { + Node lhs = add.in(1); + Node rhs = add.in(2); + 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 ) { + long imm = toff.value(); + assert imm!=0; // Folded in peeps + if( (int)imm != imm ) // Full 64bit immediate + return new AddX86(add); + // Now imm <= 32bits + if( lhs instanceof AddNode ladd ) + // ((base + (idx << scale)) + off) + return _lea(add, ladd.in(1), ladd.in(2), (int)imm); + if( lhs instanceof ShlNode shift ) + // (idx << scale) + off; no base + return _lea(add, null, shift, (int)imm); + + // lhs + rhs1 + return new AddIX86(add, (int)imm); + } + return _lea(add, lhs, rhs, 0); + } + + + 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) ) + throw Utils.TODO(); // Bug in canonicalization, should on RHS + if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && + shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) { + idx = shift.in(1); + scale = ((int) tscale.value()); + } + // (base + idx) + off + return off == 0 && scale == 0 + ? new AddX86(add) + : new LeaX86(add, base, idx, scale, off); + } + + + private Node and(AndNode and) { + if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new AndIX86(and, (int)ti.value()); + return new AndX86(and); + } + + private Node call(CallNode call) { + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallX86(call, tfp) + : new CallRX86(call); + } + + // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz". + // Ideal If reading from a setz will skip it and use the "cmp" instead. + private static boolean swap, unsigned; + private Node cmp( BoolNode bool ) { + swap = unsigned = false; + Node cmp = _cmp(bool); + return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned); + } + + private Node _cmp(BoolNode bool) { + // Float variant + if( bool.isFloat() ) { + unsigned = true; + return new CmpFX86(bool); + } + Node lhs = bool.in(1); + Node rhs = bool.in(2); + + // 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) ) + 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()=="!=") ) + return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true ); + + // Vs immediate + if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value(), false); + + // Operand swap compare; Set and Jmp need to swap `bop` + if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value(), swap=true); + + // x vs y + return new CmpX86(bool); + } + + private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) + return new ConstantNode(con); // Default unknown caller inputs + 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 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); + }; + } + + private Node i2f8(ToFloatNode tfn) { + assert tfn.in(1)._type instanceof TypeInteger ti; + return new I2f8X86(tfn); + } + + private Node jmp(IfNode iff) { + // If/Bool combos will match to a Cmp/Set which sets flags. + // 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 = 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))); + return new JmpX86(iff, op); + } + + private Node ld(LoadNode ld) { + return new LoadX86(address(ld), ld.ptr(), idx, off, scale); + } + + private Node mul(MulNode mul) { + if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new MulIX86(mul, (int)ti.value()); + return new MulX86(mul); + } + + private Node or(OrNode or) { + if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new OrIX86(or, (int)ti.value()); + return new OrX86(or); + } + + private Node prj( ProjNode prj ) { + TypeInteger ret = cReturn(prj); + if( ret!=null ) return new ExtendX86(prj,ret); + return new ProjX86(prj); + } + + private Node sar(SarNode sar) { + if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new SarIX86(sar, (int)(ti.value() & 0xff) ); + return new SarX86(sar); + } + + private Node shl(ShlNode shl) { + if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value())) + return new ShlIX86(shl, (int)(ti.value() & 0xff) ); + return new ShlX86(shl); + } + + private Node shr( ShrNode shr ) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) ) + return new ShrIX86(shr, (int)(ti.value() & 0xff) ); + return new ShrX86(shr); + } + + private Node st( StoreNode st ) { + // Look for "*ptr op= val" + Node op = st.val(); + if( op instanceof AddNode ) { + if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val); + if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val); + } + return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val); + } + private static boolean stld_match(StoreNode st, LoadNode ld ) { + return + ld.in(0) == st.in(0) && + ld.mem() == st.mem() && + ld.ptr() == st.ptr() && + ld.off() == st.off(); + } + + + private Node sub (SubNode sub ){ + return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti + ? new AddIX86(sub, (int)-ti.value()) + : new SubX86(sub); + } + + private Node xor (XorNode xor){ + if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) ) + return new XorIX86(xor, (int)ti.value()); + return new XorX86(xor); + } + + // Gather X86 addressing mode bits prior to constructing. This is a + // builder pattern, but saving the bits in a *local* *global* here to keep + // mess contained. + private static int off, scale, imm; + private static Node idx, val; + private <N extends MemOpNode > N address(N mop) { + off = scale = imm = 0; // Reset + idx = val = null; + Node base = mop.ptr(); + // Skip/throw-away a ReadOnly, only used to typecheck + if(base instanceof ReadOnlyNode read) base = read.in(1); + assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived + if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + off = (int) ti.value(); + assert off == ti.value(); // In 32-bit range + idx = add.in(1); + if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon && + shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) { + idx = shift.in(1); + scale = (int) tscale.value(); + } + } else { + if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + off = (int) ti.value(); + assert off == ti.value(); // In 32-bit range + } else { + idx = mop.off(); + } + } + return mop; + } + + private int imm( Node xval ) { + assert val == null && imm == 0; + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { + val = null; + imm = (int) ti.value(); + assert imm == ti.value(); // In 32-bit range + } else { + val = xval; + } + return imm; + } + +} |
