diff options
| author | Kevin Menard <kevin@nirvdrum.com> | 2025-11-14 14:43:57 -0500 |
|---|---|---|
| committer | Max Bernstein <tekknolagi@gmail.com> | 2025-11-20 14:35:09 -0800 |
| commit | b06dd644da0ae87f33649ade6fc827a301b55b0c (patch) | |
| tree | 32db79d2a0a4556efdbc0b48a225475db982b9f8 | |
| parent | 604fc059618b8f1f94b19efa51d468d827a766d1 (diff) | |
ZJIT: Compile the VM_OPT_NEWARRAY_SEND_HASH variant of opt_newarray_send
| -rw-r--r-- | test/ruby/test_zjit.rb | 20 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 28 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 16 | ||||
| -rw-r--r-- | zjit/src/hir/tests.rs | 44 |
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)) "); } |
