Phase 1: Seq<T> ghost type#143
Draft
coord-e wants to merge 4 commits into
Draft
Conversation
A spec-only `thrust_models::model::Seq<T>` analogous to the existing `model::Vec`: the logical representation is `(Array<Int, T>, Int)` (the array carries elements, the int carries length). The `#[thrust::def::seq_model]` marker lets the analyzer recognise `Seq` ADTs in future patches. This commit introduces only the type and its `Model` impl; the operations (`empty`, `singleton`, `len`, `push`, `concat`, `subsequence`, indexing) are added in follow-up commits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
a09bb38 to
877868a
Compare
Adds the non-recursive operations of `Seq<T>`. Each lowers mechanically to existing CHC primitives — no SMT-side function definition or peephole is involved here: | API | Lowering | |--------------------|-----------------------------------------------------| | `Seq::<T>::empty()`| `(seq_default_arr_int, 0)` | | `Seq::singleton(e)`| `(store(seq_default_arr_int, 0, e), 1)` | | `s.len()` | `tuple_proj(s, 1)` | | `s.push(e)` | `(store(fst(s), snd(s), e), snd(s) + 1)` | | `s[i]` | `select(fst(s), i)` | `seq_default_arr_int` is a designated constant array `((as const (Array Int Int)) 0)` always emitted in the SMT preamble; its concrete contents are irrelevant because callers never access indices past the length they keep track of. The `s[i]` lowering reuses the existing `ExprKind::Index` analyzer path, gated on `def_ids.seq_model()` to detect `Seq`-typed receivers (otherwise the path expects an `Array`). Paired pass/fail `seq_basic` test covering the five operations in one place, plus an end-to-end `seq_specs_vec_build` test that specifies a Vec-building runtime function through Seq operations. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
`s.concat(t)` and `s.subsequence(l, r)` lower in `annot_fn.rs` to
two new CHC function symbols whose bodies are emitted as recursive
SMT-LIB2 `define-fun-rec`s:
(define-fun-rec seq_concat_arr_int
((sa (Array Int Int)) (sn Int) (ta (Array Int Int)) (tn Int))
(Array Int Int)
(ite (<= tn 0) sa
(store (seq_concat_arr_int sa sn ta (- tn 1))
(+ sn (- tn 1))
(select ta (- tn 1)))))
(define-fun-rec seq_subseq_arr_int
((a (Array Int Int)) (l Int) (r Int)) (Array Int Int)
(ite (<= r l) seq_default_arr_int
(store (seq_subseq_arr_int a l (- r 1))
(- (- r 1) l)
(select a (- r 1)))))
The Thrust analyzer wraps each result in the usual
`(array, length)` tuple, with the length computed inline as
`s.len + t.len` / `r - l` so length-only properties (`len(concat(s, t))
== len(s) + len(t)`) verify on z3 + pcsat directly — the SMT
obligation simply never references the recursive function.
The emissions are gated by `System::uses_seq_concat` /
`uses_seq_subseq`, set when the analyzer lowers the operation.
Unconditionally emitting the `define-fun-rec` definitions
significantly slows pcsat on unrelated tests (measured: a baseline
1-second test rises into the tens of seconds), so gating is needed
for the benefit of code paths that never touch `Seq`.
pcsat unfolds the definitions for any concrete recursion bound.
The follow-up peephole commit covers the unbounded indexed case.
z3-Spacer in HORN logic treats every `define-fun-rec` as
uninterpreted and returns `unknown` (by user direction, we don't
aim to support indexed reasoning on Z3).
Paired pass/fail length tests (`seq_concat`, `seq_subsequence`)
exercise the length-only encoding. Paired pass/fail constant-bound
whole-Seq equality tests (`seq_concat_const`, `seq_subseq_const`)
exercise pcsat's unfolding of the `define-fun-rec` definitions
directly: whole-Seq equality reduces to tuple/array equality without
ever constructing a `select(seq_concat_arr_int(...), _)` term, so
the follow-up commit's `select` peephole does not apply and pcsat
must consult the recursive definition itself.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
`select(seq_concat_arr_int(sa, sn, ta, tn), i)` and
`select(seq_subseq_arr_int(a, l, r), i)` are simplified at
`chc::Term::select` construction time to equivalent
`(ite ...)` expressions over the underlying sequences:
select(seq_concat_arr_int(sa, sn, ta, tn), i)
↦ ite(i < sn, select(sa, i), select(ta, i - sn))
select(seq_subseq_arr_int(a, l, r), i)
↦ ite(0 <= i < r - l,
select(a, l + i),
select(seq_default_arr_int, 0))
The `define-fun-rec` definitions from the previous commit remain
the ground truth — the rewrites are exactly one unfolding step of
those definitions — but pcsat can prove unbounded indexed
properties against the inlined ITE form, where unfolding through
`define-fun-rec` over a universally quantified recursion bound
requires an inductive invariant pcsat doesn't find in a reasonable
budget. Bounded (constant) cases still work via direct unfolding
of the recursive definitions.
`chc::Function::ITE` and `chc::Term::ite` are introduced for this
rewrite, and `Term::select` gains a `V: Clone` bound (the rewrites
need to duplicate `index` and `sn`). That bound cascades into
`Resolver::Output: Clone` plus matching bounds on the three
`Resolver` impls (`RefinementResolver`, `MappedResolver`,
`StackedResolver`) — all concrete usages already satisfy `Clone`,
so this is a non-breaking strengthening.
Paired pass/fail indexed tests for both operations, plus an
end-to-end `seq_specs_concat_run` test exercising the same
rewrites through a real runtime function.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
877868a to
f2666fd
Compare
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.
Summary
Phase 1 of the Creusot-style ghost-sequence support tracked in
/root/.claude/plans/how-can-we-implement-buzzing-cascade.md. Eventualgoal: verify Creusot's
iter::Map/iter::MapExtin Thrust.Introduces
thrust_models::model::Seq<T>as(Array<Int, T>, Int)(mirroring the existing
Vecmodel). Operations:Seq::<T>::empty()(seq_default_arr_int, 0)Seq::singleton(e)(store(seq_default_arr_int, 0, e), 1)s.len()tuple_proj(s, 1)s.push(e)(store(fst(s), snd(s), e), snd(s) + 1)s[i]select(fst(s), i)s.concat(t)(seq_concat_arr_int(fst(s), snd(s), fst(t), snd(t)), snd(s) + snd(t))s.subsequence(l, r)(seq_subseq_arr_int(fst(s), l, r), r - l)seq_default_arr_intis((as const (Array Int Int)) 0), always emittedin the SMT preamble. Its concrete contents are irrelevant because callers
never access indices past the tracked length.
seq_concat_arr_intandseq_subseq_arr_intare emitted via SMT-LIB2(define-fun-rec ...), gated bySystem::uses_seq_concat/uses_seq_subseqso the recursive declarations are only in scope whenthe analyzer has lowered at least one use. Unconditionally emitting the
two
define-fun-recdefinitions noticeably slows pcsat on unrelatedtests (a baseline ≈1 s test ran into the tens of seconds in
measurement), so gating is needed even for code paths that never touch
Seq.Indexed reasoning
concat/subsequencelength-only properties verify directly undereither solver because the length is computed inline by the analyzer.
For whole-Seq equality with a constant recursion bound (e.g.
Seq::singleton(x).concat(Seq::empty()) == Seq::singleton(x)), pcsatunfolds the
define-fun-recdefinitions directly. The third commitships paired pass/fail tests (
seq_concat_const,seq_subseq_const)that exercise this without any peephole help — whole-Seq equality
reduces to tuple/array equality without ever constructing a
select(seq_concat_arr_int(...), _)term, so the follow-up commit'speephole does not apply.
For unbounded indexed access (e.g.
s.concat(t)[i] == s[i]wheni < s.len()), the fourth commit adds achc::Term::selectpeepholethat rewrites one unfolding step of the recursive definitions:
The
define-fun-recdefinitions remain the ground truth — the rewritesare exactly one unfolding step — but pcsat can prove unbounded indexed
properties against the inlined ITE form, where unfolding through
define-fun-recover a universally quantified recursion bound requiresan inductive invariant pcsat doesn't find in a reasonable budget.
z3 / Spacer in HORN logic treats every
define-fun-recasuninterpreted and returns
unknown; per user direction, indexedreasoning is not targeted at z3.
Tests gated on pcsat opt in with
//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper.Term simplifiers added to
chc::Termtuple_proj(tuple(ts...), i)↦ts[i]— Spacer doesn't reducedatatype projections of literal constructors, so this is needed for
e.g.
Seq::empty().len()to reduce syntactically to0.select(store(a, i, v), j)↦v/select(a, j)for concretei,jinteger literals — needed forSeq::singleton(x)[0] == x.select(seq_concat_arr_int(...), i)/select(seq_subseq_arr_int(...), i)↦
ite(...)— described above (commit 4 only).chc::Function::ITEandchc::Term::iteare introduced;Term::selectgains a
V: Clonebound (the rewrites duplicateindex/sn) whichcascades into
Resolver::Output: Cloneplus matching bounds on thethree
Resolverimpls — all concrete usages already satisfyClone,so this is a non-breaking strengthening.
Also fixes
chc::Term::Appemission to print baref(not(f)) fornullary functions, matching SMT-LIB conventions for
declare-const.Commit layout
Seq<T>ghost type — the type andModelimpl, no operations.empty,singleton,len,push,indexing. Each lowers to existing CHC primitives. Consolidated
seq_basicpass/fail pair plus an end-to-endseq_specs_vec_buildprogram test.
concat/subsequenceviadefine-fun-rec— adds the tworecursive SMT definitions and the analyzer arms that lower the
methods. Paired length-only tests (
seq_concat,seq_subsequence)plus paired whole-Seq equality tests with a constant recursion bound
(
seq_concat_const,seq_subseq_const) that exercise pcsat'sunfolding of the
define-fun-recdefinitions directly, withoutpassing through any
selectpeephole.chc::Function::ITE,Term::ite, and the twoselectrewrites described above. Pairedpass/fail unbounded indexed tests for both ops (
seq_concat_index,seq_concat_index_right,seq_subseq_index) plus an end-to-endseq_specs_concat_runtest exercising the rewrites through a realruntime function.
Phases that follow (separate PRs)
snapshot!{}/proof_assert!{}macros#[predicate]propagationmap.rs/map_ext.rsTest plan
cargo buildcargo fmt --all -- --checkcargo clippy -- -D warningscargo test --test ui(272 / 272 passing, including 20 newpaired
seq_*tests:seq_basic,seq_specs_vec_build,seq_concat,seq_subsequence,seq_concat_const,seq_subseq_const,seq_concat_index,seq_concat_index_right,seq_subseq_index,seq_specs_concat_run)Generated with Claude Code.
https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX