summaryrefslogtreecommitdiff
path: root/chapter24/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-19 11:48:37 -0700
committerCliff Click <cliffc@acm.org>2026-10-19 11:48:37 -0700
commit3617495d8efbcce23bbc9f1754c7d27d34d30a52 (patch)
tree65684567f224af8018626dc52d529e206af1a659 /chapter24/src
parente7eb2db06d1b9c529f94f2da3a087c83c0498e05 (diff)
downloadSimple-3617495d8efbcce23bbc9f1754c7d27d34d30a52.tar.gz
Simple-3617495d8efbcce23bbc9f1754c7d27d34d30a52.zip
Fix a missed int-widening around recursion
Diffstat (limited to 'chapter24/src')
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java10
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java16
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java4
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java14
4 files changed, 44 insertions, 0 deletions
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 115f70c6..aa586b17 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -57,6 +57,16 @@ public class CallEndNode extends CFGNode implements MultiNode {
Type tret = in(i)._type instanceof TypeTuple rtt ? rtt.ret() : in(i)._type;
ret = ret.meet(tret);
}
+ // Recursion is a loop in disguise. For f(x-1)+1, the result
+ // flows Return -> CallEnd -> Add -> Phi -> Return, without
+ // crossing ParmNode.compute's widening point. Widen here too,
+ // using the loop rule in PhiNode.compute. Keep constants and
+ // above-center ranges narrow, and stay within the return type.
+ Type oldret = _type instanceof TypeTuple tt ? tt.ret() : Type.TOP;
+ if( ret instanceof TypeInteger ti && ret != oldret &&
+ !ti.isHigh() && !ti.isConstant() &&
+ (!(oldret instanceof TypeInteger oldi) || ti._widen <= oldi._widen) )
+ ret = ti.same_but_slightly_wider_than(TypeInteger.BOT).join(tfp.ret());
}
}
return TypeTuple.make(call._type,TypeMem.BOT,ret);
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 95934e4b..cc654abb 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -4,6 +4,7 @@ import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.type.TypeMemPtr;
public class ParmNode extends PhiNode {
@@ -35,6 +36,21 @@ public class ParmNode extends PhiNode {
@Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); }
@Override
+ public Type compute() {
+ Type t = super.compute();
+ // Recursion is a loop in disguise. A Parm merges the first argument
+ // with those from recursive calls, just as a loop Phi merges its entry
+ // and backedge. Use the widening rule in PhiNode.compute here too.
+ // CallEndNode.compute handles the separate cycle through return values;
+ // widening this Parm alone cannot stop f(x-1)+1 from growing its result.
+ if( t instanceof TypeInteger ti && t != _type &&
+ !ti.isHigh() && !ti.isConstant() &&
+ (!(_type instanceof TypeInteger oldi) || ti._widen <= oldi._widen) )
+ return ti.same_but_slightly_wider_than(_minType);
+ return t;
+ }
+
+ @Override
public Node idealize() {
if( !(region() instanceof FunNode) )
return in(1); // Input has collapse to e.g. starting control.
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 6cfeecfe..7f2bb1f4 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -80,6 +80,10 @@ public class PhiNode extends Node {
}
Type newt = t.join( _minType );
+ // Widen loop Phis so integer ranges cannot grow one step per trip forever.
+ // Recursion is a loop in disguise: ParmNode.compute widens arguments,
+ // and CallEndNode.compute widens results. Both are needed because the
+ // recursive result can feed itself without passing through a Parm.
// Above-center ranges may still fall to unwidened constants.
// Widen only below-center ranges to preserve monotonicity.
if( region() instanceof LoopNode && // Only around loops
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 8cd300c2..3e5dc2a9 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -17,6 +17,20 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter24Test {
+ @Test public void testRecursiveWidening() {
+ String[] srcs = {
+ // Parm(x) must widen even though the recursive result is constant.
+ "val f = { int x, int limit -> x >= limit ? 0 : f(x+1,limit); }; return f(0,arg);",
+ // The growing result has its own cycle, which never crosses Parm(x).
+ "val f = { int x -> x <= 0 ? 0 : f(x-1)+1; }; return f(3);"
+ };
+ for( int i=0; i<srcs.length; i++ )
+ for( long seed : new long[]{0,42,126} ) {
+ var code = new CodeGen(srcs[i],TypeInteger.BOT,seed,true).driver(CodeGen.Phase.LocalSched);
+ assertEquals(i==0 ? "0" : "3",Eval2.eval(code,3));
+ }
+ }
+
@Test public void testColdLoopSelfConflict() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coldLoopSelfConflict(); }
@Test public void testPrintingDeadFunction() throws Exception {