ZJIT: Add codegen for FixnumDiv (#15452)

Fixes https://github.com/Shopify/ruby/issues/902

This pull request adds code generation for dividing fixnums.
Testing confirms the normal case, flooring, and side-exiting on division by zero.
This commit is contained in:
Abrar Habib 2025-12-09 07:41:09 -05:00 committed by GitHub
parent 5ae2bd240f
commit edca81a1bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2025-12-09 12:41:39 +00:00
Merged-By: tekknolagi <donotemailthisaddress@bernsteinbear.com>
11 changed files with 56 additions and 10 deletions

6
jit.c
View File

@ -761,6 +761,12 @@ rb_jit_fix_mod_fix(VALUE recv, VALUE obj)
return rb_fix_mod_fix(recv, obj);
}
VALUE
rb_jit_fix_div_fix(VALUE recv, VALUE obj)
{
return rb_fix_div_fix(recv, obj);
}
// YJIT/ZJIT need this function to never allocate and never raise
VALUE
rb_yarv_str_eql_internal(VALUE str1, VALUE str2)

View File

@ -960,6 +960,37 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2, insns: [:opt_mult]
end
def test_fixnum_div
assert_compiles '12', %q{
C = 48
def test(n) = C / n
test(4)
test(4)
}, call_threshold: 2, insns: [:opt_div]
end
def test_fixnum_floor
assert_compiles '0', %q{
C = 3
def test(n) = C / n
test(4)
test(4)
}, call_threshold: 2, insns: [:opt_div]
end
def test_fixnum_div_zero
assert_runs '"divided by 0"', %q{
def test(n)
n / 0
rescue ZeroDivisionError => e
e.message
end
test(0)
test(0)
}, call_threshold: 2, insns: [:opt_div]
end
def test_opt_not
assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not])
def test(obj) = !obj

6
yjit.c
View File

@ -292,12 +292,6 @@ rb_yjit_rb_ary_subseq_length(VALUE ary, long beg)
return rb_ary_subseq(ary, beg, len);
}
VALUE
rb_yjit_fix_div_fix(VALUE recv, VALUE obj)
{
return rb_fix_div_fix(recv, obj);
}
// Return non-zero when `obj` is an array and its last item is a
// `ruby2_keywords` hash. We don't support this kind of splat.
size_t

View File

