ZJIT: Add ArrayAset instruction to HIR (#15747)

Inline `Array#[]=` into `ArrayAset`.
This commit is contained in:
Nozomi Hijikata 2026-01-08 08:24:29 +09:00 committed by GitHub
parent 3ea6ec8344
commit 950ffa90b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2026-01-07 23:24:58 +00:00
Merged-By: tekknolagi <donotemailthisaddress@bernsteinbear.com>
6 changed files with 313 additions and 7 deletions

View File

@ -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

View File

@ -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,

View File

@ -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<hir::InsnId> {
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<hir::InsnId> {
// Inline only the case of `<<` or `push` when called with a single argument.
if let &[val] = args {

View File

@ -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),

View File

@ -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@<compiled>: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@<compiled>: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@<compiled>: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("

View File

@ -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,