Skip to content
Merged
Show file tree
Hide file tree
Changes from 92 commits
Commits
Show all changes
146 commits
Select commit Hold shift + click to select a range
7e34b04
Improve Laurel printer
keyboardDrummer May 26, 2026
f246686
Introduce transparency pass
keyboardDrummer May 26, 2026
b11e042
Fixes
keyboardDrummer May 26, 2026
9e4ee85
Refactoring
keyboardDrummer May 26, 2026
7408103
Remove pack multiple outputs pass
keyboardDrummer May 26, 2026
4e8fa8b
Add error file
keyboardDrummer May 26, 2026
2d19629
Update tests
keyboardDrummer May 26, 2026
c9fd151
Add sources to lift phase
keyboardDrummer May 26, 2026
19987e8
Update tests and improve errors
keyboardDrummer May 26, 2026
24c3983
Update comment
keyboardDrummer May 26, 2026
3c23531
Trigger CI
keyboardDrummer May 26, 2026
6046381
Trigger CI
keyboardDrummer May 26, 2026
86962e4
Trigger CI
keyboardDrummer May 26, 2026
d192346
Replace panic with safe access
keyboardDrummer May 27, 2026
7a04e66
Move proof skipping logic from LaurelToCore translator to transparenc…
keyboardDrummer May 28, 2026
a45f8f0
Small refactoring
keyboardDrummer May 28, 2026
96b2686
Merge branch 'formatting-and-debugging-improvements' into transparenc…
keyboardDrummer May 28, 2026
d0fdd35
Cleanup
keyboardDrummer May 28, 2026
2e0d7e1
Merge branch 'transparency-pass-only' of github.com:keyboardDrummer/S…
keyboardDrummer May 28, 2026
c62cfb4
Add contract pass
keyboardDrummer May 28, 2026
a95da10
Cleanup
keyboardDrummer May 28, 2026
49255b4
Fixes
keyboardDrummer May 28, 2026
10640f7
Fixes
keyboardDrummer May 28, 2026
8c15c16
Do not run the contract pass for functions yet
keyboardDrummer May 28, 2026
573a36b
Reduce id changes
keyboardDrummer May 28, 2026
74ec111
Fixes
keyboardDrummer May 28, 2026
800f6c4
Test fixes
keyboardDrummer May 28, 2026
e56cb20
Fix test
keyboardDrummer May 28, 2026
a352b47
Fix test
keyboardDrummer May 28, 2026
65c92f4
Fix test
keyboardDrummer May 28, 2026
4a8c667
Remove unused files
keyboardDrummer May 28, 2026
d7d7205
Undo Python test changes
keyboardDrummer May 28, 2026
60c9ca3
Fixes
keyboardDrummer May 28, 2026
a526dad
Add missing bindings for PrimitiveOp
keyboardDrummer May 28, 2026
ace39ca
Improve comment
keyboardDrummer May 28, 2026
2e94fc5
Add missing bindings for PrimitiveOp
keyboardDrummer May 28, 2026
200e828
Improve comment
keyboardDrummer May 28, 2026
8875ffd
Merge branch 'transparency-pass-only' into issue-924-contract-and-pro…
keyboardDrummer May 28, 2026
9400328
Generate separate $pre and $post procedures for each condition
keyboardDrummer-bot May 28, 2026
65ab6ae
Rename SepFormat.semicolon to SepFormat.semicolonNewline
keyboardDrummer-bot May 28, 2026
6c0c39c
Fix contract pass: preserve postconditions in Opaque/Abstract bodies
keyboardDrummer-bot May 28, 2026
d6bff50
Fix contract pass: make postcondition helpers functional
keyboardDrummer-bot May 28, 2026
2cae5e0
Update golden-file expected outputs for changed label counters
keyboardDrummer-bot May 28, 2026
5c0a458
Extract emitCoreDiagnostic helper to reduce repetition
keyboardDrummer-bot May 28, 2026
ff09522
Code review
keyboardDrummer Jun 1, 2026
afaf9a2
Stop dropping source location from callee
keyboardDrummer Jun 1, 2026
9f65463
Code review
keyboardDrummer Jun 1, 2026
d143eeb
Merge remote-tracking branch 'origin/formatting-and-debugging-improve…
keyboardDrummer Jun 1, 2026
35d7d75
Merge commit 'a995388cb~1' into transparency-pass-only
keyboardDrummer Jun 1, 2026
fc62415
Merge commit 'a995388cb' into transparency-pass-only
keyboardDrummer Jun 1, 2026
e1caee4
Trigger CI
keyboardDrummer Jun 1, 2026
3e76abc
Fix nit
keyboardDrummer Jun 1, 2026
6f0d365
Use HashSet for asFunctionNames and add isDestructorName/isTesterName…
keyboardDrummer-bot Jun 1, 2026
80f22a6
Merge branch 'transparency-pass-only' of github.com:keyboardDrummer/S…
keyboardDrummer Jun 1, 2026
1e9710b
Fix warning
keyboardDrummer Jun 1, 2026
944ffbd
Merge branch 'transparency-pass-only' into issue-924-contract-and-pro…
keyboardDrummer Jun 1, 2026
20225bd
Trigger CI
keyboardDrummer Jun 2, 2026
b52f50b
Trigger CI
keyboardDrummer Jun 2, 2026
b6f0f64
Don't generate intermediate files
keyboardDrummer Jun 2, 2026
6ee3c7e
Exclude hidden files from the lake cache hash
keyboardDrummer Jun 2, 2026
4b44fbf
Exclude hidden files from the lake cache hash
keyboardDrummer Jun 2, 2026
1c5a9e1
Add polymorphism comment and recursive transparent procedure test
keyboardDrummer-bot Jun 2, 2026
81b558c
Fixes
keyboardDrummer Jun 2, 2026
ac0fdb1
Update recursive test to use procedures
keyboardDrummer Jun 3, 2026
cf29a09
Delete recursive transparent
keyboardDrummer Jun 3, 2026
780cbee
Merge branch 'main2' into transparency-pass-only
keyboardDrummer Jun 3, 2026
3dc9527
Add a test-case
keyboardDrummer Jun 3, 2026
74d532a
Merge upstream/main2 into transparency-pass-only
keyboardDrummer-bot Jun 4, 2026
e9887a6
Merge branch 'transparency-pass-only' into issue-924-contract-and-pro…
keyboardDrummer Jun 4, 2026
70cb26e
Fixes
keyboardDrummer Jun 4, 2026
17c1d84
Fix test after merge
keyboardDrummer Jun 4, 2026
68cec09
Fix test expectation
keyboardDrummer Jun 4, 2026
dbfcb95
Remove unused module
keyboardDrummer Jun 4, 2026
2f8eb11
Fix bug in Resolution.lean
keyboardDrummer Jun 4, 2026
389e214
Merge branch 'transparency-pass-only' into issue-924-contract-and-pro…
keyboardDrummer Jun 10, 2026
d141467
Merge commit 'a364a70a~1' into issue-924-contract-and-proof-pass
keyboardDrummer Jun 10, 2026
87d7214
Merge commit 'a364a70a' into issue-924-contract-and-proof-pass
keyboardDrummer Jun 10, 2026
61c2e83
Address quality follow-ups from review
keyboardDrummer-bot Jun 11, 2026
9ca5c77
Add test and fix for counter created by contract pass
keyboardDrummer Jun 11, 2026
b4938f0
Update Strata/Languages/Laurel/ContractPass.lean
keyboardDrummer Jun 11, 2026
3c0e745
Merge branch 'issue-924-contract-and-proof-pass' of github.com:keyboa…
keyboardDrummer Jun 11, 2026
9c9ace9
Update Strata/Languages/Laurel/EliminateValuesInReturns.lean
keyboardDrummer Jun 11, 2026
d8d9b96
Merge branch 'issue-924-contract-and-proof-pass' of github.com:keyboa…
keyboardDrummer Jun 11, 2026
bef6885
Cleanup
keyboardDrummer Jun 11, 2026
96982e3
Merge remote-tracking branch 'fork/quality-followups-from-fabiomadge'…
keyboardDrummer Jun 11, 2026
551e699
partial
keyboardDrummer Jun 11, 2026
67f43bf
Simplify termination proof
keyboardDrummer Jun 11, 2026
ba8524a
Merge branch 'main2' into issue-924-contract-and-proof-pass
keyboardDrummer Jun 11, 2026
ac0b6be
Fix test
keyboardDrummer Jun 11, 2026
31e9007
Merge branch 'noHiddenFilesInHash' into issue-924-contract-and-proof-…
keyboardDrummer Jun 11, 2026
7eef859
Code review changes
keyboardDrummer Jun 12, 2026
4b8ef55
Merge remote-tracking branch 'origin/main2' into issue-924-contract-a…
keyboardDrummer Jun 12, 2026
237eabe
Pass tweaks
keyboardDrummer Jun 12, 2026
4ffc62f
Fixes
keyboardDrummer Jun 12, 2026
5178451
More pass refactoring changes
keyboardDrummer Jun 12, 2026
0ba057d
Fixes
keyboardDrummer Jun 12, 2026
1627ce4
Fixes
keyboardDrummer Jun 12, 2026
816a738
Include final pass in the all pass list
keyboardDrummer Jun 13, 2026
8470d93
Fixes
keyboardDrummer Jun 13, 2026
aebe44c
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 13, 2026
b7508d7
Fix test
keyboardDrummer Jun 13, 2026
f1507d1
Update Laurel docs
keyboardDrummer Jun 13, 2026
1077979
Change text
keyboardDrummer Jun 13, 2026
b30f4cb
Fix invokeOn axiom: guard with preconditions + reject output-referenc…
keyboardDrummer-bot Jun 13, 2026
6dd545a
Fix if-then-else prepend duplication in LiftImperativeExpressions
keyboardDrummer-bot Jun 13, 2026
d1f9d6c
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 16, 2026
0322796
Fix test
keyboardDrummer Jun 16, 2026
878f325
Code review
keyboardDrummer Jun 16, 2026
e0c8b68
Fix early return
keyboardDrummer Jun 16, 2026
a63384a
Fix lifting bug
keyboardDrummer Jun 16, 2026
02ad82c
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 16, 2026
799f167
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 16, 2026
83e793b
Port fixes to lifting pass
keyboardDrummer Jun 18, 2026
6e0ce93
Merge branch 'reviewed-kbd-will-merge-to-main' into issue-924-contrac…
keyboardDrummer Jun 18, 2026
e1fc044
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 18, 2026
dcfb0e9
Some fixes for resolution during phases
keyboardDrummer Jun 18, 2026
a0ec015
Fixes to resolution so it throws fewer errors during passes
keyboardDrummer Jun 18, 2026
25f1d5d
Emit resolution errors during passes
keyboardDrummer Jun 18, 2026
fa7f844
Source fix
keyboardDrummer Jun 18, 2026
441ec1e
Improve well-formedness of the 3 heap passes
keyboardDrummer Jun 18, 2026
cf706b2
Fix for type hierarchy
keyboardDrummer Jun 18, 2026
66589c5
Refactoring
keyboardDrummer Jun 18, 2026
903f89e
Allow dangling addition in blocks
keyboardDrummer Jun 18, 2026
1a0e8ae
Fix diagnostic and test
keyboardDrummer Jun 18, 2026
018939b
Remove function return type expectation check
keyboardDrummer Jun 18, 2026
8d34b32
Update resolver check to use procedure instead of function
keyboardDrummer Jun 18, 2026
d9b39ac
Small improvements
keyboardDrummer Jun 18, 2026
a24cc86
Merge branch 'issue-924-contract-and-proof-pass' of github.com:keyboa…
keyboardDrummer Jun 18, 2026
c438d8c
Merge branch 'fixResolutionErrorsDuringPhases' into issue-924-contrac…
keyboardDrummer Jun 18, 2026
fcb3903
Fix merge
keyboardDrummer Jun 18, 2026
4efaebd
Remove obsolete test
keyboardDrummer Jun 18, 2026
8ce6fc6
Add multi output test-case to ImpureExpressions
keyboardDrummer Jun 19, 2026
157ec6b
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 19, 2026
d7a4c3c
Refactor liftImperative
keyboardDrummer Jun 19, 2026
0b03bff
Simplify lift pass
keyboardDrummer Jun 19, 2026
97e3146
Bring back test
keyboardDrummer Jun 19, 2026
ffcc39d
Recursion proof refactoring
keyboardDrummer Jun 19, 2026
1ef148f
Fix proofs
keyboardDrummer Jun 19, 2026
a279ff7
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 22, 2026
34c850f
Cleanup
keyboardDrummer Jun 22, 2026
12b5e59
Fix
keyboardDrummer Jun 22, 2026
bfc0b6c
Fix test
keyboardDrummer Jun 22, 2026
2e2a7b3
Fix tests
keyboardDrummer Jun 22, 2026
5f46178
Update expect file
keyboardDrummer Jun 22, 2026
16be9b5
Merge remote-tracking branch 'origin/reviewed-kbd-will-merge-to-main'…
keyboardDrummer Jun 23, 2026
97870a3
Undo added test and fix related to that
keyboardDrummer Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Strata/Languages/Laurel/ConstrainedTypeElim.lean
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public def constrainedTypeElim (_model : SemanticModel) (program : Program)
funcDiags)

