Support arithmetic in quantifier predicates via pure expression inlining#4578
Support arithmetic in quantifier predicates via pure expression inlining#4578feliperodri wants to merge 1 commit intomodel-checking:mainfrom
Conversation
c15450d to
89a4c44
Compare
|
The three quantifier tests that use pointer arithmetic in predicates ( I'll address this in a follow-up PR ( |
89a4c44 to
0f4443f
Compare
483542a to
f353315
Compare
…ped variables Add two-path quantifier codegen that handles both simple and complex predicate expressions, plus typed quantifier variable syntax. Quantifier codegen (hooks.rs): - build_quantifier_predicate extracts the closure body and substitutes parameters directly, returning Option<Expr>. Returns None when the result contains side effects (StatementExpression from checked arithmetic or pointer ops) since substitute_symbol cannot recurse into StatementExpression nodes. - When None, falls back to find_closure_call_expr (closure function call), which the handle_quantifiers post-pass inlines after all functions are codegen'd. - extract_return_expr refactored with ReturnExpr enum (Symbol/Direct). - Graceful fallback with compiler diagnostic instead of ICE when parameter count is unexpected. Pure expression inlining infrastructure (goto_ctx.rs): - inline_as_pure_expr handles StatementExpression flattening, struct field extraction, and overflow simplification (OverflowResultPlus to Plus, etc.). Currently dead_code — used by follow-up PR for pointer arithmetic support. Typed quantifier variables (kani_core/src/lib.rs): - Added |d: u64 in (lo, hi)| syntax to forall!/exists! macros using $t:tt capture since $t:ty cannot be followed by 'in'. Tests: - arithmetic.rs: 5 harnesses for +, -, *, % in quantifier bodies - typed_variables.rs: 2 harnesses for typed variable syntax - gcd_invariant.rs: GCD loop invariant with typed u64 and modulo - array.rs, contracts.rs, from_raw_parts.rs: un-fixme'd, now pass via the closure call fallback path RFC updated: rfc/src/rfcs/0010-quantifiers.md Detailed Design section.
f353315 to
3e2b3d6
Compare
|
The Kani Extra failure is unrelated to the changes in this PR. It should not impact the review of this PR. Here is a deep-dive into the CI failure in more details: The main concern: Secondary regressions worth noting
Improvements (
|
| Benchmark | Metric | Old | New | Change |
|---|---|---|---|---|
btreeset/insert_multi |
solver_runtime | 5.07s | 2.56s | -49% |
s2n-quic/.../vectored_copy_fuzz_test |
solver_runtime | 257s | 165s | -36% |
s2n-quic/.../cmsg::iter_test |
solver_runtime | 12.9s | 11.3s | -13% |
s2n-quic/.../cmsg::collect_test |
solver_runtime | 83.4s | 77.2s | -7% |
misc/display_trait/slow |
verification_time | 110s | 104s | -6% |
Summary
The pattern is almost entirely in solver_runtime: symex times and program structure (steps/VCCs) are essentially unchanged.
Enables arithmetic operations (
+,-,*,%) insideforall!andexists!predicates, and adds typed quantifier variables. Previously, any arithmetic in a quantifier body was rejected by CBMC as a "side effect."Resolves #4135 — This branch adds typed quantifier variables (
|d: u64 in (lo, hi)|), removing theusizerestriction.Problem
CBMC requires quantifier bodies to be side-effect-free. But even
i + 1in Rust compiles toOverflowResultPluswrapped in aStatementExpression, which CBMC rejects. This meant quantifiers could only contain comparisons, not meaningful arithmetic predicates.Solution
Pure expression inlining infrastructure (
goto_ctx.rs)The
inline_as_pure_exprfunction handles the checked arithmetic pattern end-to-end:StatementExpressionflattening: CollectsDeclassignments and resolves intermediate variables, eliminating the statement wrapper.Member(Struct([f0, f1, ...]), "N")→fN, avoiding invalid.member()calls on intermediate structs.Member(OverflowResultPlus(a, b), "result")→Plus(a, b). Same forMinusandMult. The overflow check is dropped (sound for symbolic evaluation in quantifier bodies).Quantifier codegen (
hooks.rs)build_quantifier_predicateextracts the closure body and substitutes parameters directly, producing a side-effect-free expression for simple predicates (comparisons, logical operators).Option<Expr>— when the substituted expression still contains side effects (e.g.,StatementExpressionfrom checked arithmetic or pointer operations likewrapping_byte_offset), it returnsNone. This is becausesubstitute_symboldoes not recurse intoStatementExpressionnodes.Noneis returned, the caller falls back tofind_closure_call_expr, which creates a closure function call. Thehandle_quantifierspost-pass (incompiler_interface.rs) then inlines this call after all functions are codegen'd — the same approach used onmain.extract_return_exprrefactored withReturnExprenum (Symbol/Direct) for clarity.gcx.tcx.dcx().warn()instead ofassert!ICE when parameter count is unexpected.Typed quantifier variables (
kani_core/src/lib.rs)Added
|d: u64 in (lo, hi)|syntax toforall!andexists!:Uses
$t:ttcapture since$t:tycannot be followed byinin macro rules.RFC update
Updated
rfc/src/rfcs/0010-quantifiers.mdDetailed Design section with the implementation approach, soundness notes, and typed variable syntax.Example
This GCD loop invariant (with typed
u64variable,saturating_add, modulo, equality, and logical operators) verifies successfully with Z3 in ~0.5s.Testing
arithmetic.rs— 5 harnesses testing+,-,*,%in quantifier bodiestyped_variables.rs— 2 harnesses testing|i: u64 in ...|syntaxgcd_invariant.rs— GCD loop invariant with typed variable and modulocontracts_fail,multiple_quantifiers,for_loop_for_zip,for_loop_for_tuple_with_quantifiers) continue to work via the fallback path.Design: Two-path quantifier codegen
build_quantifier_predicate|i in (0,n)| a[i] > 0handle_quantifierspost-pass|k in (0,8)| *ptr.wrapping_byte_offset(k)The
is_side_effect()check on the substituted expression determines which path is taken. This ensures:main.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.