Skip to content

Add pure expression inliner infrastructure for quantifier bodies#4567

Open
feliperodri wants to merge 6 commits intomodel-checking:mainfrom
feliperodri:inline-pure-expressions
Open

Add pure expression inliner infrastructure for quantifier bodies#4567
feliperodri wants to merge 6 commits intomodel-checking:mainfrom
feliperodri:inline-pure-expressions

Conversation

@feliperodri
Copy link
Copy Markdown
Contributor

What

Adds Expr::substitute_symbol() and inline_as_pure_expr() — infrastructure for inlining function calls as side-effect-free expression trees. This is a prerequisite for generating CBMC quantifier bodies without StatementExpression nodes, which will enable nested quantifier support.

Why

CBMC rejects side effects inside quantifier expressions (forall/exists). Today, Kani's handle_quantifiers post-pass inlines function calls into StatementExpression nodes, which CBMC accepts via convert_statement_expression, but this breaks for nested quantifiers (inner quantifiers are treated as side effects, causing a CBMC invariant violation crash).

The pure expression inliner resolves this by producing expression trees that contain no statements, no gotos, and no labels; only pure expressions that CBMC can handle directly, including in nested contexts.

How

Expr::substitute_symbol() (cprover_bindings/src/goto_program/expr.rs)

  • Recursively replaces all occurrences of a symbol with a replacement expression across all 25+ ExprValue variants.

inline_as_pure_expr() (kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs)

  • For a function call: looks up the body in the symbol table, collects all assignments, resolves the return expression through the assignment chain, substitutes parameters with arguments, and flattens StatementExpression nodes (e.g., checked arithmetic).
  • Recursively inlines nested function calls.
  • Handles Forall/Exists sub-expressions for future nested quantifier support.

Soundness note

When flattening StatementExpression nodes from checked arithmetic (e.g., %, +), the inliner drops Assert/Assume statements that check for overflow and division by zero. This is a known trade-off documented in docs/dev/pure-expression-inliner.md: CBMC requires pure expressions in quantifier bodies, and runtime checks are inherently side effects. A future improvement could hoist these checks outside the quantifier as preconditions.

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

@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 Mar 30, 2026
@feliperodri feliperodri marked this pull request as ready for review March 30, 2026 04:10
@feliperodri feliperodri requested a review from a team as a code owner March 30, 2026 04:10
Add Expr::substitute_symbol() and inline_as_pure_expr() for inlining
function calls as side-effect-free expression trees. This infrastructure
is needed to generate CBMC quantifier bodies without StatementExpression
nodes, which will enable nested quantifier support.

Changes:
- cprover_bindings: Expr::substitute_symbol() — recursive symbol
  replacement across all ExprValue variants, with 6 unit tests
- goto_ctx.rs: inline_as_pure_expr() — inlines function calls by
  extracting return expressions, resolving intermediate variables,
  and substituting parameters. Handles StatementExpression flattening
  for checked arithmetic (drops Assert/Assume runtime checks).
- docs/dev/pure-expression-inliner.md — developer documentation
  including soundness implications

Soundness note: The pure inliner drops overflow and division-by-zero
checks when flattening StatementExpression nodes from checked arithmetic.
This is a known trade-off — CBMC requires pure expressions in quantifier
bodies, and runtime checks are side effects.

The existing handle_quantifiers post-pass is NOT modified — this is
purely additive infrastructure.

Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
- substitute_symbol returns (Expr, bool) for reliable change detection
  instead of Debug-format string comparison
- Graceful fallback for recursive functions (tracing::warn + return
  original) instead of assert! panic
- Diagnostics for edge cases: multiple assignments to same variable,
  non-symbol return expressions, unknown UnaryOperator variants
- Public API cleanup: inline_as_pure_expr_toplevel wrapper hides the
  visited set implementation detail
- Document StatementExpression non-recursion in substitute_symbol

Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
@feliperodri feliperodri force-pushed the inline-pure-expressions branch from 0fcc5c3 to 8b2600a Compare March 30, 2026 04:33
Comment on lines +10 to +20
## Soundness Implications

**Checked arithmetic in quantifier bodies**: When flattening `StatementExpression`
nodes (e.g., from checked division or remainder), the pure inliner drops the
`Assert` and `Assume` statements that check for overflow and division by zero.

- **Division by zero** inside a quantifier body will NOT be detected.
- **Arithmetic overflow** inside a quantifier body will NOT be detected.

**Future improvement**: The dropped assertions could be hoisted outside the
quantifier as preconditions, preserving soundness while keeping the body pure.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tautschnig this is similar to CBMC behavior, correct? Let me know if this raises any concerns.

- Replace destructuring assignment with .0 for substitute_symbol calls
- Restore step-by-step 'How It Works' section in dev documentation

Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
@feliperodri feliperodri added [C] Internal Tracks some internal work. I.e.: Users should not be affected. Z-Contracts Issue related to code contracts labels Mar 30, 2026
@feliperodri feliperodri added this to the Contracts milestone Mar 30, 2026
@thanhnguyen-aws
Copy link
Copy Markdown
Contributor

Could you please add some document to explain why this transformation preserves the semantic of Rust (for the return values of functions), especially for the case the code contains aliasing, memory reference ...?

Explain why the substitution-based transformation preserves Rust
semantics for function return values, covering:
- Straight-line code (SSA-form algebraic substitution)
- Aliasing and memory references (symbol substitution preserves
  dereferences; CBMC evaluates them with full alias analysis)
- When the transformation is NOT safe (multi-assignment locals,
  side effects between assignments, mutable aliasing)
- Scope of applicability (quantifier closures are pure single-
  expression functions where substitution equals beta-reduction)

Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
@feliperodri
Copy link
Copy Markdown
Contributor Author

@thanhnguyen-aws I added a section in the docs/dev/pure-expression-inliner.md doc to document why this transformation preserves the semantic of Rust. Could you take another look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[C] Internal Tracks some internal work. I.e.: Users should not be affected. Z-CompilerBenchCI Tag a PR to run benchmark CI Z-Contracts Issue related to code contracts Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants