Skip to content

Commit 1f1c90c

Browse files
authored
Merge pull request #575 from FStarLang/_nik_while_decreases_examples
Fix lex orderings in pulse decreases clauses + examples
2 parents 85241bc + 8ba25d0 commit 1f1c90c

15 files changed

+171
-41
lines changed

lib/pulse/lib/Pulse.Lib.InsertionSort.fst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ ensures exists* s'. (a |-> s') **
158158
sorted (Seq.slice (value_of a) 0 (SZ.v !j)) /\
159159
permutation s (value_of a)
160160
)
161+
decreases (SZ.v len - SZ.v !j)
161162
{
162163
pts_to_len a;
163164
let vj = !j;
@@ -173,13 +174,14 @@ ensures exists* s'. (a |-> s') **
173174
invariant live done
174175
invariant exists* (s':Seq.seq t { inner_invariant ss s' key (!i) vj (!done)}).
175176
a |-> s'
177+
decreases (SZ.v !i)
176178
{
177179
let vi = !i;
178180
with s0. assert (a |-> s0);
179181
a.(SZ.(vi +^ 1sz)) <- a.(vi);
180182
with s1. assert (a |-> s1);
181183
step_inner_invariant ss s0 s1 key vi vj;
182-
if (vi = 0sz) { done := true }
184+
if (vi = 0sz) { done := true; break }
183185
else { i := SZ.(vi -^ 1sz); }
184186
};
185187
with s0. assert (a |-> s0);

lib/pulse/lib/Pulse.Lib.LinkedList.fst

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ let rec is_list #t ([@@@mkey]x:llist t) (l:list t)
4747
pts_to v { head; tail } **
4848
is_list tail tl
4949

50+
ghost
51+
fn list_of (#t:Type) (x:llist t) (#y:list t)
52+
requires is_list x y
53+
returns z:erased (list t)
54+
ensures is_list x y ** rewrites_to z (hide y)
55+
{
56+
hide y
57+
}
58+
5059
let is_list_cases #t ([@@@mkey]x:llist t) (l:list t)
5160
: Tot slprop
5261
= match x with
@@ -339,8 +348,6 @@ fn move_next (#t:Type) (x:llist t)
339348
node.tail
340349
}
341350

342-
343-
344351
fn length_iter (#t:Type) (x: llist t)
345352
preserves is_list x 'l
346353
returns n:nat
@@ -353,7 +360,7 @@ fn length_iter (#t:Type) (x: llist t)
353360
let v = Pulse.Lib.Reference.(!cur);
354361
Some? v
355362
)
356-
invariant
363+
invariant
357364
exists* (n:int) ll suffix.
358365
pts_to ctr n **
359366
pts_to cur ll **
@@ -365,6 +372,8 @@ fn length_iter (#t:Type) (x: llist t)
365372
(* ^ Having the bounded_int nat instance in BoundedIntegers means we try to
366373
to check the subtraction as a nat, which fails without the extra condition.
367374
We can also just write `n + len suff = len 'l`. *)
375+
//Also below, the bounded integer stuff leads to problems
376+
decreases (List.Tot.length 'l `Prims.op_Subtraction` value_of ctr)
368377
{
369378
with _n _ll suffix. _;
370379
let n = Pulse.Lib.Reference.(!ctr);
@@ -509,6 +518,7 @@ fn append_iter (#t:Type) (x y:llist t)
509518
is_list ll sfx **
510519
(forall* sfx'. is_list ll sfx' @==> is_list x (pfx @ sfx')) **
511520
pure (pfx @ sfx == 'l1 /\ Cons? sfx)
521+
decreases reveal (list_of (value_of cur))
512522
{
513523
with ll pfx sfx. _;
514524
some_iff_cons ll;
@@ -580,6 +590,7 @@ fn split (#t:Type0) (x:llist t) (n:U32.t) (#xl:erased (list t))
580590
Some? ll /\
581591
pfx@sfx == xl
582592
)
593+
decreases reveal (list_of (value_of cur))
583594
{
584595
with i ll pfx sfx. _;
585596
let next = move_next_forall Pulse.Lib.Reference.(!cur);
@@ -660,6 +671,7 @@ fn reverse (#t:Type0) (x:llist t)
660671
is_list p rev_pfx **
661672
is_list c suffix **
662673
pure (List.Tot.rev rev_pfx @ suffix == 'l)
674+
decreases reveal (list_of (value_of cur))
663675
{
664676
with _p _c _rev_pfx _suffix. _;
665677
let p = Pulse.Lib.Reference.(!prev);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module WhileDecreases
2+
#lang-pulse
3+
open Pulse.Lib.Pervasives
4+
5+
fn test (x:ref nat)
6+
requires live x
7+
ensures x |-> (0<:nat)
8+
{
9+
while (!x > 0)
10+
invariant live x
11+
decreases (!x)
12+
{
13+
x := !x - 1;
14+
}
15+
}
16+
17+
fn test2 (x:ref nat)
18+
requires live x
19+
ensures x |-> (0<:nat)
20+
{
21+
while (!x > 0)
22+
invariant exists* y. pts_to x y
23+
decreases (!x)
24+
{
25+
x := !x - 1;
26+
}
27+
}
28+
29+
fn test3 (x:ref nat)
30+
requires live x
31+
ensures x |-> (0<:nat)
32+
{
33+
while (!x > 0)
34+
invariant exists* y. pts_to x y
35+
decreases reveal (value_of x)
36+
{
37+
x := !x - 1;
38+
}
39+
}
40+
41+
let rec lex_rec (x y:nat)
42+
: Tot unit (decreases %[x;y])
43+
= if x = 0 && y = 0 then ()
44+
else if x > 0 then lex_rec (x - 1) y
45+
else lex_rec x (y - 1)
46+
47+
fn lex (x y:ref nat)
48+
requires live x ** live y
49+
ensures x |-> (0<:nat) ** y |-> (0<:nat)
50+
{
51+
while (!x > 0 || !y > 0)
52+
invariant live x
53+
invariant live y
54+
decreases %[(!x); (!y)]
55+
{
56+
if (!x = 0) { y := !y - 1; }
57+
else { x := !x - 1; }
58+
}
59+
}

src/checker/Pulse.Checker.While.fst

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,41 @@ it directly calls this checker and pass the post as the loop_ensures argument he
112112
113113
*)
114114

115-
#push-options "--fuel 0 --ifuel 0 --z3rlimit_factor 64"
115+
#push-options "--fuel 0 --ifuel 0 --z3rlimit_factor 72"
116116
module RT = FStar.Reflection.Typing
117+
118+
#push-options "--fuel 1 --ifuel 1"
119+
let rec compute_meas_infos (g:env) (pre:term) (ms: list term)
120+
: T.Tac (list (term & term & universe))
121+
= match ms with
122+
| [] -> []
123+
| m :: rest ->
124+
let m' = purify_term g { ctxt_now = pre; ctxt_old = Some pre } m in
125+
let (| _, _, ty, (| u, _ |), _ |) = compute_term_type_and_u g m' in
126+
(m, ty, u) :: compute_meas_infos g pre rest
127+
128+
let rec build_tuple_info (infos: list (term & term & universe))
129+
: T.Tac (term & term & universe & (term -> term -> term))
130+
= match infos with
131+
| [(m, ty, u)] -> (m, ty, u, mk_precedes u ty)
132+
| (m, ty, u) :: rest ->
133+
let (m_rest, ty_rest, u_rest, mk_dec_rest) = build_tuple_info rest in
134+
let ty_tup = mk_tuple2 u u_rest ty ty_rest in
135+
let m_tup = mk_mktuple2 u u_rest ty ty_rest m m_rest in
136+
let mk_dec_tup (new_m: term) (old_m: term) : term =
137+
let new_hd = mk_fst u u_rest ty ty_rest new_m in
138+
let old_hd = mk_fst u u_rest ty ty_rest old_m in
139+
let new_tl = mk_snd u u_rest ty ty_rest new_m in
140+
let old_tl = mk_snd u u_rest ty ty_rest old_m in
141+
let precedes_hd = mk_precedes u ty new_hd old_hd in
142+
let eq_hd = mk_eq2 u ty new_hd old_hd in
143+
let rest_dec = mk_dec_rest new_tl old_tl in
144+
tm_l_or precedes_hd (tm_l_and eq_hd rest_dec)
145+
in
146+
(m_tup, ty_tup, u, mk_dec_tup)
147+
| _ -> (unit_const, tm_unit, u0, mk_precedes u0 tm_unit)
148+
#pop-options
149+
117150
let check_while
118151
(g:env)
119152
(pre:term)
@@ -156,20 +189,27 @@ let check_while
156189
if loop_requires `eq_tm` tm_l_true then inv else
157190
(inv `tm_star` tm_pure (mk_loop_requires_marker loop_requires)) in
158191
let x_meas: nvar = mk_ppname_no_range "meas", fresh g in
159-
let u_meas, ty_meas, meas, is_tot =
192+
let u_meas, ty_meas, meas_val, is_tot, mk_dec =
160193
match meas with
161-
| None -> u0, tm_unit, unit_const, false
162-
| Some meas ->
194+
| [] -> u0, tm_unit, unit_const, false, mk_precedes u0 tm_unit
195+
| [meas] ->
163196
let meas' = purify_term g { ctxt_now = pre; ctxt_old = Some pre } meas in
164197
let (| _, _, ty, (| u, _ |), _ |) = compute_term_type_and_u g meas' in
165-
u, ty, meas, true
198+
u, ty, meas, true, mk_precedes u ty
199+
| _ ->
200+
let meas_infos = compute_meas_infos g pre meas in
201+
let (meas_val, _ty_approx, _u_approx, mk_dec) = build_tuple_info meas_infos in
202+
let meas_val' = purify_term g { ctxt_now = pre; ctxt_old = Some pre } meas_val in
203+
let (| _, _, ty, (| u, _ |), _ |) = compute_term_type_and_u g meas_val' in
204+
u, ty, meas_val, true, mk_dec
166205
in
206+
let dec_formula = mk_dec (tm_bvar {bv_index=0;bv_ppname=fst x_meas}) (term_of_nvar x_meas) in
167207
let inv_range = term_range inv in
168208
let g_meas = push_binding g (snd x_meas) (fst x_meas) ty_meas in
169209
let inv = dfst <|
170210
purify_and_check_spec (push_context "invariant" inv_range g_meas)
171211
{ ctxt_now = pre; ctxt_old = Some pre }
172-
(inv `tm_star` tm_pure (mk_eq2 u_meas ty_meas (term_of_nvar x_meas) meas))
212+
(inv `tm_star` tm_pure (mk_eq2 u_meas ty_meas (term_of_nvar x_meas) meas_val))
173213
in
174214
let loop_pre0 = tm_exists_sl u_meas (as_binder ty_meas) (close_term inv (snd x_meas)) in
175215
let (| g0, remaining, k |) = Pulse.Checker.Prover.prove t.range g pre loop_pre0 false in
@@ -242,7 +282,7 @@ let check_while
242282
(| t, c, typ |) in
243283

244284
let body_pre_open = post_cond.post in
245-
let body_post_typing : tot_typing g2 (comp_post (comp_while_body u_meas ty_meas is_tot x_meas inv body_pre_open)) tm_slprop = RU.magic () in
285+
let body_post_typing : tot_typing g2 (comp_post (comp_while_body u_meas ty_meas is_tot dec_formula x_meas inv body_pre_open)) tm_slprop = RU.magic () in
246286
let body_ph : post_hint_for_env g2 = inv_as_post_hint body_post_typing in
247287
assert body_ph.ret_ty == tm_unit;
248288
let x = fresh g2 in
@@ -258,21 +298,21 @@ let check_while
258298
let (| cond, comp_cond, cond_typing |) = r_cond in
259299
let (| body, comp_body, body_typing |) = apply_checker_result_k r_body ppname_default in
260300
assert (comp_cond == (comp_while_cond inv body_pre_open));
261-
assert (comp_post comp_body == comp_post (comp_while_body u_meas ty_meas is_tot x_meas inv body_pre_open));
262-
assert (comp_pre comp_body == comp_pre (comp_while_body u_meas ty_meas is_tot x_meas inv body_pre_open));
263-
assert (comp_u comp_body == comp_u (comp_while_body u_meas ty_meas is_tot x_meas inv body_pre_open));
264-
assert (comp_res comp_body == comp_res (comp_while_body u_meas ty_meas is_tot x_meas inv body_pre_open));
265-
assert (comp_body == comp_while_body u_meas ty_meas is_tot x_meas inv body_pre_open);
301+
assert (comp_post comp_body == comp_post (comp_while_body u_meas ty_meas is_tot dec_formula x_meas inv body_pre_open));
302+
assert (comp_pre comp_body == comp_pre (comp_while_body u_meas ty_meas is_tot dec_formula x_meas inv body_pre_open));
303+
assert (comp_u comp_body == comp_u (comp_while_body u_meas ty_meas is_tot dec_formula x_meas inv body_pre_open));
304+
assert (comp_res comp_body == comp_res (comp_while_body u_meas ty_meas is_tot dec_formula x_meas inv body_pre_open));
305+
assert (comp_body == comp_while_body u_meas ty_meas is_tot dec_formula x_meas inv body_pre_open);
266306
let inv_typing2 : tot_typing g2 inv tm_slprop = RU.magic () in
267307

268-
let while = wtag (Some STT) (Tm_While { invariant = inv; loop_requires = tm_unknown; meas = None; condition = cond; body }) in
308+
let while = wtag (Some STT) (Tm_While { invariant = inv; loop_requires = tm_unknown; meas = []; condition = cond; body }) in
269309
let typ_meas: universe_of g1' ty_meas u_meas = RU.magic () in
270310
assume ~(snd x_meas `Set.mem` freevars_st cond);
271311
assume ~(snd x_meas `Set.mem` freevars_st body);
272312
let d: st_typing g1' while (comp_while u_meas ty_meas x_meas inv body_pre_open) =
273313
let h = RU.magic () in
274314
T_While g1' inv body_pre_open cond body
275-
u_meas ty_meas typ_meas is_tot
315+
u_meas ty_meas typ_meas is_tot dec_formula
276316
x_meas g2
277317
inv_typing2 h cond_typing body_typing
278318
in

src/checker/Pulse.Reflection.Util.fst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ let mk_snd (u1 u2:R.universe) (a1 a2 e:R.term) : R.term =
105105
let t = pack_ln (Tv_App t (a2, Q_Implicit)) in
106106
pack_ln (Tv_App t (e, Q_Explicit))
107107

108+
let mktuple2_lid = ["FStar"; "Pervasives"; "Native"; "Mktuple2"]
109+
110+
let mk_mktuple2 (u1 u2:R.universe) (a1 a2 v1 v2:R.term) : R.term =
111+
let open R in
112+
let t = pack_ln (Tv_UInst (pack_fv mktuple2_lid) [u1; u2]) in
113+
let t = pack_ln (Tv_App t (a1, Q_Implicit)) in
114+
let t = pack_ln (Tv_App t (a2, Q_Implicit)) in
115+
let t = pack_ln (Tv_App t (v1, Q_Explicit)) in
116+
pack_ln (Tv_App t (v2, Q_Explicit))
117+
108118
let true_tm = R.pack_ln (R.Tv_Const (R.C_True))
109119
let false_tm = R.pack_ln (R.Tv_Const (R.C_False))
110120

src/checker/Pulse.Syntax.Base.fst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ let rec eq_st_term (t1 t2:st_term)
230230
Tm_While { invariant=inv2; loop_requires=cr2; meas=d2; condition=cond2; body=body2 } ->
231231
eq_tm inv1 inv2 &&
232232
eq_tm cr1 cr2 &&
233-
eq_tm_opt d1 d2 &&
233+
eq_tm_list d1 d2 &&
234234
eq_st_term cond1 cond2 &&
235235
eq_st_term body1 body2
236236

src/checker/Pulse.Syntax.Base.fsti

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ type st_term' =
264264
| Tm_While {
265265
invariant:term;
266266
loop_requires:term;
267-
meas:option term;
267+
meas:list term;
268268
condition:st_term;
269269
body:st_term;
270270
}

src/checker/Pulse.Syntax.Naming.fst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ let rec close_open_inverse_st' (t:st_term)
179179
| Tm_While { invariant; loop_requires; meas; condition; body } ->
180180
close_open_inverse' invariant x i;
181181
close_open_inverse' loop_requires x i;
182-
(match meas with | Some d -> close_open_inverse' d x i | None -> ());
182+
close_open_inverse_list' meas x i;
183183
close_open_inverse_st' condition x i;
184184
close_open_inverse_st' body x i
185185

src/checker/Pulse.Syntax.Naming.fsti

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ let rec freevars_st (t:st_term)
127127
| Tm_While { invariant; loop_requires; meas; condition; body } ->
128128
freevars invariant ++
129129
freevars loop_requires ++
130-
freevars_term_opt meas ++
130+
freevars_list meas ++
131131
freevars_st condition ++
132132
freevars_st body
133133

@@ -315,7 +315,7 @@ let rec ln_st' (t:st_term) (i:int)
315315
| Tm_While { invariant; loop_requires; meas; condition; body } ->
316316
ln' invariant i &&
317317
ln' loop_requires i &&
318-
ln_opt' ln' meas i &&
318+
ln_list' meas i &&
319319
ln_st' condition i &&
320320
ln_st' body i
321321

@@ -564,7 +564,7 @@ let rec subst_st_term (t:st_term) (ss:subst)
564564
| Tm_While { invariant; loop_requires; meas; condition; body } ->
565565
Tm_While { invariant = subst_term invariant ss;
566566
loop_requires = subst_term loop_requires ss;
567-
meas = subst_term_opt meas ss;
567+
meas = subst_term_list meas ss;
568568
condition = subst_st_term condition ss;
569569
body = subst_st_term body ss }
570570

src/checker/Pulse.Syntax.Printer.fst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ let rec st_term_to_string' (level:string) (t:st_term)
357357
level
358358
(term_to_string invariant)
359359
level
360-
(match meas with | Some d -> sprintf "decreases %s\n%s" (term_to_string d) level | None -> "")
360+
(match meas with | [] -> "" | [d] -> sprintf "decreases %s\n%s" (term_to_string d) level | ds -> sprintf "decreases %%[%s]\n%s" (term_list_to_string "; " ds) level)
361361
(indent level)
362362
(st_term_to_string' (indent level) body)
363363
level

0 commit comments

Comments
 (0)