/-- Pipeline pass: constrained type elimination. -/
public def constrainedTypeElimPass : LaurelPass where
public def constrainedTypeElimPass : LoweringPass where
name := "ConstrainedTypeElim"
documentation := "Eliminates constrained types by replacing them with their base types and generating constraint-checking functions and witness procedures. Type tests against constrained types are rewritten to call the generated constraint function."
needsResolves := true
Expand Down
315 changes: 315 additions & 0 deletions Strata/Languages/Laurel/ContractPass.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
/-
Copyright Strata Contributors

SPDX-License-Identifier: Apache-2.0 OR MIT
-/
module

public import Strata.Languages.Laurel.MapStmtExpr
public import Strata.Languages.Laurel.LaurelPass

/-!
## Contract Pass (Laurel → Laurel)

Removes pre- and postconditions from all procedures and replaces them with
explicit precondition/postcondition helper procedures, assumptions, and
assertions.

For each procedure with contracts:
- Generate a separate precondition procedure (`foo$pre0`, `foo$pre1`, ...) for each precondition.
- Generate a separate postcondition procedure (`foo$post0`, `foo$post1`, ...) for each postcondition.
Each takes all inputs and all outputs as parameters and returns the condition.
- Insert `assume foo$pre0(inputs); assume foo$pre1(inputs); ...` at the start of the body.
- Insert `assert foo$post0(inputs, outputs); assert foo$post1(inputs, outputs); ...` at the end of the body.

