Skip to content

Autoharness: per-parameter and trait-impl-derived generic instantiation - #4706

Merged
feliperodri merged 1 commit into
model-checking:mainfrom
tautschnig:autoharness-generics-c5
Aug 25, 2026
Merged

Autoharness: per-parameter and trait-impl-derived generic instantiation#4706
feliperodri merged 1 commit into
model-checking:mainfrom
tautschnig:autoharness-generics-c5

Conversation

@tautschnig

@tautschnig tautschnig commented Jul 31, 2026

Copy link
Copy Markdown
Member

Description

The top-100 crates.io evaluation (#3832) showed ~8,700 functions skipped as "Generic Function: no candidate type satisfies the function's trait bounds". This PR extends the instantiation search in three ways:

  1. Wider primitive candidates: add u8, i64, u64, f64, f32. The float candidates alone unlock the numerous Float/FloatCore-bounded functions in num-traits and its dependents. The existing candidates keep their positions (i32 first), so instantiations that already worked are unchanged.
  2. Per-parameter search: after the cheap uniform pass (same candidate for every parameter), search per-parameter combinations, so functions whose parameters need different types (e.g. fn cast<T: Float, U: PrimInt>) are instantiated. Capped at 256 trait-solver queries per function.
  3. Trait-impl-derived candidates: for each type parameter, derive additional candidates from the concrete implementations of the traits it is bound by (capped at 16 per parameter). A parameter bound by a crate-local trait can thus be instantiated with a crate-local type implementing it.

Candidate collection walks the parent chain of the predicate list, so bounds on an impl's type parameters count too: for impl<W: Frobnicate> Container<W> { fn mix<T: FloatLike>(..) }, W: Frobnicate is a predicate of the parent rather than of mix, and without walking the chain W would be left with primitive candidates only and the method skipped. (args_satisfy_predicates already sees parent predicates, via GenericPredicates::instantiate, so this was a missed opportunity rather than a source of bogus instantiations.)

Measured impact (with --bounded-arguments, -j 16): num-traits 75 generic skips → 0 (2,107 → 2,154 harnesses; every public function now harnessed); serde_json 594 → 276; syn 167 → 41.

Note that this only widens the search; each candidate is still accepted only if the trait solver confirms it satisfies every predicate, and verifying one monomorphic instantiation remains an underapproximation, as documented.

Testing

cargo_autoharness_generics gains a case for each capability:

  • a float-only trait bound (halve::<f64>),
  • a crate-local trait satisfied only by a struct (frob_it::<Widget>),
  • parameters needing different types (mixed::<f64, Widget>),
  • an impl method whose impl-level parameter needs an impl-derived candidate, i.e. the parent-predicate case (Container::<Widget>::mix::<f64>).

needs_exotic (a trait with no implementations at all) remains skipped, with the candidate list in the message updated.

All 21 script-based autoharness tests pass, as do cargo test -p kani-compiler/-p kani-driver, clippy --all-targets and kani-fmt --check.

Rebased onto main now that #4679 has landed, so this is a single commit.

Towards #3832.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@tautschnig
tautschnig requested a review from a team as a code owner July 31, 2026 21:49
Copilot AI review requested due to automatic review settings July 31, 2026 21:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added Z-EndToEndBenchCI Tag a PR to run benchmark CI Z-CompilerBenchCI Tag a PR to run benchmark CI labels Jul 31, 2026
@feliperodri feliperodri added the Z-Autoharness Issue related to autoharness subcommand label Aug 1, 2026
@feliperodri feliperodri added this to the Autoharness milestone Aug 18, 2026
Extend the generic-instantiation search in three ways, found by evaluating
autoharness on the top-100 crates.io crates, where ~8,700 functions were
skipped because no candidate type satisfied their trait bounds:

1. Widen the primitive candidate list with u8, i64, u64, f64 and f32; float
   candidates alone unlock the numerous Float/FloatCore-bounded functions in
   num-traits and its dependents.
2. Search per-parameter candidate combinations (after the cheap uniform
   pass), so functions whose parameters need different types, e.g.
   fn cast<T: Float, U: PrimInt>, are instantiated. The search is capped at
   256 trait-solver queries per function.
3. Derive additional per-parameter candidates from the concrete
   implementations of the traits each parameter is bound by (capped at 16
   per parameter), so parameters bound by crate-local traits can be
   instantiated with the crate's own types implementing them.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@feliperodri
feliperodri force-pushed the autoharness-generics-c5 branch from 7e81251 to 836682a Compare August 25, 2026 02:40
@feliperodri
feliperodri requested a review from a team as a code owner August 25, 2026 02:40
@feliperodri
feliperodri enabled auto-merge August 25, 2026 02:56
@feliperodri
feliperodri added this pull request to the merge queue Aug 25, 2026
Merged via the queue into model-checking:main with commit 2457093 Aug 25, 2026
34 checks passed
feliperodri pushed a commit to tautschnig/kani that referenced this pull request Aug 25, 2026
…hecking#4716)

### Description

A layout niche (`rustc_layout_scalar_valid_range`, as used by std's
`NonZero` and `core::time::Duration`'s `Nanoseconds` field) is a
language-level validity invariant: a value outside the niche is as
invalid as a `bool` holding 3, and rustc packs enum variants into the
invalid bit patterns. Autoharness's compiler-derived generation for
types without an `Arbitrary` implementation previously produced such
values — unsound in the garbage-in sense, and a source of false alarms
in every harness generating the type (found in the top-100/500 crates.io
evaluations for model-checking#3832: e.g. `std::time::Duration` receivers via time's
`InstantExt::signed_duration_since`).

After each generated value of a scalar-ABI type with a restricted valid
range, emit `kani::assume(<raw bits> in valid_range)` (transmute to the
width-matched uint; wrapping ranges like `NonZero`'s `1..=0` handled).
The assumption is sound by construction — assuming a *necessary*
condition of language-level validity keeps every valid value in the
explored set — so no flag or report marker is needed.

Scope: both autoharness passes are gated on `ReachabilityType::AllFns`,
so plain `kani`/`cargo kani` verification is unaffected.

`scalar_niche` bails out if the layout size does not match the scalar
width, rather than emitting an ill-sized `Transmute`. rustc is not known
to report a `Scalar` ABI with padding (verified: a `#[repr(align(4))]`
wrapper around a ranged newtype gets `BackendRepr::Memory`, and its
inner field is constrained anyway), but the asymmetry matters: losing a
constraint can only cause a *visible* false alarm, whereas comparing the
wrong bits could assume `false` and silently make the harness vacuous.

The `Assume` hook now lives in the shared `AnyModels` struct alongside
the other model `FnDef`s, so both passes pick it up without separate
fields.

### Testing

New `autoharness_niche` test covering:
- a `rustc_layout_scalar_valid_range` ranged newtype reached through a
struct field — the function asserting the range now verifies (previously
a false alarm);
- cover checks pinning that both range extremes remain reachable (no
over-constraining);
- `std::time::Duration`, the motivating real-world case from the
evaluation;
- `NonZeroU8`, exercising the *wrapping* range where the check must be a
disjunction;
- a niche on a *signed* scalar, exercising that the comparison is on raw
bit patterns (unsigned);
- a niche reached through *generic instantiation*
(`check_monthly::<Month>`), where the candidate type is derived from the
trait's only implementor by model-checking#4706's instantiation search rather than
named in the signature — the two features compose, and this pins that.

Counterfactual verified: the test fails against a build without the
compiler change, and passes with it.

Also verified by hand, beyond the checked-in test: a niche type behind
`#[repr(align(4))]`, a niche nested in an enum variant, and a niche type
with a `Drop` impl (the assume reads the value with `Operand::Copy`,
which must not introduce a double drop). Measured the cost of the
redundant assume on primitives that carry niches but whose `Arbitrary`
impls already respect them (`bool`, `char`): +3% program-expression
steps with identical VCC counts, all simplified away — not worth gating
the assume on ADTs.

Rebased onto main after model-checking#4706 landed and re-ran the autoharness suite on
the combined tree. Full `script-based-pre` suite (67 tests, including
all 22 autoharness tests), `cargo test -p kani-compiler`, `clippy
--all-targets`, and `kani-fmt --check` pass.

Towards model-checking#3832.

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Z-Autoharness Issue related to autoharness subcommand Z-CompilerBenchCI Tag a PR to run benchmark CI Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants