ZJIT: Avoid compiling and direct sends to forwardable ISEQs

These `...` ISEQs have a special calling convention in the interpreter
and our stubs and JIT calling convention don't deal well. Reject for now.
Debugged with help from `@tekknolagi` and `tool/zjit_bisect.rb`.

Merely avoiding direct sends is enough to pass the attached test, but also
avoid compiling ISEQs with `...` parameter to limit exposure for now.

`SendWithoutBlock`, which does dynamic dispatch using interpreter code,
seems to handle calling into forwardable ISEQs correctly, so they are
fine -- we can't predict where these dynamic sends land anyways.
This commit is contained in:
Alan Wu 2025-08-08 14:54:53 -04:00 committed by GitHub
parent eb931a09c5
commit 0ba488d7f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 22 additions and 17 deletions

View file

@ -969,6 +969,7 @@ fn can_direct_send(iseq: *const rb_iseq_t) -> bool {
else if unsafe { rb_get_iseq_flags_has_kw(iseq) } { false }
else if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { false }
else if unsafe { rb_get_iseq_flags_has_block(iseq) } { false }
else if unsafe { rb_get_iseq_flags_forwardable(iseq) } { false }
else { true }
}
@ -2581,6 +2582,9 @@ pub enum CallType {
#[derive(Debug, PartialEq)]
pub enum ParameterType {
Optional,
/// For example, `foo(...)`. Interaction of JIT
/// calling convention and side exits currently unsolved.
Forwardable,
}
#[derive(Debug, PartialEq)]
@ -2650,6 +2654,7 @@ pub const SELF_PARAM_IDX: usize = 0;
fn filter_unknown_parameter_type(iseq: *const rb_iseq_t) -> Result<(), ParseError> {
if unsafe { rb_get_iseq_body_param_opt_num(iseq) } != 0 { return Err(ParseError::UnknownParameterType(ParameterType::Optional)); }
if unsafe { rb_get_iseq_flags_forwardable(iseq) } { return Err(ParseError::UnknownParameterType(ParameterType::Forwardable)); }
Ok(())
}
@ -4583,11 +4588,13 @@ mod tests {
eval("
def test(...) = super(...)
");
assert_method_hir("test", expect![[r#"
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject):
SideExit UnknownOpcode(invokesuperforward)
"#]]);
assert_compile_fails("test", ParseError::UnknownParameterType(ParameterType::Forwardable));
}
#[test]
fn test_cant_compile_forwardable() {
eval("def forwardable(...) = nil");
assert_compile_fails("forwardable", ParseError::UnknownParameterType(ParameterType::Forwardable));
}
// TODO(max): Figure out how to generate a call with OPT_SEND flag
@ -4631,11 +4638,7 @@ mod tests {
eval("
def test(...) = foo(...)
");
assert_method_hir("test", expect![[r#"
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject):
SideExit UnknownOpcode(sendforward)
"#]]);
assert_compile_fails("test", ParseError::UnknownParameterType(ParameterType::Forwardable));
}
#[test]
@ -5691,7 +5694,6 @@ mod opt_tests {
def kw_rest(**k) = k
def post(*rest, post) = post
def block(&b) = nil
def forwardable(...) = nil
");
assert_optimized_method_hir("rest", expect![[r#"
@ -5721,12 +5723,6 @@ mod opt_tests {
bb0(v0:BasicObject, v1:ArrayExact, v2:BasicObject):
Return v2
"#]]);
assert_optimized_method_hir("forwardable", expect![[r#"
fn forwardable@<compiled>:7:
bb0(v0:BasicObject, v1:BasicObject):
v3:NilClass = Const Value(nil)
Return v3
"#]]);
}
#[test]