Skip to content

Laurel: avoid quantified modifies frames when using array theory#1374

Open
julesmt wants to merge 12 commits into
reviewed-kbd-will-merge-to-mainfrom
julesmt/features/laurel-modifies-array-theory
Open

Laurel: avoid quantified modifies frames when using array theory#1374
julesmt wants to merge 12 commits into
reviewed-kbd-will-merge-to-mainfrom
julesmt/features/laurel-modifies-array-theory

Conversation

@julesmt

@julesmt julesmt commented Jun 16, 2026

Copy link
Copy Markdown
Member

Under --use-array-theory, a modifies clause naming only individual object
references is now encoded as a single quantifier-free heap equation instead of
a -quantified frame, so the solver discharges it by array rewriting instead of
instantiating a quantifier per object/field.

∀ obj, fld. obj ≠ c ⇒ readField(old,obj,fld) == readField(new,obj,fld)   -- before
data(new) == store(data(old), c, select(data(new), c))                   -- now

The frame is unchanged for set-valued/empty modifies and when array theory is
off, so default behavior is untouched.

## Summary

- Periodic merge of `main` into `main2` to keep main2 up to date with
core improvements
- 46 new commits from main since PR #1346 (mostly repo-removal PRs:
#1339, #1351, #1343, #1329, #1334, plus code changes)
- All conflicts resolved by keeping main2's versions (sub-repos stay
local, no module migration, preserves main2-only features like
Procedure.Body sum type, transparent procs, array axiomatization)
- Fixed duplicate StrataDDM git entries that auto-merged into
`docs/api/lake-manifest.json` and `docs/verso/lake-manifest.json`

---------

Co-authored-by: Aaron Tomb <aarotomb@amazon.com>
Co-authored-by: Michael Tautschnig <mt@debian.org>
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Co-authored-by: Juneyoung Lee <136006969+aqjune-aws@users.noreply.github.com>
Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
Co-authored-by: Mikaël Mayer <MikaelMayer@users.noreply.github.com>
Co-authored-by: thanhnguyen-aws <ntson@amazon.com>
Co-authored-by: Fabio Madge <fmadge@amazon.com>
Co-authored-by: Joe Hendrix <joehx@amazon.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: June Lee <lebjuney@amazon.com>
Co-authored-by: David Deng <daviddenghaotian@gmail.com>
Co-authored-by: David Deng <htd@amazon.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Mikael Mayer <mimayere@amazon.com>
Co-authored-by: Remy Willems <rwillems@amazon.com>
Co-authored-by: keyboardDrummer-bot <keyboarddrummer.bot@gmail.com>
Co-authored-by: Sagar Joshi <72283186+sagjoshi@users.noreply.github.com>
@keyboardDrummer

keyboardDrummer commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

I found the term "array-theory" in this PR misleading because the PR does not directly use any SMT array theory. Instead, the PR relies on Core Map's which are not necessarily implemented using SMT array theory. In fact, I think Core is or at least was not using array theory for their maps, simply because it is or was not implemented yet.

@keyboardDrummer keyboardDrummer 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.

Very cool!

Comment thread Strata/Languages/Laurel/HeapParameterization.lean Outdated
Comment thread Strata/Languages/Laurel/ModifiesClauses.lean Outdated
Comment thread Strata/Languages/Laurel/ModifiesClauses.lean
Comment thread Strata/Languages/Laurel/ModifiesClauses.lean Outdated
Jules added 3 commits June 19, 2026 15:51
Each `LaurelPass.run` now receives `LaurelTranslateOptions`, instead of passes
being parameterized at construction. `LaurelTranslateOptions` moves into
`LaurelPass`, the pipeline threads the options into every pass, and the
`comesBefore` lists no longer carry an arbitrary placeholder argument. No
behavioural change: every pass ignores the new argument here.
Under `--use-array-theory` the heap `Map` is an SMT array, so for a `modifies`
clause naming only individual references we can emit an enumerated, quantifier-free
frame (`buildEnumeratedFrame`: overwrite the named rows) instead of the `∀`-based
`buildPointwiseFrame`. The dispatch (`onlyIndividualRefs`) is restricted to bodiless
procedures: a body that allocates cannot prove the whole-array equality, so bodies
keep the pointwise frame, whose `< nextReference` guard tolerates allocation.
Structural #guards that the enumerated frame is quantifier-free while the
pointwise frame is not (ModifiesFrameQuantifierFreeTest), the array-theory path
including allocating bodies (T2c), and a perf/completeness regression for a
chained spec procedure (T2e).
@julesmt julesmt force-pushed the julesmt/features/laurel-modifies-array-theory branch from bf25905 to d5ece2c Compare June 19, 2026 22:59
@keyboardDrummer keyboardDrummer changed the base branch from main2 to reviewed-kbd-will-merge-to-main June 22, 2026 13:40
@keyboardDrummer

