CRuby's Integer is arbitrary precision: it never overflows, it grows. Spinel
compiles Integer to a fixed-width machine integer (sp_int, the target's
pointer width: 64-bit on amd64 and arm64, range -2**63 .. 2**63 - 1;
32-bit on a 32-bit target, see below) because a
machine word is what makes the generated C fast. --int-overflow=MODE chooses
what happens when an Integer result crosses that boundary. The rest of this
page says 64 bits; read 32 on a 32-bit target.
spinel app.rb --int-overflow=raise # default
spinel app.rb --int-overflow=wrap
spinel app.rb --int-overflow=promote| mode | on overflow | matches CRuby? | use it for |
|---|---|---|---|
raise (default) |
raises RangeError (integer overflow in +) |
no -- CRuby would grow the integer | catching overflow loudly; never silently wrong |
wrap |
two's-complement wraparound, like C (a + b with no check) |
no | modular arithmetic, hashes, checksums, PRNGs -- anywhere defined wraparound is the intent |
promote |
promotes the result to an arbitrary-precision integer (bigint) | yes | CRuby-faithful integer math (experimental, see below) |
The mode applies to integer +, -, *, unary -, and (under promote) **
and shifts. It does not change division: 1 / 0 is always a
ZeroDivisionError regardless of mode.
The default refuses to be silently wrong. A computation that exceeds 64 bits is
almost always a bug or a case that needs promote; raising surfaces it at the
point it happens rather than producing a truncated value. This is a deliberate
deviation from CRuby (which would never raise here) in favour of loudness.
wrap skips the overflow check entirely, so arithmetic is plain C wraparound.
Choose it when wraparound is the algorithm -- hashing, checksums, fixed-width bit
manipulation, RNGs -- not as a blanket "make overflow go away", since it will
silently truncate a value the program genuinely needed. It is the fastest mode
(no checks); for example the optcarrot build uses wrap.
promote makes integers behave like CRuby's: a result that exceeds 64 bits
becomes a bigint instead of overflowing. Small values stay unboxed machine
integers (like CRuby's fixnum), and only the ones that actually overflow pay the
bigint cost, so it is more practical than widening everything.
promote is experimental: most integer code works, but coverage is not yet
complete (some overflow paths through method arguments, closures, and certain
containers still raise rather than promote, and very large integer literals
are not yet represented). Treat it as opt-in CRuby fidelity, not a finished
guarantee. It also carries a runtime cost (bigint allocation and GC pressure),
so the default stays raise.
sp_int is intptr_t (lib/sp_types.h): the Integer is as wide as a pointer
on the machine the program is compiled for. On amd64, arm64 and every other
64-bit target that is 64 bits; on i386 (cc -m32) and wasm32 it is 32, with
the range -2**31 .. 2**31 - 1, and the overflow modes above apply at that
boundary. Everything that depends on the width follows the target, not the
host the compiler runs on:
- An integer literal past the target's
sp_intis a Bignum there, as it is in a 32-bit CRuby:0xdeadbeefor4_000_000_000compile to a bigint constant for a 32-bit target and to a plainsp_intfor a 64-bit one. - The width comes from the C compiler that builds the program.
spineluses its own (it was built with the same toolchain); with--ccnaming another compiler it asks that one once (-dM -E) and classifies literals for its pointer width, sospinel --cc='cc -m32' app.rbon a 64-bit host produces a correct 32-bit program. - A 32-bit target also gets 64-bit
time_tand file offsets on glibc (-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64) and, on i386, SSE arithmetic (-msse2 -mfpmath=sse): the x87 unit rounds every intermediate at 80 bits and3.7.round(1)would come out3.8. The driver adds these to the program's compile;common.mkadds them to the runtime's when$(CC)is a 32-bit compiler (SPINEL_INT_BITS). Integer#sizeis 4,Integer#bit_lengthand the shift helpers use the width,String#unpackof a 64-bit directive (Q,q) boxes a Bignum when the value does not fit, and a Bignum read out of a poly slot into an Integer is aRangeErrorwhen it does not fit.- Known gaps on 32-bit: FFI marshalling of 64-bit C types is not done, and
INT32_MINis the sentinel anInteger | nilslot uses fornil, asINT64_MINis on 64-bit.
For the developer: make test-corpus CC='cc -m32' runs the test corpus as
32-bit programs. Use a separate work tree, or make clean first: the runtime
objects and the precompiled header are built for one width. A test that
assumes a 64-bit Integer (values or arithmetic past 2^31, Integer#size, a
printed hash, a 64-bit FFI width) says # spinel: int64 in its first line and
is filtered out there; CI runs that lane on every push.
In the normal spinel app.rb flow the driver compiles and links in one step and
passes the matching -DSP_INT_OVERFLOW_MODE_{RAISE,WRAP,PROMOTE} to the C
compiler for you, so --int-overflow=MODE is all you need.
If you emit C with -c and compile it separately, the generated code and the
runtime must agree on the mode, so pass the same define to your own cc:
spinel app.rb --int-overflow=wrap -c -o app.c
cc app.c -DSP_INT_OVERFLOW_MODE_WRAP -Ilib libspinel_rt.a -lm -o app- limitations.md -- where Spinel's static, fixed-width model differs from CRuby, including integer precision.