ZJIT: Implement defined? codegen for non-yield calls (#14101)

This commit is contained in:
Stan Lo 2025-08-07 23:41:05 +01:00 committed by GitHub
parent c41c323f1a
commit 2edc944702
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 12 deletions

View file

@ -1043,6 +1043,26 @@ class TestZJIT < Test::Unit::TestCase
}
end
def test_defined_with_defined_values
assert_compiles '["constant", "method", "global-variable"]', %q{
class Foo; end
def bar; end
$ruby = 1
def test = return defined?(Foo), defined?(bar), defined?($ruby)
test
}, insns: [:defined]
end
def test_defined_with_undefined_values
assert_compiles '[nil, nil, nil]', %q{
def test = return defined?(Foo), defined?(bar), defined?($ruby)
test
}, insns: [:defined]
end
def test_defined_yield
assert_compiles "nil", "defined?(yield)"
assert_compiles '[nil, nil, "yield"]', %q{

View file

@ -369,7 +369,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::SideExit { state, reason } => return gen_side_exit(jit, asm, reason, &function.frame_state(*state)),
Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type),
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
Insn::Defined { op_type, obj, pushval, v } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v))?,
Insn::Defined { op_type, obj, pushval, v, state } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v), &function.frame_state(*state))?,
&Insn::IncrCounter(counter) => return Some(gen_incr_counter(asm, counter)),
Insn::ArrayExtend { .. }
| Insn::ArrayMax { .. }
@ -438,7 +438,7 @@ fn gen_get_ep(asm: &mut Assembler, level: u32) -> Opnd {
ep_opnd
}
fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, _obj: VALUE, pushval: VALUE, _tested_value: Opnd) -> Option<Opnd> {
fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, pushval: VALUE, tested_value: Opnd, state: &FrameState) -> Option<Opnd> {
match op_type as defined_type {
DEFINED_YIELD => {
// `yield` goes to the block handler stowed in the "local" iseq which is
@ -455,7 +455,17 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, _obj: VALUE,
Some(Qnil.into())
}
}
_ => None
_ => {
// Save the PC and SP because the callee may allocate or call #respond_to?
gen_prepare_non_leaf_call(jit, asm, state)?;
// TODO: Inline the cases for each op_type
// Call vm_defined(ec, reg_cfp, op_type, obj, v)
let def_result = asm_ccall!(asm, rb_vm_defined, EC, CFP, op_type.into(), obj.into(), tested_value);
asm.cmp(def_result.with_num_bits(8).unwrap(), 0.into());
Some(asm.csel_ne(pushval.into(), Qnil.into()))
}
}
}

View file

@ -472,7 +472,7 @@ pub enum Insn {
Test { val: InsnId },
/// Return C `true` if `val` is `Qnil`, else `false`.
IsNil { val: InsnId },
Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId },
Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId },
GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId },
/// Get a global variable named `id`
@ -1173,7 +1173,7 @@ impl Function {
&ArrayDup { val, state } => ArrayDup { val: find!(val), state },
&HashDup { val, state } => HashDup { val: find!(val), state },
&CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun, args: find_vec!(args), name, return_type, elidable },
&Defined { op_type, obj, pushval, v } => Defined { op_type, obj, pushval, v: find!(v) },
&Defined { op_type, obj, pushval, v, state } => Defined { op_type, obj, pushval, v: find!(v), state: find!(state) },
&DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state },
&NewArray { ref elements, state } => NewArray { elements: find_vec!(elements), state: find!(state) },
&NewHash { ref elements, state } => {
@ -2788,7 +2788,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let obj = get_arg(pc, 1);
let pushval = get_arg(pc, 2);
let v = state.stack_pop()?;
state.stack_push(fun.push_insn(block, Insn::Defined { op_type, obj, pushval, v }));
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
state.stack_push(fun.push_insn(block, Insn::Defined { op_type, obj, pushval, v, state: exit_id }));
}
YARVINSN_definedivar => {
// (ID id, IVC ic, VALUE pushval)
@ -4061,12 +4062,12 @@ mod tests {
fn test@<compiled>:2:
bb0(v0:BasicObject):
v2:NilClass = Const Value(nil)
v3:BasicObject = Defined constant, v2
v4:BasicObject = Defined func, v0
v5:NilClass = Const Value(nil)
v6:BasicObject = Defined global-variable, v5
v8:ArrayExact = NewArray v3, v4, v6
Return v8
v4:BasicObject = Defined constant, v2
v6:BasicObject = Defined func, v0
v7:NilClass = Const Value(nil)
v9:BasicObject = Defined global-variable, v7
v11:ArrayExact = NewArray v4, v6, v9
Return v11
"#]]);
}