Repository navigation
Description
#8167 fixed the raw-i32 half of "a specialized clone never re-enters itself". The Tier-B ($spec_b) half is untouched and is the shape most real code hits, because it does not need a literal call site to exist.
Change fib40.ts's entry call from a literal to a variable and the same function takes the guarded route:
function fib(n: number): number { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
const k = Number(process.argv[2] ?? "30");
console.log(fib(k));--trace llvm on perf/spec-clone-self-recursion (i.e. WITH #8167):
define internal double @…__fib$spec_b(double %arg1) {
%r3 = fcmp olt double %r2, 2.0 ; the clone is fast
%r9 = fsub double %r8, 1.0
%r10 = call double @…__fib(double %r9) ; …but re-enters the PUBLIC TRAMPOLINE
%r13 = call double @…__fib(double %r12)
}
define double @…__fib(double %arg1) noinline { … js_param_type_guard … }So every recursive step pays a noinline trampoline call plus a js_param_type_guard runtime call, then lands back in the clone it just left.
Why it happens
try_emit_spec_guarded_call routes a site only when guarded_argument_proves(ctx, arg, expected) holds, and guarded_path_type has no arm for Expr::Binary — so a derived argument (n - 1) yields None and the site keeps the public wrapper. Identical in structure to the i32 defect #8167 fixed, one tier over.
Why the fix should be cheaper than #8167's
A Boxed slot takes the double verbatim — a JS Number's NaN-box is its double. There is no conversion, so none of #8167's three obligations apply: no 32-bit range test, no exact-integer window, no -0 hazard. The whole obligation is "this value is a Number", which the codebase already has a predicate for (type_analysis::is_numeric_expr — the same one that gates fadd versus js_dynamic_add, so if it were wrong arithmetic would already be miscompiling).
Sketch: in guarded_path_type (crates/perry-codegen/src/lower_call/func_ref.rs), fall back to Type::Number when is_numeric_expr(ctx, expr) holds and no structural arm matched.
Why it was NOT done in #8167
guarded_path_type also feeds guarded_expr_proof and guarded_call_return_proof, so widening it widens more than the call-site router, and #8167's owner-set scope was explicitly "fix fib40's mechanism, do not claim the other eight regressed rows without measuring each". This wants its own A/B of cargo test -p perry-codegen --no-fail-fast (by name) and its own corpus measurement — several of the still-regressed rows (tree, interp, shapes) have number-typed hot parameters with no literal call site, which is exactly this shape.
Activity
perf(codegen): decide scalar parameter descriptors with the typed-abi…
perf(runtime): resolve the param guard's per-object facts once per ob…
proggeramlug commentedon Aug 17, 2026
I checked the current open PRs, branches, and worktrees and found no active implementation for this issue. I am taking it and will reproduce it against current
mainbefore submitting a focused fix.perf(codegen): re-enter guarded specialization clones (#8290)