For each call to a contracted procedure:
- Assign all input arguments to temporary variables before the call.
- Insert `assert foo$pre0(temps); assert foo$pre1(temps); ...` before the call.
- After the call, insert `assume foo$post0(temps, outputs); assume foo$post1(temps, outputs); ...`.
-/

namespace Strata.Laurel

public section

private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none }
private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none }

/-- Name for the i-th precondition helper procedure. -/
def preCondProcName (procName : String) (i : Nat) : String := s!"{procName}$pre{i}"

/-- Name for the i-th postcondition helper procedure. -/
def postCondProcName (procName : String) (i : Nat) : String := s!"{procName}$post{i}"

/-- Get postconditions from a procedure body. -/
private def getPostconditions (body : Body) : List Condition :=
match body with
| .Opaque postconds _ _ => postconds
| .Abstract postconds => postconds
| _ => []

/-- Build a call expression. -/
private def mkCall (callee : String) (args : List StmtExprMd) : StmtExprMd :=
mkMd (.StaticCall (mkId callee) args)

/-- Convert parameters to identifier expressions. -/
private def paramsToArgs (params : List Parameter) : List StmtExprMd :=
params.map fun p => mkMd (.Var (.Local p.name))

/-- Build a helper function for a single condition over the given parameters.
Preconditions pass `proc.inputs`; postconditions pass `proc.inputs ++ proc.outputs`. -/
private def mkConditionProc (name : String) (params : List Parameter)
(condition : Condition) : Procedure :=
{ name := mkId name
inputs := params
outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩]
preconditions := []
decreases := none
isFunctional := true
body := .Transparent condition.condition }

/-- Information about a procedure's contracts. -/
private structure ContractInfo where
preNames : List (String × Option String) -- (procName, summary) for each precondition
postNames : List (String × Option String) -- (procName, summary) for each postcondition
inputParams : List Parameter
outputParams : List Parameter

private def ContractInfo.hasPreCondition (info : ContractInfo) : Bool := !info.preNames.isEmpty
private def ContractInfo.hasPostCondition (info : ContractInfo) : Bool := !info.postNames.isEmpty

/-- Collect contract info for all procedures with contracts. -/
private def collectContractInfo (procs : List Procedure) : Std.HashMap String ContractInfo :=
procs.foldl (fun m proc =>
let postconds := getPostconditions proc.body
let hasPre := !proc.preconditions.isEmpty
let hasPost := !postconds.isEmpty
if !proc.isFunctional && (hasPre || hasPost) then
let preNames := proc.preconditions.zipIdx.map fun (c, i) =>
(preCondProcName proc.name.text i, c.summary)
let postNames := postconds.zipIdx.map fun (c, i) =>
(postCondProcName proc.name.text i, c.summary)
m.insert proc.name.text {
preNames := preNames
postNames := postNames
inputParams := proc.inputs
outputParams := proc.outputs
}
else m) {}

/-- Transform a procedure body to add assume/assert for its own contracts. -/
private def transformProcBody (proc : Procedure) (info : ContractInfo) : Body :=
let inputArgs := paramsToArgs proc.inputs
let postconds := getPostconditions proc.body
let preAssumes : List StmtExprMd :=
proc.preconditions.zip info.preNames |>.map fun (pc, name, _) =>
⟨.Assume (mkCall name inputArgs), pc.condition.source⟩
match proc.body with
| .Transparent body =>
let postAsserts : List StmtExprMd :=
postconds.zip info.postNames |>.map fun (pc, _name, _summary) =>
let summary := pc.summary.getD "postcondition"
⟨.Assert { condition := pc.condition, summary := some summary }, pc.condition.source⟩
.Transparent ⟨.Block (preAssumes ++ [body] ++ postAsserts) none, body.source⟩
| .Opaque _ (some impl) _ =>
.Opaque postconds (some ⟨.Block (preAssumes ++ [impl]) none, impl.source⟩) []
| .Opaque _ none mods =>
.Opaque postconds none mods
| .Abstract _ =>
.Abstract postconds
| b => b

