diff options
| author | Max Bernstein <ruby@bernsteinbear.com> | 2026-01-12 10:32:10 -0500 |
|---|---|---|
| committer | Max Bernstein <tekknolagi@gmail.com> | 2026-01-12 17:11:47 -0500 |
| commit | 328655633bc46887f46d7be2df974beb4ff89b7c (patch) | |
| tree | 77c21a5a5d88e457d7779dd7ab094f761da35e3c | |
| parent | ee1aa78bee5f5c46ebcd75a3fe3eff03787b0b44 (diff) | |
ZJIT: Optimize Integer#[]
This is used a lot in optcarrot.
| -rw-r--r-- | zjit/src/codegen.rs | 5 | ||||
| -rw-r--r-- | zjit/src/cruby_methods.rs | 12 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 13 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 106 |
4 files changed, 136 insertions, 0 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8d832c2e25..d6a2ebec74 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -439,6 +439,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio gen_fixnum_rshift(asm, opnd!(left), shift_amount) } &Insn::FixnumMod { left, right, state } => gen_fixnum_mod(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), + &Insn::FixnumAref { recv, index } => gen_fixnum_aref(asm, opnd!(recv), opnd!(index)), Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), @@ -1911,6 +1912,10 @@ fn gen_fixnum_mod(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, righ asm_ccall!(asm, rb_fix_mod_fix, left, right) } +fn gen_fixnum_aref(asm: &mut Assembler, recv: lir::Opnd, index: lir::Opnd) -> lir::Opnd { + asm_ccall!(asm, rb_fix_aref, recv, index) +} + // Compile val == nil fn gen_isnil(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { asm.cmp(val, Qnil.into()); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 2d5bb3b62f..b808202472 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -255,6 +255,7 @@ pub fn init() -> Annotations { annotate!(rb_cInteger, "<=", inline_integer_le); annotate!(rb_cInteger, "<<", inline_integer_lshift); annotate!(rb_cInteger, ">>", inline_integer_rshift); + annotate!(rb_cInteger, "[]", inline_integer_aref); annotate!(rb_cInteger, "to_s", types::StringExact); annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; @@ -679,6 +680,17 @@ fn inline_integer_rshift(fun: &mut hir::Function, block: hir::BlockId, recv: hir try_inline_fixnum_op(fun, block, &|left, right| hir::Insn::FixnumRShift { left, right }, BOP_GTGT, recv, other, state) } +fn inline_integer_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> { + let &[index] = args else { return None; }; + if fun.likely_a(recv, types::Fixnum, state) && fun.likely_a(index, types::Fixnum, state) { + let recv = fun.coerce_to(block, recv, types::Fixnum, state); + let index = fun.coerce_to(block, index, types::Fixnum, state); + let result = fun.push_insn(block, hir::Insn::FixnumAref { recv, index }); + return Some(result); + } + None +} + fn inline_basic_object_eq(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; }; let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 36aee8056c..6cdb7993d0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -765,6 +765,7 @@ pub enum Insn { /// Convert a C `long` to a Ruby `Fixnum`. Side exit on overflow. BoxFixnum { val: InsnId, state: InsnId }, UnboxFixnum { val: InsnId }, + FixnumAref { recv: InsnId, index: 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 }, @@ -1050,6 +1051,7 @@ impl Insn { Insn::FixnumXor { .. } => false, Insn::FixnumLShift { .. } => false, Insn::FixnumRShift { .. } => false, + Insn::FixnumAref { .. } => false, Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, Insn::LoadPC => false, @@ -1254,6 +1256,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::BoxBool { val } => write!(f, "BoxBool {val}"), Insn::BoxFixnum { val, .. } => write!(f, "BoxFixnum {val}"), Insn::UnboxFixnum { val } => write!(f, "UnboxFixnum {val}"), + Insn::FixnumAref { recv, index } => write!(f, "FixnumAref {recv}, {index}"), 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}") } @@ -1971,6 +1974,7 @@ impl Function { &BoxBool { val } => BoxBool { val: find!(val) }, &BoxFixnum { val, state } => BoxFixnum { val: find!(val), state: find!(state) }, &UnboxFixnum { val } => UnboxFixnum { val: find!(val) }, + &FixnumAref { recv, index } => FixnumAref { recv: find!(recv), index: find!(index) }, 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) }, @@ -2184,6 +2188,7 @@ impl Function { Insn::BoxBool { .. } => types::BoolExact, Insn::BoxFixnum { .. } => types::Fixnum, Insn::UnboxFixnum { .. } => types::CInt64, + Insn::FixnumAref { .. } => types::Fixnum, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, @@ -4150,6 +4155,10 @@ impl Function { &Insn::ObjectAllocClass { state, .. } | &Insn::SideExit { state, .. } => worklist.push_back(state), &Insn::UnboxFixnum { val } => worklist.push_back(val), + &Insn::FixnumAref { recv, index } => { + worklist.push_back(recv); + worklist.push_back(index); + } &Insn::IsA { val, class } => { worklist.push_back(val); worklist.push_back(class); @@ -4817,6 +4826,10 @@ impl Function { Insn::UnboxFixnum { val } => { self.assert_subtype(insn_id, val, types::Fixnum) } + Insn::FixnumAref { recv, index } => { + self.assert_subtype(insn_id, recv, types::Fixnum)?; + self.assert_subtype(insn_id, index, types::Fixnum) + } Insn::FixnumAdd { left, right, .. } | Insn::FixnumSub { left, right, .. } | Insn::FixnumMult { left, right, .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 93e59b35fb..1d360bed71 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -1202,6 +1202,112 @@ mod hir_opt_tests { } #[test] + fn integer_aref_with_fixnum_emits_fixnum_aref() { + eval(" + def test(a, b) = a[b] + test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :a, l0, SP@5 + v3:BasicObject = GetLocal :b, l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, []@0x1008, cme:0x1010) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumAref v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn elide_fixnum_aref() { + eval(" + def test + 1[2] + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Integer@0x1000, []@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn do_not_optimize_integer_aref_with_too_many_args() { + eval(" + def test = 1[2, 3] + "); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:Fixnum[2] = Const Value(2) + v14:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Integer@0x1000, []@0x1008, cme:0x1010) + v23:BasicObject = CCallVariadic v10, :Integer#[]@0x1038, v12, v14 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn do_not_optimize_integer_aref_with_non_fixnum() { + eval(r#" + def test = 1["x"] + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@<compiled>:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:StringExact = StringCopy v12 + PatchPoint MethodRedefined(Integer@0x1008, []@0x1010, cme:0x1018) + v23:BasicObject = CCallVariadic v10, :Integer#[]@0x1040, v13 + CheckInterrupts + Return v23 + "); + } + + #[test] fn test_optimize_send_into_fixnum_lt_both_profiled() { eval(" def test(a, b) = a < b |
