Overview
gen_fixnum_div only guards against division by zero. For FIXNUM_MIN / -1, rb_jit_fix_div_fix returns 2^62 as a Bignum (heap pointer), but FixnumDiv is unconditionally typed as types::Fixnum in HIR.
The type is a lie, so downstream specialized ops (FixnumAnd/Add/Sub/Mult/...) treat the heap pointer as a tagged integer without a type guard → silent miscompile.
Details
The same MIN / -1 overflow is already handled elsewhere (constant folding in hir.rs, and FloatToInt returns types::Integer); only the non-constant codegen path was missing the guard.
Note: a / b alone looks correct (it just returns the real Bignum object). The bug only surfaces when the result feeds a downstream specialized Fixnum op.
Reproduction
def f(a, b) = (a / b) & 1
100.times { f(10, 3) } # warm up the JIT
p f(-(2**62), -1) # FIXNUM_MIN / -1
Expected
ruby -e 'def f(a,b)=(a/b)&1; 100.times{f(10,3)}; p f(-(2**62),-1)'
Actual
ruby --zjit --zjit-call-threshold=2 -e 'def f(a,b)=(a/b)&1; 100.times{f(10,3)}; p f(-(2**62),-1)'
Also (a/b)+1 returns 2.0000002396370213 (the Bignum pointer is added as a tagged int and the bit pattern happens to decode as a Float) instead of 4611686018427387905.
Environment
dak2 ruby (master) % ruby -v
ruby 4.1.0dev (2026-05-31T10:35:05Z master 12c8599ffa) +PRISM [arm64-darwin25]
Overview
gen_fixnum_divonly guards against division by zero. ForFIXNUM_MIN / -1,rb_jit_fix_div_fixreturns2^62as a Bignum (heap pointer), butFixnumDivis unconditionally typed astypes::Fixnumin HIR.The type is a lie, so downstream specialized ops (
FixnumAnd/Add/Sub/Mult/...) treat the heap pointer as a tagged integer without a type guard → silent miscompile.Details
The same
MIN / -1overflow is already handled elsewhere (constant folding inhir.rs, andFloatToIntreturnstypes::Integer); only the non-constant codegen path was missing the guard.Note:
a / balone looks correct (it just returns the real Bignum object). The bug only surfaces when the result feeds a downstream specialized Fixnum op.Reproduction
Expected
ruby -e 'def f(a,b)=(a/b)&1; 100.times{f(10,3)}; p f(-(2**62),-1)'Actual
ruby --zjit --zjit-call-threshold=2 -e 'def f(a,b)=(a/b)&1; 100.times{f(10,3)}; p f(-(2**62),-1)'Also
(a/b)+1returns2.0000002396370213(the Bignum pointer is added as a tagged int and the bit pattern happens to decode as a Float) instead of4611686018427387905.Environment