diff options
| -rw-r--r-- | test/ruby/test_zjit.rb | 15 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 12 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 24 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 27 | ||||
| -rw-r--r-- | zjit/src/hir/tests.rs | 26 |
5 files changed, 104 insertions, 0 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 7b068e9898..095b690c7b 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -3380,6 +3380,21 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 1, insns: [:opt_getconstant_path] end + def test_getconstant + assert_compiles '1', %q{ + class Foo + CONST = 1 + end + + def test(klass) + klass::CONST + end + + test(Foo) + test(Foo) + }, call_threshold: 2, insns: [:getconstant] + end + def test_expandarray_no_splat assert_compiles '[3, 4]', %q{ def test(o) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e9949705fc..29ea47c68e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -558,6 +558,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::IsBlockParamModified { level } => gen_is_block_param_modified(asm, level), &Insn::GetBlockParam { ep_offset, level, state } => gen_getblockparam(jit, asm, ep_offset, level, &function.frame_state(state)), &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), + Insn::GetConstant { klass, id, allow_nil, state } => gen_getconstant(jit, asm, opnd!(klass), *id, opnd!(allow_nil), &function.frame_state(*state)), Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)), Insn::GetClassVar { id, ic, state } => gen_getclassvar(jit, asm, *id, *ic, &function.frame_state(*state)), Insn::SetClassVar { id, val, ic, state } => no_output!(gen_setclassvar(jit, asm, *id, opnd!(val), *ic, &function.frame_state(*state))), @@ -811,6 +812,17 @@ fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_in asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic)) } +fn gen_getconstant(jit: &mut JITState, asm: &mut Assembler, klass: Opnd, id: ID, allow_nil: Opnd, state: &FrameState) -> Opnd { + unsafe extern "C" { + fn rb_vm_get_ev_const(ec: EcPtr, klass: VALUE, id: ID, allow_nil: VALUE) -> VALUE; + } + + // Constant lookup can raise and run arbitrary Ruby code via const_missing. + gen_prepare_non_leaf_call(jit, asm, state); + + asm_ccall!(asm, rb_vm_get_ev_const, EC, klass, id.0.into(), allow_nil) +} + fn gen_fixnum_bit_check(asm: &mut Assembler, val: Opnd, index: u8) -> Opnd { let bit_test: u64 = 0x01 << (index + 1); asm.test(val, bit_test.into()); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3fd5fcc5cb..fe0122d297 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -803,6 +803,7 @@ pub enum Insn { FixnumAref { recv: InsnId, index: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, + GetConstant { klass: InsnId, id: ID, allow_nil: InsnId, state: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, /// Kernel#block_given? but without pushing a frame. Similar to [`Insn::Defined`] with /// `DEFINED_YIELD` @@ -1139,6 +1140,7 @@ impl Insn { Insn::UnboxFixnum { .. } => effects::Any, Insn::FixnumAref { .. } => effects::Empty, Insn::Defined { .. } => effects::Any, + Insn::GetConstant { .. } => effects::Any, Insn::GetConstantPath { .. } => effects::Any, Insn::IsBlockGiven { .. } => Effect::read_write(abstract_heaps::Other, abstract_heaps::Empty), Insn::FixnumBitCheck { .. } => effects::Any, @@ -1562,6 +1564,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { write!(f, "GetBlockParam {name}l{level}, EP@{ep_offset}") }, Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, + Insn::GetConstant { klass, id, allow_nil, .. } => { + write!(f, "GetConstant {klass}, :{}, {allow_nil}", id.contents_lossy()) + } Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven { lep } => { write!(f, "IsBlockGiven {lep}") }, Insn::FixnumBitCheck {val, index} => { write!(f, "FixnumBitCheck {val}, {index}") }, @@ -2356,6 +2361,7 @@ impl Function { }, &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 }, + &GetConstant { klass, id, allow_nil, state } => GetConstant { klass: find!(klass), id, allow_nil: find!(allow_nil), state }, &NewArray { ref elements, state } => NewArray { elements: find_vec!(elements), state: find!(state) }, &NewHash { ref elements, state } => NewHash { elements: find_vec!(elements), state: find!(state) }, &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, @@ -2527,6 +2533,7 @@ impl Function { Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject), Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), + Insn::GetConstant { .. } => types::BasicObject, Insn::GetConstantPath { .. } => types::BasicObject, Insn::IsBlockGiven { .. } => types::BoolExact, Insn::FixnumBitCheck { .. } => types::BoolExact, @@ -4881,6 +4888,11 @@ impl Function { worklist.push_back(self_val); worklist.push_back(state); } + &Insn::GetConstant { klass, allow_nil, state, .. } => { + worklist.push_back(klass); + worklist.push_back(allow_nil); + worklist.push_back(state); + } &Insn::SetIvar { self_val, val, state, .. } => { worklist.push_back(self_val); worklist.push_back(val); @@ -5507,6 +5519,10 @@ impl Function { self.assert_subtype(insn_id, left, types::BasicObject)?; self.assert_subtype(insn_id, right, types::BasicObject) } + Insn::GetConstant { klass, allow_nil, .. } => { + self.assert_subtype(insn_id, klass, types::BasicObject)?; + self.assert_subtype(insn_id, allow_nil, types::BoolExact) + } // Instructions with recv and a Vec of Ruby objects Insn::SendWithoutBlock { recv, ref args, .. } | Insn::SendDirect { recv, ref args, .. } @@ -6626,8 +6642,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { }; state.stack_push(fun.push_insn(block, Insn::FixnumBitCheck { val, index })); } + YARVINSN_getconstant => { + let id = ID(get_arg(pc, 0).as_u64()); + let allow_nil = state.stack_pop()?; + let klass = state.stack_pop()?; + let result = fun.push_insn(block, Insn::GetConstant { klass, id, allow_nil, state: exit_id }); + state.stack_push(result); + } YARVINSN_opt_getconstant_path => { let ic = get_arg(pc, 0).as_ptr(); + // TODO: Remove this extra Snapshot and pass `exit_id` to `GetConstantPath` instead. let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state }); state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic, state: snapshot })); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index afbbc8bedc..4ce01c438a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2357,6 +2357,33 @@ mod hir_opt_tests { } #[test] + fn test_do_not_eliminate_getconstant() { + eval(" + def test(klass) + klass::ARGV + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :klass, l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:FalseClass = Const Value(false) + v16:BasicObject = GetConstant v9, :ARGV, v14 + v20:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v20 + "); + } + + #[test] fn kernel_itself_const() { eval(" def test(x) = x.itself diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index ef1d9597dd..f3ab0fa57c 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2630,6 +2630,32 @@ pub mod hir_build_tests { } #[test] + fn test_getconstant() { + eval(" + def test(klass) + klass::ARGV + end + "); + assert_contains_opcode("test", YARVINSN_getconstant); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :klass, l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:FalseClass = Const Value(false) + v16:BasicObject = GetConstant v9, :ARGV, v14 + CheckInterrupts + Return v16 + "); + } + + #[test] fn test_getinstancevariable() { eval(" def test = @foo |
