Skip to content

Commit aebc960

Browse files
kondylidoushigoeljoscoh
authored
while loops with multiple invariants (#382)
This PR adds support for multiple loop invariants in while statements. The parser and translation phase are extended to accept multiple invariant clauses and translate them into a list, while keeping the Core backend and invariant semantics unchanged. The example `LoopSimple.core.st` has been adapted to demonstrate the change. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution under the terms of your choice. --------- Co-authored-by: Shilpi Goel <shigoel@gmail.com> Co-authored-by: Josh Cohen <cohenjo@amazon.com>
1 parent 36b1457 commit aebc960

7 files changed

Lines changed: 53 additions & 22 deletions

File tree

Examples/LoopSimple.core.st

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ spec {
1111
sum := 0;
1212
i := 0;
1313
while(i < n)
14-
invariant (i <= n && ((i * (i-1)) div 2 == sum));
14+
invariant i <= n
15+
invariant ((i * (i-1)) div 2 == sum)
1516
{
1617
sum := sum + i;
1718
i := i + 1;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Successfully parsed.
22
LoopSimple.core.st(13, 2) [entry_invariant_0]: ✅ pass
33
LoopSimple.core.st(13, 2) [arbitrary_iter_maintain_invariant_0]: ✅ pass
4-
LoopSimple.core.st(19, 2) [sum_assert]: ✅ pass
5-
LoopSimple.core.st(20, 2) [neg_cond]: ✅ pass
4+
LoopSimple.core.st(20, 2) [sum_assert]: ✅ pass
5+
LoopSimple.core.st(21, 2) [neg_cond]: ✅ pass
66
All 4 goals passed.

Strata/Languages/Core/DDMTransform/Parse.lean

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,13 @@ op havoc_statement (v : Ident) : Statement => "havoc " v ";\n";
203203
category Invariant;
204204
op invariant (e : Expr) : Invariant => "invariant" e ";";
205205

206-
op while_statement (c : bool, i : Option Invariant, body : Block) : Statement =>
207-
"while" "(" c ")" i body;
206+
category Invariants;
207+
op nilInvariants : Invariants => ;
208+
op consInvariants(e : Expr, is : Invariants) : Invariants =>
209+
"invariant" e is;
210+
211+
op while_statement (c : bool, is : Invariants, body : Block) : Statement =>
212+
"while" "(" c ")" is body;
208213

209214
op call_statement (vs : CommaSepBy Ident, f : Ident, expr : CommaSepBy Expr) : Statement =>
210215
"call" vs ":=" f "(" expr ")" ";\n";

Strata/Languages/Core/DDMTransform/Translate.lean

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,29 @@ def translateInvariant (p : Program) (bindings : TransBindings) (arg : Arg) : Tr
918918
translateExpr p bindings args[0]!
919919
| _ => pure none
920920

921+
partial def translateInvariants (p : Strata.Program) (bindings : TransBindings) (arg : Arg) :
922+
TransM (List Expression.Expr) := do
923+
let .op op := arg
924+
| TransM.error s!"translateInvariants expects an op {repr arg}"
925+
match op.name with
926+
| q`Core.nilInvariants =>
927+
pure []
928+
| q`Core.consInvariants =>
929+
let args ← checkOpArg arg q`Core.consInvariants 2
930+
let i ← translateExpr p bindings args[0]!
931+
let is ← translateInvariants p bindings args[1]!
932+
pure (i::is)
933+
| _ => TransM.error s!"translateInvariants unimplemented for {repr op}"
934+
935+
private def invariantsToOption (invs : List Core.Expression.Expr) : Option Core.Expression.Expr :=
936+
match invs with
937+
| [] => none
938+
| i :: is =>
939+
-- ((i ∧ i2) ∧ i3) ∧ ...
940+
some <| is.foldl
941+
(fun acc j => .app () (.app () Core.boolAndOp acc) j)
942+
i
943+
921944
def initVarStmts (tpids : ListMap Expression.Ident LTy) (bindings : TransBindings) :
922945
TransM ((List Core.Statement) × TransBindings) := do
923946
match tpids with
@@ -1005,10 +1028,11 @@ partial def translateStmt (p : Program) (bindings : TransBindings) (arg : Arg) :
10051028
return ([.ite c tss fss md], bindings)
10061029
| q`Core.while_statement, #[ca, ia, ba] =>
10071030
let c ← translateExpr p bindings ca
1008-
let i ← translateInvariant p bindings ia
1031+
let invs ← translateInvariants p bindings ia
1032+
let inv? := invariantsToOption invs
10091033
let (bodyss, bindings) ← translateBlock p bindings ba
10101034
let md ← getOpMetaData op
1011-
return ([.loop c .none i bodyss md], bindings)
1035+
return ([.loop c .none inv? bodyss md], bindings)
10121036
| q`Core.call_statement, #[lsa, fa, esa] =>
10131037
let ls ← translateCommaSep (translateIdent CoreIdent) lsa
10141038
let f ← translateIdent String fa

StrataTest/Languages/Core/Examples/Loops.lean

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ spec {
2323
i := 0;
2424
s := 0;
2525
while (i < n)
26-
invariant 0 <= i &&
27-
i <= n &&
28-
s == (i * (i + 1)) div 2;
26+
invariant 0 <= i
27+
invariant i <= n
28+
invariant s == (i * (i + 1)) div 2
2929
{
3030
i := (i + 1);
3131
s := (s + i);
@@ -99,11 +99,14 @@ spec {
9999
var y: int;
100100
x := 0;
101101
while (x < n)
102-
invariant x >= 0 && x <= n && n < top;
102+
invariant x >= 0
103+
invariant x <= n
104+
invariant n < top
103105
{
104106
y := 0;
105107
while (y < x)
106-
invariant y >= 0 && y <= x;
108+
invariant y >= 0
109+
invariant y <= x
107110
{
108111
y := y + 1;
109112
}

Tools/BoogieToStrata/Source/StrataGenerator.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -927,12 +927,10 @@ private void EmitWhileCmd(WhileCmd whileCmd) {
927927
}
928928

929929
WriteLine(")");
930-
if (whileCmd.Invariants.Count != 0) {
931-
IncIndent();
932-
Indent("invariant");
933-
EmitSeparated(whileCmd.Invariants, i => VisitExpr(i.Expr), " && ");
934-
WriteLine(";");
935-
DecIndent();
930+
foreach (var inv in whileCmd.Invariants) {
931+
Indent("invariant ");
932+
VisitExpr(inv.Expr);
933+
WriteLine("");
936934
}
937935
IndentLine("{");
938936
IncIndent();

Tools/BoogieToStrata/Tests/Bubble.expect

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
Successfully parsed.
22
Bubble.core.st(31, 4) [entry_invariant_0]: ✅ pass
33
Bubble.core.st(31, 4) [arbitrary_iter_maintain_invariant_0]: ✅ pass
4-
Bubble.core.st(43, 4) [entry_invariant_0]: ✅ pass
4+
Bubble.core.st(44, 4) [entry_invariant_0]: ✅ pass
55
Bubble.core.st(20, 2) [BubbleSort_ensures_1]: ✅ pass
66
Bubble.core.st(21, 2) [BubbleSort_ensures_2]: ✅ pass
77
Bubble.core.st(22, 2) [BubbleSort_ensures_3]: ✅ pass
88
Bubble.core.st(23, 2) [BubbleSort_ensures_4]: ✅ pass
9-
Bubble.core.st(57, 8) [entry_invariant_1]: ✅ pass
10-
Bubble.core.st(57, 8) [arbitrary_iter_maintain_invariant_1]: ✅ pass
11-
Bubble.core.st(43, 4) [arbitrary_iter_maintain_invariant_0]: ✅ pass
9+
Bubble.core.st(62, 8) [entry_invariant_1]: ✅ pass
10+
Bubble.core.st(62, 8) [arbitrary_iter_maintain_invariant_1]: ✅ pass
11+
Bubble.core.st(44, 4) [arbitrary_iter_maintain_invariant_0]: ✅ pass
1212
Bubble.core.st(20, 2) [BubbleSort_ensures_1]: ✅ pass
1313
Bubble.core.st(21, 2) [BubbleSort_ensures_2]: ✅ pass
1414
Bubble.core.st(22, 2) [BubbleSort_ensures_3]: ✅ pass

0 commit comments

Comments
 (0)