summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_zjit.rb15
-rw-r--r--zjit/src/codegen.rs12
-rw-r--r--zjit/src/hir.rs24
-rw-r--r--zjit/src/hir/opt_tests.rs27
-rw-r--r--zjit/src/hir/tests.rs26
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