Repository navigation
to-wasm: check overflow for every integer width - #275
Merged
Merged
Conversation
The spec says integer overflow and underflow trap for every integer type, but codegen only checked i64 and u64 (#110 predates the narrower types added in #113), and u64 went through the signed i64 checks: - i8 through u32 `+`, `-` and `*` wrapped silently; - u64 used signed checks, so `1 + 9223372036854775807` trapped while `18446744073709551615 + 1` wrapped to 0; - unary `-` was never checked, not even `-i64::MIN`; - i8/i16 `MIN / -1` left an out-of-range value (`128` in an i8). Every checked operation now goes through `get_or_create_checked(op, width)`, which keeps the existing i64 intrinsics and generates, once per operation and type: - u64: unsigned checks (`sum <u x`, `x <u y`, `y != 0 && p /u y != x`); - i32/u32: compute exactly in i64, trap if the result doesn't fit; - 8/16-bit: compute exactly in i32, trap if normalizing changes it. Negation goes through the checked `0 - x`. With `check_overflows: false` everything still wraps, and narrow signed division now wraps too. The overflow tests' `compile_program` helper also checked the parse errors twice instead of the compile errors; it now asserts on the compile errors. Signed-off-by: rvcas <x@rvcas.dev>
Contributor
surface thought: should the old functions also be merged in? not sure what value there is in special-casing them ("handled above" type stuff) |
The i64 add/sub/mul checks were the only ones with their own cached get-or-create methods, special-cased at the top of get_or_create_checked. They're now body builders dispatched like every other width, cached in the same map. The generated Wasm is unchanged. Signed-off-by: rvcas <x@rvcas.dev>
SpaceManiac
approved these changes
Oct 21, 2026
SpaceManiac
left a comment
Contributor
There was a problem hiding this comment.
not rigorously analyzed, but looks like it gets the job done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The spec says integer overflow and underflow trap for every integer type (
docs/language-spec.md, "Overflow and underflow"). Codegen only checkedi64andu64. The checks in #110 predate the narrower types added in #113, andu64went through the signedi64checks.What was wrong
i8throughu32:+,-and*wrapped silently.u64: used signed checks, so1 + 9223372036854775807trapped while18446744073709551615 + 1wrapped to0.-was never checked, not even-i64::MIN.i8/i16MIN / -1left an out-of-range value, such as128in ani8, because it fits in thei32it's computed in. 32- and 64-bit division traps natively.Fix
Every checked operation now goes through
get_or_create_checked(op, width)inintrinsics.rs. It keeps #110's pattern (a small check function generated once and called) and the existingi64functions unchanged. Per operation and type, it generates:u64: unsigned checks:sum <u x,x <u y, andy != 0 && p /u y != x.i32/u32: compute exactly ini64, and trap if the result doesn't fit in 32 bits.i32, and trap if normalizing the result to the type changes it.Negation goes through the checked
0 - x. Withcheck_overflows: falseeverything still wraps as before, and narrow signed division now wraps too instead of leaving an out-of-range value.Testing
tests/overflow.rsgains two tests that run all eight types through add, sub, mul, div and negation at their boundaries: one in checked mode and one in wrapping mode. Both fail onmainand pass with this change.compile_programhelper asserted on the parse errors twice instead of the compile errors, so codegen errors went unnoticed. It now checks the compile errors.score.stardoesu64arithmetic, which now uses the unsigned checks.starstream-to-wasm,starstream-proving-runtime,starstream-runtimeandstarstream-ledgertests pass.Side effect
The trace-row census drops from 6,001 to 5,890 rows. The test contracts mostly use
u64, and the correct unsigned add check costs 13 rows where the signed one cost 23. The census's checked-intrinsic columns undercount now, because its operator-pattern matcher doesn't recognize the newu64multiply. The totals are accurate.Not in this PR
Signed
/and%truncate, but the spec says they're floored:-3 / 16gives0, where the spec says-1. That's a separate bug.