diff --git a/pyre/pyre-interpreter/src/function.rs b/pyre/pyre-interpreter/src/function.rs index d12ac880ded..bdfbded2250 100644 --- a/pyre/pyre-interpreter/src/function.rs +++ b/pyre/pyre-interpreter/src/function.rs @@ -3516,11 +3516,22 @@ pub fn funccall_valuestack( if fast_natural_arity == crate::PASSTHROUGHARGS1 as usize && nargs >= 1 { let w_obj = frame.peekvalue(nargs - 1); let rest = frame.make_arguments(nargs - 1, false, func); - let mut args_w = Vec::with_capacity(nargs); - args_w.push(w_obj); - args_w.extend_from_slice(&rest); + // Same live-variable set as the fixed-arity arm above, for the same + // two reasons: `dropvalues()` retires the frame slots that root these + // values, and `args_w` is a native Vec no root walker updates. + // `builtin_code_call` roots nothing of its own, so the whole + // `[code, w_obj, ...rest]` set has to be published here and read back + // for the call. + let _roots = pyre_object::gc_roots::push_roots(); + let root_base = _roots.base(); + _roots.pin_root(code as PyObjectRef); + _roots.pin_root(w_obj); + for &w_arg in &rest { + _roots.pin_root(w_arg); + } frame.dropvalues(dropvalues); - return match unsafe { crate::builtin_code_call(code as PyObjectRef, &args_w) } { + let args_w: Vec = (0..nargs).map(|i| _roots.get(root_base + 1 + i)).collect(); + return match unsafe { crate::builtin_code_call(_roots.get(root_base), &args_w) } { Ok(v) => v, Err(e) => { crate::call::set_call_error(e); diff --git a/pyre/pyre-interpreter/src/gateway.rs b/pyre/pyre-interpreter/src/gateway.rs index cf695e4be46..7c9265a7d62 100644 --- a/pyre/pyre-interpreter/src/gateway.rs +++ b/pyre/pyre-interpreter/src/gateway.rs @@ -829,6 +829,14 @@ pub unsafe fn builtin_code_set_owner(obj: PyObjectRef, owner: &'static MethodOwn /// here, so a call can never reach the implementation with an unchecked /// receiver or a slice the implementation is not written for. /// +/// Roots nothing of its own: `args` is a native slice the collector does not +/// update, and the implementation it dispatches to runs Python. Every caller +/// therefore has to publish `[obj, args...]` on the shadow stack and read the +/// slice back from it, so the set stays live for the whole body and the slice +/// is current at entry. `dropvalues()` retires the frame slots that would +/// otherwise root a peeked argument, so a frame dispatch has to publish before +/// it drops, not after. +/// /// # Safety /// `obj` must point to a valid `BuiltinCode`. #[inline] diff --git a/pyre/pyre-interpreter/src/runtime_ops.rs b/pyre/pyre-interpreter/src/runtime_ops.rs index 23e360288ac..5282b4b51f0 100644 --- a/pyre/pyre-interpreter/src/runtime_ops.rs +++ b/pyre/pyre-interpreter/src/runtime_ops.rs @@ -223,17 +223,34 @@ pub(crate) fn jit_publish_exception(exc_obj: PyObjectRef) { } } +/// Widest `jit_call_known_builtin_N` published by [`known_builtin_call_helper`], +/// plus the leading code slot. +const MAX_KNOWN_BUILTIN_ARGS: usize = 9; + fn call_builtin_with_args(callable: i64, args: &[i64]) -> i64 { + debug_assert!(args.len() <= MAX_KNOWN_BUILTIN_ARGS); let callable = callable as PyObjectRef; - unsafe { - let code = crate::getcode(callable); - let arg_slice = std::slice::from_raw_parts(args.as_ptr() as *const PyObjectRef, args.len()); - match crate::builtin_code_call(code as PyObjectRef, arg_slice) { - Ok(result) => result as i64, - Err(mut e) => { - jit_publish_exception(e.to_exc_object()); - 0 // garbage — GuardNoException will fire - } + let code = unsafe { crate::getcode(callable) } as PyObjectRef; + // `args` is the compiled call's own argument array; no root walker updates + // it, and `builtin_code_call` roots nothing of its own — the direct call + // sites hand it an already-live slice. Publish `[code, args...]` for the + // duration of the body and read the call arguments back from it, the same + // frame `call_builtin_code_positional` builds for the interpreter entry. + let _roots = pyre_object::gc_roots::push_roots(); + let root_base = _roots.base(); + _roots.pin_root(code); + for &arg in args { + _roots.pin_root(arg as PyObjectRef); + } + let mut rooted = [PY_NULL; MAX_KNOWN_BUILTIN_ARGS]; + for (index, slot) in rooted[..args.len()].iter_mut().enumerate() { + *slot = _roots.get(root_base + 1 + index); + } + match unsafe { crate::builtin_code_call(_roots.get(root_base), &rooted[..args.len()]) } { + Ok(result) => result as i64, + Err(mut e) => { + jit_publish_exception(e.to_exc_object()); + 0 // garbage — GuardNoException will fire } } }