@ -368,7 +368,7 @@ fn main() {
.allowlist_function("rb_str_neq_internal")
.allowlist_function("rb_yarv_ary_entry_internal")
.allowlist_function("rb_yjit_ruby2_keywords_splat_p")
.allowlist_function("rb_yjit_fix_div_fix")
.allowlist_function("rb_jit_fix_div_fix")
.allowlist_function("rb_jit_fix_mod_fix")
.allowlist_function("rb_FL_TEST")
.allowlist_function("rb_FL_TEST_RAW")

View File

@ -197,7 +197,7 @@ pub use rb_get_cikw_keywords_idx as get_cikw_keywords_idx;
pub use rb_get_call_data_ci as get_call_data_ci;
pub use rb_yarv_str_eql_internal as rb_str_eql_internal;
pub use rb_yarv_ary_entry_internal as rb_ary_entry_internal;
pub use rb_yjit_fix_div_fix as rb_fix_div_fix;
pub use rb_jit_fix_div_fix as rb_fix_div_fix;
pub use rb_jit_fix_mod_fix as rb_fix_mod_fix;
pub use rb_FL_TEST as FL_TEST;
pub use rb_FL_TEST_RAW as FL_TEST_RAW;

View File

@ -1174,7 +1174,6 @@ extern "C" {
pub fn rb_str_neq_internal(str1: VALUE, str2: VALUE) -> VALUE;
pub fn rb_ary_unshift_m(argc: ::std::os::raw::c_int, argv: *mut VALUE, ary: VALUE) -> VALUE;
pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE;
pub fn rb_yjit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE;
pub fn rb_yjit_ruby2_keywords_splat_p(obj: VALUE) -> usize;
pub fn rb_yjit_splat_varg_checks(
sp: *mut VALUE,
@ -1312,6 +1311,7 @@ extern "C" {
end: *mut ::std::os::raw::c_void,
);
pub fn rb_jit_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE;
pub fn rb_jit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE;
pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE;
pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE);
}

View File

@ -279,6 +279,7 @@ fn main() {
.allowlist_function("rb_jit_mark_unused")
.allowlist_function("rb_jit_get_page_size")
.allowlist_function("rb_jit_array_len")
.allowlist_function("rb_jit_fix_div_fix")
.allowlist_function("rb_jit_iseq_builtin_attrs")
.allowlist_function("rb_jit_str_concat_codepoint")
.allowlist_function("rb_zjit_iseq_inspect")

View File

@ -396,6 +396,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
Insn::FixnumMult { left, right, state } => gen_fixnum_mult(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
Insn::FixnumDiv { left, right, state } => gen_fixnum_div(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
Insn::FixnumEq { left, right } => gen_fixnum_eq(asm, opnd!(left), opnd!(right)),
Insn::FixnumNeq { left, right } => gen_fixnum_neq(asm, opnd!(left), opnd!(right)),
Insn::FixnumLt { left, right } => gen_fixnum_lt(asm, opnd!(left), opnd!(right)),
@ -484,7 +485,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
&Insn::IsA { val, class } => gen_is_a(asm, opnd!(val), opnd!(class)),
&Insn::ArrayMax { state, .. }
| &Insn::FixnumDiv { state, .. }
| &Insn::Throw { state, .. }
=> return Err(state),
};
@ -1704,6 +1704,16 @@ fn gen_fixnum_mult(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, rig
asm.add(out_val, Opnd::UImm(1))
}
/// Compile Fixnum / Fixnum
fn gen_fixnum_div(jit: &mut JITState, asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd, state: &FrameState) -> lir::Opnd {
gen_prepare_leaf_call_with_gc(asm, state);
// Side exit if rhs is 0
asm.cmp(right, Opnd::from(VALUE::fixnum_from_usize(0)));
asm.je(side_exit(jit, state, FixnumDivByZero));
asm_ccall!(asm, rb_jit_fix_div_fix, left, right)
}
/// Compile Fixnum == Fixnum
fn gen_fixnum_eq(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd {
asm.cmp(left, right);

View File

@ -2198,6 +2198,7 @@ unsafe extern "C" {
start: *mut ::std::os::raw::c_void,
end: *mut ::std::os::raw::c_void,
);
pub fn rb_jit_fix_div_fix(recv: VALUE, obj: VALUE) -> VALUE;
pub fn rb_yarv_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE;
pub fn rb_jit_str_concat_codepoint(str_: VALUE, codepoint: VALUE);
pub fn rb_jit_shape_capacity(shape_id: shape_id_t) -> attr_index_t;

View File

@ -501,6 +501,7 @@ pub enum SideExitReason {
BlockParamProxyNotIseqOrIfunc,
StackOverflow,
FixnumModByZero,
FixnumDivByZero,
BoxFixnumOverflow,
}

View File

@ -183,6 +183,7 @@ make_counters! {
exit_fixnum_mult_overflow,
exit_fixnum_lshift_overflow,
exit_fixnum_mod_by_zero,
exit_fixnum_div_by_zero,
exit_box_fixnum_overflow,
exit_guard_type_failure,
exit_guard_type_not_failure,
@ -492,6 +493,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter {
FixnumMultOverflow => exit_fixnum_mult_overflow,
FixnumLShiftOverflow => exit_fixnum_lshift_overflow,
FixnumModByZero => exit_fixnum_mod_by_zero,
FixnumDivByZero => exit_fixnum_div_by_zero,
BoxFixnumOverflow => exit_box_fixnum_overflow,
GuardType(_) => exit_guard_type_failure,
GuardTypeNot(_) => exit_guard_type_not_failure,