/-- Monad used by the contract-pass rewriter; carries a global counter for
generating fresh temporary variable names. -/
private abbrev ContractM := StateM Nat

/-- Allocate a fresh temporary name with the `$cp_` prefix. The global counter
guarantees uniqueness across the entire pass. -/
private def freshTemp : ContractM String := do
let n ← get
set (n + 1)
return s!"$cp_{n}"

/-- Generate temporary variable assignments for input arguments at a call site.
Returns (temp declarations+assignments, temp variable references). -/
private def mkTempAssignments (args : List StmtExprMd)
(inputParams : List Parameter) (src : Option FileRange)
: ContractM (List StmtExprMd × List StmtExprMd) := do
let mut decls : List StmtExprMd := []
let mut refs : List StmtExprMd := []
for arg in args, i in List.range args.length do
let tempName ← freshTemp
let paramType := match inputParams[i]? with
| some p => p.type
| none => { val := .Unknown, source := none }
let param : Parameter := { name := mkId tempName, type := paramType }
decls := decls ++ [⟨StmtExpr.Assign [mkVarMd (.Declare param)] arg, src⟩]
refs := refs ++ [mkMd (.Var (.Local (mkId tempName)))]
return (decls, refs)

/-- Generate precondition checks (one per precondition) for a call site. -/
private def mkPreChecks (info : ContractInfo) (isFunctional : Bool)
(tempRefs : List StmtExprMd) (src : Option FileRange) : List StmtExprMd :=
if !info.hasPreCondition then []
else info.preNames.map fun (name, summary) =>
let call := mkCall name tempRefs
if isFunctional then
⟨.Assume call, src⟩
else
⟨.Assert { condition := call, summary := some (summary.getD "precondition") }, src⟩

