Skip to content

Avoid stack overflow on deep mutual recursion under --effects=double-translation#2243

Draft
hhugo wants to merge 2 commits into
masterfrom
fix-double-translation-mutual-recursion-stackoverflow
Draft

Avoid stack overflow on deep mutual recursion under --effects=double-translation#2243
hhugo wants to merge 2 commits into
masterfrom
fix-double-translation-mutual-recursion-stackoverflow

Conversation

@hhugo
Copy link
Copy Markdown
Member

@hhugo hhugo commented May 21, 2026

Summary

  • Under --effects=double-translation, cps_needed mutually recursive functions get both a direct and a CPS version (paired via caml_cps_closure). The CPS side trampolines on caml_stack_check_depth, but the direct side called its siblings via ordinary JS calls, so a sufficiently deep direct-style mutual recursion blew the JS stack.
  • Generate_closure now runs under double-translation. For each SCC of paired closures it renames the direct half, gates every recursive tail call on caml_stack_check_depth (mirroring the CPS-side pattern), and replaces the direct slot of caml_cps_closure with a wrapper that drives a runtime caml_direct_trampoline loop. When the guard fires the call bounces to the same inner direct closure via caml_trampoline_return, and the wrapper's loop reapplies it with a fresh stack budget — no CPS involvement.
  • New regression test compiler/tests-jsoo/lib-effects/mutual_recursion_deep.ml runs ping/pong mutual recursion at depth 1,000,000.

Test plan

  • dune runtest compiler/tests-jsoo/lib-effects/ clean under default, with-effects, and with-effects-double-translation profiles
  • dune build @compiler/tests-jsoo/lib-effects/runtest-js clean under all three profiles
  • dune runtest compiler/tests-jsoo --profile=with-effects-double-translation clean
  • dune build @compiler/tests-jsoo/runtest-js --profile=with-effects-double-translation clean
  • dune build @compiler/tests-ocaml/effects/runtest --profile=with-effects-double-translation clean
  • dune build @lib/tests/runtest-js --profile=with-effects-double-translation clean
  • Pre-existing js_parser_printer.ml (using / html comments) failures unrelated to this change (reproduce on master under the same Node version)

🤖 Generated with Claude Code

hhugo added 2 commits May 21, 2026 10:59
Direct-style mutually recursive functions overflow the JS stack under
--effects=double-translation because the trampoline pass (Generate_closure.f)
is short-circuited for that mode and the per-function direct version has no
depth guard. The test runs the deep recursion under the double-translation
profile and emits the snapshot synthetically under other modes.
…ts=double-translation

Under --effects=double-translation, cps_needed mutually recursive functions
get both a direct and a CPS version (paired via caml_cps_closure). The CPS
side trampolines on caml_stack_check_depth, but the direct side called its
siblings via ordinary JS calls, so a sufficiently deep direct-style mutual
recursion blew the JS stack.

Generate_closure now runs under double-translation. For each SCC of paired
closures it renames the direct half, gates every recursive tail call on
caml_stack_check_depth (mirroring the CPS-side pattern), and replaces the
direct slot of caml_cps_closure with a wrapper that drives a runtime
trampoline. When the guard fires the call bounces to the same inner
direct closure via caml_trampoline_return, and the wrapper's loop
reapplies it with a fresh stack budget — no CPS involvement.

The regression test in mutual_recursion_deep.ml now succeeds at depth
1,000,000 under double-translation (it already did under cps and native).
@hhugo hhugo marked this pull request as draft May 21, 2026 10:35
@hhugo
Copy link
Copy Markdown
Member Author

hhugo commented May 21, 2026

@4y8 do you want to try this PR with --effects=double-translation.

@4y8
Copy link
Copy Markdown

4y8 commented May 21, 2026

That worked, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants