summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Bernstein <ruby@bernsteinbear.com>2026-01-12 10:32:10 -0500
committerMax Bernstein <tekknolagi@gmail.com>2026-01-12 17:11:47 -0500
commit328655633bc46887f46d7be2df974beb4ff89b7c (patch)
tree77c21a5a5d88e457d7779dd7ab094f761da35e3c
parentee1aa78bee5f5c46ebcd75a3fe3eff03787b0b44 (diff)
ZJIT: Optimize Integer#[]
This is used a lot in optcarrot.
-rw-r--r--zjit/src/codegen.rs5
-rw-r--r--zjit/src/cruby_methods.rs12
-rw-r--r--zjit/src/hir.rs13
-rw-r--r--zjit/src/hir/opt_tests.rs106
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