/-- Generate postcondition assumes (one per postcondition) for a call site. -/
private def mkPostAssumes (info : ContractInfo)
(tempRefs : List StmtExprMd) (outputArgs : List StmtExprMd) (src : Option FileRange) : List StmtExprMd :=
if !info.hasPostCondition then []
else info.postNames.map fun (name, _) =>
⟨.Assume (mkCall name (tempRefs ++ outputArgs)), src⟩

/-- Rewrite call sites in a statement/expression tree. -/
private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo)
(isFunctional : Bool) (expr : StmtExprMd) : ContractM StmtExprMd := do
let rewriteStaticCall (callee : Identifier) (args : List StmtExprMd)
(info : ContractInfo) (src : Option FileRange)
: ContractM (List StmtExprMd) := do
let (tempDecls, tempRefs) ← mkTempAssignments args info.inputParams src
let preCheck := mkPreChecks info isFunctional tempRefs src
let (callStmt, postAssume, returnValue) ←
if info.hasPostCondition && !info.outputParams.isEmpty then do
let mut outputTempDecls : List VariableMd := []
let mut outputRefs : List StmtExprMd := []
for p in info.outputParams do
let tempName ← freshTemp
outputTempDecls := outputTempDecls ++ [mkVarMd (.Declare { name := mkId tempName, type := p.type })]
outputRefs := outputRefs ++ [mkMd (.Var (.Local (mkId tempName)))]
let callWithOutputs : StmtExprMd :=
⟨.Assign outputTempDecls ⟨.StaticCall callee tempRefs, src⟩, src⟩
let assume := mkPostAssumes info tempRefs outputRefs src
let retVal : List StmtExprMd := match outputRefs with
| [single] => [single]
| _ => []
pure (callWithOutputs, assume, retVal)
else
pure (⟨.StaticCall callee tempRefs, src⟩, [], [])
return tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue
let result ←
mapStmtExprFlattenM (m := ContractM)
-- Pre: intercept Assign targets (StaticCall ...) before recursion
(fun e => do
match e.val with
| .Assign targets (.mk (.StaticCall callee args) callSrc) =>
match contractInfoMap.get? callee.text with
| some info =>
let src := e.source
-- Recurse into arguments
let args' ← args.mapM (mapStmtExprM (m := ContractM) (fun e' => do
match e'.val with
| .StaticCall callee' args' =>
match contractInfoMap.get? callee'.text with
| some info' =>
let stmts ← rewriteStaticCall callee' args' info' e'.source
return ⟨.Block stmts none, e'.source⟩
| none => return e'
| _ => return e'))
let (tempDecls, tempRefs) ← mkTempAssignments args' info.inputParams src
let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩
let preCheck := mkPreChecks info isFunctional tempRefs src
let outputArgs := targets.filterMap fun t =>
match t.val with
| .Local name => some (mkMd (.Var (.Local name)))
| .Declare param => some (mkMd (.Var (.Local param.name)))
| _ => none
let postAssume := mkPostAssumes info tempRefs outputArgs src
return some (tempDecls ++ preCheck ++ [callWithTemps] ++ postAssume)
| none => return none
| _ => return none)
-- Post: handle bare StaticCall
(fun e => do
match e.val with
| .StaticCall callee args =>
match contractInfoMap.get? callee.text with
| some info =>
let stmts ← rewriteStaticCall callee args info e.source
return stmts
| none => return [e]
| _ => return [e]) expr
return result

