Skip to content

# ZJIT: FixnumDiv miscompiles FIXNUM_MIN / -1 (returns a Bignum typed as Fixnum) #990

@dak2

Description

@dak2

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)'
0

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)'
false

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]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions