You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A guarded $spec_b clone never re-enters itself either — the Tier-B half of #8167 #8169
#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:
functionfib(n: number): number{returnn<2 ? n : fib(n-1)+fib(n-2);}constk=Number(process.argv[2]??"30");console.log(fib(k));
--trace llvm on perf/spec-clone-self-recursion (i.e. WITH #8167):
defineinternaldouble @…__fib$spec_b(double%arg1) {
%r3 = fcmpoltdouble%r2, 2.0; the clone is fast%r9 = fsubdouble%r8, 1.0%r10 = calldouble @…__fib(double%r9) ; …but re-enters the PUBLIC TRAMPOLINE%r13 = calldouble @…__fib(double%r12)
}
definedouble @…__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.
A Boxed slot takes the doubleverbatim — 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.
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.
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 main before submitting a focused fix.
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)