/-- Rewrite call sites in all bodies of a procedure. -/
private def rewriteCallSitesInProc (contractInfoMap : Std.HashMap String ContractInfo)
(proc : Procedure) : ContractM Procedure := do
let rw := rewriteCallSites contractInfoMap proc.isFunctional
match proc.body with
| .Transparent body =>
let body' ← rw body
return { proc with body := .Transparent body' }
| .Opaque posts impl mods =>
let posts' ← posts.mapM (·.mapM rw)
let impl' ← impl.mapM rw
let mods' ← mods.mapM rw
return { proc with body := Body.Opaque posts' impl' mods' }
| _ => return proc

/-- Build an axiom expression from `invokeOn` trigger and ensures clauses.
Produces `∀ p1, ∀ p2, ..., ∀ pn :: { trigger } (ensures1 && ensures2 && ...)`.
The trigger controls when the SMT solver instantiates the axiom. -/
private def mkInvokeOnAxiom (params : List Parameter) (trigger : StmtExprMd)
Comment thread
keyboardDrummer marked this conversation as resolved.
(postconds : List Condition) : StmtExprMd :=
let body := match postconds.map (·.condition) with
| [] => mkMd (.LiteralBool true)
| [e] => e
| e :: rest => rest.foldl (fun acc x => mkMd (.PrimitiveOp .And [acc, x])) e
-- Wrap in nested Forall from last param (innermost) to first (outermost).
-- The trigger is placed on the innermost quantifier.
params.foldr (init := (body, true)) (fun p (acc, isInnermost) =>
let trig := if isInnermost then some trigger else none
(mkMd (.Quantifier .Forall p trig acc), false)) |>.1

/-- Run the contract pass on a Laurel program.
All procedures with contracts are transformed. -/
def lowerContracts (program : Program) : Program :=
let contractInfoMap := collectContractInfo program.staticProcedures

-- Generate helper procedures for all procedures with contracts
let helperProcs := (program.staticProcedures.filter (fun proc => !proc.isFunctional)).flatMap fun proc =>
let postconds := getPostconditions proc.body
let preProcs := proc.preconditions.zipIdx.map fun (c, i) =>
mkConditionProc (preCondProcName proc.name.text i) proc.inputs c
let postProcs := postconds.zipIdx.map fun (c, i) =>
mkConditionProc (postCondProcName proc.name.text i) (proc.inputs ++ proc.outputs) c
preProcs ++ postProcs

-- Transform procedures: strip contracts, add assume/assert, rewrite call sites
-- Run all call-site rewriting in a single ContractM to share the global counter.
let (transformedProcs, _) := (program.staticProcedures.mapM fun (proc : Procedure) => do
if proc.isFunctional then
return proc
else
let proc : Procedure := match proc.invokeOn with
| some trigger =>
let postconds := getPostconditions proc.body
if postconds.isEmpty then { proc with invokeOn := none }
else { proc with
axioms := [mkInvokeOnAxiom proc.inputs trigger postconds]
invokeOn := none }
| none => proc
let proc : Procedure := match contractInfoMap.get? proc.name.text with
| some info =>
{ proc with
preconditions := []
body := transformProcBody proc info }
| none => proc
-- Rewrite call sites in the procedure body
rewriteCallSitesInProc contractInfoMap proc).run 0

{ program with staticProcedures := helperProcs ++ transformedProcs }

public def contractPass : LoweringPass where
name := "LowerContract"
documentation := "Lowers pre and postcondition to assertions and assumptions around call-sites and procedure bodies"
run := fun p _m =>
let p' := lowerContracts p
(p', [], {})
comesBefore := []

end -- public section
end Strata.Laurel
11 changes: 8 additions & 3 deletions Strata/Languages/Laurel/CoreDefinitionsForLaurel.lean
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ program Laurel;

datatype LaurelResolutionErrorPlaceholder {}
datatype Float64IsNotSupportedYet {}
datatype LaurelUnit { MkLaurelUnit() }

// The types for these Map functions are incorrect.
// We'll fix them when Laurel supports polymorphism
function select(map: int, key: int) : int
// And then we can remove the datatype Box as well
// And remove the hacky filter in HeapParameterization
datatype Box { MkBox() }

function select(map: int, key: int) : Box
external;

function update(map: int, key: int, value: int) : int
function update(map: int, key: int, value: int) : Box
external;

function const(value: int) : int
function const(value: int) : Box
external;

#end
Expand Down
15 changes: 8 additions & 7 deletions Strata/Languages/Laurel/CoreGroupingAndOrdering.lean
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def collectStaticCallNames (expr : StmtExprMd) : List String :=
match val with
| .StaticCall callee args =>
callee.text :: args.flatMap (fun a => collectStaticCallNames a)
| .PrimitiveOp _ args => args.flatMap (fun a => collectStaticCallNames a)
| .PrimitiveOp _ args _ => args.flatMap (fun a => collectStaticCallNames a)
| .IfThenElse cond t e =>
collectStaticCallNames cond ++
collectStaticCallNames t ++
Expand Down Expand Up @@ -114,18 +114,18 @@ Build the procedure call graph, run Tarjan's SCC algorithm, and return each SCC
as a list of procedures paired with a flag indicating whether the SCC is recursive.
Results are in reverse topological order: dependencies before dependents.

Procedures with `invokeOn` are placed as early as possible — before
Procedures with axioms are placed as early as possible — before
unrelated procedures without them — by stably partitioning them first before building
the graph. Tarjan then naturally assigns them lower indices, causing them to appear
earlier in the output.
-/
public def computeSccDecls (program : UnorderedCoreWithLaurelTypes) : List (List Procedure × Bool) :=
-- Stable partition: procedures with invokeOn come first, preserving relative
-- Stable partition: procedures with axioms come first, preserving relative
-- order within each group. Tarjan then places them earlier in the topological output.
let allProcs := program.functions ++ program.coreProcedures
let (withInvokeOn, withoutInvokeOn) :=
allProcs.partition (fun p => p.invokeOn.isSome)
let orderedProcs : List Procedure := withInvokeOn ++ withoutInvokeOn
let (withAxioms, withoutAxioms) :=
allProcs.partition (fun p => !p.axioms.isEmpty)
let orderedProcs : List Procedure := withAxioms ++ withoutAxioms

-- Build a call-graph over all procedures.
-- An edge proc → callee means proc's body/contracts contain a StaticCall to callee.
Expand All @@ -143,7 +143,8 @@ public def computeSccDecls (program : UnorderedCoreWithLaurelTypes) : List (List
| _ => []
let contractExprs : List StmtExprMd :=
proc.preconditions.map (·.condition) ++
proc.invokeOn.toList
proc.invokeOn.toList ++
proc.axioms
(bodyExprs ++ contractExprs).flatMap collectStaticCallNames

-- Build the OutGraph for Tarjan.
Expand Down
Loading
Loading