summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAiden Fox Ivey <aiden@aidenfoxivey.com>2025-11-07 22:47:32 -0500
committerGitHub <noreply@github.com>2025-11-07 19:47:32 -0800
commit4aea392e69488209d1f3c7b671aadafc0d2d76b3 (patch)
tree993c1d376cb9364bf3da71fa5dc4a85c6f444069
parentc65f8b6370f5ef692148786b5e8de54c4d14c132 (diff)
ZJIT: Specialize String#setbyte for fixnum case (#14927)
-rw-r--r--zjit/src/codegen.rs30
-rw-r--r--zjit/src/cruby_methods.rs26
-rw-r--r--zjit/src/hir.rs48
-rw-r--r--zjit/src/hir/opt_tests.rs110
-rw-r--r--zjit/src/stats.rs4
5 files changed, 218 insertions, 0 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 3ef93ee3d9..68e8ad8966 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -348,6 +348,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
let out_opnd = match insn {
&Insn::Const { val: Const::Value(val) } => gen_const_value(val),
&Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val),
+ &Insn::Const { val: Const::CInt64(val) } => gen_const_long(val),
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
@@ -365,6 +366,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state),
Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)),
&Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)),
+ Insn::StringSetbyteFixnum { string, index, value } => gen_string_setbyte_fixnum(asm, opnd!(string), opnd!(index), opnd!(value)),
Insn::StringAppend { recv, other, state } => gen_string_append(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)),
Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)),
Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)),
@@ -407,12 +409,15 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)),
&Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)),
&Insn::BoxFixnum { val, state } => gen_box_fixnum(jit, asm, opnd!(val), &function.frame_state(state)),
+ &Insn::UnboxFixnum { val } => gen_unbox_fixnum(asm, opnd!(val)),
Insn::Test { val } => gen_test(asm, opnd!(val)),
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
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 { val, state } => gen_guard_not_frozen(jit, asm, opnd!(val), &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))),
Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)),
// Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it.
@@ -571,6 +576,10 @@ fn gen_is_block_given(jit: &JITState, asm: &mut Assembler) -> Opnd {
}
}
+fn gen_unbox_fixnum(asm: &mut Assembler, val: Opnd) -> Opnd {
+ asm.rshift(val, Opnd::UImm(1))
+}
+
/// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs.
/// We generate this instruction with level=0 only when the local variable is on the heap, so we
/// can't optimize the level=0 case using the SP register.
@@ -642,6 +651,18 @@ fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, val: Opnd, state: &
val
}
+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));
+ left
+}
+
+fn gen_guard_greater_eq(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd {
+ asm.cmp(left, right);
+ asm.jl(side_exit(jit, state, SideExitReason::GuardGreaterEq));
+ left
+}
+
fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd {
unsafe extern "C" {
fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE;
@@ -1047,6 +1068,10 @@ fn gen_const_cptr(val: *const u8) -> lir::Opnd {
Opnd::const_ptr(val)
}
+fn gen_const_long(val: i64) -> lir::Opnd {
+ Opnd::Imm(val)
+}
+
/// Compile a basic block argument
fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd {
// Allocate a register or a stack slot
@@ -2302,6 +2327,11 @@ fn gen_string_getbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd) ->
asm_ccall!(asm, rb_str_getbyte, string, index)
}
+fn gen_string_setbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd, value: Opnd) -> Opnd {
+ // rb_str_setbyte is not leaf, but we guard types and index ranges in HIR
+ asm_ccall!(asm, rb_str_setbyte, string, index, value)
+}
+
fn gen_string_append(jit: &mut JITState, asm: &mut Assembler, string: Opnd, val: Opnd, state: &FrameState) -> Opnd {
gen_prepare_non_leaf_call(jit, asm, state);
asm_ccall!(asm, rb_str_buf_append, string, val)
diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs
index 37d75f4597..7fba755a6f 100644
--- a/zjit/src/cruby_methods.rs
+++ b/zjit/src/cruby_methods.rs
@@ -199,6 +199,7 @@ pub fn init() -> Annotations {
annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "getbyte", inline_string_getbyte);
+ annotate!(rb_cString, "setbyte", inline_string_setbyte);
annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cString, "<<", inline_string_append);
annotate!(rb_cString, "==", inline_string_eq);
@@ -338,6 +339,31 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir
None
}
+fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
+ let &[index, value] = args else { return None; };
+ if fun.likely_a(index, types::Fixnum, state) && fun.likely_a(value, types::Fixnum, state) {
+ let index = fun.coerce_to(block, index, types::Fixnum, state);
+ let value = fun.coerce_to(block, value, types::Fixnum, state);
+
+ let unboxed_index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index });
+ let len = fun.push_insn(block, hir::Insn::LoadField {
+ recv,
+ id: ID!(len),
+ offset: RUBY_OFFSET_RSTRING_LEN as i32,
+ return_type: types::CInt64,
+ });
+ let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state });
+ let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) });
+ let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state });
+ let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state });
+ let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value });
+ // String#setbyte returns the fixnum provided as its `value` argument back to the caller.
+ Some(value)
+ } else {
+ None
+ }
+}
+
fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
let &[other] = args else { return None; };
// Inline only StringExact << String, which matches original type check from
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 7c180929ab..04f1291e2d 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -471,6 +471,8 @@ pub enum SideExitReason {
GuardShape(ShapeId),
GuardBitEquals(Const),
GuardNotFrozen,
+ GuardLess,
+ GuardGreaterEq,
PatchPoint(Invariant),
CalleeSideExit,
ObjToStringFallback,
@@ -600,6 +602,7 @@ pub enum Insn {
StringConcat { strings: Vec<InsnId>, state: InsnId },
/// Call rb_str_getbyte with known-Fixnum index
StringGetbyteFixnum { string: InsnId, index: InsnId },
+ StringSetbyteFixnum { string: InsnId, index: InsnId, value: InsnId },
StringAppend { recv: InsnId, other: InsnId, state: InsnId },
/// Combine count stack values into a regexp
@@ -659,6 +662,7 @@ pub enum Insn {
BoxBool { val: InsnId },
/// Convert a C `long` to a Ruby `Fixnum`. Side exit on overflow.
BoxFixnum { val: InsnId, state: InsnId },
+ UnboxFixnum { val: 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 },
GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId },
@@ -844,6 +848,10 @@ pub enum Insn {
GuardBlockParamProxy { level: u32, state: InsnId },
/// Side-exit if val is frozen.
GuardNotFrozen { val: 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).
+ GuardLess { left: InsnId, right: InsnId, state: InsnId },
/// Generate no code (or padding if necessary) and insert a patch point
/// that can be rewritten to a side exit when the Invariant is broken.
@@ -1036,6 +1044,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::StringGetbyteFixnum { string, index, .. } => {
write!(f, "StringGetbyteFixnum {string}, {index}")
}
+ Insn::StringSetbyteFixnum { string, index, value, .. } => {
+ write!(f, "StringSetbyteFixnum {string}, {index}, {value}")
+ }
Insn::StringAppend { recv, other, .. } => {
write!(f, "StringAppend {recv}, {other}")
}
@@ -1068,6 +1079,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::IsBitNotEqual { left, right } => write!(f, "IsBitNotEqual {left}, {right}"),
Insn::BoxBool { val } => write!(f, "BoxBool {val}"),
Insn::BoxFixnum { val, .. } => write!(f, "BoxFixnum {val}"),
+ Insn::UnboxFixnum { val } => write!(f, "UnboxFixnum {val}"),
Insn::Jump(target) => { write!(f, "Jump {target}") }
Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") }
Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") }
@@ -1148,6 +1160,8 @@ 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 { val, .. } => write!(f, "GuardNotFrozen {val}"),
+ 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)) },
Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) },
Insn::IsBlockGiven => { write!(f, "IsBlockGiven") },
@@ -1702,6 +1716,7 @@ impl Function {
&StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) },
&StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) },
&StringGetbyteFixnum { string, index } => StringGetbyteFixnum { string: find!(string), index: find!(index) },
+ &StringSetbyteFixnum { string, index, value } => StringSetbyteFixnum { string: find!(string), index: find!(index), value: find!(value) },
&StringAppend { recv, other, state } => StringAppend { recv: find!(recv), other: find!(other), state: find!(state) },
&ToRegexp { opt, ref values, state } => ToRegexp { opt, values: find_vec!(values), state },
&Test { val } => Test { val: find!(val) },
@@ -1711,6 +1726,7 @@ impl Function {
&IsBitNotEqual { left, right } => IsBitNotEqual { left: find!(left), right: find!(right) },
&BoxBool { val } => BoxBool { val: find!(val) },
&BoxFixnum { val, state } => BoxFixnum { val: find!(val), state: find!(state) },
+ &UnboxFixnum { val } => UnboxFixnum { val: find!(val) },
Jump(target) => Jump(find_branch_edge!(target)),
&IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) },
&IfFalse { val, ref target } => IfFalse { val: find!(val), target: find_branch_edge!(target) },
@@ -1720,6 +1736,8 @@ impl Function {
&GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state },
&GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) },
&GuardNotFrozen { val, state } => GuardNotFrozen { val: find!(val), 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 },
&FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state },
&FixnumMult { left, right, state } => FixnumMult { left: find!(left), right: find!(right), state },
@@ -1906,10 +1924,12 @@ impl Function {
Insn::IsBitNotEqual { .. } => types::CBool,
Insn::BoxBool { .. } => types::BoolExact,
Insn::BoxFixnum { .. } => types::Fixnum,
+ Insn::UnboxFixnum { .. } => types::CInt64,
Insn::StringCopy { .. } => types::StringExact,
Insn::StringIntern { .. } => types::Symbol,
Insn::StringConcat { .. } => types::StringExact,
Insn::StringGetbyteFixnum { .. } => types::Fixnum.union(types::NilClass),
+ Insn::StringSetbyteFixnum { .. } => types::Fixnum,
Insn::StringAppend { .. } => types::StringExact,
Insn::ToRegexp { .. } => types::RegexpExact,
Insn::NewArray { .. } => types::ArrayExact,
@@ -1932,6 +1952,8 @@ impl Function {
Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)),
Insn::GuardShape { val, .. } => self.type_of(*val),
Insn::GuardNotFrozen { val, .. } => self.type_of(*val),
+ Insn::GuardLess { left, .. } => self.type_of(*left),
+ Insn::GuardGreaterEq { left, .. } => self.type_of(*left),
Insn::FixnumAdd { .. } => types::Fixnum,
Insn::FixnumSub { .. } => types::Fixnum,
Insn::FixnumMult { .. } => types::Fixnum,
@@ -3284,6 +3306,11 @@ impl Function {
worklist.push_back(string);
worklist.push_back(index);
}
+ &Insn::StringSetbyteFixnum { string, index, value } => {
+ worklist.push_back(string);
+ worklist.push_back(index);
+ worklist.push_back(value);
+ }
&Insn::StringAppend { recv, other, state } => {
worklist.push_back(recv);
worklist.push_back(other);
@@ -3316,6 +3343,16 @@ impl Function {
worklist.push_back(val);
worklist.push_back(state);
}
+ &Insn::GuardGreaterEq { left, right, state } => {
+ worklist.push_back(left);
+ worklist.push_back(right);
+ worklist.push_back(state);
+ }
+ &Insn::GuardLess { left, right, state } => {
+ worklist.push_back(left);
+ worklist.push_back(right);
+ worklist.push_back(state);
+ }
Insn::Snapshot { state } => {
worklist.extend(&state.stack);
worklist.extend(&state.locals);
@@ -3430,6 +3467,7 @@ impl Function {
&Insn::GetSpecialNumber { state, .. } |
&Insn::ObjectAllocClass { state, .. } |
&Insn::SideExit { state, .. } => worklist.push_back(state),
+ &Insn::UnboxFixnum { val } => worklist.push_back(val),
}
}
@@ -3792,6 +3830,7 @@ impl Function {
}
Insn::BoxBool { val } => self.assert_subtype(insn_id, val, types::CBool),
Insn::BoxFixnum { val, .. } => self.assert_subtype(insn_id, val, types::CInt64),
+ Insn::UnboxFixnum { val } => self.assert_subtype(insn_id, val, types::Fixnum),
Insn::SetGlobal { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject),
Insn::GetIvar { self_val, .. } => self.assert_subtype(insn_id, self_val, types::BasicObject),
Insn::SetIvar { self_val, val, .. } => {
@@ -3867,9 +3906,18 @@ impl Function {
}
Insn::GuardShape { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject),
Insn::GuardNotFrozen { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject),
+ Insn::GuardLess { left, right, .. } | Insn::GuardGreaterEq { left, right, .. } => {
+ self.assert_subtype(insn_id, left, types::CInt64)?;
+ self.assert_subtype(insn_id, right, types::CInt64)
+ },
Insn::StringGetbyteFixnum { string, index } => {
self.assert_subtype(insn_id, string, types::String)?;
self.assert_subtype(insn_id, index, types::Fixnum)
+ },
+ Insn::StringSetbyteFixnum { string, index, value } => {
+ self.assert_subtype(insn_id, string, types::String)?;
+ self.assert_subtype(insn_id, index, types::Fixnum)?;
+ self.assert_subtype(insn_id, value, types::Fixnum)
}
_ => Ok(()),
}
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 9b757433e1..543e1b5287 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -5802,6 +5802,116 @@ mod hir_opt_tests {
}
#[test]
+ fn test_optimize_string_setbyte_fixnum() {
+ eval(r#"
+ def test(s, idx, val)
+ s.setbyte(idx, val)
+ end
+ test("foo", 0, 127)
+ "#);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@6
+ v3:BasicObject = GetLocal l0, SP@5
+ v4:BasicObject = GetLocal 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(String@0x1000, setbyte@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(String@0x1000)
+ v29:StringExact = GuardType v13, StringExact
+ v30:Fixnum = GuardType v14, Fixnum
+ v31:Fixnum = GuardType v15, Fixnum
+ v32:CInt64 = UnboxFixnum v30
+ v33:CInt64 = LoadField v29, :len@0x1038
+ v34:CInt64 = GuardLess v32, v33
+ v35:CInt64[0] = Const CInt64(0)
+ v36:CInt64 = GuardGreaterEq v34, v35
+ v37:StringExact = GuardNotFrozen v29
+ v38:Fixnum = StringSetbyteFixnum v37, v30, v31
+ IncrCounter inline_cfunc_optimized_send_count
+ CheckInterrupts
+ Return v31
+ ");
+ }
+
+ #[test]
+ fn test_optimize_string_subclass_setbyte_fixnum() {
+ eval(r#"
+ class MyString < String
+ end
+ def test(s, idx, val)
+ s.setbyte(idx, val)
+ end
+ test(MyString.new('foo'), 0, 127)
+ "#);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:5:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@6
+ v3:BasicObject = GetLocal l0, SP@5
+ v4:BasicObject = GetLocal 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(MyString@0x1000, setbyte@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(MyString@0x1000)
+ v29:StringSubclass[class_exact:MyString] = GuardType v13, StringSubclass[class_exact:MyString]
+ v30:Fixnum = GuardType v14, Fixnum
+ v31:Fixnum = GuardType v15, Fixnum
+ v32:CInt64 = UnboxFixnum v30
+ v33:CInt64 = LoadField v29, :len@0x1038
+ v34:CInt64 = GuardLess v32, v33
+ v35:CInt64[0] = Const CInt64(0)
+ v36:CInt64 = GuardGreaterEq v34, v35
+ v37:StringSubclass[class_exact:MyString] = GuardNotFrozen v29
+ v38:Fixnum = StringSetbyteFixnum v37, v30, v31
+ IncrCounter inline_cfunc_optimized_send_count
+ CheckInterrupts
+ Return v31
+ ");
+ }
+
+ #[test]
+ fn test_do_not_optimize_string_setbyte_non_fixnum() {
+ eval(r#"
+ def test(s, idx, val)
+ s.setbyte(idx, val)
+ end
+ test("foo", 0, 3.14)
+ "#);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@6
+ v3:BasicObject = GetLocal l0, SP@5
+ v4:BasicObject = GetLocal 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(String@0x1000, setbyte@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(String@0x1000)
+ v29:StringExact = GuardType v13, StringExact
+ v30:BasicObject = CCallWithFrame setbyte@0x1038, v29, v14, v15
+ CheckInterrupts
+ Return v30
+ ");
+ }
+
+ #[test]
fn test_specialize_string_empty() {
eval(r#"
def test(s)
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index 6b3f9b5ce8..099609b90a 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -146,6 +146,8 @@ make_counters! {
exit_guard_int_equals_failure,
exit_guard_shape_failure,
exit_guard_not_frozen_failure,
+ exit_guard_less_failure,
+ exit_guard_greater_eq_failure,
exit_patchpoint_bop_redefined,
exit_patchpoint_method_redefined,
exit_patchpoint_stable_constant_names,
@@ -390,6 +392,8 @@ 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,
+ GuardLess => exit_guard_less_failure,
+ GuardGreaterEq => exit_guard_greater_eq_failure,
CalleeSideExit => exit_callee_side_exit,
ObjToStringFallback => exit_obj_to_string_fallback,
Interrupt => exit_interrupt,