creusot-std: Add specs for Wrapping arithmetic operators#2196
Open
MavenRain wants to merge 1 commit into
Open
Conversation
`std::num::Wrapping<T>` is the transparent integer newtype whose operators wrap around on overflow (`Wrapping(a) + Wrapping(b) == Wrapping(a.wrapping_add(b))`, and likewise for `-`, `*` and unary `-`). It previously had no Creusot specs. Add, in `creusot-std/src/std/num.rs`: - A `DeepModel` instance for `Wrapping<T>` (modelled on the existing `Reverse<T>` instance): `deep_model(Wrapping(x)) = Wrapping(x.deep_model())`, so the model of a `Wrapping` is the wrapped model. - `extern_spec!` specs for the `Add`, `Sub`, `Mul` and `Neg` operators, generated per integer type by a new `spec_wrapping!` macro. Each spec relates the wrapped field of the result to the wrapping operation on the operands' fields, e.g. `result.0 == self.0 + rhs.0`, where `+`/`-`/`*`/unary-`-` on a machine integer already denote the wrapping (builtin) operations — matching how the existing `wrapping_add`/`wrapping_neg` specs are written. The reference bodies mirror the std impls (`Wrapping(self.0.wrapping_add(rhs.0))`, ...). The generated `tests/creusot-std/creusot-std.coma` is regenerated accordingly (48 new `_body` proof modules: Add/Sub/Mul/Neg over the 12 integer types). Each obligation was checked with Z3 and discharges (`result.0 == self.0 + rhs.0` from the `wrapping_*` reference body). Follow-ups (not in this change): the assigning operators (`AddAssign`, ...), `Div`/`Rem` (which carry a non-zero-divisor precondition), and the bitwise/shift operators. Closes creusot-rs#1923. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Lysxia
reviewed
Jul 10, 2026
| ($($type:ty)*) => {$( | ||
| extern_spec! { | ||
| impl core::ops::Add<Wrapping<$type>> for Wrapping<$type> { | ||
| #[allow(dead_code)] |
Collaborator
|
While you're at it, can you add extern specs for the impls with reference types? To update the why3session.xml file, And for the doc failure, this is going to be addressed by #2197 |
Lysxia
reviewed
Jul 10, 2026
|
|
||
| // `Wrapping<T>` is a transparent newtype whose arithmetic operators wrap around on overflow, | ||
| // i.e. `Wrapping(a) + Wrapping(b) == Wrapping(a.wrapping_add(b))`, and likewise for `-`, `*` and | ||
| // unary `-`. Its model is the model of the wrapped value. |
Collaborator
There was a problem hiding this comment.
We can get rid of this and the other comments. They don't say anything informative.
Collaborator
|
Thanks! Could you indeed add instances for |
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.
Closes #1923.
std::num::Wrapping<T>is the transparent integer newtype whose arithmeticoperators wrap around on overflow . . .
Wrapping(a) + Wrapping(b) == Wrapping(a.wrapping_add(b)),and likewise for
-,*and unary-. It had no specs, so verified code could not reason through it.This adds, in
creusot-std/src/std/num.rs:DeepModelforWrapping<T>, modelled on the existingReverse<T>instance in
cmp.rs:extern_spec!specs forAdd,Sub,MulandNeg, generated perinteger type (
u8…isize) by a smallspec_wrapping!macro. Each spec relatesthe wrapped field of the result to the wrapping operation on the operands:
This mirrors how the existing primitive-integer specs are written: on a machine
integer,
+/-/*/unary-already denote the wrapping (builtin) operations,so
wrapping_addis specced asresult == self + rhsandwrapping_negasresult == -self. TheWrappingoperator specs reuse exactly that, and thereference bodies match the std impls.
Scope
Kept intentionally to the wrapping arithmetic operators the issue calls out. Natural
follow-ups, deferred to keep this reviewable: the assigning operators
(
AddAssign, …),Div/Rem(which carry a non-zero-divisor precondition), andthe bitwise/shift operators. I'd be happy to add these in a follow-up (or here) if you'd
prefer.
Verification
creusot-stdtranslates cleanly with the new specs (./t ui creusot-std), andtests/creusot-std/creusot-std.comais regenerated. The delta is exactly 48 new_bodymodules —Add/Sub/Mul/Neg× the 12 integer types, plus reordering;nothing is removed.
regenerated coma) reports
Validfor all of them, e.g.vc_extern_spec_core_ops_Add_Wrapping_u32_Wrapping_u32_add_body … Valid. The proofis the trivial rewrite: the reference body
Wrapping(self.0.wrapping_add(rhs.0))gives
result.0 == self.0.wrapping_add(rhs.0), andwrapping_add's own spec isresult == self + rhs.why3session.xmlstill needs regenerating for the new goals (I verified theyprove, but haven't committed the session — a from-scratch regen rewrites the whole
session with fresh prover times, and my local run also tripped an unrelated,
pre-existing goal on a macOS alt-ergo/cvc4 quirk). I'd be happy to regenerate it however
you prefer, or leave it to the standard
./testsuite_regeneraterun.Note
Drafted with AI assistance; the specs and the Z3 checks above were reviewed and run by me.