Skip to content

feat(Boole): SMT-LIB 2.7 cast operators (as_int, as_sint, as_bv{n})#1191

Open
kondylidou wants to merge 21 commits into
strata-org:main2from
kondylidou:pr/casts-clean
Open

feat(Boole): SMT-LIB 2.7 cast operators (as_int, as_sint, as_bv{n})#1191
kondylidou wants to merge 21 commits into
strata-org:main2from
kondylidou:pr/casts-clean

Conversation

@kondylidou
Copy link
Copy Markdown
Contributor

@kondylidou kondylidou commented May 20, 2026

Summary

Implements all three SMT-LIB 2.7 cast operators end-to-end in the Boole language,
covering the full pipeline from grammar syntax to SMT-LIB 2 serialization.

New operators

Boole syntax Semantics Core op SMT-LIB 2.7
e as_int unsigned bv → Int Bv{n}.ToUInt ubv_to_int
e as_sint signed bv → Int (two's complement) Bv{n}.ToInt sbv_to_int
e as_bv{n} Int → bv (truncating mod 2^n) Int.ToBv{n} (_ int_to_bv n)

Supported widths: 1, 8, 16, 32, 64, 128.

Implementation layers touched

  • Grammar (Boole/Grammar.lean): 9 new grammar rules with @[prec(80)] so casts bind tighter than comparisons.
  • Lowering (Boole/Verify.lean): bvWidth helper dispatches to Bv{n}.ToUInt / Bv{n}.ToInt; Int.ToBv{n} for the reverse direction. Type errors (as_sint on int, as_int on Map, etc.) produce descriptive messages.
  • Core ops (Core/CoreOp.lean): ToUInt and ToInt added to BvOpKind; intToBv (n : Nat) added as a top-level CoreOp (source type is Int, not BitVec).
  • Factory (Core/Factory.lean): bvToUIntFunc, bvToIntFunc, intToBvFunc registered for all 6 widths (18 new entries, factory count 292 → 304).
  • SMT encoding (Core/SMTEncoder.lean): maps Core ops to Op.ubv_to_int, Op.sbv_to_int, Op.int_to_bv.
  • SMT-LIB serialization (DL/SMT/DDMTransform/Translate.lean): int_to_bv uses iden_indexed pattern (_ int_to_bv n), matching zero_extend.
  • Lean kernel (DL/SMT/Translate.lean, Denote.lean): denotations use BitVec.toNat (unsigned), BitVec.toInt (signed), BitVec.ofInt n (int→bv).

Naming

BvOpKind.ToUInt (unsigned bv → Int) and BvOpKind.ToInt (signed bv → Int) follow
the SMT-LIB ubv_to_int / sbv_to_int naming convention and are symmetric with each other.

Tests

  • cast_all_directions.lean: 7 obligations covering all 3 cast directions, round-trip, and sign-agreement.
  • cast_errors.lean: 3 #guard tests verifying descriptive error messages for invalid cast sources.
  • cast_expr.lean, widening_casts.lean: updated obligation IDs and ToNat→ToUInt comment fixes.
  • ProgramEvalTests.lean, StatisticsTest.lean: updated for renamed ops and new factory entries.

Closes

Gap 6 in BooleFeatureRequests.md.

kondylidou and others added 12 commits May 19, 2026 23:50
Boole `e as_int` lowers to `Bv{n}.ToNat` (Core named op); the SMT
encoder maps it to standard SMT-LIB `bv2nat`; no axioms injected.
Supported widths: bv1/8/16/32/64/128 — covers all bitvector cast
patterns in B2–B5 (u8/u64/u128 as nat in dalek-lite scalar.rs).

Changes:
- CoreOp: add BvOpKind.ToNat (cross-sort conversion)
- SMTEncoder: .bv ⟨_, .ToNat⟩ → Op.bv2nat / .int
- Op.lean: add Op.BV.bv2nat + mkName entry
- Denote/Translate: handle .app .bv2nat for semantics and Lean reflection
- Core Grammar: add type bv128 + bv128Lit
- Core DDM Translate: add bv128 → .bitvec 128
- Factory: add bvToNatFunc for widths 1/8/16/32/64/128, register in WFFactory
- Boole Verify: wire bv128 through typeRange/toCoreMonoType/bvWidth/cast_to_int
- Boole Grammar: update cast_to_int comment
- Tests: cast_expr.lean (new, all 6 widths), widening_casts.lean refactored
  to use as_int; StatisticsTest/ProgramEvalTests snapshots updated (+6 factory ops)
- Docs: BooleFeatureRequests/BooleBenchmarks updated to reflect Gap strata-org#6 closed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collect nullary type synonyms whose RHS is `int` (e.g. `type nat :=
int`) in a pre-pass into `TranslateState.natSynonyms`.  For each
non-parameterised datatype whose constructor has a `nat`-typed field,
automatically emit a Core axiom:

  ∀ x : DT . DT..isCtor(x) ⟹ DT..field(x) ≥ 0

`isNatType` recognises these fields; `toCoreTypedBin`/`toCoreTypedUn`
route nat-typed arithmetic to `Int.*` ops instead of the BV path.

New test files:
- cast_nested.lean  — as_int in quantifier bodies, compound BV exprs,
  let bindings, exists, and a rec function with decreases (16 VCs pass)
- nat_axiom_discussion.lean  — meeting document: working NatList case,
  the mixed nat/int Pair soundness hole, the minimal SMT reproducer,
  and four design alternatives (A–D)
- from_bytes_mod_order_wide_minimal.lean  — B2 seed: bytes_seq_as_nat /
  seq_as_nat_52 using as_int (107 pass, 6 unknown for abstract UFs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Drop dead declarations (Seq_new, Seq_lib_map, Seq_lib_filter, etc.)
  that were never called in any VC and would crash the SMT encoder if
  they ever were (arrow-typed parameters)
- Define group_order as its concrete Ed25519 prime value
- Define group_canonical(n) := n mod group_order (from Verus source)
- Add assume Arithmetic_Power2_pow2(252) == 2^252 inside
  lemma_group_order_bound so the assert group_order < 2*pow2(252) can
  be discharged by integer arithmetic

Result: 113 pass, 0 fail, 0 unknown (was 107/0/6)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename quantifier binder `""` → `"x"` in the auto-generated non-negativity
  axiom so it prints as a named binder in Core pretty-prints.
- Detect constructors with both `nat`- and `int`-typed fields and emit a
  `dbg_trace` warning at translation time; the mixed case is a known soundness
  hole (documented in `nat_axiom_discussion.lean`).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A `nat`-typed expression (`type nat := int` synonym) used with `e as_int`
now returns the expression unchanged instead of throwing, since nat is
already modeled as int.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joscoh
Copy link
Copy Markdown
Contributor

joscoh commented May 20, 2026

Maybe subsumed by the discussion at the meeting, but this axiomatization is unsound even without mixed nat-int constructors:


private def minimalGlobalUnsound : Strata.Program :=
#strata
program Boole;

type nat := int;

datatype NatBox { MkBox(value: nat) };

procedure minus_one_is_nonneg(n: int) returns ()
spec {
  requires n == -1;
  ensures false;
} {
  exit minus_one_is_nonneg;
};
#end

/-- info: Obligation: minus_one_is_nonneg_ensures_1_837
Property: assert
Result: ✅ pass-/
#guard_msgs in
#eval Strata.Boole.verify "cvc5" minimalGlobalUnsound (options := .quiet)

@kondylidou kondylidou changed the title feat(Boole): widening casts (e as_int) feat(Boole): SMT-LIB 2.7 cast operators (as_int, as_sint, as_bv{n}) May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants