From 3617495d8efbcce23bbc9f1754c7d27d34d30a52 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 19 Oct 2026 11:48:37 -0700 Subject: Fix a missed int-widening around recursion --- .../java/com/seaofnodes/simple/node/CallEndNode.java | 10 ++++++++++ .../main/java/com/seaofnodes/simple/node/ParmNode.java | 16 ++++++++++++++++ .../main/java/com/seaofnodes/simple/node/PhiNode.java | 4 ++++ 3 files changed, 30 insertions(+) (limited to 'chapter24/src/main') 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 { @@ -34,6 +35,21 @@ public class ParmNode extends PhiNode { // Always in-progress until we run out of unknown callers @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) ) 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 -- cgit v1.2.3