Copy link
Copy Markdown
Contributor

@keyboardDrummer-bot please resolve merge conflicts

@keyboardDrummer

keyboardDrummer commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

How come you can't make the enumerated encoding work with procedures that have a body? Suppose I have:

composite C { var x: int }
procedure creator() returns (c: C)
  ensures c#x == 3
  ensures fresh(c)
  modifies {}
  opaque
{
  c := new C;
  c#x := 3;
}

then that would translate to something like:

procedure creator() returns (c: C)
// c#x == 3
  ensures read($heap, c, C.x) == 3 
  
// fresh(c)
  ensures c.ref >= old($heap).nextRef

// modifies {}, enumerated encoding
  ensures $heap.data == old($heap).data 

// implicit postcondition stating that every output object is allocated in the new heap
  ensures c.ref < $heap.nextRef

which has the curious implication that read(old($heap), c, C.x) == 3, so the old heap contains data for an object that did not yet exist at the time of the old heap. However, I think that might not cause any issues. What issues did you run into?

@julesmt

julesmt commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

Yeah, I had pretty much the same example.
data == old data fails because the body writes the fresh row, even though fresh objects shouldn't count for modifies.

The pointwise frame dodges this with the obj < nextReference($heap_in) guard, which I can't keep without a quantifier, so I this is why I wanted to restrict the enumerated frame to bodiless procedures.

However to get it for bodies too, I split the jobs: callers assume the enumerated frame, and the body is checked separately by asserting the pointwise frame at every exit.

what do you think (see next commit) ? @keyboardDrummer

For an individual-ref modifies clause under array theory, callers now assume the quantifier-free enumerated frame as a free ensures, while the body proves the allocation-tolerant pointwise frame via asserts inserted before every exit, so callers never assume the quantifier. This drops the bodiless-only restriction: allocating bodies and writes-via-calls verify, and illegal writes are still rejected.
@keyboardDrummer

Copy link
Copy Markdown
Contributor

However to get it for bodies too, I split the jobs: callers assume the enumerated frame, and the body is checked separately by asserting the pointwise frame at every exit.
what do you think (see next commit) ? @keyboardDrummer

Brilliant!

Comment thread Strata/Languages/Laurel/ModifiesClauses.lean Outdated
Comment thread Strata/Languages/Laurel/LaurelPass.lean Outdated
Comment thread Strata/Languages/Laurel/LaurelPass.lean Outdated
Comment thread Strata/Languages/Laurel/ModifiesClauses.lean Outdated
/-- Build and attach `proc`'s modifies frame, then clear the clause. -/
def transformModifiesClauses (model: SemanticModel)
(proc : Procedure) : Except (Array DiagnosticModel) Procedure :=
(proc : Procedure) (useEnumeratedFrame : Bool := false) : Except (Array DiagnosticModel) Procedure :=

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.

Please remove the default values in the private methods. I think the only default should be in LaurelTranslateOptions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh yeah sorry, I thought I had modified that, will do

@julesmt julesmt requested a review from keyboardDrummer June 23, 2026 17:43
@keyboardDrummer

Copy link
Copy Markdown
Contributor

This will have a few merge conflicts after #1352 merges. I'll try to resolve these before you get to it @julesmt

Comment thread Strata/Languages/Laurel/LaurelPass.lean
@julesmt julesmt changed the title Laurel: quantifier-free modifies frame under --use-array-theory Laurel: avoid quantified modifies frames when using array theory Jun 23, 2026
Merge reviewed-kbd-will-merge-to-main into PR1374-kiro.

Resolution kept the reviewed branch's generic LaurelPass/PassMeta pass
framework, but threads the LaurelTranslateOptions argument as the first
parameter of each pass's run (options-first), per review preference.

Preserved the PR's enumeratedModifiesClauses option and its threading
through modifiesClausesTransformPass. Dropped the PR's now-superseded
translateInvokeOnAxiom (invokeOn-axiom generation is handled by ContractPass
on the reviewed branch).

Also documents that enumeratedModifiesClauses has no effect when the
procedure's modifies clause contains sets.
@julesmt julesmt force-pushed the julesmt/features/laurel-modifies-array-theory branch from cc440f0 to 5caabd8 Compare June 23, 2026 20:42
Comment thread Strata/Languages/Laurel/ModifiesClauses.lean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants