Proof models, verified runtime APIs, and differential checks for MoonBit
moon add mizchi/veriimport {
"mizchi/veri/bounds",
}
options("proof-enabled": true)pub fn digit(value : Int) -> Int {
proof_ensure: result => 0 <= result && result <= 9,
} {
@bounds.clamp(value, 0, 9)
}
test "clamp includes its upper bound" {
assert_eq(digit(10), 9)
}moon test --target js
moon provejust setup-solvers # Install the additional prover locally (network needed once)
just doctor # Check versions and bundled Why3 data
just verify # Run proofs, negative controls, reference checks, and backend testsjust prove # Prove both workspace modules: MoonBit → Why3 → SMT
just prove-machine # Prove bounds, runtime bridges, and their example with machine integers
just prove-collections-machine # Prove collection implementations and clients with machine integers
just prove-foundations-machine # Prove order, arithmetic, real and IEEE error contracts with machine integers
just core-capabilities # Probe direct calls to core from contracted functions
just conversion-capabilities # Probe native numeric casts and UInt16 support
just array-capabilities # Report old-state/snapshot support and mutable-alias rejection
just smt # UNSAT proofs and SAT witnesses for FP, bitvectors, arrays, and strings
just negative # Check that deliberately false claims in each model are not proved
just negative bitvector runtime/uint32 runtime/uint64 # Select affected negative controls
just test js # Run runtime checks
just quickcheck js # Run only the QuickCheck properties
just bench native # Compare collections with core; save timings and ratios
just bench-backends # Run the comparisons sequentially on all four backends
just bench-check native 1.0 # Fail unless both measurement orders meet the ratio limit
just test-backends # JS / wasm / wasm-gc / native
just test-release # Run the same checks in optimized builds
just fp-capabilities # Report native Float/Double proof-lowering support
just vectors # Regenerate expected results from Z3
just vectors-check # Compare checked-in reference values with the current Z3 results
just fmt # Format sources and generate public interfaces
just package-check # Execute and prove both README quickstarts from the package ZIP| Path | Contents | What is checked |
|---|---|---|
| bounds | Inclusive clamp and explicit half-open clamp | Given valid bounds, the result lies within the interval and preserves inputs already in range |
| fset | Bindings to Why3's finite-set model | Lemmas for empty sets, insertion, and union |
| seq / list | Finite sequence and inductive list models | Concatenation, lengths, reversal, indexing; list/conversions connects the models |
| bag / fmap | Multisets and finite maps | Multiplicity, union, lookup after update, domain and removal |
| bintree | Logical binary trees | Size, height, membership, and traversal length |
| runtime/list / runtime/stack / runtime/queue | Persistent lists, LIFO stacks, and FIFO queues | Structural list correspondence and operation contracts |
| runtime/pqueue | Persistent Int minimum priority queue | Sortedness, minimum removal, and exact multiplicities |
| runtime/bintree / runtime/bintree/search | Binary trees and Int search trees | Inorder correspondence; insertion preserves strict BST order and membership |
| examples/collections | Collection clients in the example module | Composed LIFO, FIFO, minimum, and search contracts |
| testing/commands | QuickCheck operation generators and shrinkers | Replayable traces; a deliberate failure shrinks to [Push(3)] |
| ieee754 / ieee754/float32 | Logical binary64 / binary32 values and shared rounding modes | Lemmas about NaN, signed zero, and self-subtraction of finite values |
| ieee754/conversions | Logical widening and narrowing | Binary32 widening roundtrip and preservation of NaN classification |
| bitvector | Why3 fixed-size bitvector bindings | Operations and modular integer conversions |
| bitvector/laws | Bitvector lemmas, with integer laws in laws/integers | Bitwise laws, conversions in both directions, and unsigned value bounds |
| arrays | Total maps with select/store | Read after write, unchanged other keys, and last-write-wins |
| strings | Logical SMT strings | Concatenation, length, substring, search, and replacement |
| examples/models | Examples importing the libraries | Cross-module proofs, including arrays with bitvector keys and string values |
| integer | Unbounded integers and runtime value projections | Shared contract vocabulary for Int / UInt / Int64 / UInt64 |
| runtime/uint32 / runtime/uint64 | Wrapping add/sub implementations | Direct BV contracts for add/sub/less under both integer preludes; Z3 differential checks |
| runtime/array | FixedArray reads, writes, swaps, fills and range copies | Safe bounds and operation contracts; full before/after correspondence is checked by QuickCheck |
| arrays/range | Mathematical range and before/after models | Frame preservation, swap, fill and copy lemmas |
| runtime/text | Validated SMT-compatible text | Code point length, char_at, and substring checked against Z3 |
| examples/bridges | Runtime bridge clients | Contracts compose across module boundaries |
| runtime/float32 / runtime/float64 | Runtime FP APIs and exact reference comparisons | Arithmetic, sqrt, neg/abs, comparisons, classification, and non-NaN bit roundtrips |
| runtime/float_conversions | Precision conversion checks | Float ↔ Double compared against Z3, including rounding boundaries |
| examples/floating | Runtime FP usage | Different rounding precision in Float and Double |
| runtime/int32 / runtime/int64 | Checked signed add/sub/mul | Exact mathematical results or overflow rejection, proved under both integer preludes; BigInt comparisons |
| runtime/conversion | 9 checked conversions and 3 unconditional widenings across signedness and widths | Representability checks proved; native cast results compared with BigInt |
| encoding / encoding/bitvector | Positional LE/BE byte models | Roundtrips, byte bounds and lengths for 8/16/32/64 bits, plus BV correspondence |
| runtime/bytes | Byte / UInt16 / UInt / UInt64 codecs over BytesView | Proved read bounds and consumed length; native codecs differential-tested |
| algebra | Explicit operation laws and map/fold models | Identity/composition, ordered splits, monoid partitions and commutative reordering |
| graph | Paths, reachability, weights and finite-set certificates | Composition/decomposition, closed sets, feasible-label shortest-path and BFS certificates |
| runtime/graph | Immutable directed graphs, BFS and Dijkstra | Independent distance model, executable certificate checks, graph/trace shrinking |
| examples/toolkit | Checked allocation, binary headers, aggregation and routes | Cross-module contracts and runtime clients |
| checks/ | SMT-LIB 2 checks for FP, bitvectors, arrays, and strings | Properties over all inputs and counterexamples with fixed inputs |
| checks/negative | Deliberately false lemmas | False claims must not be reported as successfully proved |
veri/
├── moon.mod # mizchi/veri
├── moon.work # members: ".", "examples"
├── bounds/
├── bitvector/
├── encoding/
├── algebra/
├── graph/
├── arrays/
├── strings/
├── fset/
├── seq/
├── list/ # laws, indexed, conversions
├── bag/
├── fmap/
├── bintree/
├── ieee754/
├── integer/
├── runtime/ # numeric/text bridges and collection implementations
├── testing/commands/
└── examples/
├── moon.mod # mizchi/veri-examples; imports mizchi/veri@0.1.0
├── models/
├── bridges/
├── floating/
├── collections/
└── toolkit/// Import mizchi/veri/ieee754 and enable proof-enabled in moon.pkg.
// Place the following in a .mbtp file.
lemma adding_nan_is_nan(x : @ieee754.Float64, y : @ieee754.Float64) {
proof_require: @ieee754.is_nan(x),
proof_ensure: @ieee754.is_nan(
@ieee754.add(@ieee754.nearest_even(), x, y),
),
} {}using @bitvector {type Bv8, type Bv16, type Bv32, type Bv64}| Package | Contents |
|---|---|
| relations | Equivalence, preorders, partial/total orders, reverse and lexicographic order, with explicit laws for the supplied relation |
| seq/order | Sortedness, sorted permutations, subrange permutations, exchanges and swap laws, using mathematical indices |
| integer / integer/laws | Arithmetic, absolute value, min/max, Euclidean and truncating division, and their remainder correspondence |
| integer/aggregate / integer/aggregate/laws | Nonnegative integer powers, half-open interval sums and their decomposition |
| number / number/laws / number/parity | Divisibility, GCD, coprimality, parity and laws |
| runtime/number | UInt GCD, safe Int quotient/remainder and Euclidean remainder for a positive modulus |
| real / real/laws | Proof-only mathematical reals, integer embedding, floor/ceil, distance and error composition |
| ieee754/error / ieee754/error/operations | Real projections, rounding, binary32/64 operation bounds and input-error propagation |
| examples/foundations | Cross-module examples composing order, GCD, real and rounding contracts |
// Import "mizchi/veri/runtime/queue".
test "persistent queue and Iter" {
let original : @queue.Queue[Int] = @queue.Queue::new()
let queue = original.push(1).push(2)
assert_true(original.is_empty())
assert_eq(queue.iter().map(x => x * 2).to_array(), [2, 4])
}| API | Behavior |
|---|---|
| valid_range(length, range) | Validate endpoints without overflowing, including for extreme invalid inputs |
| set(array, index, value) | Write one cell |
| swap(array, left, right) | Exchange two cells; equal indices are valid |
| fill(array, value, range) | Fill a range |
| blit(source, target, source_range, target_start) | Copy between arrays, allowing overlapping source and destination |
| blit_disjoint(source, target, source_range, target_start) | Verified copy between distinct arrays |
| copy_within(array, source_range, target_start) | Copy within one array, including overlapping ranges, using the original source values |
// Import "mizchi/veri/runtime/array".
test "range updates" {
let xs : FixedArray[Int] = [0, 1, 2, 3]
assert_true(@array.fill(xs, 9, { start: 1, count: 2 }))
assert_true(@array.copy_within(xs, { start: 0, count: 3 }, 1))
assert_eq(xs, [0, 0, 9, 9])
}test "quickcheck: wrapping roundtrip" {
@quickcheck.check(
(input : (UInt, UInt)) => {
let (value, delta) = input
@uint32.sub(@uint32.add(value, delta), delta) == value
},
count=1000,
seed=20260914,
)
}| Workload | Core comparison | Timed work |
|---|---|---|
| List | core/list | Reverse/append followed by array conversion; length |
| Stack | core/list, built-in Array | Push all elements, then drain into a fresh array |
| Queue | core/queue | Push and drain; repeated peek on a prebuilt queue |
| Minimum priority queue | core/immut/priority_queue, core/priority_queue, both using Reverse[Int] | Push and drain, including duplicates, shuffled and ascending inputs |
| BST | core/immut/sorted_set | Insert, then query every key and equally many missing keys, with shuffled and ascending insertion |
| Binary-tree traversal | core/immut/sorted_set | Materialize the same contents from balanced and left-skewed trees, measuring linked-list intermediates and direct arrays separately |
| Logical package | Bundled Why3 theories | Interpretation |
|---|---|---|
| seq | seq.Seq, Reverse, Mem, Occ, Permut | Finite sequences with mathematical lengths and indices |
| list / list/indexed | list.List, Length, Append, Reverse, NthNoOpt, NumOcc | Inductive lists, with optional indexing/counting imports |
| list/conversions | seq.OfList, seq.ToList | Conversion between logical lists and sequences |
| bag | bag.Bag | Multisets; union adds counts and difference truncates at zero |
| fmap | fmap.Fmap | Finite maps with a finite-set domain |
| bintree | bintree.Tree, Size, Height, Occ, Inorder, Preorder | Binary trees and their content/traversal models |
| Runtime package | Operations and representation | Cost |
|---|---|---|
| runtime/list | empty/cons/uncons/is_empty/append/reverse/length; immutable linked spine | Basic operations O(1); append/reverse/length O(n) |
| runtime/stack | empty/push/pop/peek/is_empty; linked cells also used as pop results | O(1) |
| runtime/queue | empty/push/pop/peek/is_empty; front list and reversed back list | Push/peek O(1); pop amortized O(1), worst-case O(n) |
| runtime/pqueue | empty/push/pop/peek/is_empty; Int two-pass pairing heap, duplicates retained | Push/peek O(1); pop O(k) for k root children, worst-case O(n) |
| runtime/bintree | empty/node/inorder/to_array; generic binary tree | Constructors O(1); traversal O(n) |
| runtime/bintree/search | empty/insert/contains; Int red-black tree without duplicates | Insert/contains O(log n) |
MoonBit contracts / .mbtp → Why3 theories → SMT-LIB 2 → Z3 / CVC5
checks/**/*.smt2 ─────────────────────────→ SMT-LIB 2 → Z3| Package | Why3 theory | Main operations |
|---|---|---|
| bitvector | bv.BV8 / bv.BV16 / bv.BV32 / bv.BV64 | add/sub/mul, udiv/urem, sdiv/srem, bw_and/or/xor/not, shl/lshr/ashr, ult/ule/slt/sle |
| arrays | map.Map, map.Const | select, store, const_array, extensional eq |
| strings | string.String | concat, length, char_at, substring, contains, prefix_of, suffix_of, index_of, replace, to_integer/from_integer, lt/le |
| Runtime type | Model / adapter | Current guarantee |
|---|---|---|
| Int, UInt, Int64, UInt64 | @integer.from_int/from_uint/from_int64/from_uint64 → Integer | Proof-only numerical projections from the selected MoonBit prelude |
| UInt, UInt64 | runtime/uint32, runtime/uint64 | model encodes the value as Bv32/Bv64; add/sub/less prove direct agreement with BV operations under both integer preludes |
| Int / UInt, Int64 / UInt64 | Their 32-/64-bit encodings | 48 Z3 reference cases for native add/sub/mul, bitwise operations, signed/unsigned order, and valid shifts; also check the wrapping adapters |
| FixedArray[T] | @runtime_array.model(a) → SmtArray[Integer, T], plus a.length() | Reads, range validation and update contracts under both preludes; see the array section for the limits of before/after proofs |
| String | @text.from_string(s) → @text.Text? | Validate UTF-16 and the shared alphabet; 72 Z3 reference cases for length, char_at, substring |
| Float / Double | binary32 / binary64 encoding | 122 operation cases per format plus 62 precision conversion cases against Z3; native FP operations still lack a universal correspondence proof |
model(add(x, y)) = bv.add(model(x), model(y))
model(sub(x, y)) = bv.sub(model(x), model(y))
less(x, y) = bv.ult(model(x), model(y))// Import mizchi/veri/runtime/uint32 and mizchi/veri/runtime/text.
test {
assert_eq(@uint32.add(0xffffffffU, 1U), 0U)
let text = @text.from_string("A😀é").unwrap()
assert_eq(text.length(), 3)
assert_eq(text.substring(1, 2).to_string(), "😀é")
}| Purpose | Float API | Double API |
|---|---|---|
| Bit representation | @float32.from_bits(UInt), to_bits(Float) | @float64.from_bits(UInt64), to_bits(Double) |
| Operations | add/sub/mul/div/sqrt/neg/abs | Same names |
| Comparisons / classification | eq/less/is_nan/is_infinite | Same names |
| Precision conversion | @float32.to_double(Float) | @float64.to_float(Double) |
| Reference matching | matches(value, Bits(UInt)) or AnyNaN | matches(value, Bits(UInt64)) or AnyNaN |
// Import mizchi/veri/runtime/float32 and mizchi/veri/runtime/float64.
test {
let one = @float32.from_bits(0x3f800000U)
let half_ulp = @float32.from_bits(0x33800000U)
assert_true(@float32.matches(@float32.add(one, half_ulp), Bits(0x3f800000U)))
let wide = @float32.to_double(one)
assert_true(@float64.matches(wide, Bits(0x3ff0000000000000UL)))
}a = 2^53, b = -2^53, c = 0.5, rounding mode RNE
(a + b) + c = 0.5
a + (b + c) = 0.0Install
Download zipProof models, verified runtime APIs, and differential checks for MoonBit