mirror of
https://github.com/ruby/ruby.git
synced 2026-01-27 04:24:23 +00:00
ZJIT: Specialize String#setbyte for fixnum case (#14927)
This commit is contained in:
parent
c65f8b6370
commit
4aea392e69
Notes:
git
2025-11-08 03:47:58 +00:00
Merged-By: k0kubun <takashikkbn@gmail.com>
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(()),
|
||||
}
|
||||
|
||||
@ -5801,6 +5801,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#"
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user