summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Menard <kevin@nirvdrum.com>2025-11-14 14:43:57 -0500
committerMax Bernstein <tekknolagi@gmail.com>2025-11-20 14:35:09 -0800
commitb06dd644da0ae87f33649ade6fc827a301b55b0c (patch)
tree32db79d2a0a4556efdbc0b48a225475db982b9f8
parent604fc059618b8f1f94b19efa51d468d827a766d1 (diff)
ZJIT: Compile the VM_OPT_NEWARRAY_SEND_HASH variant of opt_newarray_send
-rw-r--r--test/ruby/test_zjit.rb20
-rw-r--r--zjit/src/codegen.rs28
-rw-r--r--zjit/src/hir.rs16
-rw-r--r--zjit/src/hir/tests.rs44
4 files changed, 107 insertions, 1 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 64372c231c..ee046ad9bf 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -1049,6 +1049,26 @@ class TestZJIT < Test::Unit::TestCase
}, insns: [:opt_duparray_send], call_threshold: 1
end
+ def test_opt_newarray_send_hash
+ assert_compiles 'Integer', %q{
+ def test(x)
+ [1, 2, x].hash
+ end
+ test(20).class
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
+ def test_opt_newarray_send_hash_redefinition
+ assert_compiles '42', %q{
+ Array.class_eval { def hash = 42 }
+
+ def test(x)
+ [1, 2, x].hash
+ end
+ test(20)
+ }, insns: [:opt_newarray_send], call_threshold: 1
+ end
+
def test_new_hash_empty
assert_compiles '{}', %q{
def test = {}
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 71e40c640f..082db3fae4 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -464,6 +464,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
&Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)),
+ Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
&Insn::ArrayMax { state, .. }
| &Insn::FixnumDiv { state, .. }
| &Insn::Throw { state, .. }
@@ -1427,6 +1428,33 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd {
asm_ccall!(asm, rb_jit_array_len, array)
}
+/// Compile opt_newarray_hash - create a hash from array elements
+fn gen_opt_newarray_hash(
+ jit: &JITState,
+ asm: &mut Assembler,
+ elements: Vec<Opnd>,
+ state: &FrameState,
+) -> lir::Opnd {
+ // `Array#hash` will hash the elements of the array.
+ gen_prepare_non_leaf_call(jit, asm, state);
+
+ let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
+
+ // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack.
+ // Get a pointer to the first element on the Ruby stack.
+ let stack_bottom = state.stack().len() - elements.len();
+ let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32));
+
+ unsafe extern "C" {
+ fn rb_vm_opt_newarray_hash(ec: EcPtr, array_len: u32, elts: *const VALUE) -> VALUE;
+ }
+
+ asm.ccall(
+ rb_vm_opt_newarray_hash as *const u8,
+ vec![EC, (array_len as u32).into(), elements_ptr],
+ )
+}
+
fn gen_array_include(
jit: &JITState,
asm: &mut Assembler,
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index face61f1f6..4232410f23 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -231,6 +231,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
BOP_FREEZE => write!(f, "BOP_FREEZE")?,
BOP_UMINUS => write!(f, "BOP_UMINUS")?,
BOP_MAX => write!(f, "BOP_MAX")?,
+ BOP_HASH => write!(f, "BOP_HASH")?,
BOP_AREF => write!(f, "BOP_AREF")?,
_ => write!(f, "{bop}")?,
}
@@ -650,6 +651,7 @@ pub enum Insn {
NewRange { low: InsnId, high: InsnId, flag: RangeType, state: InsnId },
NewRangeFixnum { low: InsnId, high: InsnId, flag: RangeType, state: InsnId },
ArrayDup { val: InsnId, state: InsnId },
+ ArrayHash { elements: Vec<InsnId>, state: InsnId },
ArrayMax { elements: Vec<InsnId>, state: InsnId },
ArrayInclude { elements: Vec<InsnId>, target: InsnId, state: InsnId },
DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId },
@@ -1040,6 +1042,15 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
+ Insn::ArrayHash { elements, .. } => {
+ write!(f, "ArrayHash")?;
+ let mut prefix = " ";
+ for element in elements {
+ write!(f, "{prefix}{element}")?;
+ prefix = ", ";
+ }
+ Ok(())
+ }
Insn::ArrayInclude { elements, target, .. } => {
write!(f, "ArrayInclude")?;
let mut prefix = " ";
@@ -1887,6 +1898,7 @@ impl Function {
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
&ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) },
&DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) },
+ &ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state },
&SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
&GetIvar { self_val, id, ic, state } => GetIvar { self_val: find!(self_val), id, ic, state },
&LoadField { recv, id, offset, return_type } => LoadField { recv: find!(recv), id, offset, return_type },
@@ -2032,6 +2044,7 @@ impl Function {
Insn::ArrayMax { .. } => types::BasicObject,
Insn::ArrayInclude { .. } => types::BoolExact,
Insn::DupArrayInclude { .. } => types::BoolExact,
+ Insn::ArrayHash { .. } => types::Fixnum,
Insn::GetGlobal { .. } => types::BasicObject,
Insn::GetIvar { .. } => types::BasicObject,
Insn::LoadPC => types::CPtr,
@@ -3346,6 +3359,7 @@ impl Function {
worklist.push_back(val)
}
&Insn::ArrayMax { ref elements, state }
+ | &Insn::ArrayHash { ref elements, state }
| &Insn::NewHash { ref elements, state }
| &Insn::NewArray { ref elements, state } => {
worklist.extend(elements);
@@ -4102,6 +4116,7 @@ impl Function {
| Insn::InvokeBuiltin { ref args, .. }
| Insn::InvokeBlock { ref args, .. }
| Insn::NewArray { elements: ref args, .. }
+ | Insn::ArrayHash { elements: ref args, .. }
| Insn::ArrayMax { elements: ref args, .. } => {
for &arg in args {
self.assert_subtype(insn_id, arg, types::BasicObject)?;
@@ -4908,6 +4923,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let elements = state.stack_pop_n(count)?;
let (bop, insn) = match method {
VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }),
+ VM_OPT_NEWARRAY_SEND_HASH => (BOP_HASH, Insn::ArrayHash { elements, state: exit_id }),
VM_OPT_NEWARRAY_SEND_INCLUDE_P => {
let target = elements[elements.len() - 1];
let array_elements = elements[..elements.len() - 1].to_vec();
diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs
index b487352748..1e058ce11a 100644
--- a/zjit/src/hir/tests.rs
+++ b/zjit/src/hir/tests.rs
@@ -2013,7 +2013,49 @@ pub mod hir_build_tests {
Jump bb2(v8, v9, v10, v11, v12)
bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
v25:BasicObject = SendWithoutBlock v15, :+, v16
- SideExit UnhandledNewarraySend(HASH)
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH)
+ v32:Fixnum = ArrayHash v15, v16
+ PatchPoint NoEPEscape(test)
+ v39:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v40:ArrayExact = ArrayDup v39
+ v42:BasicObject = SendWithoutBlock v14, :puts, v40
+ PatchPoint NoEPEscape(test)
+ CheckInterrupts
+ Return v32
+ ");
+ }
+
+ #[test]
+ fn test_opt_newarray_send_hash_redefined() {
+ eval("
+ Array.class_eval { def hash = 42 }
+
+ def test(a,b)
+ sum = a+b
+ result = [a,b].hash
+ puts [1,2,3]
+ result
+ end
+ ");
+ assert_contains_opcode("test", YARVINSN_opt_newarray_send);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:5:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@7
+ v3:BasicObject = GetLocal l0, SP@6
+ v4:NilClass = Const Value(nil)
+ v5:NilClass = Const Value(nil)
+ Jump bb2(v1, v2, v3, v4, v5)
+ bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject):
+ EntryPoint JIT(0)
+ v11:NilClass = Const Value(nil)
+ v12:NilClass = Const Value(nil)
+ Jump bb2(v8, v9, v10, v11, v12)
+ bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass):
+ v25:BasicObject = SendWithoutBlock v15, :+, v16
+ SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_HASH))
");
}