Skip to content

Commit 6cd7531

Browse files
committed
ZJIT: Strength-reduce Integer#[] with a constant index
1 parent 3da63e6 commit 6cd7531

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

zjit/src/codegen_tests.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,6 +2842,38 @@ fn test_fixnum_mod_negative() {
28422842
assert_snapshot!(assert_compiles("[test(-7, 3), test(7, -3), test(-7, -3)]"), @"[2, -2, -1]");
28432843
}
28442844

2845+
#[test]
2846+
fn test_fixnum_aref_constant_index() {
2847+
eval("
2848+
def test(a) = a[12]
2849+
test(4096) # profile opt_aref
2850+
");
2851+
assert_contains_opcode("test", YARVINSN_opt_aref);
2852+
assert_snapshot!(assert_compiles("[test(4096), test(4095), test(0), test(-1), test(-4096)]"), @"[1, 0, 0, 1, 1]");
2853+
}
2854+
2855+
#[test]
2856+
fn test_fixnum_aref_constant_index_beyond_fixnum_width() {
2857+
// An index beyond the fixnum width is not strength-reduced; FixnumAref handles it
2858+
eval("
2859+
def test(a) = a[100]
2860+
test(1) # profile opt_aref
2861+
");
2862+
assert_contains_opcode("test", YARVINSN_opt_aref);
2863+
assert_snapshot!(assert_compiles("[test(1), test(-1), test(4611686018427387903), test(-4611686018427387904)]"), @"[0, 1, 0, 1]");
2864+
}
2865+
2866+
#[test]
2867+
fn test_fixnum_aref_constant_index_bignum_receiver() {
2868+
// A Bignum receiver fails the Fixnum guard and side-exits to the correct result
2869+
eval("
2870+
def test(a) = a[1]
2871+
test(5) # profile opt_aref
2872+
");
2873+
assert_contains_opcode("test", YARVINSN_opt_aref);
2874+
assert_snapshot!(assert_compiles_allowing_exits("[test(5), test(2**100 + 2)]"), @"[0, 1]");
2875+
}
2876+
28452877
#[test]
28462878
fn test_fixnum_mod_by_zero() {
28472879
eval("

zjit/src/cruby_methods.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,24 @@ fn inline_integer_rshift(fun: &mut hir::Function, block: hir::BlockId, recv: hir
800800

801801
fn inline_integer_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
802802
let &[index] = args else { return None; };
803+
804+
// Optimize a compile-time constant index that fits in the Fixnum payload as a bit test.
805+
// A Fixnum payload is VALUE_BITS - 1 bits wide (one bit is the tag), so its highest
806+
// (sign) bit is at index VALUE_BITS - 2.
807+
const FIXNUM_SIGN_BIT_INDEX: i64 = VALUE_BITS as i64 - 2;
808+
if fun.likely_a(recv, types::Fixnum, state) {
809+
if let Some(index_value) = fun.type_of(index).fixnum_value() {
810+
if (0..=FIXNUM_SIGN_BIT_INDEX).contains(&index_value) {
811+
// Optimize `recv[i]` into `(recv >> i) & 1`.
812+
let recv = fun.coerce_to(block, recv, types::Fixnum, state);
813+
let shift = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(VALUE::fixnum_from_usize(index_value as usize)) });
814+
let shifted = fun.push_insn(block, hir::Insn::FixnumRShift { left: recv, right: shift });
815+
let mask = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(VALUE::fixnum_from_usize(1)) });
816+
return Some(fun.push_insn(block, hir::Insn::FixnumAnd { left: shifted, right: mask }));
817+
}
818+
}
819+
}
820+
// Use a C call (FixnumAref) for any other (e.g. non-constant) index.
803821
if fun.likely_a(recv, types::Fixnum, state) && fun.likely_a(index, types::Fixnum, state) {
804822
let recv = fun.coerce_to(block, recv, types::Fixnum, state);
805823
let index = fun.coerce_to(block, index, types::Fixnum, state);

zjit/src/hir/opt_tests.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,67 @@ mod hir_opt_tests {
20642064
");
20652065
}
20662066

2067+
#[test]
2068+
fn integer_aref_with_constant_index_strength_reduced() {
2069+
eval("
2070+
def test(a) = a[12]
2071+
test(4096)
2072+
");
2073+
assert_snapshot!(hir_string("test"), @"
2074+
fn test@<compiled>:2:
2075+
bb1():
2076+
EntryPoint interpreter
2077+
v1:BasicObject = LoadSelf
2078+
v2:CPtr = LoadSP
2079+
v3:BasicObject = LoadField v2, :a@0x1000
2080+
Jump bb3(v1, v3)
2081+
bb2():
2082+
EntryPoint JIT(0)
2083+
v6:BasicObject = LoadArg :self@0
2084+
v7:BasicObject = LoadArg :a@1
2085+
Jump bb3(v6, v7)
2086+
bb3(v9:BasicObject, v10:BasicObject):
2087+
v15:Fixnum[12] = Const Value(12)
2088+
PatchPoint MethodRedefined(Integer@0x1008, []@0x1010, cme:0x1018)
2089+
v26:Fixnum = GuardType v10, Fixnum recompile
2090+
v27:Fixnum[12] = Const Value(12)
2091+
v28:Fixnum = FixnumRShift v26, v27
2092+
v29:Fixnum[1] = Const Value(1)
2093+
v30:Fixnum = FixnumAnd v28, v29
2094+
CheckInterrupts
2095+
Return v30
2096+
");
2097+
}
2098+
2099+
#[test]
2100+
fn integer_aref_with_constant_index_beyond_fixnum_width() {
2101+
eval("
2102+
def test(a) = a[100]
2103+
test(-1)
2104+
");
2105+
assert_snapshot!(hir_string("test"), @"
2106+
fn test@<compiled>:2:
2107+
bb1():
2108+
EntryPoint interpreter
2109+
v1:BasicObject = LoadSelf
2110+
v2:CPtr = LoadSP
2111+
v3:BasicObject = LoadField v2, :a@0x1000
2112+
Jump bb3(v1, v3)
2113+
bb2():
2114+
EntryPoint JIT(0)
2115+
v6:BasicObject = LoadArg :self@0
2116+
v7:BasicObject = LoadArg :a@1
2117+
Jump bb3(v6, v7)
2118+
bb3(v9:BasicObject, v10:BasicObject):
2119+
v15:Fixnum[100] = Const Value(100)
2120+
PatchPoint MethodRedefined(Integer@0x1008, []@0x1010, cme:0x1018)
2121+
v26:Fixnum = GuardType v10, Fixnum recompile
2122+
v27:Fixnum = FixnumAref v26, v15
2123+
CheckInterrupts
2124+
Return v27
2125+
");
2126+
}
2127+
20672128
#[test]
20682129
fn elide_fixnum_aref() {
20692130
eval("

0 commit comments

Comments
 (0)