Skip to content

feat: stop on Int and UInt overflow, with explicit wrapping methods - #1401

Merged
kacy merged 6 commits into
mainfrom
feat-overflow-trap
Nov 14, 2026
Merged

kacy merged 6 commits into
mainfrom
feat-overflow-trap

Conversation

@kacy

@kacy kacy commented Nov 14, 2026

Copy link
Copy Markdown
Owner

+, - and * on an Int, and -x on the most negative one, now stop the program with pith runtime error: integer overflow when the result does not fit in 64 bits. UInt and UInt64 do the same against 0 to 2^64 - 1. The narrow types (Int8 to UInt32) keep wrapping into their range, as documented. The fused counting update m[k] += d checks the same way.

wrapping_add, wrapping_sub and wrapping_mul give the wrapped result on purpose. The std code that relied on wrapping now uses them: the sha-512 kernel, xxhash, bits.add32/add64, a zstd byte-replication multiply and its hash, and a test seed in std.hash.

How the checks are lowered:

  • Source arithmetic emits new IR ops (addc, subc, mulc and the unsigned uaddc, usubc, umulc). The compiler's own index and loop arithmetic keeps the plain ops.
  • With a constant operand (i + 1, n * 10, 0 - x) the check is one compare of the other operand against the range that cannot overflow, which fuses with its branch.
  • The overflow path is a trap instruction, not a call. A call on the cold path made every value live across the check survive it, which doubled the instructions in the sort's merge loop. The runtime reports the trap from a SIGILL handler through pith_runtime_error, after checking that the faulting instruction is the trap.
  • No check is emitted where overflow cannot happen or is caught anyway:
    • a single i = i + 1 (or i += 1) in a while i < e loop, or while i <= n for a length n, and the matching i - 1 under i > e;
    • a small constant offset inside a bounds-checked list, Bytes or String index, where a wrapped value fails the bounds check.

Benchmarks against main (t2.sh, 5 interleaved rounds, median ms):

bench main this change
tight_loop (only + and * in the loop) 493 970 +97%
path_clean_count 530 603 +14%
closure_calls 113 119 +5%
generic_sort int / string 362 / 704 358 / 707 -1% / +0%
hash_kernels sha512 505 514 +2%
everything else in the set within +-2%

The tight_loop and path cleaning costs are the check itself in loops that do nothing else. Docs: docs/numbers.md (a new overflow section), docs/ir-contract.md and docs/limitations.md.

Tests:

  • seven abort goldens: add, sub, mul, negate, compound assignment, the map update, and UInt sub;
  • tests/cases/test_wrapping_arithmetic.pith;
  • one regression case that overflowed by accident is fixed.

The local suite passes, including the abort goldens.

@kacy
kacy merged commit c92c69b into main Nov 14, 2026
4 checks passed
@kacy
kacy deleted the feat-overflow-trap branch November 14, 2026 23:50
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