Fix optimizer bug: TailCall inside If not converted on inline

When the tail-call pass converted a Call to TailCall inside an If branch,
and the inliner subsequently inlined that word, the TailCall was not
converted back to Call in nested control-flow bodies. The TailCall codegen
emits a Return instruction, which would exit the *caller* instead of just
the inlined callee — silently corrupting the return stack.

Root cause: the inliner only converted top-level TailCalls in the body
(line-by-line iteration), missing TailCalls nested inside If/DoLoop/Begin
structures.

Fix: add detailcall() that recursively walks the entire IR tree and
converts all TailCall ops back to Call before inlining.

This unblocks defining complex Forth words (like SM/REM, FM/MOD) that
use DABS → DNEGATE → D+ chains with return-stack operations inside
conditional branches.

426 tests pass (including new regression test).
This commit is contained in:
2026-04-07 13:36:26 +02:00
parent b40725615d
commit d3b4382440
2 changed files with 63 additions and 6 deletions
+12
View File
@@ -7944,6 +7944,18 @@ mod tests {
assert_eq!(eval_stack("-1 0 10 WITHIN"), vec![0]);
}
#[test]
fn test_inline_tailcall_rstack_interaction() {
// Regression: inlining a word that had a TailCall inside an If branch
// caused the TailCall's Return to exit the *caller*, corrupting the
// return stack. The fix: detailcall() recursively converts TailCall
// back to Call inside all nested control-flow bodies when inlining.
assert_eq!(
eval_stack(": T 42 >R 99 >R -7 -1 DABS R> R> ; T"),
vec![42, 99, 0, 7]
);
}
#[test]
fn test_do_loop_with_i_and_step() {
// +LOOP with step of 2