Skip to content

Add support for void loop invariant functions with ensures#537

Merged
andreistefanescu merged 17 commits intomainfrom
worktree-more-loop-inv
Feb 26, 2026
Merged

Add support for void loop invariant functions with ensures#537
andreistefanescu merged 17 commits intomainfrom
worktree-more-loop-inv

Conversation

@andreistefanescu
Copy link
Copy Markdown
Contributor

Loop invariant functions can now be void and express invariants via ensures() calls instead of returning a bool. The void function's bytecodes are inlined into the target function so that ensures/requires substitution in the loop analysis transform works unchanged.

andreistefanescu and others added 13 commits February 25, 2026 05:28
Loop invariant functions can now be void and express invariants via
ensures() calls instead of returning a bool. The void function's
bytecodes are inlined into the target function so that ensures/requires
substitution in the loop analysis transform works unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mark spec functions (requires, ensures, asserts) as non-aborting in the
no-abort analysis, and substitute ensures/asserts to requires in
translate_function_no_abort so they become assumptions during abort
checking. Also revert the is_loop_invariant_function guard in
check_abort_check_scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove pre-extracted void_inv_data map and instead look up invariant
function data directly from targets inside inline_void_invariant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only generate a jump label and emit it when there are multiple Ret
bytecodes (early returns). The last Ret is always dropped rather than
converted to a jump.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip offsets never contain Ret bytecodes and the last Ret is always
the final instruction, so just count Rets directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Find the last Ret by position rather than assuming it is the last
instruction, so the logic is correct even if void functions omit a
trailing Ret.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check for Ret instructions before the last instruction using a slice
instead of tracking indices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Put targets as the 3rd argument after func_env and data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for void loop invariant functions that express invariants through ensures() calls instead of returning a boolean value. The implementation inlines the bytecodes of void invariant functions into the target function, allowing the existing ensures/requires substitution logic in the loop analysis transform to work unchanged.

Changes:

  • Loop invariant functions can now return void (0 return values) in addition to bool
  • Void invariant functions are inlined into target functions rather than called
  • Boogie backend substitutes ensures/asserts with requires for no_abort function translations

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/move-stackless-bytecode/src/move_loop_invariants.rs Core implementation: handles void invariant validation, inlining logic with bytecode remapping, temp allocation, and label substitution
crates/move-stackless-bytecode/src/stackless_bytecode.rs Adds replace_attr_id method to support attribute replacement during bytecode inlining
crates/move-prover-boogie-backend/src/boogie_backend/bytecode_translator.rs Substitutes ensures/asserts operations with requires in no_abort translations to support inlined void invariants
crates/sui-prover/tests/inputs/loop_invariant/external_void.ok.move Test cases for valid void invariant functions with various scenarios (simple, multiple ensures, old values, loop constructs)
crates/sui-prover/tests/inputs/loop_invariant/external_void_wrong.fail.move Test case for incorrect void invariant that should fail verification
crates/sui-prover/tests/snapshots/loop_invariant/external_void.ok.move.snap Snapshot showing successful verification for void invariants
crates/sui-prover/tests/snapshots/loop_invariant/external_void_wrong.fail.move.snap Snapshot showing expected verification errors for incorrect void invariants
crates/sui-prover/tests/snapshots/loop_invariant/external_returns.fail.move.snap Updated error message to reflect "at most one" instead of "exactly one" return value

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

andreistefanescu and others added 3 commits February 26, 2026 01:00
When the target function has a &mut T arg and the invariant function
expects &T (eliminated to T by EliminateImmRefs), emit ReadRef inside
the invariant's attr range so loop analysis properly duplicates it in
the assert-havoc-assume pattern. Adds test4 with &mut u128.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EliminateImmRefs already removes ReadRef from the invariant's bytecodes,
so there are no ReadRef instructions on dereferenced args to skip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Return empty vec instead of panicking when all bytecodes are param-copy
Assigns (skipped) and a trailing Ret (skipped).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@msaaltink
Copy link
Copy Markdown
Collaborator

What happens if loop invariant returns a bool but also has some ensures clauses? whether that's an error or allowed, a test case would be useful.

@andreistefanescu
Copy link
Copy Markdown
Contributor Author

@msaaltink good catch, it shouldn't be allowed, but currently there's no check

Bool invariants must not transitively call requires, ensures, or asserts.
Void invariants must not transitively call requires or asserts (only
ensures is allowed). Adds failing tests for both cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@andreistefanescu
Copy link
Copy Markdown
Contributor Author

@msaaltink fixed, I think you may like this.

@andreistefanescu andreistefanescu merged commit 884fceb into main Feb 26, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants