mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Clones now cover more recursive number shapes: - Recursion over array indices: a function may take number arrays it borrows (the caller keeps them alive for the call) and read `length` and elements. An element inside the array's hole-free prefix that is exactly an int32 is read inline; holes, other indices and other numbers bail to the ordinary body. Borrowed entries get dispatchers too. - Mutual tail recursion: tail calls between members of a recursive component return the callee's result as is, so LLVM inlines them into one loop (isEven/isOdd), as it already does for the ordinary body. - The proven int32-slots ranges of number parameters (valid at every call site, recursive ones included) become `llvm.assume`s in each activation, so `%` of non-negative values divides unsigned without -0 handling (gcd). A clone now overflows the stack where the ordinary body would, instead of bailing after 2^14 loop iterations. LLVM turns the ordinary body's plain tail calls into loops, so the clone's plain tail calls (self or mutual) run unchecked too: endless tail recursion runs forever on either path, as it did before clones. An accumulating call (`1 + f(n - 1)`, fib's `f(n - 1) + f(n - 2)`) is real recursion in the ordinary body, because a double sum is not associative. Its clone loop carries a virtual stack pointer that starts at the real one (an inlined activation one frame below its parent's) and drops 32 bytes, about an ordinary frame of these functions, per iteration; passing the stack guard throws the stack-overflow RangeError. Deep finite chains stay on the fast path, and `1 + down(n - 1)` with n = 1e7 still throws like Node (4984-generator-exceptions, corpus 5342). Corpus 5343 covers array-index recursion, binomials, holes and non-integer elements.
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
// Pure integer recursion that overflows the stack, never ends, recurses
|
|
// mutually, takes and returns booleans, or nests calls in its arguments.
|
|
|
|
// Non-tail recursion with an int32 result: infinite for negative input.
|
|
function down(n: number): number {
|
|
if (n === 0) return 0;
|
|
return down(n - 1) | 0;
|
|
}
|
|
|
|
// An accumulating self call that never reaches its base case.
|
|
function spin(n: number): number {
|
|
if (n === 1) return 0;
|
|
return spin(n + 0) + 1;
|
|
}
|
|
|
|
// An accumulating chain: deep or endless input overflows the stack even
|
|
// though the recursion runs as a loop.
|
|
function count(n: number): number {
|
|
return n === 0 ? 0 : 1 + count(n - 1);
|
|
}
|
|
|
|
// Plain mutual tail calls (kept within Node's stack depth: compiled code
|
|
// runs them as a loop, so an endless chain would not overflow).
|
|
function isEven(n: number): boolean {
|
|
return n === 0 ? true : isOdd(n - 1);
|
|
}
|
|
function isOdd(n: number): boolean {
|
|
return n === 0 ? false : isEven(n - 1);
|
|
}
|
|
|
|
function ping(n: number): number {
|
|
return n <= 0 ? 1 : pong(n - 1) + n;
|
|
}
|
|
function pong(n: number): number {
|
|
return n <= 0 ? 2 : ping(n - 2) * 2;
|
|
}
|
|
|
|
function alternate(flag: boolean, n: number): number {
|
|
if (n === 0) return 0;
|
|
const step = flag ? 1 : 1000000000;
|
|
return step + alternate(!flag, n - 1);
|
|
}
|
|
|
|
function ack(m: number, n: number): number {
|
|
if (m === 0) return n + 1;
|
|
if (n === 0) return ack(m - 1, 1);
|
|
return ack(m - 1, ack(m, n - 1));
|
|
}
|
|
|
|
function tak(x: number, y: number, z: number): number {
|
|
return y < x ? tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y)) : z;
|
|
}
|
|
|
|
function attempt(label: string, run: () => number | boolean): void {
|
|
try {
|
|
console.log(label, run());
|
|
} catch (e) {
|
|
console.log(label, e instanceof RangeError, (e as Error).message);
|
|
}
|
|
}
|
|
|
|
attempt("down 1000", () => down(1000));
|
|
attempt("down -1", () => down(-1));
|
|
attempt("down 10 after overflow", () => down(10));
|
|
attempt("spin", () => spin(2));
|
|
attempt("count 3000", () => count(3000));
|
|
attempt("count -1", () => count(-1));
|
|
attempt("count 1e7", () => count(1e7));
|
|
attempt("count 5 after overflow", () => count(5));
|
|
attempt("isEven 7", () => isEven(7));
|
|
attempt("isOdd 7", () => isOdd(7));
|
|
attempt("isEven 1001", () => isEven(1001));
|
|
for (const n of [0, 1, 5, 20, 40, 60, 61, 62, 100]) attempt(`ping ${n}`, () => ping(n));
|
|
for (const n of [0, 1, 2, 3, 4, 5, 6, 7]) attempt(`alternate ${n}`, () => alternate(true, n));
|
|
attempt("ack 2 3", () => ack(2, 3));
|
|
attempt("ack 3 5", () => ack(3, 5));
|
|
attempt("tak 18 12 6", () => tak(18, 12, 6));
|
|
attempt("tak 12.5 6 0", () => tak(12.5, 6, 0));
|
|
let total = 0;
|
|
for (let i = 0; i < 4; i++) total += tak(12 + i, 8, 4) + ack(2, i);
|
|
console.log(total);
|