From 950ffa90b7939885cc35d376a2e10aabfdc7170d Mon Sep 17 00:00:00 2001 From: Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:24:29 +0900 Subject: [PATCH] ZJIT: Add ArrayAset instruction to HIR (#15747) Inline `Array#[]=` into `ArrayAset`. --- test/ruby/test_zjit.rb | 109 ++++++++++++++++++++++++++++++++++++ zjit/src/codegen.rs | 35 ++++++++++++ zjit/src/cruby_methods.rs | 26 +++++++++ zjit/src/hir.rs | 33 +++++++++-- zjit/src/hir/opt_tests.rs | 115 +++++++++++++++++++++++++++++++++++++- zjit/src/stats.rs | 2 + 6 files changed, 313 insertions(+), 7 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 805ecb98b2..43db676d5d 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1646,6 +1646,115 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2, insns: [:opt_aref] end + def test_array_fixnum_aset + assert_compiles '[1, 2, 7]', %q{ + def test(arr, idx) + arr[idx] = 7 + end + arr = [1,2,3] + test(arr, 2) + arr = [1,2,3] + test(arr, 2) + arr + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_fixnum_aset_returns_value + assert_compiles '7', %q{ + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + test([1,2,3], 2) + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_fixnum_aset_out_of_bounds + assert_compiles '[1, 2, 3, nil, nil, 7]', %q{ + def test(arr) + arr[5] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + }, call_threshold: 2 + end + + def test_array_fixnum_aset_negative_index + assert_compiles '[1, 2, 7]', %q{ + def test(arr) + arr[-1] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + }, call_threshold: 2 + end + + def test_array_fixnum_aset_shared + assert_compiles '[10, 999, -1, -2]', %q{ + def test(arr, idx, val) + arr[idx] = val + end + arr = (0..50).to_a + test(arr, 0, -1) + test(arr, 1, -2) + shared = arr[10, 20] + test(shared, 0, 999) + [arr[10], shared[0], arr[0], arr[1]] + }, call_threshold: 2 + end + + def test_array_fixnum_aset_frozen + assert_compiles 'FrozenError', %q{ + def test(arr, idx, val) + arr[idx] = val + end + arr = [1,2,3] + test(arr, 1, 9) + test(arr, 1, 9) + arr.freeze + begin + test(arr, 1, 9) + rescue => e + e.class + end + }, call_threshold: 2 + end + + def test_array_fixnum_aset_array_subclass + assert_compiles '7', %q{ + class MyArray < Array; end + def test(arr, idx) + arr[idx] = 7 + end + arr = MyArray.new + test(arr, 0) + arr = MyArray.new + test(arr, 0) + arr[0] + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_aset_non_fixnum_index + assert_compiles 'TypeError', %q{ + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 0) + test([1,2,3], 0) + begin + test([1,2,3], "0") + rescue => e + e.class + end + }, call_threshold: 2 + end + def test_empty_array_pop assert_compiles 'nil', %q{ def test(arr) = arr.pop diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index b05c011090..c728df7255 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -375,6 +375,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), + Insn::ArrayAset { array, index, val } => { + no_output!(gen_array_aset(asm, opnd!(array), opnd!(index), opnd!(val))) + } Insn::ArrayPop { array, state } => gen_array_pop(asm, opnd!(array), &function.frame_state(*state)), Insn::ArrayLength { array } => gen_array_length(asm, opnd!(array)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), @@ -449,6 +452,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)), + Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), @@ -692,6 +696,15 @@ fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: recv } +fn gen_guard_not_shared(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { + let recv = asm.load(recv); + // It's a heap object, so check the shared flag + let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS)); + asm.test(flags, (RUBY_ELTS_SHARED as u64).into()); + asm.jnz(side_exit(jit, state, SideExitReason::GuardNotShared)); + recv +} + fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd { asm.cmp(left, right); asm.jge(side_exit(jit, state, SideExitReason::GuardLess)); @@ -1529,6 +1542,20 @@ fn gen_aref_fixnum( asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) } +fn gen_array_aset( + asm: &mut Assembler, + array: Opnd, + index: Opnd, + val: Opnd, +) { + let unboxed_idx = asm.load(index); + let array = asm.load(array); + let array_ptr = gen_array_ptr(asm, array); + let elem_offset = asm.lshift(unboxed_idx, Opnd::UImm(SIZEOF_VALUE.trailing_zeros() as u64)); + let elem_ptr = asm.add(array_ptr, elem_offset); + asm.store(Opnd::mem(VALUE_BITS, elem_ptr, 0), val); +} + fn gen_array_pop(asm: &mut Assembler, array: Opnd, state: &FrameState) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); asm_ccall!(asm, rb_ary_pop, array) @@ -1545,6 +1572,14 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { asm.csel_nz(embedded_len, heap_len) } +fn gen_array_ptr(asm: &mut Assembler, array: Opnd) -> lir::Opnd { + let flags = Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RBASIC_FLAGS); + asm.test(flags, (RARRAY_EMBED_FLAG as u64).into()); + let heap_ptr = Opnd::mem(usize::BITS as u8, array, RUBY_OFFSET_RARRAY_AS_HEAP_PTR); + let embedded_ptr = asm.lea(Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RARRAY_AS_ARY)); + asm.csel_nz(embedded_ptr, heap_ptr) +} + /// Compile opt_newarray_hash - create a hash from array elements fn gen_opt_newarray_hash( jit: &JITState, diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 60060f149c..4aa9068cb1 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -225,6 +225,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cArray, "[]", inline_array_aref); + annotate!(rb_cArray, "[]=", inline_array_aset); annotate!(rb_cArray, "<<", inline_array_push); annotate!(rb_cArray, "push", inline_array_push); annotate!(rb_cArray, "pop", inline_array_pop); @@ -332,6 +333,31 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In None } +fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if let &[index, val] = args { + if fun.likely_a(recv, types::ArrayExact, state) + && fun.likely_a(index, types::Fixnum, state) + { + let recv = fun.coerce_to(block, recv, types::ArrayExact, state); + let index = fun.coerce_to(block, index, types::Fixnum, state); + let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); + let recv = fun.push_insn(block, hir::Insn::GuardNotShared { recv, state }); + + // Bounds check: unbox Fixnum index and guard 0 <= idx < length. + let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); + let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); + let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, state }); + let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, state }); + + let _ = fun.push_insn(block, hir::Insn::ArrayAset { array: recv, index, val }); + fun.push_insn(block, hir::Insn::WriteBarrier { recv, val }); + return Some(val); + } + } + None +} + fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { // Inline only the case of `<<` or `push` when called with a single argument. if let &[val] = args { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3f1ed83e4b..2ea6e94c96 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -493,6 +493,7 @@ pub enum SideExitReason { GuardShape(ShapeId), GuardBitEquals(Const), GuardNotFrozen, + GuardNotShared, GuardLess, GuardGreaterEq, PatchPoint(Invariant), @@ -580,6 +581,7 @@ impl std::fmt::Display for SideExitReason { SideExitReason::GuardType(guard_type) => write!(f, "GuardType({guard_type})"), SideExitReason::GuardTypeNot(guard_type) => write!(f, "GuardTypeNot({guard_type})"), SideExitReason::GuardBitEquals(value) => write!(f, "GuardBitEquals({})", value.print(&PtrPrintMap::identity())), + SideExitReason::GuardNotShared => write!(f, "GuardNotShared"), SideExitReason::PatchPoint(invariant) => write!(f, "PatchPoint({invariant})"), _ => write!(f, "{self:?}"), } @@ -728,6 +730,7 @@ pub enum Insn { /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, ArrayArefFixnum { array: InsnId, index: InsnId }, + ArrayAset { array: InsnId, index: InsnId, val: InsnId }, ArrayPop { array: InsnId, state: InsnId }, /// Return the length of the array as a C `long` ([`types::CInt64`]) ArrayLength { array: InsnId }, @@ -960,6 +963,9 @@ pub enum Insn { /// Side-exit if val is frozen. Does *not* check if the val is an immediate; assumes that it is /// a heap object. GuardNotFrozen { recv: InsnId, state: InsnId }, + /// Side-exit if val is shared. Does *not* check if the val is an immediate; assumes + /// that it is a heap object. + GuardNotShared { recv: InsnId, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). @@ -992,8 +998,9 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } - | Insn::HashAset { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } + | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } + | Insn::ArrayAset { .. } => false, _ => true, } } @@ -1121,6 +1128,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayArefFixnum { array, index, .. } => { write!(f, "ArrayArefFixnum {array}, {index}") } + Insn::ArrayAset { array, index, val, ..} => { + write!(f, "ArrayAset {array}, {index}, {val}") + } Insn::ArrayPop { array, .. } => { write!(f, "ArrayPop {array}") } @@ -1332,6 +1342,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), Insn::GuardNotFrozen { recv, .. } => write!(f, "GuardNotFrozen {recv}"), + Insn::GuardNotShared { recv, .. } => write!(f, "GuardNotShared {recv}"), Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, @@ -1968,6 +1979,7 @@ impl Function { &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, &GuardNotFrozen { recv, state } => GuardNotFrozen { recv: find!(recv), state }, + &GuardNotShared { recv, state } => GuardNotShared { recv: find!(recv), state }, &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, @@ -2071,6 +2083,7 @@ impl Function { &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, + &ArrayAset { array, index, val } => ArrayAset { array: find!(array), index: find!(index), val: find!(val) }, &ArrayPop { array, state } => ArrayPop { array: find!(array), state: find!(state) }, &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, @@ -2143,7 +2156,7 @@ impl Function { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } - | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } => + | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } | Insn::ArrayAset { .. } => panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -2197,7 +2210,7 @@ impl Function { Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), - Insn::GuardNotFrozen { recv, .. } => self.type_of(*recv), + Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => self.type_of(*recv), Insn::GuardLess { left, .. } => self.type_of(*left), Insn::GuardGreaterEq { left, .. } => self.type_of(*left), Insn::FixnumAdd { .. } => types::Fixnum, @@ -3940,6 +3953,7 @@ impl Function { | &Insn::GuardBitEquals { val, state, .. } | &Insn::GuardShape { val, state, .. } | &Insn::GuardNotFrozen { recv: val, state } + | &Insn::GuardNotShared { recv: val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } | &Insn::ToNewArray { val, state } @@ -4004,6 +4018,11 @@ impl Function { worklist.push_back(array); worklist.push_back(index); } + &Insn::ArrayAset { array, index, val } => { + worklist.push_back(array); + worklist.push_back(index); + worklist.push_back(val); + } &Insn::ArrayPop { array, state } => { worklist.push_back(array); worklist.push_back(state); @@ -4628,7 +4647,7 @@ impl Function { | Insn::DefinedIvar { self_val: val, .. } => { self.assert_subtype(insn_id, val, types::BasicObject) } - Insn::GuardNotFrozen { recv, .. } => { + Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => { self.assert_subtype(insn_id, recv, types::HeapBasicObject) } // Instructions with 2 Ruby object operands @@ -4716,6 +4735,10 @@ impl Function { self.assert_subtype(insn_id, array, types::Array)?; self.assert_subtype(insn_id, index, types::Fixnum) } + Insn::ArrayAset { array, index, .. } => { + self.assert_subtype(insn_id, array, types::ArrayExact)?; + self.assert_subtype(insn_id, index, types::CInt64) + } // Instructions with Hash operands Insn::HashAref { hash, .. } | Insn::HashAset { hash, .. } => self.assert_subtype(insn_id, hash, types::HashExact), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 2a70314fbb..afa97e48f1 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4772,6 +4772,36 @@ mod hir_opt_tests { "); } + #[test] + fn test_dont_optimize_array_aset_if_redefined() { + eval(r##" + class Array + def []=(*args); :redefined; end + end + + def test(arr) + arr[1] = 10 + end + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :arr, l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:Fixnum[1] = Const Value(1) + v18:Fixnum[10] = Const Value(10) + v22:BasicObject = SendWithoutBlock v9, :[]=, v16, v18 # SendFallbackReason: Uncategorized(opt_aset) + CheckInterrupts + Return v18 + "); + } + #[test] fn test_dont_optimize_array_max_if_redefined() { eval(r##" @@ -6901,7 +6931,7 @@ mod hir_opt_tests { } #[test] - fn test_optimize_array_aset() { + fn test_optimize_array_aset_literal() { eval(" def test(arr) arr[1] = 10 @@ -6924,12 +6954,93 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v31:ArrayExact = GuardType v9, ArrayExact - v32:BasicObject = CCallVariadic v31, :Array#[]=@0x1038, v16, v18 + v32:ArrayExact = GuardNotFrozen v31 + v33:ArrayExact = GuardNotShared v32 + v34:CInt64 = UnboxFixnum v16 + v35:CInt64 = ArrayLength v33 + v36:CInt64 = GuardLess v34, v35 + v37:CInt64[0] = Const CInt64(0) + v38:CInt64 = GuardGreaterEq v36, v37 + ArrayAset v33, v38, v18 + WriteBarrier v33, v18 + IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v18 "); } + #[test] + fn test_optimize_array_aset_profiled() { + eval(" + def test(arr, index, val) + arr[index] = val + end + test([], 0, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :arr, l0, SP@6 + v3:BasicObject = GetLocal :index, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v35:ArrayExact = GuardType v13, ArrayExact + v36:Fixnum = GuardType v14, Fixnum + v37:ArrayExact = GuardNotFrozen v35 + v38:ArrayExact = GuardNotShared v37 + v39:CInt64 = UnboxFixnum v36 + v40:CInt64 = ArrayLength v38 + v41:CInt64 = GuardLess v39, v40 + v42:CInt64[0] = Const CInt64(0) + v43:CInt64 = GuardGreaterEq v41, v42 + ArrayAset v38, v43, v15 + WriteBarrier v38, v15 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_optimize_array_aset_array_subclass() { + eval(" + class MyArray < Array; end + def test(arr, index, val) + arr[index] = val + end + a = MyArray.new + test(a, 0, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :arr, l0, SP@6 + v3:BasicObject = GetLocal :index, l0, SP@5 + v4:BasicObject = GetLocal :val, l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + PatchPoint MethodRedefined(MyArray@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(MyArray@0x1000) + v35:ArraySubclass[class_exact:MyArray] = GuardType v13, ArraySubclass[class_exact:MyArray] + v36:BasicObject = CCallVariadic v35, :Array#[]=@0x1038, v14, v15 + CheckInterrupts + Return v15 + "); + } + #[test] fn test_optimize_array_ltlt() { eval(" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 38e8df170d..68eeac456b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -191,6 +191,7 @@ make_counters! { exit_guard_int_equals_failure, exit_guard_shape_failure, exit_guard_not_frozen_failure, + exit_guard_not_shared_failure, exit_guard_less_failure, exit_guard_greater_eq_failure, exit_patchpoint_bop_redefined, @@ -511,6 +512,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, GuardNotFrozen => exit_guard_not_frozen_failure, + GuardNotShared => exit_guard_not_shared_failure, GuardLess => exit_guard_less_failure, GuardGreaterEq => exit_guard_greater_eq_failure, CalleeSideExit => exit_callee_side_exit,