package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; public class AddNode extends ArithNode { @Override boolean allowFloat() { return true; } public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } @Override public Tag serialTag() { return Tag.Add; } @Override public String label() { return "Add"; } @Override public String op() { return "+"; } @Override long doOp( long x, long y ) { return x + y; } @Override double doOp(double x, double y ) { return x + y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Fold ranges like {0-1} + {2-3} into {2-4}. if( !overflow(x._min,y._min) && !overflow(x._max,y._max) ) return TypeInteger.make(x._min+y._min,x._max+y._max); return TypeInteger.BOT; } @Override public Node idealize () { // Settle the one-shot arithmetic mode before applying family-specific // algebraic identities. if( _mode==0 ) return super.idealize(); Node lhs = in(1); Node rhs = in(2); if( rhs instanceof AddNode add && add.err()!=null ) 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.isa(compute()) ) return lhs; // Add of same to a multiply by 2 if( lhs==rhs && _mode==1 ) return new ShlNode(null,lhs,CodeGen.CODE.con(1)); // Goal: a left-spine set of adds, with constants on the rhs (which then fold). // 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),_mode); // 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 copy(copy(lhs,add.in(1)).peephole(), add.in(2)); // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) if( !(lhs instanceof AddNode) ) { // Rotate; look for (add (phi cons) con/(phi cons)) if( spine_cmp(lhs,rhs,this) ) return swap12(); Node n = phiCon(this,true); return n == null ? super.idealize() : n; } // Now we only see (add add non) // Dead data cycle; comes about from dead infinite loops. Do nothing, // the loop will peep as dead after a bit. if( lhs.nIns()>=2 && 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 copy(lhs.in(1),copy(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 copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2)); return super.idealize(); } // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor. // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ? // Push constant up through the phi: x + (phi con0+con0 con1+con1...) static Node phiCon(Node op, boolean rotate) { Node lhs = op.in(1); 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 hi._nid; } @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); } }