Repository navigation
Throw a catchable RangeError on deep recursion - #805
Merged
Merged
Conversation
Native executables used to terminate with a signal when recursion exhausted the stack. Functions that can take part in unbounded recursion now compare the stack pointer against a per-stack guard on entry and throw Node's `RangeError: Maximum call stack size exceeded` through the ordinary exception path, so catch handlers, finally blocks, worker error events and the uncaught-error exit code behave as they do under Node. - The checked set is every function on a cycle of the call graph, where operations that can reenter compiled code indirectly (indirect calls, callbacks, dynamic and library operations) reach closure targets and class members. Non-recursive functions, including leaf callbacks, stay check-free, and the may-throw analysis accounts for the new throw. - The runtime installs the guard per context (main thread from the stack rlimit, workers from their thread attributes) and per async fiber, with a reserve for the throw, unwinding and error reporting. Worker threads now get an explicit 8 MiB stack. - Scalar call results pass through the unwind path as its dummy value, so calls in tail position keep tail-call and recursion elimination.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deep recursion in a native executable used to stop the process with a signal. It now throws Node's catchable
RangeError: Maximum call stack size exceeded, socatch,finally, worker'error'events and exit codes, and the uncaught exit status 1 all behave as in Node.newand virtual call edges, plus reentrant operations that call closures and class members. Non-recursive code, including leaf callbacks, stays check-free, and tail calls keep tail-call and recursion elimination.limitations.mdxis updated.Measured overhead (release, minimum of 11 runs × 3 rounds, before → after):
Ackermann is the worst case: its inner non-tail call
ack(m, n - 1)now needs an exception check after the call, which also forces extra register and constant reloads in the recursion loop. The static hello executable is unchanged (68,912 bytes), and async hello grows by 120 bytes.Validation: full plain and sanitized corpus 2177/2177 each; the compiler compiles itself; self-hosting suites 54/54; compiler package tests 632 and unit tests 1858; diagnostics, coverage and surface-manifest 263; runtime-tree-shaking passes; runtime tests pass apart from known environment-only cases; npm-static 91/91; library-mode passes apart from known environment-only cases; TypeScript preflight/order baselines complete; lint and format clean. After merging the latest main, the focused stack-overflow, recursion and recently changed programs pass in the sanitized lane (34). The Effect suites are still running.
Known limits: async bodies run on 256 KiB fibers, so they overflow earlier than in Node. Recursion inside runtime C over deeply nested data is not checked. Recursive functions can now throw, so LLVM no longer folds repeated pure recursive calls.