summaryrefslogtreecommitdiff
path: root/chapter27/src/main/java/com/seaofnodes
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-28 12:26:28 -0700
committerCliff Click <cliffc@acm.org>2026-10-28 12:26:28 -0700
commite481e3d215882faa82b19f29cfde5746d4e51863 (patch)
treed6035c13e4ee471b67d7dd4732a62bc04020fbd1 /chapter27/src/main/java/com/seaofnodes
parent07a87b4461015dd81f1d40cef1e2378882c86479 (diff)
downloadSimple-e481e3d215882faa82b19f29cfde5746d4e51863.tar.gz
Simple-e481e3d215882faa82b19f29cfde5746d4e51863.zip
Backport Opto speedups and Minus fix
Diffstat (limited to 'chapter27/src/main/java/com/seaofnodes')
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/codegen/Opto.java79
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/node/FunNode.java2
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/node/MinusNode.java24
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/node/Node.java2
4 files changed, 61 insertions, 46 deletions
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter27/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index 3c34b44c..d8cef195 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.IterPeeps.WorkList;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
import java.util.BitSet;
@@ -26,8 +27,11 @@ abstract public class Opto {
public static void opto(CodeGen code) {
// Reset Types before running the worklist algorithm
- Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only
- resetToTop(code,oldTypes);
+ Ary<Type> oldTypes = new Ary<>(Type.class);
+ WorkList<Node> deps = new WorkList<>();
+ WorkList<Node> changes = new WorkList<>();
+ Ary<Node> modes = new Ary<>(Node.class);
+ resetToTop(code,oldTypes,modes);
// Main seeds a whole program. Without main, named exported functions
// seed discovery. Either way, escaping addresses can add more unknown
@@ -42,24 +46,30 @@ abstract public class Opto {
// OPTIMISTIC INTERPROCEDURAL SCCP
- sccp(code, oldTypes);
- resolveNumericModes(code);
- if( !code._iter._work.isEmpty() ) sccp(code, oldTypes);
+ sccp(code, oldTypes, changes, deps);
+ // Settle modes, then resume SCCP on affected nodes without resetting types.
+ // Any further type changes must still fall; BOTTOM results stay BOTTOM.
+ resolveNumericModes(code,modes);
+ if( !code._iter._work.isEmpty() ) sccp(code, oldTypes, changes, deps);
ProjNode startMem=code._start.proj(1);
if( startMem!=null ) { startMem.unkeep(); code.add(startMem); }
// After SCCP add everything changed to the worklist and see if
// peepholes can make more progress.
- moveChangesToWorklist(code, oldTypes);
+ Node dep;
+ while( (dep=deps.pop())!=null ) code.add(dep);
+ moveChangesToWorklist(code, oldTypes, changes);
}
// Reset Types before running the worklist algorithm
- private static void resetToTop(CodeGen code, Ary<Type> oldTypes ) {
+ private static void resetToTop(CodeGen code, Ary<Type> oldTypes, Ary<Node> modes ) {
code._start.walk( x -> {
- oldTypes.setX(x._nid,x._type); // Record original types for asserts
+ // SCCP adds call edges and default arguments, not numeric operations.
+ if( x instanceof ModeNode mode && mode.mode()==0 ) modes.push(x);
+ oldTypes.setX(x._nid,x._type); // Compare the final SCCP type with Iter's type
x._type = Type.TOP; // Reset all Nodes to TOP
code._iter.add(x); // Visit everybody at least once; most Nodes produce better than TOP
// Unlink all existing (conservative) linkages.
@@ -145,7 +155,7 @@ abstract public class Opto {
}
// The core SCCP algorithm
- private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
+ private static void sccp( CodeGen code, Ary<Type> oldTypes, WorkList<Node> changes, WorkList<Node> deps ) {
// Iter workList is empty at this point
// OPTIMISTIC PASS
Node n;
@@ -154,6 +164,8 @@ abstract public class Opto {
if( n.isDead() ) continue;
count++;
Type oval = n._type, nval = n.compute();
+ // Filter candidates against the final SCCP type after convergence.
+ if( nval!=oldTypes.atX(n._nid) || n.isUnlocked() ) changes.push(n);
if( oval == nval ) continue;
assert oval.isa(nval) : n.label()+"#"+n._nid+" old="+oval+" new="+nval; // Types start high and always fall
Type pesiVal = oldTypes.atX(n._nid);
@@ -175,6 +187,9 @@ abstract public class Opto {
// Since n._type changed, visit all output neighbors
code._iter.addAll(n._outputs);
+ // SCCP consumes these without running peepholes. Save them for
+ // the following Iter pass before moving clears the dependency list.
+ for( int i=0; i<n.nDeps(); i++ ) deps.push(n.dep(i));
n.moveDepsToWorklist(code._iter);
// Quadratic (expensive) small-step assert
assert check(code);
@@ -194,22 +209,28 @@ abstract public class Opto {
// After SCCP add everything changed to the worklist and see if peepholes
// can make more progress.
- private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes) {
- code._start.walk( x -> {
- assert x.compute() == x._type; // Hit the fixed point
- assert x._nid >= oldTypes._len || x._type.isa(oldTypes.at(x._nid)); // Hit at least the bottom-up type
- code._iter.add(x);
- return null;
- });
+ private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes, WorkList<Node> changes) {
+ Node n;
+ while( (n=changes.pop())!=null ) {
+ if( n.isDead() ) continue;
+ assert n.compute() == n._type; // Hit the fixed point.
+ Type old = oldTypes.atX(n._nid);
+ assert old==null || n._type.isa(old);
+ if( n._type!=old || n.isUnlocked() ) {
+ code.add(n);
+ code.addAll(n._inputs);
+ code.addAll(n._outputs);
+ n.moveDepsToWorklist();
+ }
+ }
}
- // Resolve arithmetic modes which are ambiguous only because they belong to
- // a recursive component. Float wins because integer operands can be
- // converted to float.
- private static void resolveNumericModes(CodeGen code) {
+ // Resolve modes left undecided by SCCP, following recursive inputs for
+ // evidence. Float wins because integer operands can be converted to float.
+ private static void resolveNumericModes(CodeGen code, Ary<Node> modes) {
final BitSet visited = new BitSet();
- code._stop.walk(n -> {
- if( n instanceof ModeNode modeNode && modeNode.mode()==0 ) {
+ for( Node n : modes ) {
+ if( !n.isDead() && n instanceof ModeNode modeNode && modeNode.mode()==0 ) {
visited.clear();
visited.set(n._nid); // Inspect inputs, not Bool's integer result
int mode = 0;
@@ -218,17 +239,11 @@ abstract public class Opto {
// A closed recursive numeric component can have no remaining
// concrete evidence after SCCP, e.g. dead all-integer loops.
// With no float evidence, settle to integer.
- if( mode == 0 )
- mode = 1;
- if( mode != 0 ) {
- // If both evidence, FP wins
- modeNode.setMode((byte)((mode&2)==2 ? 2 : 1));
- code.add(n);
- code.addAll(n.outs());
- }
+ modeNode.setMode((byte)((mode&2)==2 ? 2 : 1));
+ code.add(n);
+ code.addAll(n.outs());
}
- return null;
- });
+ }
}
// Gather numeric evidence through Phi and interprocedural return cycles.
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter27/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 2850ba92..7fd60d60 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -126,7 +126,7 @@ public class FunNode extends RegionNode {
}
// Upgrade inferred or user-written return type to actual
- if( _ret!=null && _ret._type instanceof TypeTuple tt &&
+ if( _ret!=null && addDep(_ret)._type instanceof TypeTuple tt &&
!(tt.ret() instanceof TypeMem) && tt.ret() != _sig.ret() ) {
setSig(_sig.makeFrom(tt.ret()));
return this;
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter27/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 4d7e3ddb..6409ad21 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -2,7 +2,6 @@ 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.Utils;
import java.util.HashMap;
@@ -29,28 +28,27 @@ public class MinusNode extends Node implements ModeNode {
@Override
public Type compute() {
Type t = in(1)._type;
+ // Constant array payloads lie below their element ranges in the lattice.
+ if( t instanceof TypeConAry ary ) t = ary.elem();
if( t==null )
return Type.TOP;
if( t.isHigh() )
return _mode==0 ? Type.TOP :
_mode==1 ? TypeInteger.TOP :
t instanceof TypeFloat ? t : TypeFloat.F64.dual();
- byte mode = _mode;
- if( mode==0 ) {
- if( t instanceof TypeFloat ) mode=2;
- if( t instanceof TypeInteger ) mode=1;
- }
- if( mode==0 )
- return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
- if( mode==1 ) {
- if( t instanceof TypeInteger i0 &&
- i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE )
+ if( t instanceof TypeInteger i0 ) {
+ if( _mode==2 ) return TypeFloat.F64;
+ if( 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 )
+ if( t instanceof TypeFloat f ) {
+ if( _mode==1 ) return TypeInteger.BOT;
return f.isConstant() ? TypeFloat.constant(-f.value()) : f;
- return TypeFloat.F64;
+ }
+ // No numeric family is known. This must be below both integer and
+ // float results, in either analysis direction and with any mode.
+ return Type.BOTTOM;
}
@Override
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter27/src/main/java/com/seaofnodes/simple/node/Node.java
index 230593a8..77b1dd35 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -557,6 +557,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
// probed to see if it can be inserted. His edges are "locked", because
// hacking his edges will change his hash.
int _hash;
+ // Edge and mode edits remove the node from GVN, even if its type is unchanged.
+ public boolean isUnlocked() { return _hash==0; }
// If the _hash is set, then the Node is in the GVN table; remove it.
void unlock() {