summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/node')
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddFNode.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddNode.java42
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AndNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArithFNode.java32
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArithNode.java171
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BoolNode.java115
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java333
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CFGNode.java151
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CProjNode.java28
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndMach.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndNode.java272
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CastNode.java74
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CheckCastMach.java (renamed from src/main/java/com/seaofnodes/simple/node/CastMach.java)7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CheckCastNode.java82
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java44
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConstantNode.java54
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConvertNode.java96
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CtrlNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/DivFNode.java26
-rw-r--r--src/main/java/com/seaofnodes/simple/node/DivNode.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/EscapeNode.java114
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ExternNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FRefNode.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FldOffNode.java50
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunNode.java295
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunPtrNode.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/node/GuardMach.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/GuardNode.java61
-rw-r--r--src/main/java/com/seaofnodes/simple/node/IfNode.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java209
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoopNode.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MachNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java141
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java100
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemPhiNode.java72
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusFNode.java37
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusNode.java61
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ModeNode.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MulFNode.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MulNode.java14
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NeverNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NewNode.java134
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java180
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NotNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/OrNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ParmNode.java49
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PhiNode.java169
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Proj.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ProjNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java40
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RegionNode.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReturnNode.java104
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RoundF32Node.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SarNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java304
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShlNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShrNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SplitNode.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StartCUNode.java33
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StartNode.java51
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StopCUNode.java122
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StopNode.java79
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java231
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StructNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubFNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ToFloatNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/TypeNode.java33
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XCtrlNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XorNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java (renamed from src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java)9
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java135
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java35
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java37
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java32
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java113
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java145
116 files changed, 3913 insertions, 1644 deletions
diff --git a/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/src/main/java/com/seaofnodes/simple/node/AddFNode.java
deleted file mode 100644
index 4ad02dbb..00000000
--- a/src/main/java/com/seaofnodes/simple/node/AddFNode.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class AddFNode extends ArithFNode {
- public AddFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "AddF"; }
-
- @Override public String glabel() { return "+"; }
-
- @Override double doOp( double x, double y ) { return x+y; }
-
- @Override
- public Node idealize() {
- // Add of 0.
- Type t2 = in(2)._type;
- if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
- return in(1);
-
- // Move constants to RHS: con+arg becomes arg+con
- if ( in(1)._type.isConstant() && !t2.isConstant() )
- return swap12();
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java
index c7fd4daa..46607c23 100644
--- a/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -1,16 +1,19 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
-import static com.seaofnodes.simple.Parser.con;
-
public class AddNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Add; }
@Override public String label() { return "Add"; }
@Override public String op() { return "+"; }
- @Override long doOp( long x, long y ) { return x + y; }
+ @Override long doOp( long x, long y ) { return x + y; }
+ @Override double doOp(double x, double y ) { return x + y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
// Fold ranges like {0-1} + {2-3} into {2-4}.
if( !overflow(x._min,y._min) &&
@@ -21,6 +24,11 @@ public class AddNode extends ArithNode {
@Override
public Node idealize () {
+ // Settle the one-shot arithmetic mode before applying family-specific
+ // algebraic identities.
+ if( _mode==0 )
+ return super.idealize();
+
Node lhs = in(1);
Node rhs = in(2);
if( rhs instanceof AddNode add && add.err()!=null )
@@ -33,8 +41,8 @@ public class AddNode extends ArithNode {
return lhs;
// Add of same to a multiply by 2
- if( lhs==rhs )
- return new ShlNode(null,lhs,con(1));
+ if( lhs==rhs && _mode==1 )
+ return new ShlNode(null,lhs,CodeGen.CODE.con(1));
// Goal: a left-spine set of adds, with constants on the rhs (which then fold).
@@ -44,7 +52,7 @@ public class AddNode extends ArithNode {
// x+(-y) becomes x-y
if( rhs instanceof MinusNode minus )
- return new SubNode(lhs,minus.in(1));
+ return new SubNode(lhs,minus.in(1),_mode);
// Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
@@ -52,18 +60,21 @@ public class AddNode extends ArithNode {
// Swap to (x + y) + z
// Rotate (add add add) to remove the add on RHS
if( rhs instanceof AddNode add )
- return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2));
+ return copy(copy(lhs,add.in(1)).peephole(), add.in(2));
// Now we might see (add add non) or (add non non) but never (add non add) nor (add add add)
- if( !(lhs instanceof AddNode) )
+ if( !(lhs instanceof AddNode) ) {
// Rotate; look for (add (phi cons) con/(phi cons))
- return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true);
+ if( spine_cmp(lhs,rhs,this) ) return swap12();
+ Node n = phiCon(this,true);
+ return n == null ? super.idealize() : n;
+ }
// Now we only see (add add non)
// Dead data cycle; comes about from dead infinite loops. Do nothing,
// the loop will peep as dead after a bit.
- if( lhs.in(1) == lhs )
+ if( lhs.nIns()>=2 && lhs.in(1) == lhs )
return null;
// Do we have (x + con1) + con2?
@@ -73,7 +84,7 @@ public class AddNode extends ArithNode {
// because if it later became a constant then we could make this
// transformation.
if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() )
- return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole());
+ return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole());
// Do we have ((x + (phi cons)) + con) ?
@@ -87,13 +98,13 @@ public class AddNode extends ArithNode {
// Do we rotate (x + y) + z
// into (x + z) + y ?
if( spine_cmp(lhs.in(2),rhs,this) )
- return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2));
+ return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2));
return super.idealize();
}
// Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor.
- // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ?
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ?
// Push constant up through the phi: x + (phi con0+con0 con1+con1...)
static Node phiCon(Node op, boolean rotate) {
Node lhs = op.in(1);
@@ -126,7 +137,7 @@ public class AddNode extends ArithNode {
for( int i=1; i<ns.length; i++ )
ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
- Node phi = new PhiNode(label,lphi._minType.meet(op._type),ns).peephole();
+ Node phi = new PhiNode(label,ns).peephole();
// Rotate needs another op, otherwise just the phi
return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
}
@@ -164,6 +175,5 @@ public class AddNode extends ArithNode {
return lo._nid > hi._nid;
}
- @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); }
- @Override Node copyF() { return new AddFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 70b57d6d..083a9a8c 100644
--- a/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class AndNode extends ArithNode {
- public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.And; }
- @Override public String label() { return "And"; }
@Override public String op() { return "&"; }
@Override public String glabel() { return "&"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
deleted file mode 100644
index 7fa428c6..00000000
--- a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import java.util.BitSet;
-
-public abstract class ArithFNode extends Node {
- public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
-
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append(glabel()), visited);
- return sb.append(")");
- }
-
- 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 TypeFloat.TOP;
- if( t1 instanceof TypeFloat f1 &&
- t2 instanceof TypeFloat f2 ) {
- if( f1.isConstant() && f2.isConstant() )
- return TypeFloat.constant(doOp(f1.value(),f2.value()));
- }
- return t1.meet(t2);
- }
-
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index f2beaacb..8c21aef7 100644
--- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -1,16 +1,26 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public abstract class ArithNode extends Node {
+public abstract class ArithNode extends Node implements ModeNode {
// Source location for late reported errors
Parser.Lexer _loc;
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; }
+ public ArithNode( Parser.Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
@Override public String glabel() { return op(); }
abstract String op();
@@ -23,20 +33,93 @@ public abstract class ArithNode extends Node {
abstract long doOp(long x, long y);
abstract TypeInteger doOp(TypeInteger x, TypeInteger y);
+ double doOp(double x, double y) { throw Utils.TODO(); }
+
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
+ static byte mode( Type t1, Type t2 ) {
+ if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2;
+ if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1;
+ if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1;
+ if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1;
+ return 0;
+ }
+
+ // Check that a settled mode agrees with all data inputs. Takes the Node
+ // instead of a varargs list both to avoid allocation and to keep this ready
+ // to become shared mode-aware Node behavior later.
+ static Parser.ParseException nodeErr(Node n, Parser.Lexer loc, String op, byte mode, boolean allowFloat) {
+ Type bad = null;
+ for( int i=1; i<n.nIns(); i++ ) {
+ Type t = n.in(i)._type;
+ if( t.isHigh() )
+ return null;
+ // A weak input is not evidence of a bad mode while the graph is
+ // still being built. Loop Phis commonly await their backedge.
+ if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM )
+ return null;
+ bad = switch( t ) {
+ case TypeInteger ti -> mode==1 ? null : t;
+ case TypeMemPtr ti -> mode==1 ? null : t;
+ case TypeFunPtr ti -> mode==1 ? null : t;
+ case TypeFloat tf -> mode==2 ? null : t;
+ default -> t;
+ };
+ if( bad != null ) break;
+ }
+ boolean unsupportedFloat = false;
+ if( !allowFloat )
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)._type instanceof TypeFloat ) {
+ bad = n.in(i)._type;
+ unsupportedFloat = true;
+ break;
+ }
+ if( bad == null )
+ return null;
+ if( unsupportedFloat || n instanceof BoolNode )
+ return Parser.error("Cannot '"+op+"' "+bad,loc);
+ if( n.nIns()==3 )
+ return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc);
+ return Parser.error("Cannot '"+op+"' "+bad,loc);
+ }
+
+ // Arithmetic subclasses which have a floating-point interpretation override.
+ boolean allowFloat() { return false; }
// Generic airthmetic op math: high returns high; low returns low; 2
// constants fold; only 2 non-constants call specialized math.
@Override
- public final TypeInteger compute() {
+ public final Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
- return TypeInteger.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;
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ TypeFloat.F64.dual();
+ byte mode = _mode==0 ? mode(t1,t2) : _mode;
+ if( mode==0 )
+ return Type.BOTTOM;
+ if( mode==1 ) {
+ if( t1 instanceof TypeInteger x &&
+ t2 instanceof TypeInteger y )
+ return x.isConstant() && y.isConstant()
+ ? con(x,y)
+ : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) );
+ return TypeInteger.BOT;
+ }
+ // FP handling
+ if( t1 instanceof TypeFloat f1 &&
+ t2 instanceof TypeFloat f2 ) {
+ if( f1.isConstant() && f2.isConstant() )
+ return TypeFloat.constant(doOp(f1.value(),f2.value()));
+ return t1.meet(t2);
+ }
+ return TypeFloat.F64;
}
private TypeInteger con( Type t1, Type t2 ) {
@@ -49,36 +132,50 @@ public abstract class ArithNode extends Node {
// push-down peephole cycle because the constants all fold first.
// Returns "Phi(op(con,con),op(x,y))".
- // This check is slightly different from the
-
// Expected to be called after other ideal checks are done.
@Override public Node idealize() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+
+ // Can we decide int vs flt?
+ if( _mode==0 ) {
+ byte mode = mode(t1,t2);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+ if( _mode==2 ) {
+ if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
+ if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
+ }
+
if( in(1) instanceof PhiNode lhs &&
in(2) instanceof PhiNode rhs &&
lhs.nIns() >= 2 && !lhs.inProgress() &&
- lhs.region()==rhs.region() && !(lhs.region() instanceof FunNode) &&
- lhs.nIns()>2 && // A 1-input Phi will collapse already
// Disallow with self-looping phi; these will collapse
- (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) {
- // Profit check: only 1 instance of `this` will remain, all the
- // others will fold to constants.
- int cnt=0;
- for( int i=1; i<lhs.nIns(); i++ )
- if( isCon(lhs,rhs,i) )
- cnt++;
- if( lhs.nIns()-1 - cnt <= 1 ) {
- // Profit! Push up-phi and fold
- Node[] ns = new Node[lhs.nIns()];
- ns[0] = lhs.in(0);
- for( int i=1; i<lhs.nIns(); i++ ) {
- ns[i] = isCon(lhs,rhs,i)
- ? new ConstantNode(con(lhs.in(i)._type, rhs.in(i)._type)).peephole()
- : copy(lhs.in(i), rhs.in(i)).peephole();
+ (lhs.nIns() > 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) {
+ Node r = lhs.region();
+ if( r==rhs.region() && !(r instanceof FunNode) &&
+ // For loops, the backedge *must* fold away, lest we unroll forever
+ (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) {
+ // Profit check: only 1 instance of `this` will remain, all the
+ // others will fold to constants. For loops, *all* must fold away
+ int cnt=0;
+ for( int i=1; i<lhs.nIns(); i++ )
+ if( isCon(lhs,rhs,i) )
+ cnt++;
+ if( lhs.nIns()-1 - cnt <= 1 ) {
+ // Profit! Push up-phi and fold
+ Node[] ns = new Node[lhs.nIns()];
+ ns[0] = lhs.in(0);
+ for( int i=1; i<lhs.nIns(); i++ ) {
+ ns[i] = isCon(lhs,rhs,i)
+ ? ConstantNode.make(con(lhs.in(i)._type, rhs.in(i)._type)).peephole()
+ : copy(lhs.in(i), rhs.in(i)).peephole();
+ }
+ String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label;
+ return new PhiNode(label, ns).peephole();
}
- String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label;
- return new PhiNode(label,lhs._minType,ns).peephole();
}
}
+
return null;
}
@@ -88,14 +185,14 @@ public abstract class ArithNode extends Node {
static boolean overflow( long x, long y ) {
- if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
+ if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow
}
@Override public Parser.ParseException err() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
- if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc);
- if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc);
- return null;
+ return nodeErr(this,_loc,op(),_mode,allowFloat());
}
+
+ @Override public boolean eq( Node n ) { return _mode==((ArithNode)n)._mode; }
+ @Override public int hash() { return _mode; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
index 2765c308..c8722f67 100644
--- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -1,23 +1,29 @@
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.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
import static com.seaofnodes.simple.type.TypeInteger.*;
-abstract public class BoolNode extends Node {
+abstract public class BoolNode extends Node implements ModeNode {
- public BoolNode(Node lhs, Node rhs) {
- super(null, lhs, rhs);
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
+
+ public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
}
abstract public String op(); // String opcode name
@Override
- public String label() { return getClass().getSimpleName(); }
-
- @Override
public String glabel() { return op(); }
@Override
@@ -37,9 +43,17 @@ abstract public class BoolNode extends Node {
if( in(1)==in(2) )
// LT fails, both EQ and LE succeed
return this instanceof LT ? FALSE : TRUE;
- if( t1 instanceof TypeInteger i1 &&
- t2 instanceof TypeInteger i2 )
- return doOp(i1,i2);
+ byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode;
+ if( mode==0 )
+ return BOOL;
+ // Handle integer compares
+ if( mode==1 ) {
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 )
+ return doOp(i1,i2);
+ return BOOL;
+ }
+ // Floating compare. Mixed inputs are converted in idealize().
if( t1 instanceof TypeFloat f1 &&
t2 instanceof TypeFloat f2 &&
f1.isConstant() && f2.isConstant() )
@@ -49,22 +63,39 @@ abstract public class BoolNode extends Node {
TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); }
boolean doOp(double lhs, double rhs) { throw Utils.TODO(); }
- Node copyF(Node lhs, Node rhs) { return null; }
- public boolean isFloat() { return false; }
+ public boolean isFloat() { assert _mode!=0; return _mode==2; }
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
@Override
public Node idealize() {
// Compare of same
if( in(1)==in(2) )
- return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE);
+ return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE);
+ // Can we decide int vs flt?
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( _mode==0 ) {
+ byte mode = ArithNode.mode(t1,t2);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+ if( _mode==2 ) {
+ if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
+ if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
+ }
+
// Equals pushes constant to the right; 5==X becomes X==5.
if( this instanceof EQ ) {
if( !(in(2) instanceof ConstantNode) ) {
// con==noncon becomes noncon==con
if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid )
- // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
- return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1));
+ // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
+ return new EQ(in(2),in(1));
}
// Equals X==0 becomes a !X
if( (in(2)._type == ZERO || in(2)._type == Type.NIL) )
@@ -80,32 +111,44 @@ abstract public class BoolNode extends Node {
return null;
}
+ @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; }
+ @Override public int hash() { return _mode; }
+ @Override public Parser.ParseException err() {
+ return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT));
+ }
+
public static class EQ extends BoolNode {
public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
+ public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
+ @Override public Tag serialTag() { return Tag.EQ; }
public String op() { return "=="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1==i2 && i1.isConstant() ) return TRUE;
if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); }
- Node copyF() { return new EQF(null,null); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; }
+ Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); }
}
public static class NE extends BoolNode {
public NE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
+ @Override public Tag serialTag() { return Tag.NE; }
public String op() { return "!="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1==i2 && i1.isConstant() ) return TRUE;
if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); }
- Node copyF() { throw Utils.TODO(); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; }
+ Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); }
}
public static class LT extends BoolNode {
public LT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
+ @Override public Tag serialTag() { return Tag.LT; }
public String op() { return "<" ; }
public String glabel() { return "&lt;"; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
@@ -113,12 +156,14 @@ abstract public class BoolNode extends Node {
if( i1._min >= i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); }
- Node copyF() { return new LTF(null,null); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; }
+ Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); }
}
public static class LE extends BoolNode {
public LE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
+ @Override public Tag serialTag() { return Tag.LE; }
public String op() { return "<="; }
public String glabel() { return "&lt;="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
@@ -126,32 +171,18 @@ abstract public class BoolNode extends Node {
if( i1._min > i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); }
- Node copyF() { return new LEF(null,null); }
- }
-
- public static class EQF extends EQ {
- public EQF(Node lhs, Node rhs) { super(lhs,rhs); }
- @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; }
- @Override public boolean isFloat() { return true; }
- @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); }
- }
- public static class LTF extends LT {
- public LTF(Node lhs, Node rhs) { super(lhs,rhs); }
- @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; }
- @Override public boolean isFloat() { return true; }
- @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); }
- }
- public static class LEF extends LE {
- public LEF(Node lhs, Node rhs) { super(lhs,rhs); }
- @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; }
- @Override public boolean isFloat() { return true; }
- @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; }
+ Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); }
}
// Unsigned less that, for range checks. Not directly user writable.
public static class ULT extends BoolNode {
- public ULT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public ULT(Node lhs, Node rhs) { super(lhs,rhs,(byte)1); }
+ public ULT(Node lhs, Node rhs, byte mode) {
+ super(lhs,rhs,(byte)1);
+ assert mode==1;
+ }
+ @Override public Tag serialTag() { return Tag.ULT; }
public String op() { return "u<" ; }
public String glabel() { return "u&lt;"; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..cab85e44
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,333 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * A Phi for bulk memory. It covers all aliases except {@link #_aliases};
+ * 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.
+ // Directly visible by design. Empty sets share EMPTY; splitAlias performs
+ // copy-on-first-write and removes the node from GVN before mutation.
+ public BitSet _aliases;
+
+ public BulkMemPhiNode(String label, Node... inputs) {
+ this(label,EMPTY,inputs);
+ }
+
+ private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
+ super(label, inputs);
+ _aliases = aliases;
+ }
+
+ public BulkMemPhiNode(BulkMemPhiNode phi) {
+ super(phi,phi._label);
+ _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone();
+ }
+
+ public BulkMemPhiNode(RegionNode r, Node sample) {
+ super(r,sample);
+ _aliases = EMPTY;
+ }
+
+ public boolean isSplit(int alias) { return _aliases.get(alias); }
+
+ /**
+ * Record that an alias has been split out. The graph rewrite installing
+ * its parallel precise Phi must happen before this semantic change.
+ */
+ public void splitAlias(int alias) {
+ assert alias > 1;
+ if( _aliases.get(alias) ) return;
+ unlock();
+ if( _aliases==EMPTY )
+ _aliases = new BitSet();
+ _aliases.set(alias);
+ }
+
+ @Override public Tag serialTag() { return Tag.BulkMemPhi; }
+
+ @Override
+ public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
+ IdentityHashMap<Node,Integer> anodes) {
+ super.packed(baos,strs,types,anodes);
+ baos.packed2(_aliases.cardinality());
+ for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) )
+ baos.packed2(alias);
+ }
+
+ static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ BitSet bits = new BitSet();
+ for( int i=bais.packed2(); i>0; i-- ) {
+ int alias = bais.packed2();
+ if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias);
+ bits.set(alias);
+ }
+ return new BulkMemPhiNode(label,bits.isEmpty() ? EMPTY : bits,ins);
+ }
+
+ @Override
+ public Type compute() {
+ 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( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+ if( nOuts()==0 ) return null;
+ if( nIns() <= 2 ) return null;
+
+ // "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,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<nOuts(); i++ ) {
+ int alias = outputAlias(out(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 missingAlias(mmm,false)==0;
+ }
+
+ // Return an alias required by an input but still covered by this Phi, or 0.
+ private int inputAlias(Node n) {
+ return switch(n) {
+ case ParmNode p -> { assert p._idx==1; yield 0; }
+ case ScopeNode scope -> 0;
+ case ReturnNode ret -> 0;
+ case ProjNode proj -> 0;
+ case ConstantNode con -> {
+ assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
+ yield 0;
+ }
+ case MemMergeNode mmm -> missingAlias(mmm,false);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // 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.
+ use = addDepForwards(use);
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case ParmNode parm -> 0;
+ case MemMergeNode mmm -> mmm.in(1)==this ? missingAlias(mmm,true) : 0;
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case MemPhiNode phi -> unsplit(phi._alias);
+ case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
+ default -> {
+ assert use instanceof ReturnNode || use instanceof CallNode
+ : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+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) {
+ return alias <= 1 || _aliases.get(alias) ? 0 : 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;
+ }
+
+ // First precise MemMerge slice still covered by this Phi. When inspecting
+ // a user, ignore slots which point back to this Phi.
+ private int missingAlias(MemMergeNode mmm, boolean user) {
+ for( int alias=2; alias<mmm.nIns(); alias++ )
+ if( mmm.in(alias)!=null &&
+ mmm.alias(alias)!=mmm.in(1) &&
+ (!user || mmm.in(alias)!=this) &&
+ !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // Slice out given alias
+ private MemMergeNode slice( int alias ) {
+ assert !_aliases.get(alias);
+ assert !hasDup(alias);
+ // Inputs are the same; MemPhi will sharpen his own inputs
+ MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
+ for( int i=0; i<nIns(); i++ ) mphi.addDef(in(i));
+ Node mem = mphi.peephole();
+ assert ((TypeMem)mem._type)._alias==alias;
+ // The precise Phi is a new alias-sensitive user of every memory
+ // input. Revisit those defs: a predecessor BulkMemPhi may now need
+ // to split this alias even though none of its inputs or types changed.
+ for( int i=1; i<mphi.nIns(); i++ )
+ CodeGen.CODE.add(mphi.in(i));
+
+ 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));
+ Node bulk = bphi.peephole();
+ CodeGen.CODE.add(bulk);
+ CodeGen.CODE.add(mphi);
+ 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(false,null,bulk);
+ for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
+ if( old != alias )
+ mmm.alias(old,precisePhi(old));
+ mmm.alias(alias,precise);
+ assert checkMerge(mmm);
+ mmm.init();
+ // Slicing changes representation, not the set of values in memory.
+ // Newly exposed precise inputs can carry conservative bulk escape
+ // summaries; do not let those invent escapes absent from this Phi.
+ if( _type instanceof TypeMem old && mmm._type instanceof TypeMem mem )
+ mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz,
+ mem._final,old._escFs,old._escAs);
+ return mmm;
+ }
+
+ // Find the precise Phi parallel to this bulk Phi at the same merge.
+ private Node precisePhi(int alias) {
+ Node found = null;
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias ) {
+ assert found==null;
+ found = mphi;
+ }
+ // The parallel Phi may already have collapsed to a common input. Do
+ // not recover it from downstream MemMerges: they are distinct program
+ // points and can legitimately carry different values for this alias.
+ // Reconstruct from this Phi's merge inputs instead. MemPhi selects
+ // the alias from each input MemMerge and collapses when, for example,
+ // the alias is loop-invariant here.
+ if( found==null ) {
+ MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
+ mphi.addDef(region());
+ // Publishing the Phi on the Region makes it visible to recursive
+ // precisePhi lookups before all loop inputs have been installed.
+ // Give that recursion a valid lattice value until peephole computes
+ // the completed precise-memory Phi.
+ mphi.setType(TypeMem.BOT.makeFrom(alias));
+ for( int i=1; i<nIns(); i++ )
+ mphi.addDef(preciseInput(in(i),alias));
+ found = mphi.peephole();
+ // A newly reconstructed precise Phi also creates new users of
+ // its memory inputs. In particular, a BulkMemPhi input may now
+ // need to split this alias, a backwards (user-to-def) change not
+ // covered by processing the new Phi itself.
+ for( int i=1; i<mphi.nIns(); i++ )
+ CodeGen.CODE.add(mphi.in(i));
+ CodeGen.CODE.add(found);
+ }
+ return found;
+ }
+
+ // 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;
+ }
+
+ private boolean hasDup( int alias ) {
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias )
+ return true;
+ return false;
+ }
+
+ @Override public boolean eq(Node n) {
+ return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
+ }
+
+ @Override int hash() { return _aliases.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index b9c70e5f..2b668da8 100644
--- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -1,13 +1,11 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
+
import java.util.BitSet;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.IdentityHashMap;
/** Control Flow Graph Nodes
* <p>
@@ -100,8 +98,10 @@ public abstract class CFGNode extends Node {
// Find nearest enclosing FunNode
public FunNode fun() {
CFGNode cfg = this;
- while( !(cfg instanceof FunNode fun) )
+ while( !(cfg instanceof FunNode fun) ) {
+ if( cfg==null ) return null;
cfg = cfg.idom();
+ }
return fun;
}
@@ -112,13 +112,13 @@ public abstract class CFGNode extends Node {
public LoopTree _ltree;
public int _pre; // Pre-order numbers for loop tree finding
- static class LoopTree {
- LoopTree _par;
- final LoopNode _head;
- int _depth;
+ public static class LoopTree {
+ public LoopTree _par;
+ public final LoopNode _head;
+ public int _depth;
LoopTree(LoopNode head) { _head = head; }
- @Override public String toString() { return "LOOP"+_head._nid; }
- int depth() {
+ @Override public String toString() { return label(_head); }
+ public int depth() {
return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
}
}
@@ -127,73 +127,87 @@ public abstract class CFGNode extends Node {
// Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
// also refer to *their* containing LoopNode, as well as have their depth.
// Start is a LoopNode which contains all at depth 1.
- public void buildLoopTree(StartNode start, StopNode stop) {
- // Unlink all linked calls. This can remove RPC constants which
- // shuffled the StartNode outputs so requires a while loop.
- boolean done=false;
- while(!done) {
- done = true;
- for( Node use : start._outputs )
- if( use instanceof FunNode fun )
- for( Node c : fun._inputs )
- if( c instanceof CallNode call )
- { call.unlink_all(); done=false; }
+ public void buildLoopTree( Ary<FunNode> funs, StopNode stop) {
+ _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
+ Ary<FunNode> work = new Ary<>(FunNode.class);
+
+ BitSet post = new BitSet();
+ int pre = 1;
+ _pre = pre++;
+ for( FunNode fun : funs ) {
+ if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) {
+ work.push(fun);
+ if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) {
+ start._pre = pre++;
+ start._ltree = _ltree;
+ post.set(start._nid);
+ }
+ };
}
- _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ for( int i=0; i<work._len; i++ )
+ pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
}
- int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
+
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
// Pre-walked?
if( _pre!=0 ) return pre;
_pre = pre++;
// 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;
- }
+ if( use instanceof CFGNode usecfg ) {
+ if( skip( usecfg ) ) {
+ if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 );
+ } else
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work );
}
- // 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;
+
+ if( this instanceof ReturnNode ret ) {
+ ret._ltree = stop._ltree;
+ } else {
+ // 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;
}
- // Set selected loop
- if( inner!=null )
- _ltree = inner;
// Tag as post-walked
post.set(_nid);
return pre;
}
- private boolean skip(CFGNode usecfg) {
+ public boolean skip(CFGNode usecfg) {
// Only walk control users that are alive.
// Do not walk from a Call to linked Fun's.
return usecfg instanceof XCtrlNode ||
@@ -201,8 +215,13 @@ public abstract class CFGNode extends Node {
(this instanceof ReturnNode && usecfg instanceof CallEndNode);
}
-
- public String label( CFGNode target ) {
- return (target instanceof LoopNode ? "LOOP" : "L")+target._nid;
+ public static String label( CFGNode target ) {
+ return switch(target) {
+ case StartNode start -> start.label();
+ case FunNode fun -> fun.label();
+ case LoopNode loop -> "LOOP"+target._nid;
+ default -> "L"+target._nid;
+ };
}
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index d18f779b..b74619a3 100644
--- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -1,13 +1,16 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeTuple;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public class CProjNode extends CFGNode {
+public class CProjNode extends CFGNode implements Proj {
// Which slice of the incoming multipart value
public int _idx;
@@ -21,6 +24,14 @@ public class CProjNode extends CFGNode {
_label = label;
}
public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; }
+ @Override public Tag serialTag() { return Tag.CProj; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(_idx);
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs ) {
+ return new CProjNode(null, bais.packed1(), strs[bais.packed2()] );
+ }
@Override public String label() { return _label; }
@@ -40,13 +51,13 @@ public class CProjNode extends CFGNode {
public Node idealize() {
if( ctrl()._type instanceof TypeTuple tt ) {
if( tt._types[_idx]==Type.XCONTROL )
- return Parser.XCTRL; // We are dead
+ return CodeGen.CODE.XCTRL; // We are dead
if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
return ctrl().in(0); // We become our input control
}
// Flip a negating if-test, to remove the not
- if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not )
+ if( ctrl() instanceof IfNode iff && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not )
return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
// Copy of some other input
@@ -65,4 +76,9 @@ public class CProjNode extends CFGNode {
@Override
int hash() { return _idx; }
+ @Override public int idx() { return _idx; }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
index d26ea33b..05707132 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
@@ -14,13 +14,15 @@ public class CallEndMach extends CallEndNode implements MachNode {
_retMask = code._mach.retMask(call().tfp());
// Kill mask is all caller-saves, and any mirror stack slots for args
// in registers.
- RegMaskRW kills = code._callerSave.copy();
+ RegMaskRW kills = (call().external() ? code._cCallerSave : code._callerSave).copy();
// Start of stack slots
int maxReg = code._mach.regs().length;
// Incoming function arg slots, all low numbered in the RA
int fslot = fun()._maxArgSlot;
// Killed slots for this calls outgoing args
- int xslot = code._mach.maxArgSlot(call().tfp());
+ int xslot = call().external()
+ ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv)
+ : code._mach.maxArgSlot(call().tfp());
_xslot = (maxReg+fslot)+xslot;
for( int i=0; i<xslot; i++ )
kills.set((maxReg+fslot)+i);
diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index a7e50c39..8e7f47f3 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -1,9 +1,13 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* CallEnd
@@ -11,16 +15,42 @@ import java.util.BitSet;
public class CallEndNode extends CFGNode implements MultiNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- private boolean _folding;
+ boolean _folding;
public final TypeRPC _rpc;
- public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); }
+ public CallEndNode(CallNode call, TypeRPC rpc) {
+ super(new Node[]{call});
+ _rpc = rpc;
+ }
public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; }
+ public CallEndNode(TypeRPC rpc) {
+ super(new Node[0]);
+ _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc()));
+ }
+ private CallEndNode(int nIns, TypeRPC rpc) {
+ super(new Node[nIns]);
+ _rpc = rpc;
+ }
+ @Override public Tag serialTag() { return Tag.CallEnd; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(nIns());
+ // Linked CallEnds depend on Return types which depend on CallEnds;
+ // break the cycle
+ baos.packed2(types.get(_type));
+ baos.packed2(types.get(_rpc));
+ }
+ static Node make( BAOS bais, Type[] types ) {
+ int nIns = bais.packed1();
+ Type type = types[bais.packed2()];
+ Node cend = new CallEndNode(nIns,(TypeRPC)types[bais.packed2()]);
+ cend._type = type;
+ return cend;
+ }
- @Override public String label() { return "CallEnd"; }
@Override public boolean blockHead() { return true; }
public CallNode call() { return (CallNode)in(0); }
+ boolean folding() { return _folding; }
@Override public CFGNode idom(Node dep) {
// Folding the idom is the one inlining Return
@@ -40,80 +70,100 @@ public class CallEndNode extends CFGNode implements MultiNode {
@Override
public Type compute() {
if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL )
- return TypeTuple.RET.dual();
- // Mid-fold, just take the one single callers' return type
- if( _folding ) return in(1)._type;
+ return TypeTuple.STATE.dual();
// Grab the TFP and use the functions declared return type.
+ // If the call.fptr() is a FRef, return will be BOTTOM.
Type ftype = addDep(call.fptr())._type;
- Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM;
- if( ftype instanceof TypeFunPtr tfp ) {
- ret = tfp.ret();
- // Here, if I can figure out I've found *all* callers, then I can meet
- // across the linked returns and join with the function return type.
- if( 1+tfp.nfcns() == nIns() ) { // A linked function for every concrete function
+ if( !(ftype instanceof TypeFunPtr tfp) )
+ return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE;
+ // Mid-fold, just take the one single callers' return type
+ if( _folding ) {
+ TypeTuple tt = (TypeTuple)in(1)._type;
+ return tt.makeFrom(2,tt.ret().join(tfp._ret));
+ }
+
+ // Here, if I can figure out I've linked *all* callers, then I can meet
+ // across the linked returns and join with the function return type.
+
+ // External functions have an exact FIDX but deliberately no local
+ // FunNode/Return edge. They conservatively crush public memory and
+ // produce their declared return type. A missing non-external target
+ // is still an unresolved/error call.
+ int externs = 0;
+ if( !XInt.isHigh(tfp.fidxs()) )
+ for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) )
+ if( CodeGen.CODE.externFunc(fidx)!=null )
+ externs++;
+ boolean hasExtern = externs != 0;
+
+ // If we have an error-call, e.g. wrong args, then we never link.
+ // Pre-Opto this looks like a missing target fcn, and we assume it will
+ // appear later - meanwhile, we use a conservative approx of memory
+ // effects.
+ if( (nIns()-1)+externs < tfp.nfcns() ) {
+ // If before SCCP, we might call extras or also the unknown target.
+ StartNode start = CodeGen.CODE._start;
+ addDep(start);
+ Type tmem = start._type instanceof TypeTuple tt ? tt._types[1] : (start._type.isHigh() ? TypeMem.TOP : TypeMem.BOT);
+ Type ret = tfp.ret();
+ // If during Opto, assume call won't be called and thus won't return anything.
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
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(Type.CONTROL,tmem,ret);
}
- return TypeTuple.make(call._type,TypeMem.BOT,ret);
+
+ // A linked function for every concrete function
+ TypeTuple state = hasExtern
+ ? TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret())
+ : TypeTuple.STATE.dual();
+ for( int i=1; i<nIns(); i++ )
+ state = (TypeTuple)state.meet(in(i)._type);
+ // At least as good as the TFP
+ return state.makeFrom(2,state.ret().join(tfp.ret()));
}
@Override
public Node idealize() {
- // Trivial inlining: call site calls a single function; single function
- // is only called by this call site.
+ // Worklist-based inlining. Cannot inline if folding, or calling
+ // multiple targets or no CallNode (malformed because dying).
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
+ if( fptr.isConst() && // We have an immediate call
// Function is being called, and its not-null
- fptr._type instanceof TypeFunPtr tfp && tfp.notNull() &&
+ fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && !tfp.isHigh() &&
// Arguments are correct
- call.err()==null ) {
+ call.nargs()==tfp.nargs() && goodArgs(call,tfp) ) {
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.
- CFGNode idom = call;
- while( true ) {
- 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 {
- addDep(idom);
- }
- } else {
- addDep(fun);
+ int isTrivial = trivialInlining( fptr, fun );
+
+ // Encouraged inlining because small size and constructor.
+ int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100;
+ if( isTrivial==1 && fun._approxUIDs < maxSize ) {
+ assert fun.sig().isa(tfp);
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ CodeGen.CODE.add(fun);
+ // Remove the existing function linkage
+ call.unlink_all();
+ // Clone the function body
+ FunNode fun2 = fun.copyBody();
+ // Call uses the unique new function
+ Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole();
+ call.setDef(call.nIns()-1,fptr2);
+ // Link to the new function
+ call.link(fun2);
+ fun = fun2;
+ assert trivialInlining( fptr2, fun2 )==0;
+ isTrivial = 0;
}
- } else { // Function ptr has multiple users (so maybe multiple call sites)
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ if( isTrivial==0 )
+ return doTrivialInlining(fun);
+
+ } else { // Function not (yet) a constant function pointer
addDep(fptr);
}
}
@@ -121,7 +171,105 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ boolean goodArgs( CallNode call, TypeFunPtr tfp ) {
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ Node arg = call.arg(i+2);
+ // The graph's function-pointer type can still contain open forward
+ // references here. Compare against its recursively upgraded form,
+ // so deep mutability requirements participate before inlining.
+ Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
+ if( !arg._type.isa(formal) )
+ { addDep(arg); return false; }
+ }
+ return true;
+ }
+
+
+ // Check for trivial inlining: call only calls fun; fun only called by
+ // call. Returns 0 for trivial, +1 for not-trivial because fptr/fun, and
+ // -1 for not-trivial because idoms.
+ public int trivialInlining( Node fptr, FunNode fun ) {
+ // Heuristic forced inlining off via name
+ if( fun._name != null &&
+ fun._name.endsWith("_noInline") )
+ return -1;
+
+ if( !fun.sig().isa(fptr._type) )
+ return -1; // Stall until these align
+
+ // Disallow self-recursive inlining (loop unrolling by another name).
+ for( int i=1; i < fun.nIns(); i++ )
+ if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun"
+ { addDep(fun); return -1; }
+
+ CFGNode idom = call(), prior = this;
+ while( !(idom instanceof FunNode) ) {
+ if( idom==null ) {
+ addDep(prior);
+ if( prior instanceof RegionNode && !(prior instanceof StartNode) )
+ for( int i=1; i<prior.nIns(); i++ )
+ addDep(prior.in(i));
+ return -1;
+ }
+ prior = idom;
+ idom = idom.idom();
+ }
+
+
+ // If the *inlined* function is mid-collapse, also do not inline (yet)
+ idom = fun.ret();
+ while( idom != fun ) {
+ if( idom==null ) return -1; // Forced off, half-folded call
+ if( idom instanceof FunNode fun2 )
+ { addDep(fun2); return -1; }
+ if( idom instanceof CallEndNode cend && cend._folding )
+ { addDep(cend); return -1; }
+ idom = idom.idom();
+ }
+
+ // Only fun user is this call
+ if( fun.nIns() > 2 ) { addDep(fun); return 1; }
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ assert fun.in(1)==call();
+
+ return 0;
+ }
+
+
+ // Do trivial inlining. Inlining does not need to clone code, merely
+ // triggers folding the Call/Fun and Return/CallEnd away.
+ private Node doTrivialInlining( FunNode fun ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // Add the size heuristic to grow caller, preventing self-recursive
+ // functions from endlessly inlining.
+ fun()._approxUIDs += fun._approxUIDs;
+ // Trivial inline: rewrite
+ _folding = true;
+ // Rewrite Fun so the normal RegionNode ideal collapses
+ fun._folding = true;
+ fun.setDef(1,call().ctrl());// Bypass the Call;
+ fun.ret().setDef(3,null); // Return is folding also
+
+ CodeGen.CODE.addAll(fun._outputs);
+ // Repeat defs 1 layer down, for users of Parm (Phis)
+ for( Node parm : fun._outputs )
+ if( parm instanceof ParmNode )
+ CodeGen.CODE.addAll(parm._outputs);
+
+ // Inlining immediately blows all cache idepth fields past the inline point.
+ // Bump the global version number invalidating them en-masse.
+ CodeGen.CODE.invalidateIDepthCaches();
+ return this;
+ }
+
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
+
+ @Override public Node copy() {
+ return _folding ? super.copy() : new CallEndNode(_rpc);
+ }
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 47dfdf19..f2825650 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -1,12 +1,16 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.XInt;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Call
@@ -18,8 +22,9 @@ public class CallNode extends CFGNode {
public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
public CallNode(CallNode call) { super(call); _loc = call._loc; }
-
- @Override public String label() { return "Call"; }
+ @Override public Tag serialTag() { return Tag.Call; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { return new CallNode(null,new Node[bais.packed1()]); }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
String fname = name();
@@ -33,7 +38,7 @@ public class CallNode extends CFGNode {
}
public String name() {
if( fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- FunNode fun = CodeGen.CODE.link(tfp);
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
if( fun !=null ) return fun._name;
if( fptr() instanceof ExternNode ex ) return ex._extern;
}
@@ -97,14 +102,11 @@ public class CallNode extends CFGNode {
Node progress = null;
if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
// If fidxs is negative, then infinite unknown functions
- long fidxs = tfp.fidxs();
- if( fidxs > 0 ) {
- // Wipe out the return which matching in the linker table
- // Walk the (63 max) bits and link
- for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) {
- int fidx = Long.numberOfTrailingZeros(fidxs);
- TypeFunPtr tfp0 = tfp.makeFrom(fidx);
- FunNode fun = CodeGen.CODE.link(tfp0);
+ int[] fidxs = tfp.fidxs();
+ if( !XInt.isHigh(fidxs) ) {
+ // Walk the bits and link
+ for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) {
+ FunNode fun = CodeGen.CODE.link(fidx);
if( fun!=null && !fun._folding && !linked(fun) )
progress = link(fun);
}
@@ -128,30 +130,43 @@ public class CallNode extends CFGNode {
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));
+ parm.addDef(parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx));
// Call end points to function return
CodeGen.CODE.add(cend()).addDef(fun.ret());
return this;
}
+ // Unlink a single function
+ public void unlink( FunNode fun, int path ) {
+ assert linked(fun);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode )
+ use.delDef(path);
+ fun.delDef(path);
+ CodeGen.CODE.add(fun);
+ CallEndNode cend = cend();
+ ReturnNode ret = fun.ret().keep();
+ cend.delDef(cend._inputs.find(ret));
+ CodeGen.CODE.add(cend);
+ assert !linked(fun);
+ ret.unkeep();
+ }
+
+
// Unlink all linked functions
public void unlink_all() {
for( int i=0; i<_outputs._len; i++ )
if( out(i) instanceof FunNode fun ) {
- assert linked(fun);
- int idx = fun._inputs.find(this);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode )
- use.delDef(idx);
- fun.delDef(idx);
- cend().delDef(cend()._inputs.find(fun.ret()));
- assert !linked(fun);
+ int path = fun._inputs.find(this);
+ unlink(fun,path);
i--;
}
}
@Override
public Parser.ParseException err() {
+ if( fptr()._type == Type.BOTTOM )
+ return null; // Wrong, but failing earlier
if( !(fptr()._type instanceof TypeFunPtr tfp) )
throw Utils.TODO();
if( !tfp.notNull() )
@@ -162,11 +177,13 @@ public class CallNode extends CFGNode {
// Check for args
for( int i=0; i<tfp.nargs(); i++ )
if( !arg(i+2)._type.isa(tfp.arg(i)) ) {
- cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining
- return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
+ // Constructors return private memory and carry hidden self and
+ // selfMem arguments ahead of the source-level arguments.
+ int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0);
+ return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
}
- if( tfp.fidxs() < 0 )
+ if( tfp.isHigh() )
throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
return null;
diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java
deleted file mode 100644
index 6dc8e9fe..00000000
--- a/src/main/java/com/seaofnodes/simple/node/CastNode.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import com.seaofnodes.simple.util.Utils;
-import java.util.BitSet;
-
-// 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 String uniqueName() { return "Cast_" + _nid; }
-
- @Override public boolean isPinned() { return true; }
- @Override public boolean isConst() { return true; }
-
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- return in(1)._print0(sb.append(label()), visited);
- }
-
- @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) : null;
- }
-
- @Override
- public boolean eq(Node n) {
- CastNode cast = (CastNode)n; // Contract
- return _t==cast._t;
- }
-
- @Override
- int hash() { return _t.hashCode(); }
-
- @Override
- public Parser.ParseException err() {
- // Has a condition to test, so OK
- if( in(0) != null ) return null;
- // No condition to test, so this must optimize away
- return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _t.str(), null );
- }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/CastMach.java b/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java
index e45898f5..9943c7b4 100644
--- a/src/main/java/com/seaofnodes/simple/node/CastMach.java
+++ b/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java
@@ -3,17 +3,18 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.Encoding;
import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.SB;
/**
* Cast a pointer to read-only
*/
-public class CastMach extends CastNode implements MachNode {
- public CastMach( CastNode n ) { super(n); }
+public class CheckCastMach extends CheckCastNode implements MachNode {
+ public CheckCastMach( CheckCastNode 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 int twoAddress( ) { return in(1)._type==Type.BOTTOM ? 0 : 1; }
@Override public void encoding( Encoding enc ) { }
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java b/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java
new file mode 100644
index 00000000..900ee70e
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java
@@ -0,0 +1,82 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// 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 CheckCastNode extends TypeNode {
+ public CheckCastNode(Type t, Node ctrl, Node in) { super(t,ctrl, in); }
+
+ public CheckCastNode(CheckCastNode c) {
+ super(c); // Call parent copy constructor
+ setType(compute()); // Ensure type is recomputed
+ }
+ @Override public Tag serialTag() { return Tag.CheckCast; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ }
+ static Node make( BAOS bais, Type[] types) { return new CheckCastNode(types[bais.packed2()], null, null); }
+
+ @Override public String label() { return "("+_con.str()+")"; }
+
+ @Override
+ public String uniqueName() { return "CheckCast_" + _nid; }
+
+ @Override public boolean isPinned() { return in(0)!=null; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()), visited);
+ }
+
+ @Override
+ public Type compute() {
+ // Cast array to int
+ Type t1 = in(1)._type;
+ if( _con == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return _con;
+
+ // If unconditional, it must collapse (or error)
+ if( in(0)==null )
+ return _con;
+ // If conditional, return the join. The guard test ensures
+ // the join is correct.
+ return t1.join(_con);
+ }
+
+ @Override
+ public Node idealize() {
+ return in(1)._type.isa(_con) ? in(1) : null;
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ // Has a condition to test, so OK
+ if( in(0) != null ) return null;
+ if( in(1)._type != Type.BOTTOM )
+ // No condition to test, so this must optimize away
+ return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _con.str(), null );
+ // Only happens for un-initialized refs that must init.
+ // Allow uses of initializing stores, but other uses represent a
+ // partially initialized field, and should error
+ boolean bad = false;
+ StoreNode init=null;
+ for( Node use : _outputs )
+ if( (use instanceof StoreNode st && st._init && !((TypeMemPtr)st.ptr()._type)._obj._name.startsWith("class") ) )
+ init = st;
+ else if( (use instanceof StoreNode st && st.val() instanceof CheckCastNode cast && cast.in(1)._type == Type.BOTTOM) )
+ /*bogus init store in constructor*/;
+ else bad = true;
+ if( !bad ) return null;
+ if( init==null ) return null; // Error somewhere else
+ // Expecting an init store
+ TypeMemPtr ptr = (TypeMemPtr)init.ptr()._type;
+ throw Parser.error("'"+ptr._obj._name+"' is not fully initialized, field '" + init._name + "' is only partially set in the constructor", init._loc);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
index c54da2c4..11a16d28 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -1,11 +1,14 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.type.TypeStruct;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
/**
* A field offset. This becomes a constant *after* other optimizations which
@@ -14,12 +17,13 @@ import java.util.BitSet;
*/
public class ConFldOffNode extends ConstantNode {
- public final String _name; // Struct name
+ public TypeStruct _ts; // Struct holding field
public final String _fname; // Field name
- public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; }
+ public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; }
+ @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
@Override public String label() {
- return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname;
+ return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname;
}
@Override public String glabel() { return label(); }
@Override public String uniqueName() { return "Off_" + _nid; }
@@ -29,15 +33,41 @@ public class ConFldOffNode extends ConstantNode {
return sb.append(label());
}
+ @Override public Node idealize() {
+ // Some array offsets are known early
+ if( _ts.isAry() ) {
+ // Length always at offset zero
+ if( _fname==" len" )
+ return CodeGen.CODE.ZERO;
+ // The shift depends on the element
+ if( _fname=="<<" )
+ return Node.con(_ts.aryScale());
+ // The base depends on the layout (alignment for large elements may
+ // involve padding) and MAY depend on the length if I go for
+ // smaller array lengths.
+ }
+ return null;
+ }
+
// Convert field offset to an integer
- public Node asOffset(TypeStruct ts) {
- return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole();
+ public Node asOffset() {
+ int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname);
+ if( fldx == -1 ) return null;
+ return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole();
}
+ // Upgrade the internal type
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ boolean progress = super._upgradeType(TYPES);
+ TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES);
+ if( ts == _ts ) return progress;
+ _ts = ts;
+ return true;
+ }
@Override public boolean eq(Node n) {
ConFldOffNode off = (ConFldOffNode)n; // Invariant
- return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n);
+ return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n);
}
- @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); }
+ @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
index 22bf03c4..feed8c5c 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -3,8 +3,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* A Constant node represents a constant value.
@@ -16,28 +19,63 @@ import java.util.BitSet;
* The Constant's value is the value stored in it.
*/
-public class ConstantNode extends Node {
- public final Type _con;
+public class ConstantNode extends TypeNode {
public ConstantNode( Type type ) {
- super(new Node[]{CodeGen.CODE._start});
- _con = _type = type;
+ this(type,false);
+ }
+ protected ConstantNode( Type type, boolean raw ) {
+ super(type,new Node[]{CodeGen.CODE._start});
+ assert raw || !(type instanceof TypeFunPtr tfp && tfp.isConstant()) :
+ "Unique function identities require a Return-linked FunPtrNode";
+ _type = type;
}
- public ConstantNode( ConstantNode con ) { super(con); _con = con._type; }
+ public ConstantNode( ConstantNode con ) { super(con); }
+ @Override public Tag serialTag() { return Tag.Con; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ }
+ static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); }
+ // Semantic constant construction through Opto. A unique internal
+ // function identity carries a lifetime edge to the function's Return.
public static Node make( Type type ) {
if( type==Type. CONTROL ) return new CtrlNode();
if( type==Type.XCONTROL ) return new XCtrlNode();
+ if( CodeGen.CODE._phase != null &&
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ return raw(type);
+ if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
+ if( fun != null )
+ return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init();
+ // TODO: A singleton FIDX can also emerge after merging and
+ // guarding several function pointers. Preserve that identity
+ // even if its internal Return is no longer available.
+ return raw(type);
+ }
return new ConstantNode(type);
}
+ // Exact construction for deserialization and graph-incomplete machine
+ // selection. No semantic classification, compute, idealize, or GVN.
+ public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); }
+ public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); }
+
+ // Analysis input created from whole cloth, rather than a first-class
+ // runtime value. In particular, a constructor call descriptor does not
+ // own the constructor function; the class FunPtr does.
+ public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); }
+
@Override public String label() { return "#"+_con; }
@Override public String glabel() { return "#"+_con.gprint(); }
@Override public String uniqueName() { return "Con_" + _nid; }
+ @Override public Node copy() { return raw(this); }
+
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- FunNode fun = CodeGen.CODE.link(tfp);
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
if( fun!=null && fun._name != null )
return sb.append("{ ").append(fun._name).append("}");
}
@@ -47,6 +85,4 @@ public class ConstantNode extends Node {
@Override public boolean isConst() { return true; }
@Override public Type compute() { return _con; }
@Override public Node idealize() { return null; }
- @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; }
- @Override int hash() { return _con.hashCode(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
new file mode 100644
index 00000000..4e8e93ce
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+import java.util.HashMap;
+
+// Convert a value to an authoritative destination type. The destination is
+// known structurally from a declaration; the source family might sharpen later.
+public class ConvertNode extends Node {
+ private Type _dst;
+
+ public ConvertNode(Type dst, Node val) {
+ super(null,val);
+ // Folding this conversion would replace the Return-linked FunPtrNode
+ // with an unhooked ConstantNode.
+ assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode);
+ _dst = dst;
+ }
+
+ public Type dst() { return _dst; }
+ public Node val() { return in(1); }
+
+ @Override public String label() { return "Convert_"+_dst.str(); }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return val()._print0(sb.append(label()).append("("),visited).append(")");
+ }
+
+ // The declaration fixes the result family during pessimistic parsing.
+ // Optimistic SCCP must retain stronger facts already inside that family.
+ @Override public Type compute() {
+ Type src = val()._type;
+ if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) {
+ if( src.isHigh() ) return Type.TOP;
+ if( src.isa(_dst) ) return src;
+ }
+ return _dst;
+ }
+
+ @Override public Node idealize() {
+ Type src = val()._type;
+ if( src==Type.BOTTOM || src==Type.TOP ) return null;
+ if( src.isa(_dst) ) return val();
+
+ // Managed arrays are represented by an object pointer, while C wants
+ // the address of the first element. The representation cast itself is
+ // zero-code; the Add skips the array header.
+ if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]"));
+
+ // Integer (and nil-as-zero) to floating point.
+ if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat )
+ return new ToFloatNode(val());
+
+ // Narrow integers produce the declared sign/zero extension.
+ if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) {
+ if( dst._min==0 )
+ return new AndNode(null,val(),con(dst._max));
+ int shift = Long.numberOfLeadingZeros(dst._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO ) return val();
+ return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep());
+ }
+
+ // Narrow f64 to f32.
+ if( src instanceof TypeFloat && _dst instanceof TypeFloat )
+ return new RoundF32Node(val());
+
+ // Wait for more source information, or report an error after Opto.
+ return null;
+ }
+
+ @Override boolean _upgradeType(HashMap<String,Type> TYPES) {
+ Type dst = _dst.upgradeType(TYPES);
+ if( dst==_dst ) return false;
+ unlock();
+ _dst = dst;
+ return true;
+ }
+
+ @Override public Parser.ParseException err() {
+ if( val()._type==Type.BOTTOM ||
+ val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) {
+ Node out = out(0);
+ if( out instanceof PhiNode phi ) out = phi.out(0);
+ if( out instanceof StoreNode st )
+ return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null );
+ }
+ return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null);
+ }
+
+ @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; }
+ @Override int hash() { return _dst.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
index eda7d684..39c95aa4 100644
--- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -7,6 +7,7 @@ import java.util.BitSet;
public class CtrlNode extends CFGNode {
public CtrlNode() { super(CodeGen.CODE._start); }
+ @Override public Tag serialTag() { throw com.seaofnodes.simple.util.Utils.TODO(); }
@Override public String label() { return "Ctrl"; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); }
@Override public boolean isConst() { return true; }
diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java
deleted file mode 100644
index 0d037a30..00000000
--- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class DivFNode extends ArithFNode {
- public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "DivF"; }
-
- @Override public String glabel() { return "/"; }
-
- @Override double doOp( double x, double y ) { return x/y; }
-
- @Override
- public Node idealize() {
- // Div of constant
- if( in(2)._type instanceof TypeFloat f && f.isConstant() )
- return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole());
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0de1c946..dcc9a27a 100644
--- a/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -2,15 +2,19 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeFloat;
import java.util.BitSet;
public class DivNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Div; }
- @Override public String label() { return "Div"; }
- @Override public String op() { return "//"; }
+ @Override public String op() { return _mode==1 ? "//" : "/"; }
- @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; }
+ @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; }
+ @Override double doOp(double x, double y ) { return x / y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
return TypeInteger.BOT;
}
@@ -20,9 +24,17 @@ public class DivNode extends ArithNode {
// Div of 1.
if( in(2)._type == TypeInteger.TRUE )
return in(1);
+
+ // Div of constant
+ if( _mode==2 ) {
+ if( in(2)._type == TypeFloat.constant(1.) )
+ return in(1);
+ if( in(2)._type instanceof TypeFloat f && f.isConstant() )
+ return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode);
+ }
+
return super.idealize();
}
- @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
- @Override Node copyF() { return new DivFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
new file mode 100644
index 00000000..df654291
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -0,0 +1,114 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * Merge public and private aliases.
+ * 0 - control
+ * 1 - private pointer
+ * 2 - private alias
+ * 3 - public alias
+ */
+public class EscapeNode extends TypeNode {
+
+ public EscapeNode(Field fld, Node self, Node priv, Node pub ) {
+ super(fld,null,self,priv,pub);
+ }
+ public EscapeNode(EscapeNode esc ) {
+ super(esc); // Copy constructor
+ // Except we do not want/need self ptr past Opto.
+ // *NOTE* the weird direct stomp; this is correct because this
+ // constructor does NOT set the backedges, because it is used to
+ // copy-construct an entire graph and ALL edges will get rewritten.
+ _inputs.set(1,null);
+ _con = esc._con;
+ }
+
+ // Pointer to some private (unescaped) memory from a NewNode
+ public Node self() { return in(1); }
+ public Node priv() { return in(2); }
+ public Node pub () { return in(3); }
+ public Field fld() { return (Field)_con; }
+
+ @Override public Tag serialTag() { return Tag.Escape; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con));
+ }
+ static Node make( BAOS bais, Type[] types) {
+ return new EscapeNode((Field)types[bais.packed2()], null,null,null);
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Esc#").append(fld()._alias).append(" {");
+ if( self()==null ) sb.append("---");
+ else self()._print0(sb,visited).append(",");
+ priv()._print0(sb,visited).append("}, ");
+ pub ()._print0(sb,visited);
+ return sb;
+ }
+
+ @Override
+ public Type compute() {
+ if( priv()._type.isHigh() ) return TypeMem.TOP;
+ if( pub ()._type.isHigh() ) return TypeMem.TOP;
+ TypeMem mpriv = (TypeMem)priv()._type;
+ TypeMem mpub = (TypeMem)pub ()._type;
+ // Private memory comes from an allocation or other non-aliased source.
+ // Public memory comes from anywhere, might be bulk memory with a very weak mem type.
+ Field fld = fld();
+ assert mpriv._alias==1 || mpriv._alias==fld._alias;
+ assert mpub ._alias==1 || mpub ._alias==fld._alias;
+ Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1
+ ? ts.at(ts.findAlias(fld._alias))._t
+ : mpriv._t;
+ assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize
+ Type tpub = mpub._t;
+
+ // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1)
+ // and we're no worse than GLB of private
+ Type t = tpriv.meet(tpub).join(fld._t);
+ int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs);
+ int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs);
+ return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t);
+ }
+
+ @Override public Node idealize() {
+ // Sharpen private memory input
+ if( priv() instanceof MemMergeNode mmm ) {
+ setDef(2,mmm.alias(fld()._alias));
+ return this;
+ }
+ // Sharpen public memory input
+ if( pub() instanceof MemMergeNode mmm ) {
+ setDef(3,mmm.alias(fld()._alias));
+ return this;
+ }
+ // New allocation is dead
+ if( self()._type.isHigh() )
+ return pub();
+ return null;
+ }
+
+// Upgrade the internal type
+@Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type t = _con.upgradeType(TYPES);
+ if( t == _con ) return false;
+ unlock(); // Unlock before changing _con
+ _con = t;
+ return true;
+}
+
+ @Override public boolean eq(Node n) {
+ if( fld()._alias!=((EscapeNode)n).fld()._alias )
+ return false;
+ assert _con == ((EscapeNode)n)._con;
+ return true;
+ }
+
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/src/main/java/com/seaofnodes/simple/node/ExternNode.java
index a10bb84a..9768f41f 100644
--- a/src/main/java/com/seaofnodes/simple/node/ExternNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -1,23 +1,40 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
- A constant with external linkage.
+ A constant with external linkage.
*/
public class ExternNode extends ConstantNode {
public final String _extern;
- public ExternNode(Type t, String ex) { super(t); _extern = ex; }
+ // An exact external function identity has no local FunNode/Return to own.
+ // Its lifetime and eventual relocation are carried by the extern symbol
+ // and CodeGen's FIDX-to-extern mapping instead.
+ public ExternNode(Type t, String ex) { super(t,true); _extern = ex; }
+ @Override public Tag serialTag() { return Tag.Extern; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con ));
+ baos.packed2( strs.get(_extern));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ return new ExternNode(types[bais.packed2()], strs[bais.packed2()]);
+ }
@Override public String label() { return "#"+_con+":"+_extern; }
@Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); }
@Override public String uniqueName() { return "Extern_" + _nid; }
@Override public boolean eq(Node n) { return this==n; }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_extern);
+ }
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
index 6093b8a6..723e0007 100644
--- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -9,18 +9,28 @@ import java.util.BitSet;
* A Forward Reference. Its any final constant, including functions. When
* the Def finally appears its plugged into the forward reference, which then
* peepholes to the Def.
+
+ * FRefs can be external references, to other Simple defined values in ELF
+ * files, or external FFI/C libs. If external, a file system lookup needs to
+ * find it in another file, and the name (one time) change to match the found
+ * location.
+ *
+ * Example, a ambiguous name "foo" is looked up externally; it might resolve
+ * to "A.B.foo" or "libc.foo" or the like; the name is changed to match.
*/
public class FRefNode extends ConstantNode {
public static final Type FREF_TYPE = Type.BOTTOM;
- public final Var _n;
- public FRefNode( Var n ) { super(FREF_TYPE); _n = n; }
+ public String _name;
+ public final Parser.Lexer _loc; // Source location
+
+ public FRefNode( String name, Parser.Lexer loc ) { super(FREF_TYPE); _name = name; _loc = loc; }
- @Override public String label() { return "FRef"+_n; }
+ @Override public String label() { return "FRef"+_name; }
@Override public String uniqueName() { return "FRef_" + _nid; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- return sb.append("FRef_").append(_n);
+ return sb.append("FRef_").append(_name);
}
@Override public Node idealize() {
@@ -28,8 +38,8 @@ public class FRefNode extends ConstantNode {
return nIns()==1 ? null : in(1);
}
- public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); }
+ public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); }
@Override public boolean eq(Node n) { return this==n; }
- @Override int hash() { return _n._idx; }
+ @Override int hash() { return _name.hashCode(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
new file mode 100644
index 00000000..3a44f588
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -0,0 +1,50 @@
+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.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * A field offset. This becomes a constant *after* other optimizations which
+ * determine field sizes - some fields might be dead, or hold only zeros or
+ * small values.
+ */
+
+public class FldOffNode extends TypeNode {
+ public final String _fname; // Field name
+ public final Parser.Lexer _loc; //
+ public FldOffNode( Node base, String fname, Parser.Lexer loc ) { super(TypeInteger.BOT,base); _fname = fname; _loc=loc; }
+ @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
+
+ @Override public String label() {
+ return _fname == " len" ? "sizeof()" : "#"+_fname;
+ }
+ @Override public String glabel() { return label(); }
+ @Override public String uniqueName() { return "Off_" + _nid; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override public Node idealize() {
+ // When base() gets typed, this xforms into a ConFldOffNode
+ if( in(0)._type instanceof TypeMemPtr tmp )
+ return new ConFldOffNode(tmp._obj,_fname);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) {
+ FldOffNode off = (FldOffNode)n; // Invariant
+ return _fname.equals(off._fname) && super.eq(n);
+ }
+ @Override int hash() { return super.hash() * _fname.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 2931b9ea..5018f5d6 100644
--- a/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -1,41 +1,68 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.type.TypeTuple;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
public class FunNode extends RegionNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- boolean _folding;
+ public boolean _folding;
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 int _approxUIDs; // Approximate function size, used as a inlining heuristic
+ public CompUnit _compunit; // Defined in source file
+ public final boolean _extern; // External function; no code, name only
+
+ private FunNode( Parser.Lexer loc, Node[] nodes, TypeFunPtr sig, String name, CompUnit compunit, boolean ext ) {
+ super(loc,nodes);
+ _name = name;
+ _sig = sig;
+ _extern = ext;
+ _compunit = compunit;
+ }
+ public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); }
+ public FunNode( TypeFunPtr sig, String name, boolean ext ) {
+ this(null,null,sig,name,null,ext);
+ assert ext; // No need for ext
+ throw Utils.TODO();
+ }
public FunNode( FunNode fun ) {
- super( fun, fun==null ? null : fun._loc );
- if( fun!=null ) {
- _sig = fun.sig();
- _name = fun._name;
- } else {
- _sig = TypeFunPtr.BOT;
- _name = "";
- }
+ super( fun, fun._loc );
+ _sig = fun.sig();
+ _name = fun._name;
+ _compunit = fun._compunit;
+ _extern = fun._extern;
+ }
+ @Override public Tag serialTag() { return Tag.Fun; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) {
+ assert !_folding;
+ baos.packed1(nIns()); // Number of linked calls
+ baos.packed2(types.get(_sig)); // NPE if fails lookup
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_approxUIDs);
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ TypeFunPtr sig = (TypeFunPtr)types[bais.packed2()];
+ String name = strs[bais.packed2()];
+ FunNode fun = new FunNode(null,sig,name,null,ins);
+ fun._approxUIDs = bais.packed2();
+ return fun;
}
- @Override
- public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
+ @Override public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
// Find the one CFG user from Fun. It's not always the Return, but always
// the Return *is* a CFG user of Fun.
@@ -47,13 +74,14 @@ public class FunNode extends RegionNode {
return null;
}
- public ParmNode rpc() {
- ParmNode rpc = null;
+ public ParmNode parm(int idx) {
+ ParmNode pidx = null;
for( Node n : _outputs )
- if( n instanceof ParmNode parm && parm._idx==0 )
- { assert rpc==null; rpc=parm; }
- return rpc;
+ if( n instanceof ParmNode parm && parm._idx==idx )
+ { assert pidx==null; pidx=parm; }
+ return pidx;
}
+ public ParmNode rpc() { return parm(0); }
// Cannot create the Return and Fun at the same time; one has to be first.
// So setting the return requires a second step.
@@ -66,13 +94,29 @@ public class FunNode extends RegionNode {
assert sig.isa(_sig);
if( _sig != sig ) {
CODE.add(this);
+ // Changing the signature can allow more inlining
+ if( _ret != null )
+ for( Node use : ret().outs() )
+ if( use instanceof CallEndNode || use instanceof FunPtrNode )
+ CODE.add(use);
_sig = sig;
+ unlock();
}
}
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES);
+ if( sig == _sig ) return false;
+ unlock();
+ _sig = sig;
+ return true;
+ }
+
public void setName( String name ) {
- if( _name==null ) _name=name;
- else _name += "."+name;
+ if( _name==null )
+ _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1
+ ? name
+ : (_compunit._cname+"."+name).intern();
}
@Override
@@ -81,10 +125,10 @@ public class FunNode extends RegionNode {
if( unknownCallers() )
return Type.CONTROL;
Type t = Type.XCONTROL;
- for (int i = 1; i < nIns(); i++) {
- // Since no unknown callers, we are not main and the Start input
- // will be a Tuple with XControl, so ignore it. Need to be called
- // from someplace other than Start
+ for( int i = 1; i < nIns(); i++) {
+ // Since there are no unknown callers, the Start input is only a
+ // fake hook and will be a Tuple with XControl, so ignore it. Need
+ // to be called from someplace other than Start.
if( !(in(i) instanceof StartNode) )
t = t.meet(in(i)._type);
}
@@ -94,28 +138,37 @@ public class FunNode extends RegionNode {
@Override
public Node idealize() {
- // Some linked path dies, except main never kills Start
+ // Some linked path dies, except unknown caller hooks never kill Start.
Node progress = deadPath(unknownCallers());
if( progress!=null ) {
- if( nIns()==3 && in(2) instanceof CallNode call )
- CODE.add(call.cend()); // If Start and one call, check for inline
+ if( nIns()==2 && in(1) instanceof CallNode call )
+ CODE.add(call.cend()); // If no Start and one call, check for inline
return progress;
}
// Upgrade inferred or user-written return type to actual
- if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) {
+ if( _ret!=null && _ret._type instanceof TypeTuple tt &&
+ tt.ret() != _sig.ret() && tt.ret().isa(_sig.ret()) ) {
setSig(_sig.makeFrom(tt.ret()));
return this;
}
+ // Attempt to get rid of the unknown caller.
+ // - Must be past pessimistic Iter, which discovers and links calls
+ // through class-field loads and other initially imprecise values.
+ // - FIDX is not in the escape set
+ // - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start
+ if( in(1) instanceof StartCUNode start ) {
+ boolean preOpto = CodeGen.CODE._phase == null ||
+ CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) {
+ _compunit._stop.delDef(_compunit._stop._inputs.find(ret()));
+ return removeDeadPath(1);
+ } else {
+ addDep(start);
+ }
+ }
- // When can we assume no callers? Or no other callers (except main)?
- // In a partial compilation, we assume Start gets access to any/all
- // top-level public structures and recursively what they point to.
- // This in turn is valid arguments to every callable function.
- //
- // In a total compilation, we can start from Start and keep things
- // more contained.
// If no default/unknown caller, use the normal RegionNode ideal rules
// to collapse
@@ -134,24 +187,58 @@ public class FunNode extends RegionNode {
}
// Bypass Region idom, always assume depth == 1, one more than Start,
- // unless folding then just a ID on input#1
+ // unless folding
@Override public int idepth() {
- return _folding ? super.idepth() : CodeGen.CODE.iDepthAt(1);
+ if( _folding ) return super.idepth();
+ return CodeGen.CODE.iDepthAt(1);
}
+
// Bypass Region idom, always assume idom is Start
- @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); }
+ @Override public CFGNode idom(Node dep) {
+ return _folding ? cfg(1) : null;
+ }
// Always in-progress until we run out of unknown callers
public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; }
- @Override public boolean inProgress() { return unknownCallers(); }
+ public boolean isModInit( ) {
+ // The one top-level <clinit> with no internal dots:
+ // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not.
+ return isClz() && _name.indexOf('.')==_name.lastIndexOf('.');
+ }
+ public boolean isClz ( ) { return isClz (_name); }
+ public boolean isInit( ) { return isInit(_name); }
+ public boolean isInstance() { return isInstance(_name); }
+ public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); }
+ public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); }
+ public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); }
+
+ // Function is public (callable from Start directly).
+ public boolean isPublic( ) {
+ // Never true for anonymous functions
+ if( _name == null ) return false;
+ // Only public for <clinit> and <init>, all others
+ // are found via field loads. The <init> Start hook also supplies its
+ // conservative pre-Opto construction inputs; StopCU separately keeps
+ // its private-memory return from escaping as a public result.
+ if( !isInit() ) return false;
+ // Private class, has to be found via program text, not linker
+ if( _name.charAt(0)=='_' ) return false;
+
+ // No instances of "...clz1._clz2..." where the class name path
+ // includes a private class.
+ if( _name.indexOf("._") != -1 ) return false;
+
+ // Public class <cl/init>
+ return true;
+ }
- // Add a new function exit point.
- public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
+ @Override public boolean inProgress() {
+ return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ }
- // Build the function body
+ // Build the function body set
public BitSet body() {
-
// Reverse up (stop to start) CFG only, collect bitmap.
BitSet cfgs = new BitSet();
cfgs.set(_nid);
@@ -161,7 +248,13 @@ public class FunNode extends RegionNode {
// If data use bottoms out in wrong CFG, returns false - but tries all outputs.
// If any output hits an in-CFG use (e.g. phi), then keep node.
BitSet body = new BitSet();
- walkDown(this, cfgs, body, new BitSet());
+ BitSet visit = new BitSet();
+ int old;
+ do {
+ old = body.cardinality();
+ visit.clear();
+ walkDown(this, cfgs, body, visit);
+ } while( old != body.cardinality() );
return body;
}
@@ -175,20 +268,113 @@ public class FunNode extends RegionNode {
}
private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
+ if( n==null ) return false;
if( visit.get(n._nid) ) return body.get(n._nid);
visit.set(n._nid);
-
- if( n instanceof CFGNode && !cfgs.get(n._nid) )
+ // Visit self as CFG outside the function
+ if( n instanceof CFGNode && !cfgs.get(n._nid) && unfolded( n ) )
return false;
- if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
+ // Pretend the NewNode is a CFG, so its projections stay with it in loops
+ if( n instanceof NewNode nnn ) cfgs.set(n._nid);
+ // Visit n.cfg() outside of function
+ if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) && unfolded( n.in( 0 ) ) )
return false;
+ // Phis inside the function must have their body flag set BEFORE
+ // recursion walks around a loop and finds them again.
+ if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) )
+ body.set(phi._nid); // Find data cycles
boolean in = n.in(0)!=null || n instanceof CFGNode;
- for( Node use : n._outputs )
+ for( Node use : n._outputs ) {
+ // Will hit a backedge, so 'n' MUST be in the function, and must be
+ // set before the cyclic walk finds 'n' again.
+ if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n )
+ body.set(n._nid); // Set before walk
in |= walkDown(use,cfgs,body,visit);
+ }
if( in ) body.set(n._nid);
return in;
}
+ // A CFG is folding, and so is basically Data
+ private static boolean unfolded( Node cfg) {
+ if( cfg instanceof CallNode call && call.cend().folding() ) return false;
+ if( cfg instanceof CallEndNode cend && cend.folding() ) return false;
+ if( cfg instanceof FunNode fun && fun._folding ) return false;
+ if( cfg instanceof ReturnNode ret && ret._fun._folding ) return false;
+ return true;
+ }
+
+ // Clone function body. Give function a new FIDX.
+ FunNode copyBody() {
+ // Build the function body BitSet
+ BitSet body = body();
+ assert body.cardinality() < 100;
+ // Walk the body, cloning
+ IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
+ BitSet visit = CodeGen.CODE.visit();
+ bodyCopy( visit, body, map, this );
+ visit.clear();
+ bodyEdge( visit, body, map, this );
+ visit.clear();
+ assert map.size()==body.cardinality();
+
+ // New function/return cross-link each other
+ FunNode fun2 = (FunNode )map.get(this);
+ ReturnNode ret2 = (ReturnNode)map.get(_ret);
+ fun2._ret = ret2;
+ ret2._fun = fun2;
+
+ // Remove all callers - this is a private copy
+ while( fun2.nIns() > 1 )
+ if( fun2.in(1) instanceof CallNode call && body.get(call._nid) )
+ call.unlink(fun2,1); // Recursive calls unlink
+ else fun2.removeDeadPath(1); // Start and external calls can just be removed
+
+ // Flip to a new FIDX to avoid confusion with the old one.
+ // This is a non-monotonic (sideways) type move, only applicable
+ // because fun2 is new. The fidx is also a purely local fiction,
+ // since this body will inline and the fidx goes dead.
+ fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline());
+ return fun2;
+ }
+
+ // Clone small function
+ private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
+ if( n==null ) return;
+ if( !body.get(n._nid) ) return; // Not part of the body
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid);
+ Node m = n.copy();
+ CodeGen.CODE.add(m);
+ map.put(n,m);
+ m._type = n._type;
+ for( Node x : n._outputs )
+ bodyCopy(visit,body,map,x);
+ }
+ private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
+ if( n==null ) return;
+ if( !body.get(n._nid) ) return; // Not part of the body
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid);
+ Node m = map.get(n);
+ // The cloned Call should not clone any linked edges.
+ if( n instanceof CallEndNode cend && !cend._folding ) {
+ m.addDef( map.get(cend.call()) );
+ } else {
+ for( Node e : n._inputs ) {
+ Node x = e;
+ if( e != null ) {
+ x = map.get(e);
+ if( x == null ) x = e;
+ }
+ m.addDef(x);
+ }
+ }
+ for( Node x : n._outputs )
+ bodyEdge(visit,body,map,x);
+ }
+
+
// FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able)
@Override public boolean eq( Node n ) {
return _sig == ((FunNode)n)._sig;
@@ -245,4 +431,7 @@ public class FunNode extends RegionNode {
return slotRotate*8;
}
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_name);
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
new file mode 100644
index 00000000..33757640
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Constant function pointer whose type follows the callee signature.
+public class FunPtrNode extends TypeNode {
+ public FunPtrNode(TypeFunPtr tfp, StartNode start, ReturnNode ret) {
+ super(tfp,start,ret);
+ }
+ public FunPtrNode(FunPtrNode fptr) { super(fptr); }
+ @Override public Tag serialTag() { return Tag.FunPtr; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed2(types.get(_con));
+ }
+ static Node make(BAOS bais, Type[] types) {
+ return new FunPtrNode((TypeFunPtr)types[bais.packed2()],null,null);
+ }
+ public ReturnNode ret() { return (ReturnNode)in(1); }
+ public FunNode fun() { return ret().fun(); }
+
+ @Override public String label() { return "#"+_con; }
+ @Override public String glabel() { return "#"+_con.gprint(); }
+ @Override public String uniqueName() { return "FunPtr_" + _nid; }
+ @Override public Node copy() {
+ FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null);
+ fptr._type = _type;
+ return fptr;
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ FunNode fun = CodeGen.CODE.lookupFun(((TypeFunPtr)_con).fidx());
+ return fun!=null && fun._name!=null
+ ? sb.append("{ ").append(fun._name).append("}")
+ : sb.append(_con);
+ }
+
+ @Override public boolean isConst() { return true; }
+ @Override public boolean isPinned() { return true; }
+ @Override public Type compute() {
+ ReturnNode ret = ret();
+ return ret == null ? _con : ret.fun().sig();
+ }
+ @Override public Node idealize() {
+ ReturnNode ret = ret();
+ if( ret==null ) return null;
+ // Function died (never executed), but fcn ptr is alive.
+ // Can be checked for null, or for unequals another FunPtr.
+ FunNode fun = addDep(ret.fun());
+ TypeFunPtr sig = fun.sig();
+ if( sig != _con )
+ { liftType(sig); return this; }
+ // TODO: If the function body dies but this value remains live only as
+ // a null/equality sentinel, preserve the identity without the Return.
+ if( fun.isDead() )
+ { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; }
+ return null;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/src/main/java/com/seaofnodes/simple/node/GuardMach.java
new file mode 100644
index 00000000..202b1ab8
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/GuardMach.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+
+public class GuardMach extends GuardNode implements MachNode {
+ public GuardMach(GuardNode guard) { super(guard); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress() { return in(1)._type==Type.BOTTOM ? 0 : 1; }
+ @Override public void encoding(Encoding enc) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/src/main/java/com/seaofnodes/simple/node/GuardNode.java
new file mode 100644
index 00000000..e3c6f0e2
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/GuardNode.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeScalar;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// A branch-proven refinement to zero/null or non-zero/non-null. The value
+// family is deliberately taken from the input when it becomes known.
+public class GuardNode extends Node {
+ public final boolean _nonZero;
+
+ public GuardNode(boolean nonZero, Node ctrl, Node val) {
+ super(ctrl,val);
+ _nonZero = nonZero;
+ }
+
+ public GuardNode(GuardNode guard) {
+ super(guard);
+ _nonZero = guard._nonZero;
+ }
+
+ @Override public Tag serialTag() { return Tag.Guard; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.write(_nonZero ? 1 : 0);
+ }
+ static Node make(BAOS bais) { return new GuardNode(bais.read()!=0,null,null); }
+
+ @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; }
+ @Override public String uniqueName() { return "Guard_"+_nid; }
+ @Override public boolean isPinned() { return true; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()),visited);
+ }
+
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ if( t==Type.BOTTOM || t==Type.TOP ) return t;
+ // The scalar envelope does not identify a value family. Preserve it
+ // until SCCP learns whether this is an int, float, pointer, or fptr;
+ // choosing generic ptr here can be weaker than a later concrete type.
+ if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t;
+ Type t2 = _nonZero ? t.nonZero() : t.makeZero();
+ return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero
+ }
+
+ @Override public Node idealize() {
+ if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null;
+ if( _type.isHighOrConst() && in(1)._type.isa(_type) )
+ return in(1);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; }
+ @Override int hash() { return _nonZero ? 1 : 0; }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java
index 5d751602..d8d72411 100644
--- a/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -12,9 +12,7 @@ public class IfNode extends CFGNode implements MultiNode {
CodeGen.CODE.add(this); // Because idoms are complex, just add it
}
public IfNode(IfNode iff) { super(iff); }
-
- @Override
- public String label() { return "If"; }
+ @Override public Tag serialTag() { return Tag.If; }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -45,7 +43,7 @@ public class IfNode extends CFGNode implements MultiNode {
// If constant is 0 then false branch is reachable
// Else true branch is reachable
if( t.isConstant() )
- return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
// If adding a zero makes a difference, the predicate must not have a zero/null
if( !t.makeZero().isa(t) )
return TypeTuple.IF_TRUE;
@@ -58,15 +56,14 @@ public class IfNode extends CFGNode implements MultiNode {
// Hunt up the immediate dominator tree. If we find an identical if
// test on either the true or false branch, that side wins.
if( !pred()._type.isHighOrConst() )
- for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) {
- if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) {
+ for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() )
+ if( addDep(dom) instanceof IfNode iff &&
+ prior instanceof CProjNode prj &&
+ (addDep(iff.pred())==pred() ||
+ pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) {
setDef(1,con( prj._idx==0 ? 1 : 0 ));
return this;
}
- if( dom instanceof RegionNode r )
- for( Node c : r._inputs )
- if( c!= null ) addDep(c); // If region loses input, idom changes
- }
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 58dbfc81..f67f2b97 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -2,7 +2,9 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
@@ -20,9 +22,14 @@ public class LoadNode extends MemOpNode {
* @param ptr The ptr to the struct base from where we load a field
* @param off The offset inside the struct base
*/
- public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) {
- super(loc, name, alias, true, glb, mem, ptr, off);
+ public LoadNode(Parser.Lexer loc, String name, int alias, Node ctrl, Node mem, Node ptr, Node off) {
+ super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off);
}
+ LoadNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ super(bais,strs,types,fileAliases,aliases,true);
+ _con = Type.BOTTOM; // Serialized slot retained while MemOps share TypeNode
+ }
+ @Override public Tag serialTag() { return Tag.Load; }
// GraphVis DOT code (must be valid Java identifiers) and debugger labels
@Override public String label() { return "ld_"+mlabel(); }
@@ -34,67 +41,110 @@ public class LoadNode extends MemOpNode {
@Override
public Type compute() {
- Type tmem = mem()._type;
- if( !(tmem instanceof TypeMem mem) )
- return tmem; // No memory yet? Assume TOP/BOT
- assert !_declaredType.isFRef();
- // No lifting if ptr might null-check
- Type tptr = ptr()._type;
- if( err() != null )
- return _declaredType;
- if( !(tptr instanceof TypeMemPtr tmp) )
- return tptr; // No pointer yet? Assume TOP/BOT
+ Type mem0 = aliasMem()._type;
+ Type ptr0 = ptr()._type;
+ // Validate argument types
+ if( ptr0.isHigh() )
+ return TypeScalar.TOP;
+ if( !(mem0 instanceof TypeMem mem) )
+ return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+ if( ptr0 == Type.NIL )
+ return TypeScalar.TOP;
+ if( !(ptr0 instanceof TypeMemPtr ptr) )
+ return mem0.isHigh() ? TypeScalar.TOP : scalar(mem._t);
+
// Load field from object
- Field f = tmp._obj.field(_name);
+ TypeMemPtr tmp = ptr;
+ Field pfld = tmp._obj.field(_name);
// No field? Open objects might yet get the field when falling;
// closed objects with missing field are an error.
- if( f == null )
- return tmp.isHigh() ? Type.TOP : _declaredType;
- Type t = f._t;
+ if( pfld == null )
+ return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+
+ Type t = pfld._t;
// 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(mem._t);
- if( _declaredType.isFinal() )
- t = t.makeRO(); // Deep final applied
- // Pinch between declared type
- t = t.join(_declaredType);
- return t;
+
+ // Now, do the same for memory
+ if( mem._t instanceof TypeStruct ts ) {
+ assert ts._name==tmp._obj._name;
+ Field mfld = ts.field(_name);
+ // Lift from declared type and memory input
+ if( mfld != null )
+ t = t.join(mfld._t);
+ } else if( mem._alias==_alias ) {
+ t = t.join(mem._t);
+ } else if( mem._t == Type.TOP ) {
+ return TypeScalar.TOP;
+ }
+
+ // A deeply read-only base produces a deeply read-only value. The
+ // generic declared field type must not cast this information away.
+ if( ptr0.isFinal() )
+ t = t.makeRO();
+ return scalar(t);
+ }
+
+ // Loads produce ordinary scalar values. Field/memory joins can still
+ // expose the enclosing global lattice bounds through an uninitialized
+ // Type.TOP/BOTTOM field; keep those results inside the scalar envelope.
+ private static Type scalar(Type t) {
+ return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t;
}
@Override
public Node idealize() {
Node ptr = ptr();
- Node mem = mem();
+ Node mem = aliasMem();
+
+ // Loads into structs do not need a ctrl edge, as null-ptr checking is
+ // baked into the type system. Loads into arrays DO need the ctrl
+ // edge, at least until proper range-checking is in place.
+ if( in(0)!=null && ptr()._type instanceof TypeMemPtr tmp &&
+ // Never can be an array
+ (!tmp._obj._open || (tmp._obj._fields.length > 1 && tmp._obj._fields[0]._fname != "#")) ) {
+ setDef(0,null);
+ return this;
+ }
- if( mem instanceof CastNode cast ) {
- setDef(1,cast.in(1));
+ // Forward-ref loads eventually sharpen to a declared type
+ Field fld;
+ if( ptr()._type instanceof TypeMemPtr tmp &&
+ (fld=tmp._obj.field(_name)) != null &&
+ _alias != fld._alias) {
+ assert _alias==1 || _alias == fld._alias;
+ unlock(); // Alias participates in GVN semantics
+ _alias = fld._alias;
+ // Bulk memory may already have inspected this user. Revisit it
+ // once the declaring shape is stable; an open forward reference
+ // can still change this Load's semantics.
+ if( !tmp._obj._open && !tmp._obj._fref )
+ CodeGen.CODE.add(mem());
return this;
}
+ // Must sharpen alias first
+ if( _alias == 1 )
+ return null;
// Simple Load-after-Store on same address.
if( mem instanceof StoreNode st &&
- ptr == st.ptr() && off() == st.off() ) { // Must check same object
+ ptr == st.nnptr() &&
+ off() == st.off() ) { // Must check same object
assert _name.equals(st._name); // Equiv class aliasing is perfect
return extend(st.val());
}
- // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( mem instanceof MemMergeNode mem2 ) {
- Node memA = mem2.alias(_alias);
- for( Node ld : memA._outputs )
+ // Expose the same effective memory input already observed by compute.
+ if( mem != mem() ) {
+ for( Node ld : mem._outputs )
if( ld instanceof LoadNode )
CodeGen.CODE.add(ld);
- setDef(1,memA);
+ if( mem instanceof BulkMemPhiNode ) CodeGen.CODE.add(mem);
+ setDef(1,mem);
return this;
}
- // Simple Load-after-New on same address.
- if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn &&
- ptr == nnn.proj(1) ) // Must check same object
- return zero(nnn); // Load zero from new
-
// Uplift control to a prior dominating load.
for( Node memuse : mem._outputs )
// Find a prior load, has same mem,ptr,off but higher ctrl
@@ -115,8 +165,11 @@ public class LoadNode extends MemOpNode {
return extend(castRO(st.val())); // Proved equal
// Can we prove unequal? Offsets do not overlap?
if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
- !neverAlias(ptr,st.ptr()) ) // And might alias
- break outer; // Cannot tell, stop trying
+ !neverAlias(ptr,st.ptr()) ) { // And might alias
+ addDep( off()); // Offsets can fold, proving unequal
+ addDep(st.off()); // Offsets can fold, proving unequal
+ break outer; // Cannot tell, stop trying
+ }
// Pointers cannot overlap
mem = st.mem(); // Proved never equal
break;
@@ -124,23 +177,32 @@ public class LoadNode extends MemOpNode {
// Assume related
addDep(phi);
break outer;
- case ConstantNode top: break outer; // Assume shortly dead
+ case ConstantNode con:
+ // Load from constant memory
+ if( con._con instanceof TypeMem tmem )
+ return ConstantNode.make(tmem._t);
+ break outer; // Assume shortly dead
case ProjNode mproj: // Memory projection
switch( mproj.in(0) ) {
case NewNode nnn1:
- if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) )
- return zero(nnn1);
- if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) )
- break outer; // Cannot tell, ptr not related to New
- mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New
- break;
+ // Direct load from e.g. new array elements
+ assert _name=="[]";
+ Type decl = declaredType();
+ return decl==Type.BOTTOM ? null : ConstantNode.make(decl.makeZero());
case StartNode start: break outer;
- case CallEndNode cend: break outer; // TODO: Bypass no-alias call
+ case CallEndNode cend: addDep(mproj); break outer; // TODO: Bypass no-alias call
default: throw Utils.TODO();
}
- break;
- case CastNode cast: mem = cast.in(1); break;
+ case CheckCastNode cast: mem = cast.in(1); break;
case MemMergeNode merge: mem = merge.alias(_alias); break;
+ case EscapeNode esc:
+ if( esc.self()==ptr ) // Proved equal
+ { mem = esc.priv(); break; }
+ // Two NewNodes are always unequal
+ if( esc.self().in(0) instanceof NewNode && ptr.in(0) instanceof NewNode )
+ { mem = esc.pub(); break; }
+ // TODO: Can we prove unequal?
+ break outer;
default:
throw Utils.TODO();
@@ -157,6 +219,8 @@ public class LoadNode extends MemOpNode {
if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
// Offset can be hoisted
off() instanceof ConstantNode &&
+ // Not control dependent
+ in(0)==null &&
// Pointer can be hoisted
hoistPtr(ptr,memphi) ) {
@@ -164,25 +228,34 @@ public class LoadNode extends MemOpNode {
if( profit(memphi,2) ||
// Else must not be a loop to count profit on LHS.
(!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
+ // profit() peepholes inputs and can restore a required control
+ // edge. Recheck before manufacturing control-free loads.
+ if( in(0)!=null )
+ return null;
Node ld1 = ld(1);
Node ld2 = ld(2);
- return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2);
+ PhiNode phi = new PhiNode(_name, memphi.region(),ld1,ld2);
+ phi.setType(phi.compute().join(_type));
+ return phi;
}
}
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();
- return castRO(new ConstantNode(zero).peephole());
+ // Semantic memory input for this Load. Compute defines behavior from
+ // this view; ideal merely exposes the same edge in the graph.
+ private Node aliasMem() {
+ Node mem = mem();
+ return _alias != 1 && mem instanceof MemMergeNode merge
+ ? merge.alias(_alias)
+ : mem;
}
private Node ld( int idx ) {
Node mem = mem(), ptr = ptr();
- return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ assert in(0)==null;
+ return new LoadNode(_loc,_name,_alias,null,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
}
private static boolean neverAlias( Node ptr1, Node ptr2 ) {
@@ -215,8 +288,14 @@ public class LoadNode extends MemOpNode {
private boolean profit(PhiNode phi, int idx) {
Node px = phi.in(idx);
if( px==null ) return false;
- if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true;
- if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true;
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() )
+ // To avoid cyclic pushing a Load up then down, getting here means
+ // the load *must* replace with the high/constant.
+ return true;
+ if( px instanceof StoreNode st1 && ptr()==addDep(st1.nnptr() )&& off()==st1.off() )
+ // To avoid cyclic pushing a Load up then down, getting here means
+ // the load *must* match against the Store
+ return true;
addDep(px);
return false;
}
@@ -231,15 +310,25 @@ public class LoadNode extends MemOpNode {
// When a load bypasses a store, the store might truncate bits - and the
// load will need to zero/sign-extend.
private Node extend(Node val) {
- if( !(_declaredType instanceof TypeInteger ti) ) return val;
+ if( !(declaredType() instanceof TypeInteger ti) ) return val;
if( ti._min==0 ) // Unsigned
return new AndNode(null,val,con(ti._max));
// Signed extension
int shift = Long.numberOfLeadingZeros(ti._max)-1;
Node shf = con(shift);
- if( shf._type==TypeInteger.ZERO )
+ if( shf._type==TypeInteger.ZERO ) {
+ // A freshly exposed Store value can still be globally unknown.
+ // Revisit this Load when it settles instead of replacing a typed
+ // Load with the weaker transient value.
+ if( !val._type.isa(_type) ) {
+ addDep(val);
+ return null;
+ }
return val;
+ }
Node shl = new ShlNode(null,val,shf.keep()).peephole();
return new SarNode(null,shl,shf.unkeep());
}
+
+ @Override public int log_size() { return declaredType().log_size(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java
index fc50e17f..44ca35d8 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -3,23 +3,22 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.type.TypeMem;
-import com.seaofnodes.simple.util.Utils;
-import java.util.BitSet;
-import java.util.HashSet;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
public class LoopNode extends RegionNode {
- public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); }
+ public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); }
public LoopNode( LoopNode loop ) { super(loop); }
+ @Override public Tag serialTag() { return Tag.Loop; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { }
public CFGNode entry() { return cfg(1); }
public CFGNode back () { return cfg(2); }
@Override
- public String label() { return "Loop"; }
-
- @Override
public Type compute() {
if( inProgress() ) return Type.CONTROL;
return entry()._type;
@@ -48,7 +47,7 @@ public class LoopNode extends RegionNode {
x = x.idom();
}
// Found a no-exit loop. Insert an exit
- NeverNode iff = (NeverNode)new NeverNode(back()).peephole(); // Ideal never-branch
+ NeverNode iff = (NeverNode)new NeverNode(1.2f,back()).peephole(); // Ideal never-branch
CProjNode t = new CProjNode(iff,0,"True" ).init();
CProjNode f = new CProjNode(iff,1,"False").init();
setDef(2,t); // True continues loop, False (never) exits loop
@@ -58,12 +57,14 @@ public class LoopNode extends RegionNode {
// Now fold control into the exit. Might have 1 valid exit, or an
// XCtrl or a bunch of prior NeverNode exits.
- Node top = new ConstantNode(Type.TOP).peephole();
+ Node top = ConstantNode.make(Type.TOP).peephole();
Node memout = new MemMergeNode(false);
memout.addDef(f); // placeholder for control
+ memout.addDef(top);
for( Node u : _outputs )
if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
memout.addDef(phi);
+ memout.init();
Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
if( ctrl!=null && ctrl._type != Type.XCONTROL ) {
@@ -74,7 +75,7 @@ public class LoopNode extends RegionNode {
// Nope, insert an aligned exit layer
RegionNode r = new RegionNode(_loc,null,ctrl).init();
ctrl = r; r._ltree = stop._ltree;
- mem = new PhiNode(r,mem ).init();
+ mem = new BulkMemPhiNode(r,mem ).init();
expr = new PhiNode(r,expr).init();
}
// Append new Never exit
@@ -89,8 +90,12 @@ public class LoopNode extends RegionNode {
ret.setDef(0,ctrl.unkeep());
ret.setDef(1,mem .unkeep());
ret.setDef(2,expr.unkeep());
- ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr]
- ret.setType(ret.compute());
+ // Force sane types to exit; might locally break monotonicity
+ ret.init();
+ ret.out(0).init();
+ stop.init();
+ CodeGen.CODE.add(CodeGen.CODE._start);
+ CodeGen.CODE.add(mem);
return stop;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java
index c0b3af66..025b5dc4 100644
--- a/src/main/java/com/seaofnodes/simple/node/MachNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MachNode.java
@@ -55,6 +55,6 @@ public interface MachNode {
// General form: "dst=src+src".
default void asm(CodeGen code, SB sb) { throw Utils.TODO(); }
- // Human readable opcode; e.g. "ld4" or "call".
+ // Human-readable opcode; e.g. "ld4" or "call".
default String op() { throw Utils.TODO(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index 9ab57714..a88748b5 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -2,7 +2,10 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.*;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
import java.util.*;
/**
@@ -12,18 +15,21 @@ import java.util.*;
public class MemMergeNode extends Node {
/*
- * In-Progress means this is being used by the Parser to track memory
- * aliases. No optimizations are allowed. When no longer "in progress"
- * normal peeps work.
+ * In-Progress is the mutable construction form. No optimizations are
+ * allowed. If not "in progress" then normal peeps work.
*/
public final boolean _inProgress;
public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; }
+ public MemMergeNode( boolean inProgress, Node ...nodes) { super(nodes); _type = TypeMem.BOT; _inProgress = inProgress; }
public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; }
+ @Override public Tag serialTag() { return Tag.MemMerge; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { Node mem = new MemMergeNode(false); mem.setDefX(bais.packed1()-1,null); return mem; }
- // If being used by a Scope, this is "in progress" from the Parser.
- // Otherwise, it's a memory merge
+ // The mutable construction form is "in progress"; otherwise this is a
+ // graph-semantic memory merge.
boolean inProgress() { return _inProgress; }
@Override public String label() { return "ALLMEM"; }
@@ -61,29 +67,82 @@ public class MemMergeNode extends Node {
@Override public Type compute() {
- for( Node n : _inputs )
- if( n != null && !n._type.isHigh() )
- return TypeMem.BOT;
- return TypeMem.TOP;
+ Type tmem = in(1)._type;
+ if( !(tmem instanceof TypeMem defmem) )
+ // A MemMerge is structurally memory even while its default input
+ // is a weak, not-yet-specialized Phi.
+ return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT;
+ // Is this a single private instance memory?
+ if( defmem._one ) {
+ if( !(defmem._t instanceof TypeStruct ts) )
+ return defmem;
+ // Perfect singleton memory, so all updates are parallel and
+ // independent and stack.
+ for( int i=2; i<nIns(); i++ ) {
+ Node in = in(i);
+ while( true ) {
+ if( in !=null && in._type.isHigh() )
+ return TypeMem.TOP;
+ if( in instanceof MemMergeNode mem ) {
+ in = mem.in(i);
+ } else break;
+ }
+ if( in instanceof StoreNode st ) {
+ Type val = ((TypeMem)st._type)._t;
+ Field old = ts.field(st._name);
+ // Lazy add an expected field for open structs.
+ if( old == null && ts._open ) {
+ old = Field.make(st._name,Type.BOTTOM,st._alias,st._init);
+ ts = ts.add(old);
+ }
+ Field fld = old.makeFrom(val);
+ // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases
+ ts = ts.replace(fld);
+ }
+ }
+ return TypeMem.makePrivate(ts);
+
+ } else {
+ // Default mixed mem is just BOT
+ TypeMem mem = defmem;
+ boolean progress = true;
+ while( progress ) {
+ progress = false;
+ for( int i=2; i<nIns(); i++ ) {
+ // Has this alias escaped already?
+ if( XInt.bit(mem._escAs,i) ) {
+ Node n = in(i);
+ if( n != null && !n._type.isHigh() ) {
+ TypeMem mem2 = (TypeMem)mem.meet(n._type);
+ // More escape aliases
+ if( mem2 != mem )
+ { mem = mem2; progress = true; }
+ }
+ }
+ }
+ }
+ return mem;
+ }
}
@Override public Node idealize() {
if( inProgress() ) return null;
+ if( nIns()==0 ) return null;
// Fold defaults into the default
boolean progress=false, allDefault=true;
for( int i=2; i<nIns(); i++ ) {
- if( in(i) instanceof CastNode cast )
+ if( in(i) instanceof CheckCastNode cast )
{ setDef(i,cast.in(1)); progress=true; }
- if( in(1) == in(i) ) { setDef(i,null); progress=true; }
- else { allDefault=false; }
+ if( in(1) == in(i) && in(1) != null ) { setDef(i,null); progress=true; }
+ else { allDefault=false; }
}
// If not merging any memory (all memory is just the default)
if( allDefault )
return in(1); // Become default memory
- // Collapse stacked all-mem
+ // Collapse stacked merged-mem
if( in(1) instanceof MemMergeNode mem ) {
// Goal is to swap my default mem with mem's default mem
for( int i=2; i<mem.nIns(); i++ ) {
@@ -93,6 +152,7 @@ public class MemMergeNode extends Node {
setDefX(i,mem.in(i));
}
}
+ CodeGen.CODE.add(mem.in(1));
setDef(1,mem.in(1));
return this;
}
@@ -104,13 +164,12 @@ public class MemMergeNode extends Node {
public Node in( Var v ) { return in(v._idx); }
public Node alias( int alias ) {
+ assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) &&
+ (alias >= nIns() || in(alias)==null));
return alias < nIns() && in(alias)!=null ? in(alias) : in(1);
}
- Node alias( int alias, Node st ) {
- while( alias >= nIns() ) addDef(null);
- return setDef(alias,st);
- }
+ public Node alias( int alias, Node st ) { return setDefX(alias,st); }
// Read or update from memory.
// A shared implementation allows us to create lazy phis both during
@@ -119,25 +178,19 @@ public class MemMergeNode extends Node {
// Memory projections are made lazily; if one does not exist
// then it must be START.proj(1)
Node old = alias(alias);
- if( old instanceof ScopeNode loop ) {
- MemMergeNode loopmem = loop.mem();
- Node memdef = loopmem.alias(alias);
- // Lazy phi!
- old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region()
- // Loop already has a real Phi, use it
- ? memdef
- // 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.
- : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() );
- alias(alias,old);
- }
+ assert !(old instanceof ScopeNode); // Parser lazy memory is handled by ScopeNode
// Memory projections are made lazily; expand as needed
return st==null ? old : alias(alias,st); // Not lazy, so this is the answer
}
+ // Remove lazy Phis on all aliases
+ public void removeLazyAll() {
+ for( int i = 1; i < nIns(); i++)
+ _mem(i,null);
+ }
- void _merge( MemMergeNode that, RegionNode r) {
+ public void _merge( MemMergeNode that, RegionNode r) {
+ assert _inProgress;
int len = Math.max(nIns(),that.nIns());
for( int i = 1; i < len; i++)
if( alias(i) != that.alias(i) ) { // No need for redundant Phis
@@ -145,13 +198,33 @@ public class MemMergeNode extends Node {
// by alias as it will trigger a phi creation
Node lhs = this._mem(i,null);
Node rhs = that._mem(i,null);
- alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole());
+ alias(i, new MemPhiNode(Parser.memName(i), i, r, lhs, rhs).peephole());
}
}
- // Fill in the backedge of any inserted Phis
+ // Fill in the backedge of any inserted Phis. Keep precise aliases for
+ // what is known at the loop head. For aliases discovered in the loop
+ // body, gather them into a actual (not inProgress) MemMerge and merge into
+ // the default alias.
void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
+ assert _inProgress && back._inProgress && exit._inProgress;
Node exit_def = exit.alias(1);
+ MemMergeNode more = null; // Dont often need the "more discovered" merge, make it lazy
+ int len = Math.max(nIns(),back.nIns());
+ for( int i=2; i<len; i++ ) {
+ if( !(i < this.nIns() && this.in(i)!=null) && // Alias is not known to loop head
+ (i < back.nIns() && back.in(i)!=null) ) { // Alias IS known to loop back
+ // Gather
+ if( more==null ) {
+ more = new MemMergeNode(false);
+ more.alias(1,back.in(1));
+ back.alias(1,more);
+ }
+ more.alias(i,back.in(i));
+ back.setDef(i,null);
+ }
+ }
+
for( int i=1; i<nIns(); i++ ) {
if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) {
assert phi.in(2)==null;
@@ -164,7 +237,7 @@ public class MemMergeNode extends Node {
// Now one-time do a useless-phi removal
void _useless( ) {
- for( int i=2; i<nIns(); i++ ) {
+ for( int i=1; i<nIns(); i++ ) {
if( in(i) instanceof PhiNode phi ) {
// Do an eager useless-phi removal
Node in = phi.peephole();
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index ead82322..d824bc3a 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -1,11 +1,15 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.lang.StringBuilder;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Convenience common base for Load and Store.
@@ -16,84 +20,95 @@ import java.util.BitSet;
* 3 - Offset, integer types
* 4 - Value, for Stores only
*/
-public abstract class MemOpNode extends Node {
+public abstract class MemOpNode extends TypeNode {
// The equivalence alias class
- public final int _alias;
+ public int _alias;
// True if load-like, false if store-like.
+ // Used on CPU-specific combined memory+arithmetic ops.
//
// Stores produce memory (maybe as part of a tuple with other things),
// loads do not.
//
- // Loads might pick up anti-dependences on prior Stores, and never cause an
+ // Loads might pick up anti-dependencies on prior Stores, and never cause an
// anti-dependence themselves.
//
// Stores must maximally sink to the least dominator use. Loads can be
// opportunistically hoisted.
public final boolean _isLoad;
- // Declared type; not final because it might be a forward-reference
- // which will be lazily improved when the reference is declared.
- public Type _declaredType;
-
- // A debug name, no semantic meaning
+ // Field name.
public final String _name;
// Source location for late reported errors
public final Parser.Lexer _loc;
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) {
- super(null, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off) {
+ super(glb, ctrl, mem, ptr, off);
_name = name;
_alias = alias;
- _declaredType = glb;
_loc = loc;
_isLoad = isLoad;
}
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) {
- this(loc,name, alias, isLoad, glb, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value) {
+ this(loc, name, alias, isLoad, glb, ctrl, mem, ptr, off);
addDef(value);
}
- public MemOpNode( Node mach, MemOpNode mop ) {
- super(mach);
- _name = mop==null ? null : mop._name;
- _alias = mop==null ? 0 : mop._alias;
- _loc = mop==null ? null : mop._loc;
- _isLoad= mop==null ? true : mop._isLoad;
- _declaredType = mop==null ? Type.BOTTOM : mop._declaredType;
- if( mop==null )
- throw Utils.TODO("Load or not");
+ public MemOpNode( Node ideal, MemOpNode mop ) {
+ super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con);
+ _name = mop._name;
+ _alias = mop._alias;
+ _loc = mop._loc;
+ _isLoad= mop._isLoad;
+ }
+
+ MemOpNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases, boolean isLoad ) {
+ this(null,
+ strs[bais.packed2()],
+ mapAlias(bais,fileAliases,aliases),isLoad,
+ types[bais.packed2()],null,null,null,null);
+ }
+
+ private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) {
+ int alias = bais.packed2();
+ return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias);
}
- // 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;
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_alias);
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ assert _isLoad == this instanceof LoadNode; // No machine ops
}
- static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
+ static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); }
String mlabel() { return mlabel(_name); }
public Node mem() { return in(1); }
public Node ptr() { return in(2); }
public Node off() { return in(3); }
+ // Current declared field type. This is deliberately a live lookup:
+ // cyclic/forward types can sharpen by replacing the pointer's struct.
+ public Type declaredType() {
+ if( !(ptr()._type instanceof TypeMemPtr tmp) )
+ return Type.BOTTOM;
+ Field fld = tmp._obj.field(_name);
+ return fld == null || fld._t == null ? Type.BOTTOM : fld._t;
+ }
+
@Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); }
public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); }
- public int log_size() { return _declaredType.log_size(); }
+ public int log_size() { return _con.log_size(); }
@Override
public boolean eq(Node n) {
MemOpNode mem = (MemOpNode)n; // Invariant
- return _alias==mem._alias; // When comparing types error to use "equals"; always use "=="
+ return _alias==mem._alias && super.eq(mem);
}
@Override
- int hash() { return _alias; }
+ int hash() { return _alias ^ super.hash(); }
@Override
public Parser.ParseException err() {
@@ -102,8 +117,15 @@ public abstract class MemOpNode extends Node {
if( ptr == Type.BOTTOM ) return null;
if( ptr.isHigh() ) return null; // Assume it will fall to not-null
// Better be a not-nil TMP
- if( ptr instanceof TypeMemPtr tmp && tmp.notNull() )
- return null;
- return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
+ return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ if( tmp._obj.field(_name)==null &&
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ // Sane field
+ if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ return null;
+
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..e8e2592c
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,72 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/** A Phi for one precise memory alias. */
+public class MemPhiNode extends PhiNode {
+
+ public final int _alias;
+
+ public MemPhiNode(String label, int alias, Node... inputs) {
+ super(label, inputs);
+ assert alias > 1;
+ _alias = alias;
+ }
+
+ public MemPhiNode(MemPhiNode phi) {
+ super(phi,phi._label);
+ _alias = phi._alias;
+ }
+
+ @Override public Tag serialTag() { return Tag.MemPhi; }
+
+ @Override
+ public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
+ IdentityHashMap<Node,Integer> anodes) {
+ super.packed(baos,strs,types,anodes);
+ baos.packed2(_alias);
+ }
+
+ static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ int alias = bais.packed2();
+ if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias);
+ return new MemPhiNode(label,alias,ins);
+ }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ assert BulkMemPhiNode.checkMem(this,_alias);
+ if( !(t instanceof TypeMem tmem) ) return t;
+ assert tmem._alias==1 || tmem._alias==_alias;
+ // This Phi structurally knows its alias, but not whether the field is
+ // final. Do not infer slice finality from evolving input types.
+ return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs);
+ }
+
+ @Override
+ public Node idealize() {
+ // A precise memory Phi can always bypass a MemMerge and select its
+ // matching slice.
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,mem.alias(_alias));
+ return this;
+ }
+ 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/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
deleted file mode 100644
index 5bb0889f..00000000
--- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class MinusFNode extends Node {
- public MinusFNode(Node in) { super(null, in); }
-
- @Override public String label() { return "MinusF"; }
-
- @Override public String glabel() { return "-"; }
-
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("(-"), visited);
- return sb.append(")");
- }
-
- @Override
- public Type compute() {
- if( in(1)._type.isHigh() ) return TypeFloat.TOP;
- if( in(1)._type instanceof TypeFloat i0)
- return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0;
- return TypeFloat.F64;
- }
-
- @Override
- public Node idealize() {
- // -(-x) is x
- if( in(1) instanceof MinusFNode minus )
- return minus.in(1);
-
- return null;
- }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 3ab74625..6101fba2 100644
--- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -1,16 +1,32 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public class MinusNode extends Node {
- public MinusNode(Node in) { super(null, in); }
+public class MinusNode extends Node implements ModeNode {
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
- @Override public String label() { return "Minus"; }
+ public MinusNode(Node in) { super(null, in); }
+ public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
+ @Override public Tag serialTag() { return Tag.Minus; }
@Override public String glabel() { return "-"; }
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -20,11 +36,22 @@ public class MinusNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() ) return TypeInteger.TOP;
- if( in(1)._type instanceof TypeInteger i0 &&
- i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE )
- return TypeInteger.make(-i0._max,-i0._min,i0._widen);
- return TypeInteger.BOT;
+ Type t = in(1)._type;
+ if( t.isHigh() )
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ t instanceof TypeFloat ? t : TypeFloat.F64.dual();
+ if( _mode==0 )
+ return Type.BOTTOM;
+ if( _mode==1 ) {
+ if( t instanceof TypeInteger i0 &&
+ i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE )
+ return TypeInteger.make(-i0._max,-i0._min,i0._widen);
+ return TypeInteger.BOT;
+ }
+ if( t instanceof TypeFloat f )
+ return f.isConstant() ? TypeFloat.constant(-f.value()) : f;
+ return TypeFloat.F64;
}
@Override
@@ -33,7 +60,19 @@ public class MinusNode extends Node {
if( in(1) instanceof MinusNode minus )
return minus.in(1);
+ // Can we decide int vs flt?
+ if( _mode==0 ) {
+ byte mode = (byte)(
+ in(1)._type instanceof TypeInteger ? 1 :
+ in(1)._type instanceof TypeFloat ? 2 : 0);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+
return null;
}
- @Override Node copyF() { return new MinusFNode(null); }
+ @Override public Parser.ParseException err() {
+ return ArithNode.nodeErr(this,null,"-",_mode,true);
+ }
+ @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; }
+ @Override public int hash() { return _mode; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/src/main/java/com/seaofnodes/simple/node/ModeNode.java
new file mode 100644
index 00000000..dc635447
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ModeNode.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node;
+
+/**
+ * A node whose numeric input family is selected once: unknown, integer, or
+ * floating point. Implementors remain ordinary Nodes; this interface only
+ * centralizes the structural mode contract shared by arithmetic, comparisons,
+ * and unary minus.
+ */
+public interface ModeNode {
+ byte mode();
+ Node setMode(byte mode);
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/src/main/java/com/seaofnodes/simple/node/MulFNode.java
deleted file mode 100644
index 5c04af43..00000000
--- a/src/main/java/com/seaofnodes/simple/node/MulFNode.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class MulFNode extends ArithFNode {
- public MulFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "MulF"; }
-
- @Override public String glabel() { return "*"; }
-
- @Override double doOp( double x, double y ) { return x*y; }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
- Type t1 = lhs._type;
- Type t2 = rhs._type;
-
- // Mul of 1. We do not check for (1*x) because this will already
- // canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 )
- return lhs;
-
- // Move constants to RHS: con*arg becomes arg*con
- if ( t1.isConstant() && !t2.isConstant() )
- return swap12();
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java
index debc2c56..e773ed57 100644
--- a/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -1,17 +1,20 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import java.util.BitSet;
public class MulNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public MulNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Mul; }
- @Override public String label() { return "Mul"; }
@Override public String op() { return "*"; }
- @Override long doOp( long x, long y ) { return x * y; }
+ @Override long doOp( long x, long y ) { return x * y; }
+ @Override double doOp(double x, double y ) { return x * y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
return TypeInteger.BOT;
}
@@ -33,7 +36,7 @@ public class MulNode extends ArithNode {
// canonicalize to (x*1)
long c = i.value();
if( c==1 ) return lhs;
- if( c==0 ) return Parser.ZERO;
+ if( c==0 ) return CodeGen.CODE.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
// become machine-specific.
@@ -58,6 +61,5 @@ public class MulNode extends ArithNode {
return super.idealize();
}
- @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); }
- @Override Node copyF() { return new MulFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/src/main/java/com/seaofnodes/simple/node/NeverNode.java
index 516f218b..e2c279d2 100644
--- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -10,7 +10,8 @@ import java.util.BitSet;
// "Never true" for infinite loop exits
public class NeverNode extends IfNode {
public NeverNode(NeverNode ctrl) { super(ctrl); }
- public NeverNode(Node ctrl) { super(ctrl,null); }
+ public NeverNode(float ignore, Node ctrl) { super(ctrl,null); }
+ @Override public Tag serialTag() { return Tag.Never; }
@Override public String label() { return "Never"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java
index 9310aa38..26a9c0fd 100644
--- a/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -2,107 +2,70 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
- * Allocation! Allocate a chunk of memory, and pre-zero it.
- * The inputs include control and size, and ALL aliases being set.
- * The output is large tuple, one for every alias plus the created pointer.
- * New is expected to be followed by projections for every alias.
- */
+ * Allocation! Allocate a chunk of memory, no init.
+ * Takes in ctrl and size, and TypeStruct.
+ * Produces a ptr and private memory.
+*/
public class NewNode extends Node implements MultiNode {
+ public TypeStruct _ts;
+ public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; }
- public final TypeMemPtr _ptr;
- public final int _len;
+ public Node ctrl() { return in(0); }
+ public Node size() { return in(1); }
- public NewNode(TypeMemPtr ptr, Node... nodes) {
- super(nodes);
- assert !ptr.nullable();
- _ptr = ptr;
- _len = ptr._obj._fields.length;
- // Control in slot 0
- assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL;
- // Malloc-length in slot 1
- assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL;
- for( int i=0; i<_len; i++ )
- assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT );
+ // --- Serialization
+ public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; }
+ @Override public Tag serialTag() { return Tag.New; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_ts));
+ }
+ static Node make( BAOS bais, Type[] types) {
+ TypeStruct ts = (TypeStruct)types[bais.packed2()];
+ return new NewNode(ts,null,null);
}
- public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; }
-
- public Node mem (int idx) { return in(idx+2); }
-
- @Override public String label() { return "new_"+glabel(); }
@Override public String glabel() {
- return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str();
+ return _ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str();
}
+ @Override public String label() { return "new_"+glabel(); }
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
sb.append("new ");
- return sb.append(_ptr._obj.str());
+ return sb.append(_ts.str());
}
- int findAlias(int alias) {
- int idx = _ptr._obj.findAlias(alias);
- assert idx!= -1; // Error, caller should be calling
- return idx+2; // Skip ctrl, size
+ @Override public TypeTuple compute() {
+ if( ctrl()._type.isHigh() )
+ return TypeTuple.DEAD_NEW;
+ TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit();
+ return TypeTuple.make(TypeMemPtr.make(_ts),
+ // Arrays get pre-zeroed by constructor.
+ // Objects start with default-zero memory for
+ // fields that can be zero-initialized. This is
+ // still private memory, so no aliases escape yet.
+ TypeMem.make(1,ts,true,false,false,null,null));
}
- // 0 - ctrl
- // 1 - byte size
- // 2-len+2 - aliases, one per field
- // len+2 - 2*len+2 - initial values, one per field
- public Node size() { return in(1); }
+ @Override public Node idealize() { return null; }
- @Override
- public TypeTuple compute() {
- Field[] fs = _ptr._obj._fields;
- Type[] ts = new Type[fs.length+2];
- if( in(0)._type.isHigh() ) {
- ts[0] = Type.XCONTROL;
- ts[1] = _ptr.makeHigh();
- for( int i=0; i<fs.length; i++ )
- ts[i+2] = Type.TOP;
- } else {
- ts[0] = Type.CONTROL;
- ts[1] = _ptr;
- for( int i=0; i<fs.length; i++ ) {
- if( _ptr._obj._fields[i]._one ) {
- // Once-only fields use the declared type
- ts[i+2] = _ptr._obj._fields[i]._t;
- } else {
- // Others take from the inputs
- Type mt = in(i+2)._type;
- TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt;
- //Type tfld = mem._t.meet(mem._t.makeZero());
- Type tfld = mem._t;
- Type tfld2 = tfld.join(fs[i]._t );
- ts[i+2] = TypeMem.make(fs[i]._alias,tfld2);
- }
- }
- }
- return TypeTuple.make(ts);
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts;
}
- @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;
- }
+ // Two NewNodes are never equal; NewNodes always make independent pointers
+ // so they are not functional - they internally bump-ptr allocate always a
+ // new pointer.
+ @Override public boolean eq(Node n) { return this==n; }
- @Override
- public boolean eq(Node n) { return this == n; }
-
- @Override
- int hash() { return _ptr.hashCode(); }
+ @Override int hash() { return _ts.hashCode(); }
// ------------
// MachNode specifics, shared across all CPUs
@@ -111,19 +74,19 @@ public class NewNode extends Node implements MultiNode {
private RegMask _retMask;
private RegMask _kills;
public void cacheRegs(CodeGen code) {
- _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg();
- _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0);
+ _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg();
+ _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv);
// Return mask depends on TFP (either GPR or FPR)
_retMask = code._mach.retMask(TypeFunPtr.CALLOC);
// Kill mask is all caller-saves, and any mirror stack slots for args
// in registers.
- RegMaskRW kills = code._callerSave.copy();
+ RegMaskRW kills = code._cCallerSave.copy();
// Start of stack slots
int maxReg = code._mach.regs().length;
// Incoming function arg slots, all low numbered in the RA
int fslot = cfg0().fun()._maxArgSlot;
// Killed slots for this calls outgoing args
- int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC);
+ int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv);
_xslot = (maxReg+fslot)+xslot;
for( int i=0; i<xslot; i++ )
kills.set((maxReg+fslot)+i);
@@ -132,9 +95,10 @@ public class NewNode extends Node implements MultiNode {
public String op() { return "alloc"; }
public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
public RegMask outregmap() { return null; }
- public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; }
+ public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; }
public RegMask killmap() { return _kills; }
public void asm(CodeGen code, SB sb) {
- sb.p("#calloc, ").p(code.reg(size()));
+ sb.p("#malloc, ").p(code.reg(size()));
}
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java
index 458a3407..1ce8382a 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -2,14 +2,12 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.print.IRPrinter;
import com.seaofnodes.simple.print.JSViewer;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
import java.util.*;
import java.util.function.Function;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
@@ -78,8 +76,83 @@ public abstract class Node implements Cloneable {
_hash = 0;
}
+ // Disk/serialized opcode tags
+ public enum Tag {
+ Add,And,EQ,NE,LT,LE,ULT,
+ CallEnd,Call,CheckCast,ConFldOff,Con,CProj,
+ Div,Escape,Extern,Fun,FunPtr,If,Load,Loop,
+ MemMerge,Minus,Mul,
+ New,Never,Not,Or,Parm,Phi,Proj,
+ ReadOnly,Return,Region,RoundF32,
+ Sar,Shl,Shr,Start,Stop,Store,Struct,Sub,ToFloat,
+ XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt;
+ public static final Tag[] VALS = values();
+ public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ return switch(this) {
+ case Add -> new AddNode(null,null,(byte)bais.packed1());
+ case And -> { int mode=bais.packed1(); assert mode==1; yield new AndNode(null,null,null); }
+ case Div -> new DivNode(null,null,(byte)bais.packed1());
+ case If -> new IfNode(null,null);
+ case Loop -> new LoopNode(null,null,null);
+ case Minus -> new MinusNode(null,(byte)bais.packed1());
+ case Mul -> new MulNode(null,null,(byte)bais.packed1());
+ case Never -> new NeverNode(1.2f, null);
+ case Not -> new NotNode(null);
+ case Or -> { int mode=bais.packed1(); assert mode==1; yield new OrNode(null,null,null); }
+ case ReadOnly-> new ReadOnlyNode((Node)null);
+ case Return ->new ReturnNode(null,null,null,null,null);
+ case RoundF32-> new RoundF32Node(null);
+ case PtrToInt-> new PtrToIntNode((Node)null);
+ case Sar -> { int mode=bais.packed1(); assert mode==1; yield new SarNode(null,null,null); }
+ case Shl -> { int mode=bais.packed1(); assert mode==1; yield new ShlNode(null,null,null); }
+ case Shr -> { int mode=bais.packed1(); assert mode==1; yield new ShrNode(null,null,null); }
+ case Start -> new StartNode(null,null,TypeInteger.BOT);
+ case Sub -> new SubNode(null,null,(byte)bais.packed1());
+ case ToFloat-> new ToFloatNode(null);
+ case XCtrl -> new XCtrlNode();
+ case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); }
+ case Guard -> GuardNode.make(bais);
+
+ case EQ -> new BoolNode.EQ (null,null,(byte)bais.packed1());
+ case NE -> new BoolNode.NE (null,null,(byte)bais.packed1());
+ case LT -> new BoolNode.LT (null,null,(byte)bais.packed1());
+ case LE -> new BoolNode.LE (null,null,(byte)bais.packed1());
+ case ULT -> new BoolNode.ULT(null,null,(byte)bais.packed1());
+
+ case Call -> CallNode.make(bais);
+ case CallEnd-> CallEndNode.make(bais ,types);
+ case CheckCast -> CheckCastNode.make(bais,types);
+ case CProj -> CProjNode.make(bais,strs);
+ case Con -> ConstantNode.make(bais ,types);
+ case Escape-> EscapeNode.make(bais ,types);
+ case Extern-> ExternNode.make(bais,strs,types);
+ case FunPtr-> FunPtrNode.make(bais, types);
+ case Fun -> FunNode.make(bais,strs,types);
+ case Load -> new LoadNode(bais,strs,types,fileAliases,aliases);
+ case MemMerge->MemMergeNode.make(bais);
+ case MemPhi-> MemPhiNode.make(bais,strs,types,fileAliases,aliases);
+ case BulkMemPhi-> BulkMemPhiNode.make(bais,strs,types,fileAliases,aliases);
+ case New -> NewNode.make(bais ,types);
+ case Parm -> ParmNode.make(bais,strs,types);
+ case Phi -> PhiNode.make(bais,strs,types);
+ case Proj -> ProjNode.make(bais,strs);
+ case Store -> new StoreNode(bais,strs,types,fileAliases,aliases);
+ case Stop -> StopNode.make(bais);
+ case StartCU-> StartCUNode.make(bais,strs,types);
+ case StopCU -> StopCUNode.make(bais);
+ case Struct-> StructNode.make(bais, types);
+ case Region-> RegionNode.make(bais);
+
+ default -> throw Utils.TODO();
+ };
+ }
+ };
+ public Tag serialTag() { throw Utils.TODO(); }
+ // Serialize extra data, including input counts
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {}
+
// Easy reading label for debugger, e.g. "Add" or "Region" or "EQ"
- public abstract String label();
+ public String label() { return serialTag().toString(); }
// Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99"
public String uniqueName() {
@@ -115,7 +188,7 @@ public abstract class Node implements Cloneable {
// This is the common print: check for repeats, check for DEAD and print
// "DEAD" else call the per-Node print1.
public final StringBuilder _print0(StringBuilder sb, BitSet visited) {
- if (visited.get(_nid) && !(this instanceof ConstantNode) )
+ if( visited.get(_nid) && !(this instanceof ConstantNode) )
return sb.append(label());
visited.set(_nid);
return isDead()
@@ -229,7 +302,8 @@ public abstract class Node implements Cloneable {
}
// Breaks the edge invariants, used temporarily
- protected <N extends Node> void addUse(N n) { _outputs.add(n); }
+ @SuppressWarnings("unchecked")
+ protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
// Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
// Return true if the output list is empty afterward.
@@ -250,6 +324,12 @@ public abstract class Node implements Cloneable {
old_def.kill(); // Kill old def
}
}
+ public Node removeLast() {
+ Node old_def = _inputs.pop();
+ if( old_def != null )
+ old_def.delUse(this);
+ return old_def;
+ }
/**
* Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
@@ -284,11 +364,13 @@ public abstract class Node implements Cloneable {
// Shortcuts to stop DCE mid-parse
// Add bogus null use to keep node alive
- @SuppressWarnings("unchecked")
- public <N extends Node> N keep() { addUse(null); return (N)this; }
+ public <N extends Node> N keep() { return addUse(null); }
// Remove bogus null.
@SuppressWarnings("unchecked")
- public <N extends Node> N unkeep() { delUse(null); return (N)this; }
+ public <N extends Node> N unkeep() {
+ delUse(null);
+ return (N)this;
+ }
// Test "keep" status
public boolean iskeep() { return _outputs.find(null) != -1; }
public void unkill() {
@@ -415,7 +497,15 @@ public abstract class Node implements Cloneable {
// 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() )
+ if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
+ // A high Load through a bad pointer still owns the source error;
+ // do not replace it with an unlocated constant before typeCheck.
+ !(this instanceof LoadNode && err()!=null) &&
+ // A CallEnd can be temporarily high while optimistic call-graph
+ // linking is still discovering its target Returns. Its result
+ // projections must remain attached so they can sharpen later.
+ !(this instanceof ProjNode proj && proj.in(0) instanceof CallEndNode && _type.isHigh()) &&
+ _type.isHighOrConst() )
return ConstantNode.make(_type).peephole();
// Global Value Numbering
@@ -483,7 +573,8 @@ public abstract class Node implements Cloneable {
// If changing, add users to worklist.
public Type setType(Type type) {
Type old = _type;
- assert old == null || type.isa(old) : "Monotonicity test failed";
+ assert old == null || type.isa(old) : "Monotonicity test failed: "+this+"#"+_nid+" old="+old+" new="+type;
+ //assert !type.hasClosedBuilder0(); // New types should not have closed builders
if( old == type ) return old;
_type = type; // Set _type late for easier assert debugging
CODE.addAll(_outputs);
@@ -549,19 +640,30 @@ public abstract class Node implements Cloneable {
// revisit them if `this` changes.
Ary<Node> _deps;
+ private boolean addDepImpl( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return false;
+ if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
+ if( dep._deps .find(this) != -1 ) return false; // Already on list
+ if( dep._outputs.find(this) != -1 ) return false;
+ return true;
+ }
+
/**
* Add a node to the list of dependencies. Only add it if it's not an input
* or output of this node, that is, it is at least one step away. The node
* being added must benefit from this node being peepholed.
*/
- <N extends Node> N addDep( N dep ) {
- // Running peepholes during the big assert cannot have side effects
- // like adding dependencies.
- if( CODE._midAssert ) return dep;
- if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
- if( dep._deps .find(this) != -1 ) return dep; // Already on list
+ public <N extends Node> N addDep( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
- if( dep._outputs.find(this) != -1 ) return dep;
+ dep._deps.add(this);
+ return dep;
+ }
+
+ public <N extends Node> N addDepForwards( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
dep._deps.add(this);
return dep;
}
@@ -574,6 +676,7 @@ public abstract class Node implements Cloneable {
_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 ) {
@@ -630,27 +733,6 @@ public abstract class Node implements Cloneable {
/** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */
public boolean isPinned() { 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
@@ -699,7 +781,7 @@ public abstract class Node implements Cloneable {
public Parser.ParseException err() { return null; }
// Common integer constants
- public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); }
+ public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(TypeInteger.constant(x)).peephole(); }
// Utility to walk the entire graph applying a function; return the first
// not-null result.
@@ -720,6 +802,20 @@ public abstract class Node implements Cloneable {
return null;
}
+ public void gather(HashMap<String,Integer> strs ) { }
+
+ // If a Type upgrade happens, put on worklist
+ boolean _upgradeType( HashMap<String,Type> TYPES ) { return false; }
+ public final void upgradeType( HashMap<String,Type> TYPES ) {
+ Type old = _type; _type = _type.upgradeType(TYPES);
+ boolean progress = _upgradeType(TYPES);
+ if( progress || old != _type ) {
+ CODE.add(this);
+ CODE.addAll(_outputs);
+ if( _deps!=null ) CODE.addAll(_deps);
+ }
+ }
+
/**
* Debugging utility to find a Node by index
*/
diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java
index 4b80e61c..22b5bffe 100644
--- a/src/main/java/com/seaofnodes/simple/node/NotNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -6,8 +6,7 @@ import java.util.BitSet;
public class NotNode extends Node {
public NotNode(Node in) { super(null, in); }
-
- @Override public String label() { return "Not"; }
+ @Override public Tag serialTag() { return Tag.Not; }
@Override public String glabel() { return "!"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java
index ac0d8454..ba250c26 100644
--- a/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -5,7 +5,8 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class OrNode extends ArithNode {
- public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Or; }
@Override public String label() { return "Or"; }
@Override public String op() { return "|"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 880f936c..6488b827 100644
--- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -1,30 +1,59 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
public class ParmNode extends PhiNode {
// Argument indices are mapped one-to-one on CallNode inputs
public final int _idx; // Argument index
+ public Type _declaredType;
public ParmNode(String label, int idx, Type declaredType, Node... inputs) {
- super(label,declaredType,inputs);
+ super(label, inputs);
_idx = idx;
+ _declaredType = declaredType;
+ _type = declaredType;
+ }
+ public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; }
+ @Override public Tag serialTag() { return Tag.Parm; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(nIns()); // Number of linked calls
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ baos.packed2(types.get(_declaredType)); // NPE if fails lookup
+ baos.packed1(_idx);
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ Type minType = types[bais.packed2()];
+ return new ParmNode(label, bais.packed1(), minType, ins);
}
- public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; }
@Override public String label() { return MemOpNode.mlabel(_label); }
@Override public String glabel() { return _label; }
+ @Override protected Type declaredType() { return _declaredType; }
+ @Override protected Type constrain(Type t) { return t.join(_declaredType); }
+
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type t = _declaredType.upgradeType(TYPES);
+ if( t == _declaredType ) return false;
+ unlock();
+ _declaredType = t;
+ return true;
+ }
+
public FunNode fun() { return (FunNode)in(0); }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- if( "main".equals(fun()._name) && _label.equals("arg") )
+ if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
return sb.append("arg");
sb.append("Parm_").append(_label).append("(");
for( Node in : _inputs ) {
@@ -48,14 +77,6 @@ public class ParmNode extends PhiNode {
// If function is folding, do all possible peeps
if( fun()._folding ) return super.idealize();
- // Can upgrade minType even while in-progress
- if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
- TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
- if( tmp2!=null && tmp2 != _minType ) {
- _minType = tmp2;
- return this;
- }
- }
// Skip most phi optimizations on parms
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index bdc0d0fa..2c57eb8b 100644
--- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -1,39 +1,51 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
public class PhiNode extends Node {
public final String _label;
- // The Phi type we compute must stay within the domain of the Phi. Example
- // Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
- Type _minType;
-
- int lattice_drop;
-
- public PhiNode(String label, Type minType, Node... inputs) {
+ public PhiNode( String label, Node... inputs) {
super(inputs);
_label = label;
- assert minType!=null;
- _minType = minType;
+ }
+ public static PhiNode make(String label, Type minType, Node... inputs) {
+ return minType instanceof TypeMem mem
+ ? mem._alias==1
+ ? new BulkMemPhiNode(label,inputs)
+ : new MemPhiNode(label,mem._alias,inputs)
+ : new PhiNode(label, inputs);
}
// Used by ParmNode
- public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; }
+ public PhiNode(PhiNode phi, String label) { super(phi); _label = label; }
// Used by instruction Selection
- public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); }
+ public PhiNode(PhiNode phi) { this(phi,phi._label); }
// Used by the infinite-loop exit breaker
public PhiNode(RegionNode r, Node sample) {
super(new Node[]{r});
_label = "";
- _minType = sample._type;
+ _type = sample._type;
while( nIns() < r.nIns() )
addDef(sample);
}
+ @Override public Tag serialTag() { return Tag.Phi; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(nIns());
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ return new PhiNode(strs[bais.packed2()], ins);
+ }
@Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
@@ -55,7 +67,7 @@ public class PhiNode extends Node {
}
public CFGNode region() { return (CFGNode)in(0); }
- @Override public boolean isMem() { return _minType instanceof TypeMem; }
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
@Override public boolean isPinned() { return true; }
boolean isRPC() { return false; }
@@ -65,14 +77,10 @@ public class PhiNode extends Node {
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() )
- // Loop-Phis must lift to the declared type, because that is how
- // the Parser keeps precise types until the loop finishes parsing.
- // Similar, ParmNodes use precise minType until all calls are
- // linked (post opto).
- return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : Type.BOTTOM;
+ return declaredType();
// Set type to local top of the starting type
Type t = Type.TOP;
- for (int i = 1; i < nIns(); i++) {
+ for( int i = 1; i < nIns(); i++ ) {
// If the region's control input is live, add this as a dependency
// to the control because we can be peeped should it become dead.
Type ctrl = addDep(r.in(i))._type;
@@ -82,7 +90,8 @@ public class PhiNode extends Node {
t = t.meet(in(i)._type);
}
}
- Type newt = t.join( _minType );
+ Type declared = declaredType();
+ Type newt = constrain(t);
// phi loop widening part
if( region() instanceof LoopNode && // Only around loops
@@ -90,60 +99,45 @@ public class PhiNode extends Node {
// Types changed and are falling (the optimistic case, expected to fall forever)
newi != _type ) {
if( !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) )
- return newi.same_but_slightly_wider_than(_minType);
+ return newi.same_but_slightly_wider_than(
+ declared instanceof TypeInteger ? declared : TypeInteger.BOT);
}
return newt;
}
+ // Ordinary Phis have no parser-supplied type constraint.
+ protected Type declaredType() { return Type.BOTTOM; }
+ protected Type constrain(Type t) { return t; }
+
@Override
public Node idealize() {
if( !(region() instanceof RegionNode r ) )
return in(1); // Input has collapse to e.g. starting control.
- // Can upgrade minType even while in-progress
- if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
- TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
- if( tmp2!=null && tmp2 != _minType ) {
- _minType = tmp2;
- return this;
- }
- }
- if( r.inProgress() || r.nIns()<=1 )
+ if( r.inProgress() || r.nIns()<=1 || nOuts()==0 )
return null; // Input is in-progress
// If we have only a single unique input, become it.
Node live = singleUniqueInput();
- if( live != null ) {
- if( live._type.isa(_type) )
- return live;
- // Keep the Phi upcast
- return new CastNode(_type,null,live);
- }
+ if( live != null )
+ return live;
// No bother if region is going to fold dead paths soon
for( int i=1; i<nIns(); i++ )
if( r.in(i)._type == Type.XCONTROL )
return null;
- // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( _type instanceof TypeMem tmem && tmem._alias!=1 ) {
- for( int i=1; i<nIns(); i++ )
- if( in(i) instanceof MemMergeNode mem ) {
- setDef(i,mem.alias(tmem._alias));
- return this;
- }
- }
-
// Generic "pull down op"
Node progress;
if( same_op() && (progress = drop_same_op()) != null )
return progress;
- // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
+ // If merging Phi(ZERO, guardNZ(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( in(1) instanceof GuardNode cast && cast._nonZero && in(2)._type.makeZero()==in(2)._type && cast.in(1)!=this ) return cast.in(1);
+ if( in(2) instanceof GuardNode cast && cast._nonZero && in(1)._type.makeZero()==in(1)._type && cast.in(1)!=this ) return cast.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.
@@ -153,12 +147,12 @@ public class PhiNode extends Node {
if( in(2)._type == in(2)._type.makeZero() ) nullx = 2;
if( nullx != -1 ) {
Node val = in(3-nullx);
- if( val instanceof CastNode cast )
+ if( val instanceof GuardNode cast && cast._nonZero )
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);
+ CFGNode idom = r.cfg(nullx);
while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom();
if( idom instanceof CProjNode proj && proj._idx==1 )
return val;
@@ -172,17 +166,37 @@ public class PhiNode extends Node {
// 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 busy=null;
for( int i=1; i<nIns(); i++ ) {
Node op = in(i);
+ if( op.nOuts()==0 )
+ { op.kill(); return false; }
if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() )
return false; // Wrong class or CFG bound or mismatched inputs
- if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias )
- return false;
- if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users
- for( Node out : op._outputs )
- if( out!=null && out!=this )
- addDep(out);
+ if( in(1) instanceof MemOpNode mem ) {
+ // Mismatched aliases
+ if( mem._alias != ((MemOpNode)op)._alias ) return false;
+ // Load is clobbered somewhere, and can not be pulled forward past the Phi?
+ if( mem instanceof LoadNode )
+ for( Node use : op.outs() )
+ if( use instanceof StoreNode )
+ return false;
+ }
+ if( in(1) instanceof EscapeNode )
return false;
+ if( in(1) instanceof MemMergeNode )
+ return false; // Have to keep aliases straight
+ if( op.nOuts() > 1 ) {
+ if( busy==null ) busy = op;
+ else { // Too many users, but addDep in case lose users
+ for( Node out : op._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ for( Node out : busy._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ return false;
+ }
}
for( int j=1; j<in(1).nIns(); j++ )
if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) )
@@ -205,7 +219,7 @@ public class PhiNode extends Node {
if( in(i).in(j) != x )
{ needsPhi=true; break; }
if( needsPhi ) {
- x = new PhiNode(_label,op.in(j)._type.glb(false));
+ x = make(_label,op.in(j)._type.glb(false));
x.addDef(region());
for( int i=1; i<nIns(); i++ )
x.addDef(in(i).in(j));
@@ -253,39 +267,28 @@ public class PhiNode extends Node {
}
// Never equal if inProgress.
- // Also, joins
@Override public boolean eq( Node n ) {
- if( inProgress() ) return false;
- Type min = ((PhiNode)n)._minType;
- if( _minType==min ) return true;
- Type mt = min.meet(_minType);
- if( min!=mt && _minType!=mt ) return false;
- //// Theory says these 2 Phis CAN be merged/GVNd, but I need to pick the
- //// most general minType.
- //_minType = ((PhiNode)n)._minType = mt;
- //return true;
- return false;
+ return !inProgress() && super.eq(n);
}
@Override
public Parser.ParseException err() {
- if( _type != Type.BOTTOM ) return null;
+ if( _type != Type.BOTTOM && _type != TypeScalar.BOT ) return null;
- // BOTTOM means we mixed e.g. int and ptr
+ // Scalar BOT means we mixed scalar families, e.g. int and ptr.
+ // Global BOTTOM retains the same role for non-scalar families.
for( int i=1; i<nIns(); i++ )
// Already an error, but better error messages come from elsewhere
- if( in(i)._type == Type.BOTTOM )
+ if( in(i)._type == Type.BOTTOM || in(i)._type == TypeScalar.BOT )
return null;
- // Gather a minimal set of types that "cover" all the rest
- boolean ti=false, tf=false, tp=false, tn=false;
- for( int i=1; i<nIns(); i++ ) {
- Type t = in(i)._type;
- ti |= t instanceof TypeInteger x;
- tf |= t instanceof TypeFloat x;
- tp |= t instanceof TypeMemPtr x;
- tn |= t==Type.NIL;
- }
- return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc);
+ SB sb = new SB().p("No common type amongst ");
+ for( int i=1; i<nIns(); i++ )
+ sb.p(in(i)._type.toString()).p(" and ");
+ return Parser.error(sb.unchar(5).toString(),null);
+ }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/Proj.java b/src/main/java/com/seaofnodes/simple/node/Proj.java
new file mode 100644
index 00000000..c8b9378f
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/Proj.java
@@ -0,0 +1,2 @@
+package com.seaofnodes.simple.node;
+public interface Proj { int idx(); }
diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index 586527c8..97b9ed45 100644
--- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -1,12 +1,15 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeTuple;
-
+import com.seaofnodes.simple.util.BAOS;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public class ProjNode extends Node {
+public class ProjNode extends Node implements Proj {
// Which slice of the incoming multipart value
public final int _idx;
@@ -20,6 +23,14 @@ public class ProjNode extends Node {
_label = label;
}
public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; }
+ @Override public Tag serialTag() { return Tag.Proj; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(_idx);
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs ) {
+ return new ProjNode(null, bais.packed1(), strs[bais.packed2()] );
+ }
@Override public String label() { return _label; }
@@ -51,4 +62,10 @@ public class ProjNode extends Node {
@Override
int hash() { return _idx; }
+
+ @Override public int idx() { return _idx; }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
new file mode 100644
index 00000000..9a9b94fd
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/** Zero-code machine form of {@link PtrToIntNode}. */
+public class PtrToIntMach extends PtrToIntNode implements MachNode {
+ public PtrToIntMach(PtrToIntNode ptr) { super(ptr); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress() { return 1; }
+ @Override public void encoding(Encoding enc) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
new file mode 100644
index 00000000..d781620b
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+import java.util.BitSet;
+
+/**
+ * Reinterpret a non-null managed pointer as its raw integer address.
+ *
+ * This node changes representation families but emits no code. In particular,
+ * it is not a {@link CheckCastNode}: no control-dependent proof can turn a
+ * pointer into an integer. Array-to-C-pointer conversion adds the array body
+ * offset separately.
+ */
+public class PtrToIntNode extends Node {
+ public PtrToIntNode(Node ptr) { super(null,ptr); }
+ public PtrToIntNode(PtrToIntNode ptr) { super(ptr); }
+
+ @Override public Tag serialTag() { return Tag.PtrToInt; }
+ @Override public String glabel() { return "(i64*)"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(i64*)"),visited);
+ }
+
+ @Override public Type compute() {
+ return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT;
+ }
+
+ @Override public Node idealize() { return null; }
+
+ @Override public Parser.ParseException err() {
+ Type t = in(1)._type;
+ if( t instanceof TypeMemPtr ptr )
+ return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null);
+ return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
index d8fe6286..c36be85a 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
@@ -10,7 +10,7 @@ import java.util.BitSet;
public class ReadOnlyNode extends Node {
public ReadOnlyNode( Node n ) { super(null,n); }
public ReadOnlyNode( ReadOnlyNode n ) { super(n); }
- @Override public String label() { return "ReadOnly"; }
+ @Override public Tag serialTag() { return Tag.ReadOnly; }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index b115a260..dc48f6f2 100644
--- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -1,9 +1,9 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.*;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
import java.util.*;
public class RegionNode extends CFGNode {
@@ -13,9 +13,9 @@ public class RegionNode extends CFGNode {
public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; }
public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; }
-
- @Override
- public String label() { return "Region"; }
+ @Override public Tag serialTag() { return Tag.Region; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { return new RegionNode(null,new Node[bais.packed1()]); }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -75,7 +75,7 @@ public class RegionNode extends CFGNode {
// Fold Region
for( int j=1; j<region.nIns(); j++ )
addDef(region.in(j));
- setDef(i,Parser.XCTRL);
+ setDef(i,CodeGen.CODE.XCTRL);
return this;
}
}
@@ -111,7 +111,7 @@ public class RegionNode extends CFGNode {
CodeGen.CODE.addAll(phi.delDef(path)._outputs);
}
}
- return isDead() ? Parser.XCTRL : delDef(path);
+ return isDead() ? CodeGen.CODE.XCTRL : delDef(path);
}
private int findDeadInput(int startIdx) {
diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 0aa70214..b60d27d7 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -27,6 +27,7 @@ public class ReturnNode extends CFGNode {
_fun = fun;
}
public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; }
+ @Override public Tag serialTag() { return Tag.Return; }
public Node ctrl() { return in(0); }
public Node mem () { return in(1); }
@@ -35,9 +36,6 @@ public class ReturnNode extends CFGNode {
@Override public FunNode fun() { return _fun; }
@Override
- public String label() { return "Return"; }
-
- @Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
sb.append("return ");
if( expr()==null ) sb.append("----");
@@ -50,12 +48,10 @@ public class ReturnNode extends CFGNode {
@Override
public Type compute() {
- if( inProgress () ) return TypeTuple.RET; // In progress
return TypeTuple.make(ctrl()._type,mem()._type,expr()._type);
}
@Override public Node idealize() {
- if( inProgress () ) return null;
if( _fun.isDead() ) return null;
// Upgrade signature based on return type
@@ -64,87 +60,44 @@ public class ReturnNode extends CFGNode {
if( ret != fcn.ret() && ret.isa(fcn.ret()) )
_fun.setSig(fcn.makeFrom(ret));
- // If dead (cannot be reached; infinite loop), kill the exit values
- if( ctrl()._type==Type.XCONTROL &&
- !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
- Node top = new ConstantNode(Type.TOP).peephole();
- setDef(1,top);
- setDef(2,top);
- return this;
- }
-
return null;
}
- public boolean inProgress() {
- return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress();
- }
-
- // Gather parse-time return types for error reporting
- private Type mt = Type.TOP;
- private boolean ti=false, tf=false, tp=false, tn=false;
-
- // Add a return exit to the current parsing function
- void addReturn( Node ctrl, Node rmem, Node expr ) {
- assert inProgress();
-
- // Gather parse-time return types for error reporting
- Type t = expr._type;
- mt = mt.meet(t);
- ti |= t instanceof TypeInteger x;
- tf |= t instanceof TypeFloat x;
- tp |= t instanceof TypeMemPtr x;
- tn |= t==Type.NIL;
-
- // Merge path into the One True Return
- RegionNode r = (RegionNode)ctrl();
- // Assert that the Phis are in particular outputs; not reordered or shuffled
- PhiNode mem = (PhiNode)r.out(0); assert mem._minType == TypeMem.BOT;
- PhiNode rez = (PhiNode)r.out(1); assert rez._minType == Type.BOTTOM;
- // Pop "inProgress" null off
- r ._inputs.pop();
- mem._inputs.pop();
- rez._inputs.pop();
- // Add new return point
- r .addDef(ctrl);
- mem.addDef(rmem);
- rez.addDef(expr);
- // Back to being inProgress
- r .addDef(null);
- mem.addDef(null);
- rez.addDef(null);
- }
-
@Override public Parser.ParseException err() {
- if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead
- return expr()._type==Type.BOTTOM || expr()._type==Type.TOP ? mixerr(ti,tf,tp,tn,_fun._loc) : null;
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == Type.TOP )
+ return Parser.error("No defined return type",null);
+ // With no user constructor, <init> doubles as the public no-arg
+ // constructor. Required fields remain TOP in its private memory: a
+ // typed parser poison which deliberately emitted no Store or code.
+ if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) {
+ TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj;
+ if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem )
+ for( Field fld : self._fields ) {
+ if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue;
+ Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts
+ ? ts.field(fld._fname)._t
+ : mem._alias==fld._alias ? mem._t : Type.TOP;
+ if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() )
+ return Parser.error("'"+self._name+"' is not fully initialized, field '"+
+ fld._fname+"' is only partially set in the constructor",null);
+ }
+ }
+ return null;
}
- static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) {
- if( !ti && !tf && !tp && !tn )
- // Hit when the function never returns. e.g. `f = { -> return f() };`
- return Parser.error("No defined return type", loc);
- // Rather ugly way to print conflicting return types
- int cnt = (ti?1:0) + (tf?1:0) + (tp||tn?1:0);
- if( cnt==1 ) return null; // Uniform return type, something else errored and will report
- SB sb = new SB().p("No common type amongst ");
- if( ti ) sb.p("int and ");
- if( tf ) sb.p("f64 and ");
- if( tp || tn ) sb.p("reference and ");
- return Parser.error(sb.unchar(5).toString(),loc);
+ @Override public boolean eq( Node n ) {
+ return !_fun.isDead() && !((ReturnNode)n)._fun.isDead();
}
-
// ------------
// MachNode specifics, shared across all CPUs
public String op() {
- return _fun._frameAdjust > 0 ? "addi" : "ret";
+ return _fun._frameAdjust > 0 ? "epilog" : "ret ";
}
// Correct Nodes outside the normal edges
public void postSelect(CodeGen code) {
- FunNode fun = (FunNode)rpc().in(0);
- _fun = fun;
- fun.setRet(this);
+ _fun.setRet(this);
}
public RegMask regmap(int i) {
return i==2
@@ -156,13 +109,14 @@ public class ReturnNode extends CFGNode {
public void asm(CodeGen code, SB sb) {
int frameAdjust = fun()._frameAdjust;
if( frameAdjust>0 )
- sb.p("rsp += #").p(frameAdjust).p("\nret");
+ sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret ");
// Post code-gen, just print the "ret"
- if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() ||
+ (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) )
// Prints return reg (either RAX or XMM0), RPC and then the
// callee-save registers.
for( int i=2; i<nIns(); i++ )
- sb.p(code.reg(in(i),fun())).p(" ");
+ sb.p(code.reg(in(i))).p(" ");
// If we did not get the expected rpc, print which one we got
else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() )
sb.p("[").p(code.reg(rpc())).p("]");
diff --git a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
index 7936086d..322a6bc0 100644
--- a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -7,8 +7,7 @@ import java.util.BitSet;
public class RoundF32Node extends Node {
public RoundF32Node(Node lhs) { super(null, lhs); }
-
- @Override public String label() { return "RoundF32"; }
+ @Override public Tag serialTag() { return Tag.RoundF32; }
@Override public String glabel() { return "(f32)"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java
index ee15fa65..8ad5988d 100644
--- a/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class SarNode extends ArithNode {
- public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Sar; }
- @Override public String label() { return "Sar"; }
@Override public String op() { return ">>"; }
@Override public String glabel() { return "&gt;&gt;"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 36746a81..58508a1c 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -3,11 +3,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.Var;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
-
import java.util.*;
/**
@@ -31,21 +31,23 @@ public class ScopeNode extends MemMergeNode {
// Lexical scope is typed:
public static class Kind {
// Basic block scoping
- public static class Block extends Kind { }
+ public static class Block extends Kind { }
+ // Constructor/initializer block for an allocated struct.
+ public static class Constructor extends Block {
+ public Constructor(TypeStruct self) { _self = self; }
+ public final TypeStruct _self;
+ }
// Function scope
- public static class Func extends Kind { }
- // Struct definition, mapping types to fields
- public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
- // Struct allocation, mapping values to fields
- public static class Alloc extends Kind { public Alloc (TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
+ public static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; }
public int _lexSize; // Number of Vars in this scope
}
public final Ary<Kind> _kinds;
// Lexical scope nesting depth
public int depth() { return _kinds._len; }
+ public Kind klast() { return _kinds.last(); }
- // Extra guards; tested predicates and casted results
+ // Extra guards; tested predicates and cast results
private final Ary<Node> _guards;
// A new ScopeNode
@@ -64,8 +66,8 @@ public class ScopeNode extends MemMergeNode {
int j=1;
for( int i=0; i<nIns(); i++ ) {
if( j < depth() && i == _kinds.at(j)._lexSize ) { sb.append("| "); j++; }
- Var v = _vars.at(i);
- sb.append(v.type());
+ Var v = var(i);
+ sb.append(v._type());
sb.append(" ");
if( v._final ) sb.append("!");
sb.append(v._name);
@@ -85,7 +87,7 @@ public class ScopeNode extends MemMergeNode {
public Node ctrl() { return in(0); }
- public MemMergeNode mem() { return (MemMergeNode)in(1); }
+ public Node mem() { return in(1); }
public Var var(int i) { return _vars.at(i); }
/**
@@ -108,7 +110,11 @@ public class ScopeNode extends MemMergeNode {
// Pop a lexical scope
public void pop() {
- promote(); // Promote forward references to the next outer scope
+ promote(null); // Promote forward references to the next outer scope
+ _pop();
+ }
+ // Pop a scope, no promote FRefs
+ public void _pop() {
int n = _kinds.pop()._lexSize;
popUntil(n); // Pop off inputs going out of scope
_vars.setLen(n); // Pop off variables going out of scope
@@ -117,59 +123,85 @@ public class ScopeNode extends MemMergeNode {
// Look for forward references in the last lexical scope and promote to the
// next outer lexical scope. At the last scope declare them an error.
- public void promote() {
- Kind kind = _kinds.last();
+ public void promote(CodeGen code) {
+ Kind kind = klast();
int n = kind._lexSize;
for( int i=n; i<nIns(); i++ ) {
Var v = var(i);
+ Node vn = in(i);
if( !v.isFRef() ) continue;
- if( depth()==1 )
- throw Parser.error("Undefined name '" + v._name + "'",v._loc);
- _vars .swap(n,i);
- _inputs.swap(n,i);
- v._idx = n;
+ // Shuffle vars and inputs to move the forward reference to
+ // *before* the start of the using scope ("as if" it was not
+ // actually a forward reference).
+ for( int j=n; j<=i; j++ ) {
+ Var vtmp = var(j);
+ Node ntmp = in (j);
+ v._idx = j;
+ _vars .set(j,v );
+ _inputs.set(j,vn);
+ v = vtmp;
+ vn = ntmp;
+ }
kind._lexSize = ++n;
}
}
- 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 ) {
+ public boolean inFunction () { return klast() instanceof Kind.Func; }
+ // Is any enclosing scope a constructor?
+ public boolean inConstructor() { return constructorSelf() != null; }
+ // Nearest constructor self type, if any.
+ public TypeStruct constructorSelf() {
for( int i=depth()-1; i>=0; i-- )
- if( v._idx >= _kinds.at(i)._lexSize )
- return _kinds.at(i);
- throw Utils.TODO();
+ if( _kinds.at(i) instanceof Kind.Constructor ctor )
+ return ctor._self;
+ else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) )
+ return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj;
+ return null;
}
- // 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.
+ // Find nearest enclosing function scope
+ public int enclosingFunction() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
- // - Walking out of a method and (skipping Blocks) into the matching struct
- // is OK, and this is a instance var load.
+ // Find nearest enclosing function or declaration scope
+ public int enclosingFuncOrDecl() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Func func )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
- // - Walking out of a method and into the wrong struct is an error;
- // including nested structs containing (no nested classes yet)
+ // Which lexical scope contains this Var?
+ public int kindx( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize )
+ return i;
+ return -1;
+ }
- // Returns error, forward-ref, ok containing struct, ok containing function/block.
+ // Which lexical scope contains this Var?
+ public Kind kind( Var v ) {
+ int kx = kindx(v);
+ return kx == -1 ? null : _kinds.at(kx);
+ }
- public String outOfFunction( Var v ) {
- //if( v==null ) return null; // Prolly forward reference
- //int i; for( i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- )
- // if( _kinds.at(i)=="{->}" ) return "{->}";
- //return _kinds.at(i);
- throw Utils.TODO();
+ public int kindFcnx( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func )
+ return i;
+ throw Utils.TODO("Should not reach here");
}
+ // Which function scope contains this Var?
+ public Kind kindFcn( Var v ) {
+ int kx = kindFcnx(v);
+ return kx == -1 ? null : _kinds.at(kx);
+ }
// Find name in reverse, return an index into _vars or -1. Linear scan
// instead of hashtable, but probably doesn't matter until the scan
@@ -185,9 +217,9 @@ public class ScopeNode extends MemMergeNode {
* Create a new variable name in the current scope
*/
public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) {
- assert _kinds.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory
+ assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory
if( depth() > 0 )
- for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) {
+ for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) {
Var n = var(i);
if( n._name.equals(name) ) {
if( !n.isFRef() ) return false; // Double define
@@ -195,21 +227,47 @@ public class ScopeNode extends MemMergeNode {
if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant
n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location
setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also
+ return true;
}
}
- Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL);
+ Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL);
_vars.add(v);
// Creating a forward reference
- if( init==Parser.XCTRL )
- init = new FRefNode(v).init();
+ if( init==CodeGen.CODE.XCTRL )
+ //init = new FRefNode(v).init();
+ throw Utils.TODO();
addDef(init);
return true;
}
- // Read from memory
- public Node mem( int alias ) { return mem()._mem(alias,null); }
- // Write to memory
- public void mem( int alias, Node st ) { mem()._mem(alias,st); }
+ // Normal lookup was tried and failed. Insert a forward ref outside any
+ // enclosing Kind.Func scope, skipping any nested Blocks or Allocs and
+ // return the Var. May insert at the outermost scope, which means this
+ // must be defined externally, or it's an error.
+
+ public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) {
+ // Kind/Lexical scope index
+ int kidx = enclosingFunction();
+ int idx = _kinds.at(kidx)._lexSize;
+ Var var = new Var(idx,id,t,xfinal,loc,true);
+ FRefNode fref = new FRefNode(id,loc).init();
+ fref._type = fref._con = t;
+ // Insert in the lex scope just prior to kidx
+ insert(var,fref,kidx);
+ return var;
+ }
+
+
+ // Parser state carries one complete, conservative memory value. Precise
+ // alias partitions belong to the graph and are introduced by peepholes.
+ public Node mem( int alias ) {
+ assert alias == 1;
+ return in(update(var(1),null));
+ }
+ public void mem( int alias, Node st ) {
+ assert alias == 1;
+ update(var(1),st);
+ }
/**
@@ -247,7 +305,7 @@ public class ScopeNode extends MemMergeNode {
// 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,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
+ : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init());
setDef(v._idx,old);
}
//assert !v._final || st==null;
@@ -285,15 +343,9 @@ public class ScopeNode extends MemMergeNode {
n.keep();
dup.addDef(ctrl()); // Control input is just copied
- // Memory input is a shallow copy
- MemMergeNode memdup = new MemMergeNode(true), mem = mem();
- memdup.addDef(null);
- memdup.addDef(loop ? this : mem.in(1));
- for( int i=2; i<mem.nIns(); i++ )
- // For lazy phis on loops we use a sentinel
- // that will trigger phi creation on update
- memdup.addDef(loop ? this : mem.in(i));
- dup.addDef(memdup);
+ // Memory is one ordinary parser-state value. At loop heads the
+ // Scope sentinel triggers the same lazy-Phi machinery as variables.
+ dup.addDef(loop ? this : mem());
// Copy of other inputs
for( int i=2; i<nIns(); i++ )
@@ -313,39 +365,49 @@ public class ScopeNode extends MemMergeNode {
*/
public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
- mem()._merge(that.mem(),r);
- this ._merge(that ,r);
+ this._merge(that,r);
that.kill(); // Kill merged scope
CodeGen.CODE.add(r);
return r.unkeep();
}
- private void _merge(ScopeNode that, RegionNode r) {
- for( int i = 2; i < nIns(); i++)
+ public void _merge(ScopeNode that, RegionNode r) {
+ _merge(that,r,nIns());
+ }
+ public void _merge(ScopeNode that, RegionNode r, int max ) {
+ for( int i = 1; i < max; i++)
if( in(i) != that.in(i) ) { // No need for redundant Phis
// If we are in lazy phi mode we need to a lookup
// by name as it will trigger a phi creation
Var v = var(i);
Node lhs = this.in(this.update(v,null));
Node rhs = that.in(that.update(v,null));
- setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole());
+ 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.
+ // 'this' scope is the default, 'scope' might have frefs.
public void balanceIf( ScopeNode scope ) {
- for( int i = nIns(); i < scope.nIns(); i++ ) {
+ if( nIns()==scope.nIns() )
+ return; // Fast-path cutout
+ int kidx = enclosingFunction();
+ assert _kinds.at(kidx) == scope._kinds.at(kidx);
+ if( kidx==0 ) kidx = 1;
+ for( int i = _kinds.at(kidx-1)._lexSize; i < scope.nIns(); i++ ) {
Var n = scope.var(i);
+ if( i<nIns() && var(i)==n ) continue;
if( n.isFRef() ) { // RHS has forward refs
- _vars.add(n); // Copy to LHS
- addDef(scope.in(i));
+ _vars.insert(n,i);
+ insertDef(i,scope.in(i));
} else
throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc);
}
}
+ // ------------------------------------------------
// 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 ) {
@@ -353,34 +415,30 @@ public class ScopeNode extends MemMergeNode {
assert ctrl instanceof LoopNode loop && loop.inProgress();
ctrl.setDef(2,back.ctrl());
- mem()._endLoopMem( this, back.mem(), exit.mem() );
- this ._endLoop ( this, back , exit );
+ this._endLoop( this, back, exit);
back.kill(); // Loop backedge is dead
// Now one-time do a useless-phi removal
- mem()._useless();
- this ._useless();
-
- // The exit mem's lazy default value had been the loop top,
- // now it goes back to predating the loop.
- exit.mem().setDef(1,mem().in(1));
+ this._useless();
}
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
- for( int i=2; i<nIns(); i++ ) {
+ for( int i=1; i<nIns(); i++ ) {
if( var(i)._final ) continue; // Final vars did not get modified in the loop
- if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
- if( back.in(i) != scope ) {
- PhiNode phi = (PhiNode)in(i);
+ Node n = in(i);
+ if( var(i).type().isHighOrConst() ) { // Cannot lift higher than a constant, so no Phi
+ n.subsume(n=ConstantNode.make(var(i).type()).init());
+ } else if( back.in(i) != scope ) {
+ PhiNode phi = (PhiNode)n;
assert phi.region()==scope.ctrl() && phi.in(2)==null;
phi.setDef(2,back.in(i)); // Fill backedge
}
if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also
- exit.setDef(i,in(i));
+ exit.setDef(i,n);
}
}
-
+ // ------------------------------------------------
// Up-casting: using the results of an If to improve a value.
// E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
public void addGuards( Node ctrl, Node pred, boolean invert ) {
@@ -392,33 +450,30 @@ public class ScopeNode extends MemMergeNode {
// Invert the If conditional
if( invert )
pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
+ Node zeroPred = pred instanceof NotNode not ? not.in(1) : null;
// This is a zero/null test.
// Compute the positive test type.
- Type tnz = pred._type.nonZero();
- if( tnz!=null )
- _addGuard(tnz,ctrl,pred);
+ _addGuard(true,ctrl,pred);
// Compute the negative test type.
- if( pred instanceof NotNode not ) {
- Node npred = not.in(1);
- Type tzero = npred._type.makeZero();
- _addGuard(tzero,ctrl,npred);
- }
+ if( zeroPred != null )
+ _addGuard(false,ctrl,zeroPred);
}
- private void _addGuard(Type guard, Node ctrl, Node pred) {
- Type tcast = guard.join(pred._type);
- if( tcast != pred._type && !tcast.isHigh() ) {
- Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep();
+ private void _addGuard(boolean nonZero, Node ctrl, Node pred) {
+ Node guard = new GuardNode(nonZero,ctrl,pred).peephole();
+ if( guard != pred ) {
+ pred.keep();
+ guard.keep();
_guards.add(pred);
- _guards.add(cast);
- replace(pred,cast);
+ _guards.add(guard);
+ replace(pred,guard);
}
}
// Remove matching pred/cast pairs from this guarded region.
- public ScopeNode removeGuards( Node ctrl ) {
+ public void removeGuards( Node ctrl ) {
assert ctrl instanceof CFGNode;
// 0,1 or 2 guards
while( true ) {
@@ -428,7 +483,6 @@ public class ScopeNode extends MemMergeNode {
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
@@ -465,4 +519,44 @@ public class ScopeNode extends MemMergeNode {
setDef(i,cast);
}
+ // ------------------------------------------------------------------------
+ // Insert a top-level public external symbol, "as-if" some #include/import
+ // was executed at the top of tile.
+ public Var insertExtern( ExternNode ext ) {
+ // Size of top-level scope
+ int lexSize = _kinds.at(0)._lexSize;
+ Var var = new Var(lexSize,ext._extern,ext._con,true,null);
+ insert(var,ext,0);
+ return var;
+ }
+
+ // ------------------------------------------------------------------------
+ // Insert a Var with Node n in the lex scope just prior to kidx
+ private void insert( Var var, Node n, int kidx ) {
+ // Insert just prior to scope kidx
+ int nidx = _kinds.at(kidx)._lexSize;
+ _vars.insert(var,nidx);
+ insertDef(nidx,n);
+ // All outer Kinds lexical offsets bump by one
+ for( int i = kidx; i < depth(); i++ )
+ _kinds.at(i)._lexSize++;
+ // All Vars outside the innermost lexical scope bump out one
+ for( int i=nidx+1; i<_vars._len; i++ )
+ var(i)._idx++;
+ }
+
+ // Save the existing Node state to an array
+ public Node[] save() {
+ Node[] olds = new Node[nIns()];
+ for( int i=0; i<nIns(); i++ )
+ olds[i] = in(i)==null ? null : in(i).keep();
+ return olds;
+ }
+
+ // Restore saved Node state back into scope
+ public void restore( Node[] olds ) {
+ assert olds.length<=nIns();
+ for( int i=0; i<olds.length; i++ )
+ setDef(i,olds[i]==null ? null : olds[i].unkeep());
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 2557bf6b..459b26eb 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -1,13 +1,14 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class ShlNode extends ArithNode {
- public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Shl; }
- @Override public String label() { return "Shl"; }
@Override public String op() { return "<<"; }
@Override public String glabel() { return "&lt;&lt;"; }
@@ -38,7 +39,7 @@ public class ShlNode extends ArithNode {
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
long sum = c.value() << shl.value();
if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
- return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) );
+ return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) );
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 269f6b7f..5e4870d1 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class ShrNode extends ArithNode {
- public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Shr; }
- @Override public String label() { return "Shr"; }
@Override public String op() { return ">>>"; }
@Override public String glabel() { return "&gt;&gt;&gt;"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java
index d061cdbd..4b58187f 100644
--- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -1,14 +1,17 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.LRG;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.SB;
import java.util.BitSet;
public abstract class SplitNode extends MachConcreteNode {
+ public final LRG _lrg; // Live range for split
public final String _kind; // Kind of split
- public final byte _round;
- public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; }
+ public final byte _round; // Debugging info for which RegAlloc round
+ public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; }
@Override public String op() { return "mov"; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
sb.append("mov(");
@@ -16,6 +19,7 @@ public abstract class SplitNode extends MachConcreteNode {
else in(1)._print0(sb,visited);
return sb.append(")");
}
+ public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; }
@Override public Type compute() { return in(0)._type; }
@Override public Node idealize() { return null; }
@Override public void asm(CodeGen code, SB sb) {
diff --git a/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
new file mode 100644
index 00000000..d828c5f1
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Start of a single compilation unit, distinct from the outer whole-program Start.
+public class StartCUNode extends StartNode {
+ public final String _fname;
+ public StartCUNode(StartNode start, StopCUNode stop, Type arg, String fname) { super(start,stop,arg); _fname = fname; }
+ public StartCUNode(StartCUNode start) { super(start); _fname = start._fname; }
+ @Override public Tag serialTag() { return Tag.StartCU; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) {
+ baos.packed2(types.get(_arg));
+ baos.packed2(strs.get(_fname));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types ) {
+ return new StartCUNode(null,null,types[bais.packed2()],strs[bais.packed2()]);
+ }
+
+ @Override public CFGNode cfg0() { return (CFGNode)in(0); }
+
+ // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1
+ @Override public int idepth() { return CodeGen.CODE.iDepthAt(1); }
+ @Override public CFGNode idom(Node dep) { return cfg0(); }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_fname);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java
index e3806de7..7d2b2773 100644
--- a/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -3,48 +3,78 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
-import static com.seaofnodes.simple.util.Utils.TODO;
/**
- * The Start node represents the start of the function.
+ * The Start node represents the external world calling, be it calling the
+ * entry <clinit> or just some linked caller calling.
* <p>
* Start initially has 1 input (arg) from outside and the initial control.
* In ch10 we also add mem aliases as structs get defined; each field in struct
* adds a distinct alias to Start's tuple.
+ * <p>
+ * By ch25, Start is the external world (or linker) calling with everything
+ * that has escaped. Start is NOT the entry <clinit> itself, which can assume
+ * no memory nor fidxs exist nor escaped. Start gets escaped things from Stop;
+ * together these close the Grand Cycle between the code we know and the code
+ * we don't.
*/
public class StartNode extends LoopNode implements MultiNode {
final Type _arg;
- public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); }
+ public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; }
public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
-
- @Override public String label() { return "Start"; }
+ @Override public Tag serialTag() { return Tag.Start; }
+ public Type arg() { return _arg; }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
return sb.append(label());
}
- @Override public CFGNode cfg0() { return null; }
+ @Override public CFGNode cfg0() { return null; }
// Get the one control following; error to call with more than one e.g. an
- // IfNode or other multi-way branch. For Start, its "main"
+ // IfNode or other multi-way branch. For Start, it's the class init for
+ // this compilation unit.
@Override public CFGNode uctrl() {
- // Find "main", it's the start.
+ // Find module.<clinit>, it's the start.
CFGNode C = null;
for( Node use : _outputs )
- if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) )
+ if( use instanceof FunNode fun && fun.isModInit() )
{ assert C==null; C = fun; }
+ if( C==null )
+ for( FunNode fun : CodeGen.CODE._linker )
+ if( fun!=null && !fun.isDead() && fun.isModInit() )
+ { assert C==null; C = fun; }
return C;
}
+ boolean escapedFIDX(int fidx) {
+ // Public class fields are callable by the unknown external world
+ // regardless of the current SCCP approximation of Start memory.
+ if( CodeGen.CODE.publicFIDX(fidx) ) return true;
+ if( !(_type instanceof TypeTuple tt) )
+ return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes
+ TypeMem tmem = (TypeMem)tt._types[1];
+ if( tmem == TypeMem.BOT ) return false;
+ if( tmem == TypeMem.TOP ) return false;
+ if( XInt.isHigh(tmem._escFs) )
+ return false;
+ return XInt.bit(tmem._escFs,fidx);
+ }
@Override public TypeTuple compute() {
- return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg);
+ StopNode stop = (StopNode)in(1);
+ TypeMem tmem = stop._type instanceof TypeTuple tt
+ ? tt.mem()
+ : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT;
+ assert tmem._alias==1 && !tmem._one;
+ return TypeTuple.make(Type.CONTROL,tmem,_arg);
}
@Override public Node idealize() { return null; }
@@ -53,4 +83,5 @@ public class StartNode extends LoopNode implements MultiNode {
@Override public int idepth() { return CodeGen.CODE.iDepthAt(0); }
@Override public CFGNode idom(Node dep) { return null; }
+ @Override public boolean eq( Node n ) { return true; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
new file mode 100644
index 00000000..ab0f5da6
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -0,0 +1,122 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Stop of a single compilation unit, distinct from the outer whole-program Stop.
+public class StopCUNode extends StopNode {
+ public StopCUNode() { super(); }
+ public StopCUNode(StopNode stop) { super(stop); }
+ @Override public Tag serialTag() { return Tag.StopCU; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) {
+ StopCUNode stop = new StopCUNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
+ }
+
+ public StartCUNode start() {
+ for( Node use : _outputs )
+ if( use instanceof StartCUNode start )
+ return start;
+ return null;
+ }
+
+ @Override
+ public TypeTuple compute() {
+ // During Parsing, new Stops and Returns are being added. This Stop
+ // cannot know if more are coming, so must assume the worst.
+ if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
+ return TypeTuple.STATE;
+
+ StartCUNode start = start();
+ // Memory start from all returns
+ TypeMem xmem = TypeMem.START;
+ // Escape summaries are monotone discovery facts. In Opto, a Return
+ // can widen from a precise set to FULL; the public-only normalization
+ // below must not forget private bits already discovered precisely from
+ // that Return on an earlier SCCP step.
+ if( CodeGen.CODE._phase == CodeGen.Phase.Opto &&
+ _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem )
+ xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs),
+ XInt.meet(xmem._escAs,omem._escAs));
+
+ for( Node def : _inputs ) {
+ ReturnNode ret = (ReturnNode)def;
+ if( ret._type == Type.TOP ) continue;
+ if( !(ret._type instanceof TypeTuple tret) )
+ return TypeTuple.STATE; // Must be Type.BOTTOM
+
+ FunNode fun = ret.fun();
+ boolean noUserCtor = fun.isInstance() &&
+ !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj);
+ // Instance <init> is an allocation helper, never an externally
+ // callable constructor when a user constructor exists. Otherwise
+ // the same no-arg <init> is the public constructor.
+ boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor);
+ TypeFunPtr sig = fun.sig();
+ // If public init, the function escapes and the related class escapes
+ if( pub ) {
+ xmem = xmem.escapes(sig);
+ // Classes also escape their public clazz pointer, which then
+ // escapes all their public fields. Private fields do not
+ // escape, and are only available from the local class code.
+ if( fun.isClz() )
+ xmem = xmem.escapesAliases(sig._sig[0]);
+ }
+
+ // The instance <init> Start edge supplies conservative pre-Opto
+ // inputs, but its private-memory result is consumed only by the
+ // allocation sequence and is never an externally visible return.
+ if( fun.isInstance() && !noUserCtor ) continue;
+
+ // Does this function escape? If not, then no need to MEET
+ // here as its effects are not visible to the outside world.
+ addDepForwards(start); // Check again if function escapes
+ if( pub || start.escapedFIDX(sig.fidx()) ) {
+ if( tret.ctl().isHigh() ) continue; // Return not reachable
+ // Capture (precisely?) all escaping pointer aliases and fidxs.
+ // Escaped fidxs means the linked world can call that function;
+ // escaped aliases means the linked world can R/W those aliases.
+ xmem = xmem.escapes(tret.ret());
+ xmem = publicBottom(xmem);
+ xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem));
+ }
+ }
+ assert xmem._alias==1 && !xmem._one;
+ return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM);
+ }
+
+ // FULL escape sets arise from global BOTTOM. Across a compilation-unit
+ // boundary global BOTTOM exposes every public field, while retaining any
+ // private escapes already discovered precisely.
+ private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); }
+ private TypeMem publicBottom(TypeMem mem, TypeMem prior) {
+ // The pessimistic Iter pass naturally lifts transient FULL inputs to
+ // precise finite sets. Replacing FULL early there would let a later
+ // input refinement add bits, reversing Iter's monotonic direction.
+ // This normalization is needed only for Opto's optimistic grand cycle.
+ if( CodeGen.CODE._phase != CodeGen.Phase.Opto ) return mem;
+ int[] escFs = XInt.isHigh(mem._escFs)
+ ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
+ : mem._escFs;
+ int[] escAs = XInt.isHigh(mem._escAs)
+ ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases())
+ : mem._escAs;
+ return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs);
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( in(i) instanceof ReturnNode ret && addDep(ret.fun()).isDead() )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java
index 4b03bf4e..45f5519e 100644
--- a/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -1,34 +1,45 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+// A collection of function ReturnNodes for a compilation unit.
public class StopNode extends CFGNode {
- public final String _src;
-
- public StopNode(String src) {
+ public StopNode() {
super();
- _src = src;
_type = compute();
}
- public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; }
-
- @Override
- public String label() {
- return "Stop";
+ public StopNode(StopNode stop) { super(stop); }
+ @Override public Tag serialTag() { return Tag.Stop; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) {
+ StopNode stop = new StopNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
}
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
// For the sake of many old tests, and single value prints as "return val"
- ReturnNode ret1 = ret();
+ ReturnNode ret1 = null;
+ for( Node n : _inputs ) {
+ if( n instanceof ReturnNode ret ) {
+ if( ret1 != null ) { ret1=null; break; }
+ ret1 = ret;
+ }
+ }
if( ret1!=null ) return ret1._print0(sb,visited);
+
sb.append("Stop[ ");
- for( Node ret : _inputs )
- if( ret!=null ) {
- String name = ((ReturnNode)ret).fun()._name;
+ for( Node n : _inputs )
+ if( n instanceof ReturnNode ret ) {
+ String name = ret.fun()._name;
if( name== null || !name.startsWith("sys.") )
ret._print0(sb, visited).append(" ");
}
@@ -41,25 +52,36 @@ public class StopNode extends CFGNode {
// If a single Return, return it.
// Otherwise, null because ambiguous.
public ReturnNode ret() {
- Node ret1 = this;
- for( Node ret : _inputs ) {
- String name = ((ReturnNode)ret).fun()._name;
- if( name==null || !name.startsWith("sys.") )
- ret1 = ret1==this ? ((ReturnNode)ret) : null;
+ ReturnNode ret1 = null;
+ for( Node n : _inputs ) {
+ // TODO: remove isPublic
+ if( n instanceof ReturnNode ret && ret.fun().isPublic() ) {
+ if( ret1 != null ) return null; // Ambiguous
+ ret1 = ret;
+ }
}
- return ret1==this ? null : (ReturnNode)ret1;
+ return ret1;
}
@Override
- public Type compute() {
- return Type.BOTTOM;
+ public TypeTuple compute() {
+ // During Parsing, new Stops and Returns are being added. This Stop
+ // cannot know if more are coming, so must assume the worst.
+ if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
+ return TypeTuple.STATE;
+
+ // Just meet-over-inputs.
+ TypeTuple tt = TypeTuple.STOP_HIGH;
+ for( Node def : _inputs )
+ tt = (TypeTuple)tt.meet(def._type);
+ assert tt.mem()._alias==1 && !tt.mem()._one;
+ return tt;
}
- @Override
- public Node idealize() {
+ @Override public Node idealize() {
int len = nIns();
for( int i=0; i<nIns(); i++ )
- if( addDep(((ReturnNode)in(i)).fun()).isDead() )
+ if( in(i).nIns()==0 )
delDef(i--);
if( len != nIns() ) return this;
return null;
@@ -69,7 +91,10 @@ public class StopNode extends CFGNode {
if( _idepth!=0 ) return _idepth;
int d=0;
for( Node ret : _inputs )
- d = Math.max(d,((ReturnNode)ret).idepth()+1);
+ // All public results are hooked on the Stop, including data
+ // values, so must check CFG
+ if( ret instanceof CFGNode cfg )
+ d = Math.max(d,cfg.idepth()+1);
return _idepth=d;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 8e33512a..c0006511 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -1,8 +1,14 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Store represents setting a value to a memory based object, in chapter 10
@@ -10,7 +16,10 @@ import java.util.BitSet;
*/
public class StoreNode extends MemOpNode {
- private final boolean _init; // Initializing writes are allowed to write null
+ final boolean _init; // Initializing writes are allowed to write null
+ // Semantic store width in bytes. Zero is the sole undecided state;
+ // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants.
+ private byte _size;
/**
* @param name The struct field we are assigning to
@@ -19,9 +28,25 @@ public class StoreNode extends MemOpNode {
* @param off The offset inside the struct base
* @param value Value to be stored
*/
- public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) {
- super(loc, name, alias, false, glb, mem, ptr, off, value);
+ public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
+ this(loc,name,alias,glb,ctrl,mem,ptr,off,value,init,(byte)0);
+ }
+ private StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
+ super(loc, name, alias, false, glb, ctrl, mem, ptr, off, value);
_init = init;
+ _size = size;
+ }
+ StoreNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ super(bais,strs,types,fileAliases,aliases,false);
+ addDef(null);
+ _init = bais.read() != 0;
+ _size = (byte)bais.read();
+ }
+ @Override public Tag serialTag() { return Tag.Store; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ super.packed(baos,strs,types, anodes );
+ baos.write(_init ? 1 : 0);
+ baos.write(_size);
}
// GraphVis DOT code and debugger labels
@@ -30,7 +55,10 @@ public class StoreNode extends MemOpNode {
@Override public String glabel() { return "." +_name+"="; }
@Override public boolean isMem() { return true; }
+ public Node nnptr() { return ptr() instanceof GuardNode cast && cast._nonZero ? cast.in(1) : ptr(); }
public Node val() { return in(4); }
+ public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; }
+ @Override public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -39,28 +67,108 @@ public class StoreNode extends MemOpNode {
@Override
public Type compute() {
- Type val = val()._type;
- Type mem0 = mem()._type;
- if( mem0 == Type.TOP ) return TypeMem.TOP;
- TypeMem mem = (TypeMem)mem0; // Invariant
- if( mem == TypeMem.TOP ) return TypeMem.TOP;
- Type t = Type.BOTTOM; // No idea on field contents
- // Same alias, lift val to the declared type and then meet into other fields
- if( mem._alias == _alias ) {
- assert !_declaredType.isFRef();
- // Sharpen memory value; required for narrowing stores where the parser inserts
- // zero/sign masking and somebody reads the TypeMem type.
- val = val.join(_declaredType);
- t = val.meet(mem._t);
- }
- return TypeMem.make(_alias,t);
+ Type val = val()._type;
+ Type mem0 = aliasMem()._type;
+ Type ptr0 = ptr()._type;
+ // Validate argument types
+ if( ptr0.isHigh() )
+ return TypeMem.TOP;
+ if( !(mem0 instanceof TypeMem mem) ||
+ !(ptr0 instanceof TypeMemPtr ptr) )
+ return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT;
+
+ // Sharpen memory value; required for narrowing stores where the parser
+ // inserts zero/sign masking and somebody reads the TypeMem type.
+ Type decl = declaredType();
+ if( decl != Type.BOTTOM && decl != Type.TOP )
+ val = val.join(decl);
+ //if( err()!=null )
+ // val = Type.BOTTOM;
+ // Allocation uses a private TypeMem and nothing else does. This
+ // memory is truly private; a temporary singleton until it escapes -
+ // which is never does in a constructor.
+ if( !_name.equals("[]") && mem._one )
+ // Just track the stored value
+ return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val);
+
+ // Normal aliasing Store.
+ assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing
+
+ // Single alias store. If memory is fat, attempt to get a narrow slice.
+ Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts
+ ? ts._fields[ts.findAlias(_alias)]._t
+ : mem._t;
+
+ // Same alias, meet into other fields.
+ Type t = val.meet(tfld);
+ // Bulk memory's final bit describes its conservative remainder, not
+ // this newly precise slice. Slice finality comes from the field.
+ Field fld = ptr._obj.field(_name);
+ boolean xfinal = fld != null && fld._final;
+ return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val);
}
@Override
public Node idealize() {
+ assert !(mem() instanceof CheckCastNode);
- if( mem() instanceof CastNode cast ) {
- setDef(1,cast.in(1));
+ // Freeze the semantic Store variant as soon as the pointer exposes a
+ // canonical field declaration. Waiting until after structural memory
+ // peeps can lose the declaration to a flow-sensitive constant field
+ // state. Imported Stores already carry a serialized non-zero width.
+ Type storage = storageType();
+ if( _size==0 && storage!=Type.BOTTOM && storage!=Type.TOP ) {
+ unlock(); // Width participates in GVN identity.
+ _size = storeSize(storage);
+ return init();
+ }
+
+ // A precise Store can confirm its alias from a precise memory input.
+ // Do not sharpen an alias-#1 (whole-memory) Store in place: users of
+ // that Store already treat it as the complete memory state. The
+ // pointer/field path below replaces it with a precise Store wrapped in
+ // a MemMerge, preserving any other precise aliases in its input.
+ if( mem()._type instanceof TypeMem mem ) {
+ assert mem._alias > 0;
+ if( _alias != 1 && mem._alias != 1 )
+ assert _alias == mem._alias;
+ }
+
+ // Forward-ref loads eventually sharpen to a declared type
+ Field fld;
+ if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null ) {
+ // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)).
+
+ // Normally I'd like to just drop in this xform and move on, but
+ // users of the bulk Store memory might nontheless depend on the
+ // sharp memory type (e.g. I64) from the stores memory. Using the
+ // MemMerge means using the bulk memory which e.g. might be BOT.
+ // Check for store users with the same alias, and use the new store
+ // directly, preserving the sharper graph.
+ Type decl = storageType();
+ byte size = _size==0 && decl!=Type.BOTTOM && decl!=Type.TOP ? storeSize(decl) : _size;
+ Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem(),ptr(),off(),val(),_init,size).peephole();
+ for( int i=0; i<nOuts(); i++ ) {
+ Node use = out(i);
+ if( useAlias(use)==fld._alias ) {
+ int idx = use._inputs.find(this);
+ assert idx != -1;
+ use.setDef(idx,st);
+ i--; // setDef removed use from this Store's outputs
+ }
+ }
+ MemMergeNode mmm = mem() instanceof BulkMemPhiNode bulk
+ ? bulk.aggregate(mem(),fld._alias,st)
+ : new MemMergeNode(false,null,mem());
+ if( !(mem() instanceof BulkMemPhiNode) )
+ mmm.alias(fld._alias,st);
+ return mmm.init();
+ }
+ // Expose the same effective memory input already observed by compute.
+ Node aliasMem = aliasMem();
+ if( aliasMem != mem() ) {
+ setDef(1,aliasMem);
+ CodeGen.CODE.add(aliasMem);
return this;
}
@@ -73,20 +181,26 @@ public class StoreNode extends MemOpNode {
// Must have exactly one use of "this" or you get weird
// non-serializable memory effects in the worse case.
checkOnlyUse(st) ) {
- assert _name.equals(st._name); // Equiv class aliasing is perfect
+ assert _name==st._name; // Equiv class aliasing is perfect
setDef(1,st.mem());
return this;
}
// Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( mem() instanceof MemMergeNode mem ) {
+ if( _alias != 1 && mem() instanceof MemMergeNode mem ) {
setDef(1,mem.alias(_alias));
return this;
}
+ // Resolve the semantic Store variant exactly once, after structural
+ // memory/alias peeps. This avoids publishing a half-sharpened Store
+ // while BulkMemPhi is constructing precise slices.
+ if( storage != Type.BOTTOM && storage != Type.TOP )
+ assert _size==storeSize(storage) : "Store width changed for '"+_name+"': "+_size+" from "+storage;
+
// Value is automatically truncated by narrow store
- if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
- int log = _declaredType.log_size();
+ if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ int log = log_size();
if( log<3 ) { // And-mask vs narrow store
long mask = ((TypeInteger)and.in(2)._type).value();
long bits = (1L<<(8<<log))-1;
@@ -98,7 +212,7 @@ public class StoreNode extends MemOpNode {
}
// Store will chop high order bits off; math to change those bits can be dropped.
- if( val() instanceof SarNode shr &&
+ if( _size!=0 && val() instanceof SarNode shr &&
shr.in(1) instanceof ShlNode shl &&
shr.in(2)._type.isConstant() &&
shl.in(2)._type.isConstant() ) {
@@ -114,9 +228,68 @@ public class StoreNode extends MemOpNode {
return this;
}
}
+
return null;
}
+ private static byte storeSize(Type decl) {
+ // `decl` is the target field declaration, not the stored value. In
+ // particular a constant zero stored into u32 is still a 4-byte Store;
+ // taking the constant's GLB here would incorrectly select i64.
+ byte size = (byte)(1 << decl.log_size());
+ assert size==1 || size==2 || size==4 || size==8;
+ return size;
+ }
+
+ // Flow-sensitive pointer types can carry a current field value (e.g. the
+ // constant zero in a u32 field). Storage width comes from the canonical
+ // struct declaration. If the pointer/definition is not available yet,
+ // leave the Store undecided instead of consulting the value or `_con`.
+ private Type storageType() {
+ // Freshly parsed field stores retain the declaring structure on their
+ // symbolic offset even after the pointer's flow type sharpens.
+ if( off() instanceof ConFldOffNode coff ) {
+ Field fld = coff._ts.field(_name);
+ if( fld != null && fld._t != null ) return fld._t;
+ }
+ if( !(ptr()._type instanceof TypeMemPtr tmp) ) return Type.BOTTOM;
+ Type base = Parser.TYPES.get(tmp._obj._name);
+ TypeStruct obj = base instanceof TypeStruct ts ? ts : tmp._obj;
+ Field fld = obj.field(_name);
+ // Imported optimized structs can expose the current constant field
+ // value instead of its declaration. A constant has no storage width;
+ // an imported Store already carries its serialized, frozen `_size`.
+ return fld == null || fld._t == null || fld._t.isConstant() ? Type.BOTTOM : fld._t;
+ }
+
+ // Alias required by a direct consumer of this Store's memory result.
+ // Keep in sync with BulkMemPhiNode.outputAlias.
+ private int useAlias(Node use) {
+ return switch( use ) {
+ case MemOpNode mem -> mem._alias;
+ case MemPhiNode phi -> phi._alias;
+ case EscapeNode esc -> esc.pub()==this ? esc.fld()._alias : 0;
+ default -> 0;
+ };
+ }
+
+ // Select a precise alias from a whole-memory partition. Alias #1 is the
+ // bulk remainder itself.
+ private static Node memSlice( Node mem, int alias ) {
+ return alias != 1 && mem instanceof MemMergeNode merge
+ ? merge.alias(alias)
+ : mem;
+ }
+
+ // Semantic memory input for this Store. Compute defines behavior from
+ // this view; ideal exposes the same edge in the graph.
+ private Node aliasMem() {
+ Node mem = mem();
+ if( _alias != 1 && mem instanceof MemMergeNode merge )
+ mem = merge.alias(_alias);
+ return mem;
+ }
+
// Check that "mem" has no uses except "this"
private boolean checkOnlyUse(Node mem) {
if( mem.nOuts()==1 ) return true;
@@ -132,11 +305,17 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( _size==0 && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ throw Utils.TODO("Failed to decide Store size");
if( ptr()._type == Type.TOP )
return null; // This means we have an error input, report elsewhere
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- if( tmp._obj.field(_name)._final && !_init )
+ Field f = tmp._obj.field(_name);
+ if( f!=null && f._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
return null;
}
+
+ @Override public boolean eq(Node n) { return _size==((StoreNode)n)._size && super.eq(n); }
+ @Override int hash() { return super.hash() ^ _size; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java
index b5b59b81..4dcc695f 100644
--- a/src/main/java/com/seaofnodes/simple/node/StructNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -1,8 +1,11 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Build a compound object
@@ -11,13 +14,23 @@ 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 public Tag serialTag() { return Tag.Struct; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ //baos.packed1(nIns());
+ //baos.packed2(_label==null ? 0 : strs.get(_label));
+ //baos.packed2(types.get(_type)); // Write _type not _minType, which can be higher
+ throw Utils.TODO();
+ }
+ static Node make( BAOS bais, Type[] types) {
+ //Node[] ins = new Node[bais.packed1()];
+ //return new StructNode(types[bais.packed2()], ins);
+ throw Utils.TODO();
+ }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
if( _ts==null ) return sb.append("STRUCT?");
- sb.append(_ts._name).append(" {");
+ sb.append(_ts._name).append(" { ");
for( int i=0; i<nIns(); i++ ) {
sb.append(_ts._fields[i]._fname).append(":");
sb.append(in(i)==null ? Type.BOTTOM : in(i)._type);
diff --git a/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/src/main/java/com/seaofnodes/simple/node/SubFNode.java
deleted file mode 100644
index 3fc4f7ca..00000000
--- a/src/main/java/com/seaofnodes/simple/node/SubFNode.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class SubFNode extends ArithFNode {
- public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "SubF"; }
-
- @Override public String glabel() { return "-"; }
-
- @Override double doOp( double x, double y ) { return x-y; }
-
- @Override
- public Node idealize() {
- // Sub of 0.
- Type t2 = in(2)._type;
- if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
- return in(1);
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 8e4f6f79..aba1df90 100644
--- a/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -6,12 +6,15 @@ import com.seaofnodes.simple.type.TypeInteger;
import java.util.BitSet;
public class SubNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); }
+ @Override public Tag serialTag() { return Tag.Sub; }
- @Override public String label() { return "Sub"; }
@Override public String op() { return "-"; }
- @Override long doOp( long x, long y ) { return x - y; }
+ @Override long doOp( long x, long y ) { return x - y; }
+ @Override double doOp(double x, double y ) { return x - y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
// Sub of same is 0
if( in(1)==in(2) )
@@ -38,6 +41,5 @@ public class SubNode extends ArithNode {
return super.idealize();
}
- @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); }
- @Override Node copyF() { return new SubFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
index e02183a0..2d8cc788 100644
--- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -8,8 +8,7 @@ import java.util.BitSet;
public class ToFloatNode extends Node {
public ToFloatNode(Node lhs) { super(null, lhs); }
-
- @Override public String label() { return "ToFloat"; }
+ @Override public Tag serialTag() { return Tag.ToFloat; }
@Override public String glabel() { return "(flt)"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/src/main/java/com/seaofnodes/simple/node/TypeNode.java
new file mode 100644
index 00000000..3ab2d6f9
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/TypeNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashMap;
+
+// Node carries an extra internal type
+public abstract class TypeNode extends Node {
+ public Type _con;
+ public TypeNode(Type t, Node... nodes) { super(nodes); assert t!=null; _con = t; }
+ public TypeNode(TypeNode tn) { super(tn); _con = tn._con; }
+ // Copy edges from ideal, but 'this' is a Type-Mach-Node (e.g. MemOpX86) and needs a _con
+ public TypeNode(Node ideal, Type con) { super(ideal); _con = con; }
+
+ // Upgrade the internal type
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ return liftType(_con.upgradeType(TYPES));
+ }
+
+ // Lift type
+ public boolean liftType( Type t ) {
+ if( t == _con ) return false;
+ unlock(); // Unlock before changing _con
+ _con = t;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) { return _con==((TypeNode)n)._con; }
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
index 8c620a04..a8c06b37 100644
--- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -7,7 +7,7 @@ import java.util.BitSet;
public class XCtrlNode extends CFGNode {
public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
- @Override public String label() { return "Xctrl"; }
+ @Override public Tag serialTag() { return Tag.XCtrl; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); }
@Override public boolean isConst() { return true; }
@Override public Type compute() { return Type.XCONTROL; }
diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java
index fdb4863e..95d4461e 100644
--- a/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class XorNode extends ArithNode {
- public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Xor; }
- @Override public String label() { return "Xor"; }
@Override public String op() { return "^"; }
@Override public String glabel() { return "^"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
index b9ede544..5cf8a89c 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -8,9 +8,11 @@ import com.seaofnodes.simple.util.Utils;
// Jump on flags, uses flags
public class BranchARM extends IfNode implements MachNode, RIPRelSize {
String _bop;
- BranchARM(IfNode iff, String bop ) {
+ final boolean _fp;
+ BranchARM(IfNode iff, String bop, boolean fp ) {
super(iff);
_bop = bop;
+ _fp = fp;
}
@Override public String op() { return "j"+_bop; }
@Override public String label() { return op(); }
@@ -35,7 +37,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize {
// by comparison (or sub). No need for regs because it uses flags
if( in(1)!=null ) {
// B.cond
- enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop)) );
+ enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop, _fp)) );
} else {
if( _bop=="!=" ) return; // Inverted, no code
enc.add4(arm.b(arm.OP_UJMP, 0));
@@ -55,7 +57,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
if( opLen==4 ) {
- enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop)));
+ enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop, _fp)));
} else {
throw Utils.TODO();
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
index 427cf2e2..8f070525 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -13,8 +13,7 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize {
_inputs.pop(); // Pop constant target
assert tfp.isConstant();
_tfp = tfp;
- FunNode fun = CodeGen.CODE.link(tfp);
- _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ _name = CodeGen.CODE.funcName(tfp.fidx());
}
@Override public String op() { return "call"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
index b47b65ad..42553868 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
@@ -3,21 +3,22 @@ package com.seaofnodes.simple.node.cpus.arm;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.ConstantNode;
import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.type.TypeFloat;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
//FMOV (scalar, immediate)
//Floating-point move immediate.
-public class FloatARM extends ConstantNode implements MachNode, RIPRelSize {
- FloatARM(ConstantNode con) { super(con); }
+public class FltARM extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.DMASK; }
@Override public boolean isClone() { return true; }
- @Override public FloatARM copy() { return new FloatARM(this); }
+ @Override public FltARM copy() { return new FltARM(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/);
short dst = (short)(enc.reg(this) - arm.D_OFFSET);
enc.add4(arm.load_pc(arm.OPF_ARM, 0, dst));
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
index ee74b95e..dbed9487 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -6,19 +6,22 @@ import com.seaofnodes.simple.node.MachNode;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Integer constants
public class IntARM extends ConstantNode implements MachNode {
- IntARM( ConstantNode con ) { super(con); }
+ final String _ext;
+ IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldi"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public IntARM copy() { return new IntARM(this); }
+ @Override public IntARM copy() { return new IntARM(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
short self = enc.reg(this);
long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value();
int nb0 = 0;
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index 1e5d2094..f8c9bfef 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -21,16 +21,16 @@ public class LoadARM extends MemOpARM {
private static final int[] OP_LOADS = new int[]{ arm.OP_LOAD_IMM_8, arm.OP_LOAD_IMM_16, arm.OP_LOAD_IMM_32, arm.OP_LOAD_IMM_64, };
private int imm_op() {
- return _declaredType == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32
- : _declaredType == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64
- : OP_LOADS[_declaredType.log_size()];
+ return _con == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32
+ : _con == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64
+ : OP_LOADS[_con.log_size()];
}
private static final int[] OP_LOAD_RS = new int[]{ arm.OP_LOAD_R_8 , arm.OP_LOAD_R_16 , arm.OP_LOAD_R_32 , arm.OP_LOAD_R_64, };
// ldr(immediate - unsigned offset) | ldr(register)
@Override public void encoding( Encoding enc ) {
- ldst_encode(enc, imm_op(), OP_LOAD_RS[_declaredType.log_size()], this, size());
+ ldst_encode(enc, imm_op(), OP_LOAD_RS[_con.log_size()], this, size());
}
@Override public void asm(CodeGen code, SB sb) {
sb.p(code.reg(this)).p(",");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
index e877931d..08bdf974 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
@@ -11,7 +11,8 @@ import java.util.BitSet;
public abstract class MemOpARM extends MemOpNode implements MachNode {
final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned
final int _imm; // Limit ? bits
- final char _sz = (char)('0'+(1<<_declaredType.log_size()));
+ final int _bytes;
+ final char _sz;
MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) {
super(mop,mop);
assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode);
@@ -20,6 +21,8 @@ public abstract class MemOpARM extends MemOpNode implements MachNode {
_inputs.setX(3, idx);
_off = off;
_imm = imm;
+ _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+_bytes);
}
// Store-based flavors have a value edge
@@ -36,7 +39,7 @@ public abstract class MemOpARM extends MemOpNode implements MachNode {
@Override public Type compute() { throw Utils.TODO(); }
@Override public Node idealize() { throw Utils.TODO(); }
- int size() { return 1<<_declaredType.log_size(); }
+ int size() { return _bytes; }
// Wider mask to store both GPRs and FPRs
@Override public RegMask regmap(int i) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
index c1aad15f..df2a6bc9 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -31,7 +31,8 @@ public class NewARM extends NewNode implements MachNode, RIPRelSize {
}
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("ldi x0=#1\n");
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
sb.p("call #calloc");
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
index e1b3b3b6..c597b9dd 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -8,12 +8,14 @@ import com.seaofnodes.simple.util.SB;
// e.g CSET, stores flag info into GPRS after compare
public class SetARM extends MachConcreteNode implements MachNode {
final String _bop; // One of <,<=,==
- SetARM(Node cmp, String bop) {
+ final boolean _fp; // FP compare (unsigned encoding)
+ SetARM(Node cmp, String bop, boolean fp) {
super(cmp);
_inputs.setLen(1); // Pop the cmp inputs
// Replace with the matched cmp
_inputs.push(cmp);
_bop = bop;
+ _fp = fp;
}
@Override public String op() { return "set"+_bop; }
@Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
@@ -21,7 +23,7 @@ public class SetARM extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
// Using RM==RN==31 implies a *zero* value, not R31.
- int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop), 0b011111, enc.reg(this));
+ int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop, _fp), 0b011111, enc.reg(this));
enc.add4(body);
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
index 5af45892..be975fb6 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
@@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitARM extends SplitNode {
- SplitARM(String kind, byte round) { super(kind,round,new Node[2]);}
+ SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);}
@Override public RegMask regmap(int i) { return arm.SPLIT_MASK; }
@Override public RegMask outregmap() { return arm.SPLIT_MASK; }
@@ -24,7 +24,7 @@ public class SplitARM extends SplitNode {
if(src >= arm.MAX_REG) {
throw Utils.TODO();
}
- int off = enc._fun.computeStackOffset(enc._code,dst);
+ int off = fun(enc).computeStackOffset(enc._code,dst);
if( srcX ) src -= arm.D_OFFSET;
enc.add4(arm.load_str_imm(arm.OP_STORE_IMM_64, off, arm.RSP, src, 8));
return;
@@ -32,7 +32,7 @@ public class SplitARM extends SplitNode {
if(src >= arm.MAX_REG) {
// Load from SP
- int off = enc._fun.computeStackOffset(enc._code,src);
+ int off = fun(enc).computeStackOffset(enc._code,src);
if( dstX ) dst -= arm.D_OFFSET;
enc.add4(arm.load_str_imm(arm.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
return;
@@ -56,10 +56,4 @@ public class SplitARM extends SplitNode {
enc.add4(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst));
}
}
-
- // General form: "mov dst = src"
- @Override public void asm(CodeGen code, SB sb) {
- FunNode fun = code._encoding==null ? null : code._encoding._fun;
- sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
index 91385ac9..b11a2c89 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
@@ -25,15 +25,15 @@ public class StoreARM extends MemOpARM {
private static final int[] OP_STORES = new int[]{ arm.OP_STORE_IMM_8, arm.OP_STORE_IMM_16, arm.OP_STORE_IMM_32, arm.OP_STORE_IMM_64, };
private int imm_op() {
- return _declaredType == TypeFloat.F32 ? arm.OPF_STORE_IMM_32
- : _declaredType == TypeFloat.F64 ? arm.OPF_STORE_IMM_64
- : OP_STORES[_declaredType.log_size()];
+ return val()!=null && val()._type instanceof TypeFloat
+ ? (_bytes==4 ? arm.OPF_STORE_IMM_32 : arm.OPF_STORE_IMM_64)
+ : OP_STORES[Integer.numberOfTrailingZeros(_bytes)];
}
private static final int[] OP_STORE_RS = new int[]{ arm.OP_STORE_R_8, arm.OP_STORE_R_16, arm.OP_STORE_R_32, arm.OP_STORE_R_64, };
@Override public void encoding( Encoding enc ) {
- ldst_encode(enc, imm_op(), OP_STORE_RS[_declaredType.log_size()], val(), size());
+ ldst_encode(enc, imm_op(), OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size());
}
@Override public void asm(CodeGen code, SB sb) {
asm_address(code,sb).p(",");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
index 29d490ef..9d490c8b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -1,28 +1,30 @@
package com.seaofnodes.simple.node.cpus.arm;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
- TFPARM( ConstantNode con ) { super(con); }
+public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize {
+ final String _ext;
+ // Pointer to a Simple function
+ TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; }
+ // Pointer to an Extern "C" function
+ TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
@Override public String op() { return "ldx"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TFPARM copy() { return new TFPARM(this); }
+ @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); }
@Override public void encoding( Encoding enc ) {
- enc.relo(this);
- short self = enc.reg(this);
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
+ short dst = enc.reg(this);
// adrp x0, 0
- int adrp = arm.adrp(1,0, arm.OP_ADRP, 0,self);
+ enc.add4(arm.adrp(1,0, arm.OP_ADRP, 0,dst));
// add x0, x0, 0
- enc.add4(adrp);
- arm.imm_inst(enc,arm.OPI_ADD,0, 0);
+ arm.imm_inst(enc,arm.OPI_ADD,0, dst);
}
@Override public byte encSize(int delta) {
@@ -33,15 +35,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short rpc = enc.reg(this);
if(opLen == 8 ) {
- // ARM encoding delta is from PC & 0xFFF
- int target = opStart+delta;
- int base = opStart & ~0xFFF;
- delta = target-base;
- int adrp_delta = delta >> 12;
- // patch upper 20 bits via adrp
- enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc));
- // low 12 bits via add
- enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, rpc));
+ arm.patch_adrp_add(enc, opStart, delta, rpc);
} else {
// should not happen as one instruction is 4 byte, and TFP arm encodes 2.
throw Utils.TODO();
@@ -50,7 +44,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
@Override public void asm(CodeGen code, SB sb) {
String reg = code.reg(this);
- _con.print(sb.p(reg).p(" #"));
+ _type.print(sb.p(reg).p(" #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
index cdcaa433..f86d85dc 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
@@ -9,13 +9,15 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
- TMPARM( ConstantNode con ) { super(con); }
+ final String _ext;
+ TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldp"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TMPARM copy() { return new TMPARM(this); }
+ @Override public TMPARM copy() { return new TMPARM(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
short dst = enc.reg(this);
// adrp x0, 0
@@ -32,15 +34,7 @@ public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short dst = enc.reg(this);
if(opLen == 8 ) {
- // ARM encoding delta is from PC & 0xFFF
- int target = opStart+delta;
- int base = opStart & ~0xFFF;
- delta = target-base;
- int adrp_delta = delta >> 12;
- // patch upper 20 bits via adrp
- enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst));
- // low 12 bits via add
- enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, dst));
+ arm.patch_adrp_add(enc, opStart, delta, dst);
} else {
throw Utils.TODO();
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index a5dac751..852fbdb1 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -466,9 +466,19 @@ public class arm extends Machine {
// int l
public static int adrp(int op, int imlo,int opcode, int imhi, int rd) {
assert 0 <= rd && rd < 32;
+ assert 0 <= imlo && imlo <= 0x3; // 2 bits
+ assert 0 <= imhi && imhi <= 0x7FFFF; // 19 bits
return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd;
}
+ public static void patch_adrp_add(Encoding enc, int opStart, int delta, int rd) {
+ int target = opStart + delta;
+ int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12;
+ int imm21 = pageDelta & 0x1FFFFF;
+ enc.patch4(opStart , adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd));
+ enc.patch4(opStart+4, imm_inst_l(OPI_ADD, target & 0xFFF, rd));
+ }
+
// [Rptr+Roff]
public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) {
assert 0 <= ptr && ptr < 32;
@@ -514,19 +524,31 @@ public class arm extends Machine {
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 = f_cmp(0b00011110, 3, reg1, reg2);
+ int body = f_cmp(0b00011110, 3, reg2, reg1);
enc.add4(body);
}
- public static COND make_condition(String bop) {
- return switch (bop) {
- case "==" -> COND.EQ;
- case "!=" -> COND.NE;
- case "<" -> COND.LT;
- case "<=" -> COND.LE;
- case ">=" -> COND.GE;
- case ">" -> COND.GT;
- default -> throw Utils.TODO();
+ public static COND make_condition(String bop, boolean fp) {
+ // One of {N,Z,C} and !V
+ // OR { !N, !Z, C, V }
+ return fp
+ ? switch (bop) { // FP uses unsigned encodings
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.MI; // N
+ case "<=" -> COND.LS; // Z || !C
+ case ">=" -> COND.PL; // !N
+ case ">" -> COND.HI; // !Z && C
+ default -> throw Utils.TODO();
+ }
+ : switch (bop) {
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.LT; // N != V
+ case "<=" -> COND.LE; // Z || N != V
+ case ">=" -> COND.GE; // N == V
+ case ">" -> COND.GT; // !Z && N == V
+ default -> throw Utils.TODO();
};
}
@@ -594,10 +616,16 @@ public class arm extends Machine {
if( idx==1 ) return null;
// Count floats in signature up to index
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ boolean hidden = hiddenSelf(tfp);
+ int sigidx = idx-2;
+ if( hidden ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
// Count floats in signature up to index
int fcnt=0;
- for( int i=2; i<idx; i++ )
- if( tfp.arg(i-2) instanceof TypeFloat)
+ for( int i=hidden ? 1 : 0; i<idx-2; i++ )
+ if( tfp.arg(i) instanceof TypeFloat)
fcnt++;
// Floats up to XMMS in XMM registers
if( tfp.arg(idx-2) instanceof TypeFloat ) {
@@ -605,24 +633,27 @@ public class arm extends Machine {
return XMMS[fcnt];
} else {
RegMask[] cargs = CALLINMASK;
- if( idx-2-fcnt < cargs.length )
- return cargs[idx-2-fcnt];
+ if( sigidx-fcnt < cargs.length )
+ return cargs[sigidx-fcnt];
}
// Pass on stack slot (8 and higher)
if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + (idx - 2));
+ return new RegMask(MAX_REG + 1 + sigidx);
}
// Return the max stack slot used by this signature, or 0
@Override public short maxArgSlot( TypeFunPtr tfp ) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=0; i<tfp.nargs(); i++ ) {
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
return (short)nstk;
}
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
private static final long CALLEE_SAVE =
1L<<X19 |
@@ -640,7 +671,7 @@ public class arm extends Machine {
@Override public int rpc() { return X30; }
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); }
+ @Override public SplitNode split(LRG lrg, String kind, byte round) { return new SplitARM(lrg,kind,round); }
// Return a MachNode unconditional branch
@Override public CFGNode jump() {
@@ -650,40 +681,47 @@ public class arm extends Machine {
// Instruction selection
@Override public Node instSelect(Node n ) {
return switch( n ) {
- case AddFNode addf -> new AddFARM(addf);
case AddNode add -> add(add);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CallNode call -> call(call);
- case CastNode cast -> new CastMach(cast);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend-> new CallEndMach(cend);
case CProjNode c -> new CProjNode(c);
case ConstantNode con-> con(con);
- case DivFNode divf -> new DivFARM(divf);
- case DivNode div -> new DivARM(div);
+ case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
+ case EscapeNode esc -> new EscapeNode(esc);
case FunNode fun -> new FunARM(fun);
+ case FunPtrNode fptr -> fptr(fptr);
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem-> new MemMergeNode(mem);
- case MinusNode neg -> new NegARM(neg);
- case MulFNode mulf -> new MulFARM(mulf);
- case MulNode mul -> new MulARM(mul);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegARM(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul);
case NewNode nnn -> new NewARM(nnn);
case NotNode not -> new NotARM(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmARM(parm);
+ case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> new ProjARM(prj);
+ 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 StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubFNode subf -> new SubFARM(subf);
- case SubNode sub -> sub(sub);
+ case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub);
case ToFloatNode tfn -> new I2F8ARM(tfn);
case XorNode xor -> xor(xor);
@@ -695,7 +733,7 @@ public class arm extends Machine {
private Node cmp(BoolNode bool){
Node cmp = _cmp(bool);
- return new SetARM(cmp, bool.op());
+ return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM);
}
private Node _cmp(BoolNode bool) {
if( bool.isFloat() )
@@ -714,13 +752,18 @@ public class arm extends Machine {
// Most general arith ops will also set flags, which the Jmp needs directly.
// Loads do not set the flags, and will need an explicit TEST
String op = "!=";
- if( iff.in(1) instanceof BoolNode bool ) op = bool.op();
+ boolean fp = false;
+ if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); }
else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
- return new BranchARM(iff, op);
+ else iff.setDef(1, new BoolNode.NE(iff.in(1),
+ ConstantNode.raw(TypeInteger.ZERO),
+ (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
+ return new BranchARM(iff, op, fp);
}
private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFARM(add);
return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti)
? new AddIARM(add, (int)ti.value())
: new AddARM(add);
@@ -733,22 +776,30 @@ public class arm extends Machine {
}
private Node con( ConstantNode con ) {
- if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
return switch( con._con ) {
- case TypeInteger ti -> new IntARM(con);
- case TypeFloat tf -> new FloatARM(con);
- case TypeFunPtr tfp -> new TFPARM(con);
- case TypeMemPtr tmp -> new TMPARM(con);
+ case TypeInteger ti -> new IntARM(con,ext);
+ case TypeFloat tf -> new FltARM(con,ext);
+ case TypeMemPtr tmp -> new TMPARM(con,ext);
+ case TypeFunPtr tfp -> new TFPARM(tfp,ext);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con);
+ case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con);
};
}
+ private Node fptr( FunPtrNode con ) {
+ return new TFPARM(con);
+ }
+
private Node call(CallNode call){
- return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
- ? new CallARM(call, tfp)
- : new CallRRARM(call);
+ return call.fptr() instanceof FunPtrNode fptr
+ ? new CallARM(call, (TypeFunPtr)fptr._type)
+ : call.fptr() instanceof ConstantNode con
+ ? new CallARM(call, (TypeFunPtr)con._type)
+ : new CallRRARM(call);
}
private Node or(OrNode or) {
@@ -790,6 +841,10 @@ public class arm extends Machine {
: new LsrARM(lsr);
}
+ private Node prj(ProjNode prj) {
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
+ }
+
private static int off;
private static Node idx;
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
index 2cd00845..1c157bc1 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
@@ -1,19 +1,19 @@
package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.FunPtrNode;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
// Add upper 20bits to PC. Immediate comes from the relocation info.
-public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
- AUIPC( Type tfp ) { super(tfp); }
+public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize {
+ AUIPC( FunPtrNode fptr ) { super(fptr); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public AUIPC copy() { return new AUIPC(_con); }
+ @Override public AUIPC copy() { return new AUIPC(this); }
@Override public String op() { return "auipc"; }
@Override public void encoding( Encoding enc ) {
enc.relo(this);
@@ -36,7 +36,6 @@ public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
@Override public void asm(CodeGen code, SB sb) {
String reg = code.reg(this);
sb.p(reg).p(" = PC+#");
- if( _con == null ) sb.p("---");
- else _con.print(sb);
+ _type.print(sb);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
index bd5dabe5..8e1b4a48 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -14,8 +14,7 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize {
assert tfp.isConstant();
_inputs.pop(); // Pop constant target
_tfp = tfp;
- FunNode fun = CodeGen.CODE.link(tfp);
- _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ _name = CodeGen.CODE.funcName(tfp.fidx());
}
@Override public String op() { return "call"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
index 298789e8..7369f9f5 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
@@ -13,7 +13,7 @@ public class CallRRISC extends CallNode implements MachNode {
? riscv.RMASK // Function call target
: riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument
}
- @Override public RegMask outregmap() { return null; }
+ @Override public RegMask outregmap() { return riscv.RPC_MASK; }
@Override public void encoding( Encoding enc ) {
short rpc = enc.reg(this);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
index 81726200..c1a9b018 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -8,12 +8,13 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class FltRISC extends ConstantNode implements MachNode, RIPRelSize {
- FltRISC(ConstantNode con) { super(con); }
+ final String _ext;
+ FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.FMASK; }
@Override public boolean isClone() { return true; }
- @Override public FltRISC copy() { return new FltRISC(this); }
+ @Override public FltRISC copy() { return new FltRISC(this,_ext); }
@Override public void encoding( Encoding enc ) {
enc.largeConstant(this,_con, 0, -1);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
index edd1850f..e6b4c4ed 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
@@ -4,21 +4,23 @@ import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.ConstantNode;
import com.seaofnodes.simple.node.MachNode;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Special instruction for loading 8 byte constants from the constant pool.
public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize {
- public Int8RISC(ConstantNode con) { super(con); }
-
+ final String _ext;
+ Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public Int8RISC copy() { return new Int8RISC(this); }
+ @Override public Int8RISC copy() { return new Int8RISC(this,_ext); }
@Override public byte encSize(int delta) { return 8; }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
short dst = enc.reg(this);
short tmp = (short)riscv.T6;
enc.largeConstant(this,_con, 0, -1);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
index f8af913a..b795ba96 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
@@ -10,7 +10,7 @@ import com.seaofnodes.simple.util.SB;
// 12-bit integer constant. Larger constants are made up in the instruction
// selection by adding with a LUI.
public class IntRISC extends ConstantNode implements MachNode {
- public IntRISC(ConstantNode con) { super(con); }
+ IntRISC(ConstantNode con) { super(con); }
@Override public String op() { return "ldi"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
index 7e08a01d..592cdb84 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -2,7 +2,9 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Load memory addressing on RISC
// Support imm, reg(direct), or reg+off(indirect) addressing
@@ -21,6 +23,39 @@ public class LoadRISC extends MemOpRISC {
if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET;
enc.add4(riscv.i_type(op, dst, func3(), ptr, _off));
}
+ @Override int func3() {
+ int func3 = -1;
+ // no unsigned flavour for store, so both signed and unsigned trigger the same
+ Type declType = _con;
+ if( declType == TypeInteger. I8 ) func3=0; // LB
+ if( declType == TypeInteger.I16 ) func3=1; // LH
+ if( declType == TypeInteger.I32 ) func3=2; // LW
+ if( declType == TypeInteger.BOT ) func3=3; // LD
+ if( declType == TypeInteger. U8 ) func3=4; // LBU
+ if( declType == TypeInteger.BOOL) func3=4; // LBU
+ if( declType == TypeInteger.U16 ) func3=5; // LHU
+ if( declType == TypeInteger.U32 ) func3=6; // LWU
+ if( declType instanceof TypeInteger ti ) {
+ if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB
+ }
+
+ // float
+ if( declType == TypeFloat.F32) func3 = 2; // fLW fSW
+ if( declType == TypeFloat.F64) func3 = 3; // fLD fSD
+
+ if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld)
+ if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers
+ if( declType instanceof TypeConAry ary )
+ func3 = switch( ary.log_size() ) {
+ case 0 -> 4; // LBU
+ case 1 -> 5; // LHU
+ case 2 -> 2; // LW
+ case 3 -> 3; // LD
+ default -> throw Utils.TODO("RISC-V constant-array load for "+declType);
+ };
+ if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType);
+ return func3;
+ }
@Override public void asm(CodeGen code, SB sb) {
sb.p(code.reg(this)).p(",");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
index b9e6e5ba..79f22fb8 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -10,6 +10,7 @@ import java.util.BitSet;
public abstract class MemOpRISC extends MemOpNode implements MachNode {
final int _off; // Limit 12 bits
final char _sz;
+ final int _bytes;
MemOpRISC(MemOpNode mop, Node base, int off, Node val) {
super(mop,mop);
assert base._type instanceof TypeMemPtr;
@@ -17,7 +18,8 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode {
_inputs.setX(3, null); // Never an index
_inputs.setX(4, val );
_off = off;
- _sz = (char)('0'+(1<<_declaredType.log_size()));
+ _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+_bytes);
}
@Override public String label() { return op(); }
@@ -29,38 +31,7 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode {
@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;
- }
+ abstract int func3();
// Register mask allowed on input i.
@Override public RegMask regmap(int i) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
index 6c03bda6..4a6583ce 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -40,8 +40,9 @@ public class NewRISC extends NewNode implements MachNode, RIPRelSize {
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("ldi a1=#1\n");
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
sb.p("auipc a2=#calloc\n");
- sb.p("call a2+#calloc, a0");
+ sb.p("call a2+#calloc");
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
index 6082cbbf..58fa8a4b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitRISC extends SplitNode {
- SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); }
+ SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); }
@Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; }
@Override public RegMask outregmap() { return riscv.SPLIT_MASK; }
@Override public void encoding( Encoding enc ) {
@@ -24,7 +24,7 @@ public class SplitRISC extends SplitNode {
if( src >= riscv.MAX_REG ) {
throw Utils.TODO(); // Very rare stack-stack move
}
- int off = enc._fun.computeStackOffset(enc._code,dst);
+ int off = fun(enc).computeStackOffset(enc._code,dst);
int op = srcX ? riscv.OP_STOREFP : riscv.OP_STORE;
if( srcX ) src -= riscv.F_OFFSET;
enc.add4(riscv.s_type(op, 0b011, riscv.RSP, src, off));
@@ -32,7 +32,7 @@ public class SplitRISC extends SplitNode {
}
if( src >= riscv.MAX_REG ) {
// Load from SP
- int off = enc._fun.computeStackOffset(enc._code,src);
+ int off = fun(enc).computeStackOffset(enc._code,src);
int op = dstX ? riscv.OP_LOADFP : riscv.OP_LOAD;
if( dstX ) dst -= riscv.F_OFFSET;
enc.add4(riscv.i_type(op, dst, 0b011, riscv.RSP, off));
@@ -57,10 +57,4 @@ public class SplitRISC extends SplitNode {
enc.add4(riscv.r_type(riscv.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001));
}
}
-
- // General form: "mov dst = src"
- @Override public void asm(CodeGen code, SB sb) {
- FunNode fun = code._encoding==null ? null : code._encoding._fun;
- sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
index d06c0c35..ebd6a5ae 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -3,6 +3,7 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.codegen.RegMask;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
@@ -39,6 +40,11 @@ public class StoreRISC extends MemOpRISC {
enc.add4(riscv.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off));
}
+ // func3 is based on load/store size and extend
+ @Override int func3() {
+ return Integer.numberOfTrailingZeros(_bytes);
+ }
+
@Override public void asm(CodeGen code, SB sb) {
asm_address(code,sb).p(",");
if( val()==null ) sb.p("#").p("0");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
index 7e057507..1f269152 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -1,46 +1,46 @@
package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
- TFPRISC(ConstantNode con) { super(con); }
+public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize {
+ final String _ext;
+ // Pointer to a Simple function
+ TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; }
+ // Pointer to an Extern "C" function
+ TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
@Override public String op() { return "ldx"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TFPRISC copy() { return new TFPRISC(this); }
+ @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); }
@Override public void encoding( Encoding enc ) {
- enc.relo(this);
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
// TODO: 1 op encoding, plus a TODO if it does not fit
short dst = enc.reg(this);
- TypeFunPtr tfp = (TypeFunPtr)_con;
// auipc t0,0
enc.add4(riscv.u_type(riscv.OP_AUIPC, dst, 0));
// addi t1,t0 + #0
enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, dst, 0));
}
- @Override public byte encSize(int delta) {
- if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4;
- throw Utils.TODO();
- }
+ @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 ) {
+ int hi20 = (delta + 0x800) >> 12;
+ int lo12 = delta - (hi20 << 12);
// AUIPC (upper 20 bits)
// opstart of add
- int next = opStart + opLen;
- enc.patch4(opStart,riscv.u_type(riscv.OP_AUIPC, rpc, delta>>12));
+ enc.patch4(opStart ,riscv.u_type(riscv.OP_AUIPC, rpc, hi20));
// addi(low 12 bits)
- enc.patch4(next,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, delta & 0xFFF));
+ enc.patch4(opStart+4,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF));
// addi
} else {
// should not happen as one instruction is 4 byte, and TFP arm encodes 2.
@@ -49,7 +49,7 @@ public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
}
@Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" #"));
+ _type.print(sb.p(code.reg(this)).p(" #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
index 44e73d1d..aa001e1e 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
@@ -6,15 +6,18 @@ 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 TMPRISC extends ConstantNode implements MachNode, RIPRelSize {
- TMPRISC(ConstantNode con) { super(con); }
+ final String _ext;
+ TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldp"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TMPRISC copy() { return new TMPRISC(this); }
+ @Override public TMPRISC copy() { return new TMPRISC(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
short dst = enc.reg(this);
// AUIPC dst,#hi20_constant_pool
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 7a6361bc..078a7693 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -13,7 +13,6 @@ public class riscv extends Machine {
}
@Override public String name() {return "riscv";}
- //@Override public int maxReg() { return MAX_REG; }
// Using ABI names instead of register names
public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7;
@@ -32,24 +31,24 @@ public class riscv extends Machine {
public static final int F_OFFSET = 32;
static final String[] REGS = new String[] {
- "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
- "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
- "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
- "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
- "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
- "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
- "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
- "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
- // Register-based naming hella easier to debug than "official" register
- // names when single-stepping in debugger.
- //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
- //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
- //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
- //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
+ //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
+ //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
+ //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
//"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
//"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
//"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
//"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ // Register-based naming hella easier to debug than "official" register
+ // names when single-stepping in debugger.
+ "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
+ "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
+ "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
+ "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
};
@Override public String[] regs() { return REGS; }
@@ -91,7 +90,7 @@ public class riscv extends Machine {
public static RegMask FA6_MASK = new RegMask(FA6);
public static RegMask FA7_MASK = new RegMask(FA7);
- // Int arguments calling conv
+ // Int/ptr arguments calling convention
static RegMask[] CALLINMASK = new RegMask[] {
A0_MASK,
A1_MASK,
@@ -257,10 +256,16 @@ public class riscv extends Machine {
if( idx==0 ) return RPC_MASK;
if( idx==1 ) return null;
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ boolean hidden = hiddenSelf(tfp);
+ int sigidx = idx-2;
+ if( hidden ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
// Count floats in signature up to index
int fcnt=0;
- for( int i=2; i<idx; i++ )
- if( tfp.arg(i-2) instanceof TypeFloat )
+ for( int i=hidden ? 1 : 0; i<idx-2; i++ )
+ if( tfp.arg(i) instanceof TypeFloat )
fcnt++;
// Floats up to XMMS in XMM registers
if( tfp.arg(idx-2) instanceof TypeFloat ) {
@@ -268,23 +273,26 @@ public class riscv extends Machine {
return XMMS[fcnt];
} else {
RegMask[] cargs = CALLINMASK;
- if( idx-2-fcnt < cargs.length )
- return cargs[idx-2-fcnt];
+ if( sigidx-fcnt < cargs.length )
+ return cargs[sigidx-fcnt];
}
// Pass on stack slot(8 and higher)
if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + (idx - 2));
+ return new RegMask(MAX_REG + 1 + sigidx);
}
@Override public short maxArgSlot( TypeFunPtr tfp ) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=0; i<tfp.nargs(); i++ ) {
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
return (short)nstk;
}
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
static final long CALLER_SAVE =
(1L<<RPC) |
@@ -306,7 +314,7 @@ public class riscv extends Machine {
@Override public CFGNode jump() { return new UJmpRISC(); }
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); }
+ @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); }
// True if signed 12-bit immediate
public static boolean imm12(TypeInteger ti) {
@@ -323,28 +331,34 @@ public class riscv extends Machine {
@Override public Node instSelect( Node n ) {
return switch (n) {
- case AddFNode addf -> addf(addf);
case AddNode add -> add(add);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CallNode call -> call(call);
- case CastNode cast -> new CastMach(cast);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend -> new CallEndMach(cend);
case CProjNode c -> new CProjNode(c);
case ConstantNode con -> con(con);
- case DivFNode divf -> new DivFRISC(divf);
- case DivNode div -> new DivRISC(div);
+ case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
+ case EscapeNode esc -> new EscapeNode(esc);
case FunNode fun -> new FunRISC(fun);
+ case FunPtrNode fptr -> fptr(fptr);
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> new NegRISC(neg);
- case MulFNode mulf -> new MulFRISC(mulf);
- case MulNode mul -> new MulRISC(mul);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegRISC(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul);
case NewNode nnn -> nnn(nnn);
case NotNode not -> new NotRISC(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmRISC(parm);
+ case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read-> new ReadOnlyMach(read);
@@ -352,11 +366,12 @@ public class riscv extends Machine {
case SarNode sar -> sra(sar);
case ShlNode shl -> sll(shl);
case ShrNode shr -> srl(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubFNode subf -> new SubFRISC(subf);
- case SubNode sub -> sub(sub);
+ case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
case XorNode xor -> xor(xor);
@@ -366,11 +381,9 @@ public class riscv extends Machine {
};
}
- private Node addf(AddFNode addf) {
- return new AddFRISC(addf);
- }
-
private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFRISC(add);
if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) )
return new AddIRISC(add, (int)ti.value(),true);
return new AddRISC(add);
@@ -388,8 +401,10 @@ public class riscv extends Machine {
}
private Node call(CallNode call) {
- return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
- ? new CallRISC(call, tfp)
+ return call.fptr() instanceof FunPtrNode fptr
+ ? new CallRISC(call, (TypeFunPtr)fptr._type)
+ : call.fptr() instanceof ConstantNode con
+ ? new CallRISC(call, (TypeFunPtr)con._type)
: new CallRRISC(call);
}
private Node nnn(NewNode nnn) {
@@ -428,9 +443,13 @@ public class riscv extends Machine {
}
private Node con( ConstantNode con ) {
- if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
return switch( con._con ) {
case TypeInteger ti -> {
+ // External; without knowing the size assume we have to load something
+ if( ext!=null ) yield new Int8RISC(con,ext);
if( imm12(ti) ) yield new IntRISC(con);
long x = ti.value();
if( imm20Exact(ti) ) yield new LUI((int)x);
@@ -443,18 +462,22 @@ public class riscv extends Machine {
}
// Need more complex sequence for larger constants... or a load
// from a constant pool, which does not need an extra register
- yield new Int8RISC(con);
+ yield new Int8RISC(con,null);
}
// Load from constant pool
- case TypeFloat tf -> new FltRISC(con);
- case TypeFunPtr tfp -> new TFPRISC(con);
- case TypeMemPtr tmp -> new TMPRISC(con);
+ case TypeFloat tf -> new FltRISC(con,ext);
+ case TypeMemPtr tmp -> new TMPRISC(con,ext);
+ case TypeFunPtr tfp -> new TFPRISC(tfp,ext);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con);
+ case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con);
};
}
+ private Node fptr( FunPtrNode con ) {
+ return new TFPRISC(con);
+ }
+
private Node jmp( IfNode iff ) {
if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) {
// if less than or equal switch inputs
@@ -509,7 +532,7 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
- return new ProjRISC(prj);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
}
private Node ld(LoadNode ld) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
index 2513de37..093e2bd2 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class AddFMemX86 extends MemOpX86 {
- AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
+ AddFMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
super(add,ld, base, idx, off, scale, 0, val );
}
@Override public String op() { return "addf"+_sz; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
index 460f568b..14b8e4da 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
@@ -20,7 +20,7 @@ public class CallRX86 extends CallNode implements MachNode {
// calls the function in the register
short src = enc.reg(fptr());
enc.add1(0xFF);
- enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,2,src));
}
@Override public void asm(CodeGen code, SB sb) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
index d74e3833..989895b2 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -8,21 +8,27 @@ import com.seaofnodes.simple.util.SB;
public class CallX86 extends CallNode implements MachNode, RIPRelSize {
final TypeFunPtr _tfp;
final String _name;
+ final boolean _extern;
CallX86( CallNode call, TypeFunPtr tfp ) {
super(call);
_inputs.pop(); // Pop constant target
assert tfp.isConstant();
_tfp = tfp;
- FunNode fun = CodeGen.CODE.link(tfp);
- _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ _name = CodeGen.CODE.funcName(tfp.fidx());
+ _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null;
}
@Override public String op() { return "call"; }
@Override public String label() { return op(); }
@Override public String name() { return _name; }
@Override public TypeFunPtr tfp() { return _tfp; }
- @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); }
+ @Override public RegMask regmap(int i) {
+ return _extern
+ ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv)
+ : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot);
+ }
@Override public RegMask outregmap() { return null; }
@Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
+ @Override public boolean external() { return _extern; }
@Override public void encoding( Encoding enc ) {
FunNode fun = CodeGen.CODE.link(_tfp);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
index 11f900ed..24812fea 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -25,7 +25,7 @@ public class CmpMemX86 extends MemOpX86 {
if(src == -1) {
assert !_swap;
- int log = _declaredType.log_size();
+ int log = _con.log_size();
x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
switch(log) {
case 0: enc.add1(0x80); break;
@@ -41,7 +41,7 @@ public class CmpMemX86 extends MemOpX86 {
case 3: enc.add8(_imm); break;
}
} else {
- encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap);
+ encVal(enc, _con, ptr, idx, src, _off, _scale, _swap);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
index 9bcbac8f..15e95374 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -3,17 +3,20 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{
private byte _encSize;
- FltX86(ConstantNode con ) { super(con); }
+ final String _ext;
+ FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.XMASK; }
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new FltX86(this); }
+ @Override public Node copy() { return new FltX86(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
// Load the constant into a FPR
// movsd xmm, [rip + 0]
// F2 0F 10 /r MOVSD xmm1, m64
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
index cbd5fdbf..1724109a 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -10,6 +10,7 @@ public class FunX86 extends FunNode implements MachNode {
FunX86( FunNode fun ) { super(fun); }
@Override public void postSelect(CodeGen code) {
super.postSelect(code);
+ _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv);
// One more for RPC pre-pushed; X86 special
_maxArgSlot++;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
index 3f2f696c..1973ea4e 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -5,15 +5,17 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Integer constants
public class IntX86 extends ConstantNode implements MachNode {
- IntX86( ConstantNode con ) { super(con); }
+ final String _ext;
+ IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() {
return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi";
}
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new IntX86(this); }
+ @Override public Node copy() { return new IntX86(this,_ext); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
// Zero-set uses XOR kills flags
@@ -33,6 +35,7 @@ public class IntX86 extends ConstantNode implements MachNode {
return;
}
+ if( _ext!=null ) throw Utils.TODO();
// Simply move the constant into a GPR
// Conditional encoding based on 64 or 32 bits
//REX.W + C7 /0 id MOV r/m64, imm32
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
index 441cf693..c55002c1 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -12,7 +12,7 @@ public class LoadX86 extends MemOpX86 {
super(ld,ld, base, idx, off, scale, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; }
+ @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; }
@Override public void encoding( Encoding enc ) {
// REX.W + 8B /r MOV r64, r/m64
// Zero extension for u8, u16 and u32 but sign extension i8, i16, i32
@@ -20,7 +20,7 @@ public class LoadX86 extends MemOpX86 {
short dst = enc.reg(this );
short ptr = enc.reg(ptr());
short idx = enc.reg(idx());
- enc(enc, _declaredType, dst, ptr, idx, _off, _scale);
+ enc(enc, _con, dst, ptr, idx, _off, _scale);
}
static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
index 5f090717..6069c07b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -41,7 +41,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode {
_off = off;
_scale = scale;
_imm = imm;
- _sz = (char)('0'+(1<<_declaredType.log_size()));
+ int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+size);
}
// Store-based flavors have a value edge
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
index 280b8185..1e9e708f 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
@@ -19,7 +19,7 @@ public class MulIX86 extends MachConcreteNode implements MachNode {
@Override public final void encoding( Encoding enc ) {
short dst = enc.reg(this ); // Also src1
short src = enc.reg(in(1));
- enc.add1(x86_64_v2.rex(src, dst, 0));
+ enc.add1(x86_64_v2.rex(dst, src, 0));
// opcode; 0x69 or 0x6B
enc.add1( 0x69 + (x86_64_v2.imm8(_imm) ? 2 : 0) );
enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src) );
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
index fc896dce..40a38809 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -18,7 +18,8 @@ public class NewX86 extends NewNode implements MachNode {
}
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("ldi rcx = #1\n");
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
sb.p("call #calloc");
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
index 4a14240a..bc3d421d 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -11,8 +11,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitX86 extends SplitNode {
- SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); }
- @Override public String op() { return "mov"; }
+ SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); }
@Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
@Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
@@ -49,10 +48,10 @@ public class SplitX86 extends SplitNode {
// Stack spills
if( dst >= x86_64_v2.MAX_REG ) {
- int off = enc._fun.computeStackOffset(enc._code,dst);
+ int off = fun(enc).computeStackOffset(enc._code,dst);
if( src >= x86_64_v2.MAX_REG ) {
// Rare stack-stack move. push [RSP+soff]; pop [RSP+doff]
- int soff = enc._fun.computeStackOffset(enc._code,src);
+ int soff = fun(enc).computeStackOffset(enc._code,src);
enc.add1(0xFF);
x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, soff, 6, enc);
enc.add1(0x8F);
@@ -63,7 +62,7 @@ public class SplitX86 extends SplitNode {
return;
}
if( src >= x86_64_v2.MAX_REG ) {
- int off = enc._fun.computeStackOffset(enc._code,src);
+ int off = fun(enc).computeStackOffset(enc._code,src);
LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0);
return;
}
@@ -98,10 +97,4 @@ public class SplitX86 extends SplitNode {
enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
}
-
- // General form: "mov dst = src"
- @Override public void asm(CodeGen code, SB sb) {
- FunNode fun = code._encoding==null ? null : code._encoding._fun;
- sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
index 4403b02b..faedeb8b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
@@ -31,7 +31,7 @@ public class StoreX86 extends MemOpX86 {
short src = enc.reg(val());
if( src == -1 ) {
- int log = _declaredType.log_size();
+ int log = Integer.numberOfTrailingZeros(_sz-'0');
x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
switch( log ) {
case 0: enc .add1(0xC6); break;
@@ -47,7 +47,7 @@ public class StoreX86 extends MemOpX86 {
case 3: enc.add4(_imm); break; // Limit of a 4 byte immediate
}
} else {
- encVal(enc,_declaredType,ptr,idx,src,_off,_scale);
+ encVal(enc,_con,ptr,idx,src,_off,_scale);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
index 359b6d83..8d8d3950 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
@@ -2,54 +2,51 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.cpus.riscv.riscv;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Function constants
-public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
- TFPX86( ConstantNode con ) { super(con); }
- @Override public String op() {
- return _con == Type.NIL ? "xor" : "ldx";
- }
+public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize {
+ private byte _opLen;
+ final String _ext; // External name
+ // Pointer to a Simple function
+ TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); }
+ // Spill clone: copy inputs without registering their reverse edges;
+ // insertBefore will place and register the clone in its use block.
+ private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; }
+ // Pointer to an Extern "C" function
+ TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
+ @Override public String op() { return "ldx"; }
+ @Override public String label() { return op(); }
+ // The ideal FunPtrNode is pinned because its Return input is a lifetime
+ // hook. After selection this is an ordinary cloneable load-address
+ // instruction and must be scheduled down near its actual use.
+ @Override public boolean isPinned() { return false; }
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new TFPX86(this); }
+ @Override public TFPX86 copy() { return new TFPX86(this); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- // Zero-set uses XOR kills flags
- @Override public RegMask killmap() {
- return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null;
- }
-
@Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this);
- // Short form for zero
- if( _con==Type.NIL ) {
- // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b
- // xor, which will also zero the high bits.
- if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
- enc.add1(0x33); // opcode
- enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
- return;
- }
-
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
// lea to load function pointer address
// lea rax, [rip+disp32]
- enc.relo(this);
- enc.add1(x86_64_v2.rex(dst, 0, 0));
+ short dst = enc.reg(this);
+ // 0 or 1 for REX depending on the dst.
+ // Zero/sign extend should be fine, so not wide.
+ _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc));
enc.add1(0x8D); // opcode
enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101));
enc.add4(0);
}
- @Override public byte encSize(int delta) {
- return 7;
- }
+ @Override public byte encSize(int delta) { return _opLen; }
- // Delta is from opcode start
+ // Delta is from opcode start, X86 expects it from opcode end
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart + 3, delta - 7);
+ enc.patch4(opStart + opLen - 4, delta - opLen);
}
// Human-readable form appended to the SB. Things like the encoding,
@@ -57,11 +54,9 @@ public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
// Just something like "ld4\tR17=[R18+12] // Load array base".
// General form: "op\tdst=src+src"
@Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- if( _con == Type.NIL )
- sb.p(reg).p(",").p(reg);
- else
- _con.print(sb.p(reg).p(" = #"));
+ sb.p(code.reg(this));
+ if( _ext == null ) _type.print(sb.p(" #"));
+ else sb.p(_ext);
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
index c23f6c5f..cc8741b3 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
@@ -4,32 +4,33 @@ import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{
- TMPX86(ConstantNode con ) { super(con); }
+ private byte _opLen;
+ final String _ext;
+ TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldp"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new TMPX86(this); }
+ @Override public Node copy() { return new TMPX86(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
short dst = enc.reg(this);
- x86_64_v2.rexF(dst, 0, 0, true, enc);
+ // 0 or 1 for REX depending on the dst.
+ _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc));
enc.add1(0x8D).add1(x86_64_v2.modrm(x86_64_v2.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*/);
+ enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/);
}
// Delta is from opcode start
- @Override public byte encSize(int delta) { return (byte)7; }
+ @Override public byte encSize(int delta) { return _opLen; }
// Delta is from opcode start
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart+opLen-4,delta-opLen);
+ enc.patch4(opStart + opLen - 4, delta - opLen);
}
@Override public void asm(CodeGen code, SB sb) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 026e12aa..a1ad17dc 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -70,7 +70,7 @@ public class x86_64_v2 extends Machine {
public static int REX_WB = 0x49;
public enum MOD {
- INDIRECT, // [mem]
+ INDIRECT, // [mem]
INDIRECT_disp8, // [mem + 0x12]
INDIRECT_disp32, // [mem + 0x12345678]
DIRECT, // mem
@@ -188,22 +188,29 @@ public class x86_64_v2 extends Machine {
// Map from function signature and argument index to register.
// Used to set input registers to CallNodes, and ParmNode outputs.
@Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); }
static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv);
+ }
+ static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) {
if( idx==0 ) return RPC_MASK;
if( idx==1 ) return null;
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
- return switch( CodeGen.CODE._callingConv ) {
+ return switch( conv ) {
case "SystemV" -> callSys5 (tfp,idx,maxArgSlot);
- case "Win64" -> callWin64(tfp,idx,maxArgSlot);
+ case "win64" -> callWin64(tfp,idx,maxArgSlot);
default -> throw Utils.TODO();
};
}
// Maximum stack args used by this signature
@Override public short maxArgSlot( TypeFunPtr tfp ) {
- return switch( CodeGen.CODE._callingConv ) {
+ return maxArgSlot(tfp,CodeGen.CODE._callingConv);
+ }
+ @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) {
+ return switch( conv ) {
case "SystemV" -> maxArgSlotSys5 (tfp);
- case "Win64" -> maxArgSlotWin64(tfp);
+ case "win64" -> maxArgSlotWin64(tfp);
default -> throw Utils.TODO();
};
}
@@ -236,14 +243,22 @@ public class x86_64_v2 extends Machine {
static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) {
// idx 2,3,4,5 passed in registers, with stack slot mirrors.
// idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC).
- if( idx >= 6 )
- return new RegMask(MAX_REG+maxArgSlot+(idx-2));
+ int sigidx = idx-2;
+ if( hiddenSelf(tfp) ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
+ if( sigidx >= 4 )
+ return new RegMask(MAX_REG+maxArgSlot+sigidx);
return tfp.arg(idx-2) instanceof TypeFloat
- ? XMMS8 [idx-2]
- : WIN64_CALL[idx-2];
+ ? XMMS8 [sigidx]
+ : WIN64_CALL[sigidx];
}
static short maxArgSlotWin64(TypeFunPtr tfp) {
- return (short)tfp.nargs();
+ return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0));
+ }
+ static short maxIncomingArgSlotWin64(TypeFunPtr tfp) {
+ return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4);
}
// Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing
@@ -277,9 +292,14 @@ public class x86_64_v2 extends Machine {
static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) {
// First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09
// First 8 floats passed in registers: xmm0-xmm7
+ int sigidx = idx-2;
+ if( hiddenSelf(tfp) ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=2; i<idx; i++ ) {
- if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++;
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
@@ -289,13 +309,16 @@ public class x86_64_v2 extends Machine {
}
static short maxArgSlotSys5(TypeFunPtr tfp) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=0; i<tfp.nargs(); i++ ) {
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
return (short)nstk;
}
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
static final long SYSTEM5_CALLER_SAVE =
(1L << RAX) | (1L << RCX) | (1L << RDX) |
@@ -314,12 +337,18 @@ public class x86_64_v2 extends Machine {
(1L<<XMM4) | (1L<<XMM5) ;
@Override public long callerSave() {
- return switch (CodeGen.CODE._callingConv) {
+ return callerSave(CodeGen.CODE._callingConv);
+ }
+ @Override public long callerSave(String conv) {
+ return switch (conv) {
case "SystemV" -> SYSTEM5_CALLER_SAVE;
- case "Win64" -> WIN64_CALLER_SAVE;
- default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv);
+ case "win64" -> WIN64_CALLER_SAVE;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + conv);
};
}
+ @Override public String cCallingConv(String callingConv) {
+ return callingConv;
+ }
@Override public long neverSave() { return 1L<<RSP; }
@Override public RegMask retMask( TypeFunPtr tfp ) {
return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
@@ -328,8 +357,8 @@ public class x86_64_v2 extends Machine {
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(String kind, byte round, LRG lrg) {
- return new SplitX86(kind, round);
+ @Override public SplitNode split(LRG lrg, String kind, byte round ) {
+ return new SplitX86(lrg,kind, round);
}
// Return a MachNode unconditional branch
@@ -341,28 +370,34 @@ public class x86_64_v2 extends Machine {
@Override
public Node instSelect(Node n) {
return switch (n) {
- case AddFNode addf -> addf(addf);
case AddNode add -> add(add);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CProjNode c -> new CProjNode(c);
case CallEndNode cend -> new CallEndMach(cend);
case CallNode call -> call(call);
- case CastNode cast -> new CastMach(cast);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
case ConstantNode con -> con(con);
- case DivFNode divf -> new DivFX86(divf);
- case DivNode div -> new DivX86(div);
+ case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div);
+ case EscapeNode esc -> new EscapeNode(esc);
case FunNode fun -> new FunX86(fun);
+ case FunPtrNode fptr -> fptr(fptr);
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> new NegX86(neg);
- case MulFNode mulf -> new MulFX86(mulf);
- case MulNode mul -> mul(mul);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegX86(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul);
case NewNode nnn -> new NewX86(nnn);
case NotNode not -> new NotX86(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmX86(parm);
+ case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read-> new ReadOnlyMach(read);
@@ -370,13 +405,14 @@ public class x86_64_v2 extends Machine {
case SarNode sar -> sar(sar);
case ShlNode shl -> shl(shl);
case ShrNode shr -> shr(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubFNode subf -> new SubFX86(subf);
- case SubNode sub -> sub(sub);
+ case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
- case XCtrlNode x -> new ConstantNode(Type.XCONTROL);
+ case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL);
case XorNode xor -> xor(xor);
case LoopNode loop -> new LoopNode(loop);
@@ -391,15 +427,12 @@ public class x86_64_v2 extends Machine {
// Attempt a full LEA-style break down.
private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFX86(add);
Node lhs = add.in(1);
Node rhs = add.in(2);
- if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3)
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld.declaredType().log_size() >= 3)
return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs);
-
-// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) {
-// throw Utils.TODO(); // Swap load sides
-// }
-
// 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 ) {
@@ -422,17 +455,6 @@ public class x86_64_v2 extends Machine {
}
- private Node addf(AddFNode addf) {
- if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1)
- return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2));
-
-// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1)
-// throw Utils.TODO(); // Swap load sides
-
- return new AddFX86(addf);
- }
-
-
private Node _lea(Node add, Node base, Node idx, int off) {
int scale = 0;
if( base instanceof ShlNode && !(idx instanceof ShlNode) )
@@ -456,8 +478,10 @@ public class x86_64_v2 extends Machine {
}
private Node call(CallNode call) {
- return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
- ? new CallX86(call, tfp)
+ return call.fptr() instanceof FunPtrNode con
+ ? new CallX86(call, (TypeFunPtr)con._type)
+ : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr
+ ? new CallX86(call, (TypeFunPtr)con._type)
: new CallRX86(call);
}
@@ -482,11 +506,11 @@ public class x86_64_v2 extends Machine {
// Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped.
// Vs memory
- if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) )
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld.declaredType()) )
return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
// Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
- if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) &&
+ 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 );
@@ -504,18 +528,23 @@ public class x86_64_v2 extends Machine {
private Node con( ConstantNode con ) {
if( !con._con.isConstant() )
- return new ConstantNode(con); // Default unknown caller inputs
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
return switch (con._con) {
- case TypeInteger ti -> new IntX86(con);
- case TypeFloat tf -> new FltX86(con);
- case TypeFunPtr tfp -> new TFPX86(con);
- case TypeMemPtr tmp -> new TMPX86(con);
+ case TypeInteger ti -> new IntX86(con,ext);
+ case TypeFloat tf -> new FltX86(con,ext);
+ case TypeMemPtr tmp -> new TMPX86(con,ext);
+ case TypeFunPtr tfp -> new TFPX86(tfp,ext);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con);
+ case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con);
};
}
+ private Node fptr( FunPtrNode con ) {
+ return new TFPX86(con);
+ }
+
private Node i2f8(ToFloatNode tfn) {
assert tfn.in(1)._type instanceof TypeInteger ti;
return new I2f8X86(tfn);
@@ -528,7 +557,9 @@ public class x86_64_v2 extends Machine {
String op = "!=";
if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op();
else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
+ else iff.setDef(1, new BoolNode.NE(iff.in(1),
+ ConstantNode.raw(TypeInteger.ZERO),
+ (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
return new JmpX86(iff, op);
}
@@ -549,7 +580,7 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
- return new ProjX86(prj);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
}
private Node sar(SarNode sar) {