Skip to content

perf(stable-api): inline num2long/num2ulong Bignum fast path for MRI#736

Merged
ianks merged 2 commits into
mainfrom
perf/stable-api-num2long
Apr 23, 2026
Merged

perf(stable-api): inline num2long/num2ulong Bignum fast path for MRI#736
ianks merged 2 commits into
mainfrom
perf/stable-api-num2long

Conversation

@ianks

@ianks ianks commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

For Bignums that fit in c_long/c_ulong, read BDIGIT digits directly from RBignum instead of calling rb_num2long/rb_num2ulong. Falls back to the dylib for overflow (which raises RangeError), Float coercion, and to_int dispatch.

Only active on 64-bit targets (#[cfg(target_pointer_width = "64")]). 32-bit always falls through to the dylib.

Layout

RBignum { RBasic basic; union { { usize len; u32 *digits; } heap; u32 ary[2]; } as_; }
  • BIGNUM_EMBED_FLAG = RUBY_FL_USER2 — digits are in as_.ary (embedded)
  • BIGNUM_EMBED_LEN_MASK = USER3|USER4|USER5, shift = RUBY_FL_USHIFT + 3 = 15
  • BIGNUM_POSITIVE_P = flags & RUBY_FL_USER1
  • BIGNUM_EMBED_LEN_MAX = 2 on 64-bit (= sizeof(u64)/sizeof(u32))

Fits-in-i64 check (64-bit, BDIGIT=u32)

Digit count Signed Unsigned
0 → 0 → 0
1 always fits always fits
2 range-check hi+lo range-check
3+ fall back (RangeError) fall back

Asm: fully inlined; fixnum path unchanged, Bignum fast path adds ~10 instructions before the dylib fallback.

Parity tests added (10 new)

  • 2**32 (1 BDIGIT, positive)
  • 2**62 (2 BDIGITs, fits)
  • -(2**32) (negative, 1 BDIGIT)
  • -(2**62) (negative, 2 BDIGITs)
  • Boundary values at i64::MAX, i64::MIN
  • Unsigned variants

189/189 tests pass.

Stack

  1. perf(stable-api): inline num2dbl T_FLOAT fast path for MRI #733num2dbl T_FLOAT fast path
  2. perf(stable-api): inline dbl2num flonum encode for MRI #734dbl2num flonum encode fast path
  3. perf(stable-api): inline rhash_size for MRI #735rhash_size AR/ST inline ⬅ base
  4. This PRnum2long/num2ulong Bignum fast path

🤖 Generated with Claude Code

@ianks ianks force-pushed the perf/stable-api-num2long branch from 2177cd3 to 3f5459d Compare April 23, 2026 05:44
@ianks ianks force-pushed the perf/stable-api-rhash-size branch 2 times, most recently from 88535a9 to 59d5175 Compare April 23, 2026 12:29
Base automatically changed from perf/stable-api-rhash-size to main April 23, 2026 12:29
ianks added 2 commits April 23, 2026 08:29
For Bignums that fit in c_long/c_ulong, read BDIGIT digits directly from
RBignum instead of calling rb_num2long/rb_num2ulong. Handles both embedded
(BIGNUM_EMBED_FLAG set) and heap layouts.

On 64-bit: BDIGIT=u32, BIGNUM_EMBED_LEN_MAX=2, c_long=i64.
- 0 digits → 0
- 1 digit → always fits i64 (max u32::MAX < i64::MAX)
- 2 digits → range-checked before returning
- 3+ digits → fall back to dylib (which raises RangeError)

Only active on 64-bit targets (#[cfg(target_pointer_width = "64")]).
32-bit always falls through to rb_num2long/rb_num2ulong for correctness.
Float coercion and to_int dispatch also fall through to dylib unchanged.

Parity tests added for 1-BDIGIT and 2-BDIGIT cases (positive, negative,
boundary values) confirming equality with the compiled C shim.
On Windows x64, c_long is i32 (LLP64 ABI), not i64 like on Linux/macOS
(LP64). The RBignum fast path was gated only on target_pointer_width="64",
which includes Windows, but bignum values like 2**31 exceed i32::MAX and
rb_num2long correctly raises RangeError for them on Windows. Our fast
path returned a truncated value instead of falling back, causing the
parity-test compiled path to raise a Ruby exception that longjmp'd
through Rust code — STATUS_ACCESS_VIOLATION on all Windows Ruby versions.

Fix: gate the fast path and all its supporting structs/helpers on
not(target_os = "windows"), and apply the same cfg to the parity tests
so the compiled-path call never raises on Windows.
@ianks ianks force-pushed the perf/stable-api-num2long branch from 26df2c0 to 30d124e Compare April 23, 2026 12:29
@ianks ianks merged commit b5ecb64 into main Apr 23, 2026
31 checks passed
@ianks ianks deleted the perf/stable-api-num2long branch April 23, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant