You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Date: 2026-09-11 Subject: Can the #[verifier::external_body] accessor TCB in cpu::set be eliminated by specifying SmallVec directly via assume_specification (per the coding guidelines)? Bottom line: Partially. SmallVeccan be given assume_specifications (the earlier "blocked by const-generics" claim was wrong), and a centralized spec module for the unobstructed operations compiles. But full elimination of cpu::set's external_body accessors is not achievable in the current Verus: two SmallVec methods that cpu::set needs (resize, and the IndexMut write path) hit concrete Verus obstacles that the guidelines themselves say justify retaining a wrapper.
1. Background: what's currently in place
ostd/src/cpu/set.rs is proven at "A1": the bit-masking logic of add/remove/contains/From::from is genuinely verified over a Seq<u64> model (bits_seq), bridged to the real SmallVec<[u64; 2]> field by five small #[verifier::external_body] accessor methods:
Full cargo dv verify --targets ostd → 1517 verified, 0 errors (no regression)
The external_body accessors are the TCB. The question was whether the guideline-preferred assume_specification on SmallVec's own methods could replace them (centralizing the TCB in vstd_extra, "direct form" over wrappers).
2. Correction: SmallVec is not blocked by const-generics
My earlier claim — "SmallVec is foreign + const-generic, so no external_type_specification" — was wrong.
array_ptr.rs:384's TODO is about const params (ArrayPtr<V, N> where N is a const).
SmallVec<A>'s A is a type parameter (A: smallvec::Array), not a const.
The real obstacle (surmountable) is that the foreign smallvec::Array trait (with associated type Item) must be declared to Verus first. Verus says so itself:
error: trait `smallvec::Array` not declared to Verus
(hint: use #[verifier::external_trait_specification] to declare the trait)
Built at verified_libs/vstd_extra/src/external/smallvec.rs (added smallvec = "1.13.2" to vstd_extra/Cargo.toml; registered pub mod smallvec; + pub use smallvec::*; in external/mod.rs).
cargo dv verify --targets vstd_extra → 567 verified, 0 errors.
Contents:
// Declare the foreign Array trait (assoc type Item).#[verifier::external_trait_specification]pubtraitExArray{typeExternalTraitSpecificationFor: smallvec::Array;typeItem;}// SmallVec<A> as an opaque external type (generic <A> — concrete SmallVec<[u64;2]>// is REJECTED by "expected generics to match"; no-bound <A> fails rustc E0277).#[verifier::external_type_specification]#[verifier::external_body]#[verifier::reject_recursive_types(A)]pubstructExSmallVec<A:Array>(SmallVec<A>);// View as Seq<A::Item>. Cannot `impl vstd::View for SmallVec` — View is private// (E0603) AND orphan rule (both foreign). Use a custom local trait, like btree's// CursorMutAdditionalSpecFns.pubtraitSmallVecSpecFns<A:Array>{ spec fnview(&self) -> Seq<A::Item>;}impl<A:Array>SmallVecSpecFns<A>forSmallVec<A>{ uninterp spec fnview(&self) -> Seq<A::Item>;}// Direct assume_specifications (generics go BEFORE [path]; old(s)/final(s) for &mut).pub assume_specification<A:Array>[SmallVec::<A>::new ]() -> (ret:SmallVec<A>)
ensures ret.view() == Seq::empty();pub assume_specification<A:Array>[SmallVec::<A>::with_capacity ](capacity:usize) -> (ret:SmallVec<A>)
ensures ret.view() == Seq::empty();pub assume_specification<A:Array>[SmallVec::<A>::len ](s:&SmallVec<A>) -> (ret: usize)
ensures ret == s.view().len();
This validates the full "direct form" the guideline prefers for new / with_capacity / len.
4. Concrete obstacles preventing full elimination
Per the guideline "test the direct form, record any concrete obstacle, then add a wrapper", the following are recorded obstacles that justify retaining wrappers:
4.1 SmallVec::resize — where A::Item: Clone (TESTED, confirmed blocker)
(resize clones value into each new slot, so it requires A::Item: Clone at compile time.)
Without the bound in the assume_spec: rustc E0599: trait bound '<A as Array>::Item: Clone' not satisfied.
With where A::Item: Clone, appended after ensures: Verus error: expected an expression — assume_specification does not accept a where clause.
A::Item: Clone is a bound on an associated type, which can only appear in a where clause, not in the <A: Array> generic list (you can bound a generic param there, not an assoc type). btree's <A: Allocator + Clone> bounds the paramA, which is different.
⇒ No place to express A::Item: Clone in an assume_specification ⇒ resizecannot be spec'd ⇒ the bits_resize wrapper must stay.
4.2 Index::index / IndexMut::index_mut — reference returns (INFERRED from btree precedent; not directly tested)
cpu::set's add/remove write via self.bits[i] = v, i.e. IndexMut, which hands back a &mut A::Item that the caller writes through after the call. Faithfully specifying "writing through the returned &mut changes self.view()[i]" requires the prophetic / permission machinery (the repo's btree model uses final_map / final(value) for exactly this — BTreeMap::get_mut returning &'a mut Value). It is not expressible as a one-line ensures *ret == ....
The read path Index::index (returns &A::Item) may be more tractable (btree returns borrowed values elsewhere) — not tested.
The write path IndexMut::index_mut (returns &mut A::Item) is the genuinely hard one — inferred obstacle, not directly tested.
⇒ The bits_get (read) and especially bits_set (write) wrappers stay unless one invests in prophetic specs (advanced, btree-get_mut-scale).
4.3 AtomicU64 atomic ops (separate concern)
AtomicCpuSet uses SmallVec<[AtomicU64; 2]> and fetch_or/fetch_and/load/store. Faithful atomic-memory modeling is a separate, harder problem (concurrent state), independent of the SmallVec-spec question. AtomicCpuSet stays external_body regardless.
4.4 Consumption requires moving CpuSet into verus!
Even to consume the unobstructedassume_specs (len/new/with_capacity) directly in cpu::set, bits_seq(s) would need to be s.bits.view(). But s.bits (a field of CpuSet) is only spec-accessible if CpuSet is a Verus struct inside verus!; an external_type_specification'd CpuSet is opaque (fields inaccessible). Moving CpuSet into verus! brings #[derive(Clone, Debug, Default)]-handling risk for ~1 eliminated accessor (bits_len) while §4.1/§4.2 wrappers must stay anyway — poor cost/benefit.
5. Summary: spec'd vs. wrapper-kept
SmallVec op used by cpu::set
Obstacle
Outcome
new
none
assume_spec ✅ (in vstd_extra module)
with_capacity
none
assume_spec ✅
len
none
assume_spec ✅
resize
where A::Item: Clone (TESTED)
external_body wrapper (bits_resize) stays
index (Index, read)
reference return (likely tractable; not tested)
external_body wrapper (bits_get) stays
index_mut (IndexMut, write)
&mut return + write effect (prophetic; inferred)
external_body wrapper (bits_set) stays
fill
via Deref to slice (no inherent)
avoid (rewrite) or wrapper
AtomicU64::fetch_or/...
atomic memory model
AtomicCpuSet stays external_body
Net: the two SmallVec entry points cpu::set actually uses to mutate (resize, index_mut) are exactly the ones blocked from assume_spec — so full elimination of external_body accessors is not achievable in the current Verus.
6. Files / verification state
Current (uncommitted) changes:
ostd/src/lib.rs — re-enabled pub mod cpu; (scoped cpu-module port).
ostd/src/cpu/mod.rs — scoped port (CpuId/num_cpus/all_cpus kept; local/arch::cpu/atomic_mode/init deferred as comments).
cargo dv verify --targets ostd → 1517 verified, 0 errors (no regression at the A1 checkpoint; the vstd_extra module is not yet consumed by ostd).
Note: the vstd_extra smallvec module is currently not consumed by cpu::set (consumption is blocked by §4.1/§4.2/§4.4).
7. Recommendation
Keep A1 in ostd/src/cpu/set.rs. Its external_body accessors are guideline-justified: the direct assume_spec form was tested and the obstacles (§4.1 where; §4.2 reference-return) were recorded, so a wrapper is the prescribed fallback. Trust surface is equivalent either way (both are TCB).
vstd_extra smallvec module: it is real and guideline-compliant but currently unconsumed.
Revert it (preferred for a clean repo) — the trust/reuse benefit doesn't materialize while cpu::set can't consume it; drop smallvec from vstd_extra and remove the module. The working pattern (Array-trait declaration + custom SmallVecSpecFns view + assume_spec for <A: Array>) is preserved in memory for when Verus adds where-clause / mutable-borrow-spec support.
Or keep it as dormant infrastructure — costs a vstd_extra smallvec dep + a dead module until consumption is unblocked.
If Index::index (read) is wanted: a quick assume_spec test is cheap and could potentially eliminate the bits_get wrapper (the read path may be tractable, unlike index_mut). Not done in this analysis; can be a follow-up.
Deeper unblockers (future Verus): assume_specificationwhere-clause support (unblocks resize) and a first-class story for &mut-returning method specs (unblocks index_mut / bits_set) would let the centralization complete.
8. Key references
docs/coding-guidelines/proof-engineering.md — "Prefer assume_specification… test the direct form… record any concrete obstacle"; "Centralize trusted boundaries under verified_libs/vstd_extra/src/external/"; "Restrict generic trusted models" (PR prove: id-alloc asterinas/vostd#742 bitvec model is the precedent for a foreign collection).
verified_libs/vstd_extra/src/external/btree.rs — ExCursorMut<'a,K,V,A> (generic external_type_specification with reject_recursive_types), CursorMutAdditionalSpecFns (custom view trait on a foreign type — the pattern copied here for SmallVecSpecFns), assume_specification<…>[ Path::method ], and get_mut's prophetic final_map (the reference-return precedent).
verified_libs/vstd_extra/src/array_ptr.rs:384 — TODO "external_type_specification: Const params not yet supported" (the const-param blocker I originally misapplied to SmallVec).
Memory: vostd-cpu-set-smallvec-tcb-proof.md (A1 proof + full SmallVec findings), vostd-cpu-module-scoped-port.md.
Date: 2026-09-11
Subject: Can the
#[verifier::external_body]accessor TCB incpu::setbe eliminated by specifyingSmallVecdirectly viaassume_specification(per the coding guidelines)?Bottom line: Partially.
SmallVeccan be givenassume_specifications (the earlier "blocked by const-generics" claim was wrong), and a centralized spec module for the unobstructed operations compiles. But full elimination ofcpu::set'sexternal_bodyaccessors is not achievable in the current Verus: two SmallVec methods thatcpu::setneeds (resize, and theIndexMutwrite path) hit concrete Verus obstacles that the guidelines themselves say justify retaining a wrapper.1. Background: what's currently in place
ostd/src/cpu/set.rsis proven at "A1": the bit-masking logic ofadd/remove/contains/From::fromis genuinely verified over aSeq<u64>model (bits_seq), bridged to the realSmallVec<[u64; 2]>field by five small#[verifier::external_body]accessor methods:cargo dv focus --targets ostd -- --verify-only-module cpu::set→ 6 verified, 0 errorscargo dv verify --targets ostd→ 1517 verified, 0 errors (no regression)The
external_bodyaccessors are the TCB. The question was whether the guideline-preferredassume_specificationonSmallVec's own methods could replace them (centralizing the TCB invstd_extra, "direct form" over wrappers).2. Correction:
SmallVecis not blocked by const-genericsMy earlier claim — "SmallVec is foreign + const-generic, so no
external_type_specification" — was wrong.array_ptr.rs:384's TODO is about const params (ArrayPtr<V, N>whereNis a const).SmallVec<A>'sAis a type parameter (A: smallvec::Array), not a const.The real obstacle (surmountable) is that the foreign
smallvec::Arraytrait (with associated typeItem) must be declared to Verus first. Verus says so itself:Declaring it compiles:
3. Stage 1 deliverable: centralized SmallVec spec module (compiles)
Built at
verified_libs/vstd_extra/src/external/smallvec.rs(addedsmallvec = "1.13.2"tovstd_extra/Cargo.toml; registeredpub mod smallvec;+pub use smallvec::*;inexternal/mod.rs).cargo dv verify --targets vstd_extra→ 567 verified, 0 errors.Contents:
This validates the full "direct form" the guideline prefers for
new/with_capacity/len.4. Concrete obstacles preventing full elimination
Per the guideline "test the direct form, record any concrete obstacle, then add a wrapper", the following are recorded obstacles that justify retaining wrappers:
4.1
SmallVec::resize—where A::Item: Clone(TESTED, confirmed blocker)Real signature:
(
resizeclonesvalueinto each new slot, so it requiresA::Item: Cloneat compile time.)assume_spec: rustcE0599: trait bound '<A as Array>::Item: Clone' not satisfied.where A::Item: Clone,appended afterensures: Veruserror: expected an expression—assume_specificationdoes not accept awhereclause.A::Item: Cloneis a bound on an associated type, which can only appear in awhereclause, not in the<A: Array>generic list (you can bound a generic param there, not an assoc type). btree's<A: Allocator + Clone>bounds the paramA, which is different.⇒ No place to express
A::Item: Clonein anassume_specification⇒resizecannot be spec'd ⇒ thebits_resizewrapper must stay.4.2
Index::index/IndexMut::index_mut— reference returns (INFERRED from btree precedent; not directly tested)Index<usize>::index(&self, i) -> &A::Item(read;self.bits[i])IndexMut<usize>::index_mut(&mut self, i) -> &mut A::Item(write;self.bits[i] = v)cpu::set'sadd/removewrite viaself.bits[i] = v, i.e.IndexMut, which hands back a&mut A::Itemthat the caller writes through after the call. Faithfully specifying "writing through the returned&mutchangesself.view()[i]" requires the prophetic / permission machinery (the repo's btree model usesfinal_map/final(value)for exactly this —BTreeMap::get_mutreturning&'a mut Value). It is not expressible as a one-lineensures *ret == ....Index::index(returns&A::Item) may be more tractable (btree returns borrowed values elsewhere) — not tested.IndexMut::index_mut(returns&mut A::Item) is the genuinely hard one — inferred obstacle, not directly tested.⇒ The
bits_get(read) and especiallybits_set(write) wrappers stay unless one invests in prophetic specs (advanced, btree-get_mut-scale).4.3
AtomicU64atomic ops (separate concern)AtomicCpuSetusesSmallVec<[AtomicU64; 2]>andfetch_or/fetch_and/load/store. Faithful atomic-memory modeling is a separate, harder problem (concurrent state), independent of the SmallVec-spec question.AtomicCpuSetstaysexternal_bodyregardless.4.4 Consumption requires moving
CpuSetintoverus!Even to consume the unobstructed
assume_specs (len/new/with_capacity) directly incpu::set,bits_seq(s)would need to bes.bits.view(). Buts.bits(a field ofCpuSet) is only spec-accessible ifCpuSetis a Verus struct insideverus!; anexternal_type_specification'dCpuSetis opaque (fields inaccessible). MovingCpuSetintoverus!brings#[derive(Clone, Debug, Default)]-handling risk for ~1 eliminated accessor (bits_len) while §4.1/§4.2 wrappers must stay anyway — poor cost/benefit.5. Summary: spec'd vs. wrapper-kept
cpu::setnewassume_spec✅ (in vstd_extra module)with_capacityassume_spec✅lenassume_spec✅resizewhere A::Item: Clone(TESTED)external_bodywrapper (bits_resize) staysindex(Index, read)external_bodywrapper (bits_get) staysindex_mut(IndexMut, write)&mutreturn + write effect (prophetic; inferred)external_bodywrapper (bits_set) staysfillDerefto slice (no inherent)AtomicU64::fetch_or/...AtomicCpuSetstaysexternal_bodyNet: the two SmallVec entry points
cpu::setactually uses to mutate (resize,index_mut) are exactly the ones blocked fromassume_spec— so full elimination ofexternal_bodyaccessors is not achievable in the current Verus.6. Files / verification state
Current (uncommitted) changes:
ostd/src/lib.rs— re-enabledpub mod cpu;(scoped cpu-module port).ostd/src/cpu/mod.rs— scoped port (CpuId/num_cpus/all_cpus kept; local/arch::cpu/atomic_mode/init deferred as comments).ostd/src/cpu/set.rs— A1 (verifies; 6 verified, 0 errors).verified_libs/vstd_extra/Cargo.toml— addedsmallvec = "1.13.2".verified_libs/vstd_extra/src/external/smallvec.rs— new centralized module (§3).verified_libs/vstd_extra/src/external/mod.rs— registeredsmallvecmodule.Verification:
cargo dv focus --targets ostd -- --verify-only-module cpu::set→ 6 verified, 0 errors (A1, still green after vstd_extra changes).cargo dv verify --targets vstd_extra→ 567 verified, 0 errors (smallvec module compiles).cargo dv verify --targets ostd→ 1517 verified, 0 errors (no regression at the A1 checkpoint; the vstd_extra module is not yet consumed by ostd).Note: the vstd_extra smallvec module is currently not consumed by
cpu::set(consumption is blocked by §4.1/§4.2/§4.4).7. Recommendation
ostd/src/cpu/set.rs. Itsexternal_bodyaccessors are guideline-justified: the directassume_specform was tested and the obstacles (§4.1where; §4.2 reference-return) were recorded, so a wrapper is the prescribed fallback. Trust surface is equivalent either way (both are TCB).cpu::setcan't consume it; dropsmallvecfrom vstd_extra and remove the module. The working pattern (Array-trait declaration + customSmallVecSpecFnsview +assume_specfor<A: Array>) is preserved in memory for when Verus addswhere-clause / mutable-borrow-spec support.smallvecdep + a dead module until consumption is unblocked.Index::index(read) is wanted: a quickassume_spectest is cheap and could potentially eliminate thebits_getwrapper (the read path may be tractable, unlikeindex_mut). Not done in this analysis; can be a follow-up.assume_specificationwhere-clause support (unblocksresize) and a first-class story for&mut-returning method specs (unblocksindex_mut/bits_set) would let the centralization complete.8. Key references
docs/coding-guidelines/proof-engineering.md— "Preferassume_specification… test the direct form… record any concrete obstacle"; "Centralize trusted boundaries underverified_libs/vstd_extra/src/external/"; "Restrict generic trusted models" (PR prove: id-alloc asterinas/vostd#742 bitvec model is the precedent for a foreign collection).verified_libs/vstd_extra/src/external/btree.rs—ExCursorMut<'a,K,V,A>(genericexternal_type_specificationwithreject_recursive_types),CursorMutAdditionalSpecFns(custom view trait on a foreign type — the pattern copied here forSmallVecSpecFns),assume_specification<…>[ Path::method ], andget_mut's propheticfinal_map(the reference-return precedent).verified_libs/vstd_extra/src/array_ptr.rs:384— TODO "external_type_specification: Const params not yet supported" (the const-param blocker I originally misapplied to SmallVec).vostd-cpu-set-smallvec-tcb-proof.md(A1 proof + full SmallVec findings),vostd-cpu-module-scoped-port.md.