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
ZJIT: Compile direct sends to callees with a **rest parameter
`can_direct_send` rejected every callee with a `**rest` parameter, which on
lobsters was 176k of the remaining `one_or_more_complex_arg_pass` send
fallbacks -- the largest shape left after `def foo(...)` callees, because
Rails option hashes are written as `**options` throughout.
`**rest` is the keyword planning we already do plus one more argument.
`args_setup_kw_parameters` fills the named keyword slots exactly as before and
then hands `make_rest_kw_hash` whatever slots the table did not claim, in the
order the caller wrote them, so the extras become one `NewHash` in the callee's
kwrest slot. The Hash is allocated even when nothing is left over, which is
also what makes `foo(1)` against `def foo(a, **opts)` -- the common Rails shape
-- compile: `opts` is just an empty Hash.
Relaxing the count and unknown-keyword checks for these callees is what lets
the extras through in the first place; every required keyword is still matched
by name, so a missing one keeps raising from the interpreter.
Two callee modes stay on VM dispatch: `def foo(**)`, because
`args_setup_kw_rest_parameter` leaves the anonymous slot nil instead of
allocating an empty Hash when no keywords are passed, and `ruby2_keywords`,
which needs the VM to carry RHASH_PASS_AS_KEYWORDS across the call.
The argument list also has to give the hidden `kw_bits` slot an argument of
its own when the callee has both named keywords and `**rest`: the parameter
locals run `(lead, opt, rest, post, kw..., kw_bits, kwrest)`, so pushing the
kwrest Hash right after the keywords lands it one local low, in `kw_bits`,
where the bitmask store then overwrites it, and the real `**rest` local keeps
whatever stale VALUE was on the VM stack. JIT-to-JIT calls pass arguments in
registers, so this only broke when a function stub spilled the frame in local
order and exited to the interpreter -- which is how it corrupted
`**materialization_options` in bundler's lazy_specification.rb under the
default call threshold in production. `kw_bits` therefore becomes a real
argument for these callees, the caller's separate frame store goes away, and
the JIT entry reads it as a parameter. Bisected and patched by River.
0 commit comments