-
Notifications
You must be signed in to change notification settings - Fork 108
/
local_var_extract.ML
1654 lines (1493 loc) · 67 KB
/
local_var_extract.ML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*)
(*
* Extract local variables out of converted L1 fragments.
*
* The main interface to this module is translate (and helper functions
* convert and define). See AutoCorresUtil for a conceptual overview.
*)
structure LocalVarExtract =
struct
open Prog
(* Convenience abbreviations for set manipulation. *)
infix 1 INTER MINUS UNION
val empty_set = Varset.empty
val make_set = Varset.make
val union_sets = Varset.union_sets
val dest_set = Varset.dest
fun (a INTER b) = Varset.inter a b
fun (a MINUS b) = Varset.subtract b a
fun (a UNION b) = Varset.union a b
(* Convenience shortcuts. *)
val warning = Utils.ac_warning
val apply_tac = Utils.apply_tac
val the' = Utils.the'
(* Simpset we use for automated tactics. *)
fun setup_l2_ss ctxt = put_simpset AUTOCORRES_SIMPSET ctxt
addsimps @{thms pred_conj_def}
(* Convert a set of variable names into an Isabelle list of strings. *)
fun var_set_to_isa_list prog_info s =
dest_set s
|> map fst
|> map (ProgramInfo.demangle_name prog_info)
|> map Utils.encode_isa_string
|> Utils.encode_isa_list @{typ string}
(*
* Remove references to local variables in "term", replacing them with free
* variables.
*
* We return a list of variables that were successfully extracted, along with
* the modified term itself.
*
* For instance:
*
* convert_local_vars @{term "a_' s + b + c"}
* [("x", @{term "a_' s"}), ("y", @{term "b_' s"})]
*
* would return ("x", @{term "x + b + c"}).
*)
fun convert_local_vars name_map term [] = ([], term) | convert_local_vars name_map term ((var_name, var_term) :: vars) =
if Utils.contains_subterm var_term term then
let
val free_var = name_map (var_name, fastype_of var_term)
(* Pull out "term" from "var_term". *)
val abstracted = betapply (Utils.abs_over var_name var_term term, free_var)
(* Pull out the other variables. *)
val (other_vars, other_term) = convert_local_vars name_map abstracted vars
in
(other_vars @ [(var_name, fastype_of var_term)], other_term)
end
else
convert_local_vars name_map term vars
(* Get the set of variables a function accepts and returns. *)
fun get_fn_input_output_vars prog_info l1_infos fn_name =
let
val fn_info = the (Symtab.lookup l1_infos fn_name);
val inputs = #args fn_info |> Varset.make;
(* Get the return type of a function. *)
val return_ctype =
ProgramAnalysis.get_rettype fn_name (#csenv prog_info)
|> Utils.the' ("Function missing from C-parser's csenv: " ^ quote fn_name)
val outputs =
if return_ctype = Absyn.Void then
empty_set
else
make_set [(NameGeneration.return_var_name return_ctype |> MString.dest,
#return_type fn_info)]
in
(inputs, outputs)
end
(* Get the return variable of a particular function. *)
fun get_ret_var prog_info l1_infos fn_name =
let
val (_, outputs) = get_fn_input_output_vars prog_info l1_infos fn_name
in
hd ((Varset.dest outputs) @ [("void", @{typ unit})])
end
(*
* Determine the state, return and exception type of a monad.
*
* Monads have the form:
*
* 'a => 'b => ... => 's => ('x, 'y, 's) L2_monad.
*
* We return:
*
* (['a, 'b, ...], ('x, 'y, 's))
*)
fun dest_l2monad_T t =
let
val (Type ("Product_Type.prod", [Type ("Set.set", [Type ("Product_Type.prod", [
Type ("Sum_Type.sum", [ex, ret]) ,state])]), _]))
= body_type t
val args = binder_types t
val inputs = List.take (args, length args - 1)
in
(inputs, (state, ret, ex))
end
fun l2monad_type monad =
dest_l2monad_T (fastype_of monad) |> snd
fun l2monad_state_type monad = #1 (l2monad_type monad)
fun l2monad_ret_type monad = #2 (l2monad_type monad)
fun l2monad_ex_type monad = #3 (l2monad_type monad)
(* Get the abstract/concrete term from a "L2corres" predicate. *)
fun dest_L2corres_term_abs @{term_pat "L2corres _ _ _ _ ?t _"} = t
fun dest_L2corres_term_conc @{term_pat "L2corres _ _ _ _ _ ?t"} = t
(* Make an L2 monad. *)
fun mk_l2monadT stateT retT exT =
Utils.gen_typ @{typ "('a, 'b, 'c) L2_monad"} [stateT, retT, exT]
(*
* "Spec" expressions are of the form:
*
* {(s, t). f s t}
*
* where "s" and "t" are input/output states. We want to parse the expression,
* and convert it to an L2 expression dealing only with globals in "s" and "t".
*
* If the original SIMPL spec attempts to read or write to local variables, we
* just fail.
*)
fun parse_spec ctxt prog_info term =
let
(*
* If simplification was turned off in L1, the spec may still contain
* unions and intersections, i.e. be of the form
* {(s, t). f s t} \<union> {(s, t). g s t} ...
* We blithely rewrite them here.
*)
val term = Raw_Simplifier.rewrite_term (Proof_Context.theory_of ctxt)
(map safe_mk_meta_eq @{thms Collect_prod_inter Collect_prod_union}) [] term
(* Apply a dummy old and new state variable to the term. *)
val dummy_s = Free ("_dummy_state1", #state_type prog_info)
val dummy_t = Free ("_dummy_state2", #state_type prog_info)
val dummy_tuple = HOLogic.mk_tuple [dummy_s, dummy_t]
val t = Envir.beta_eta_contract (
(Const (@{const_name "Set.member"},
fastype_of dummy_tuple --> fastype_of term --> @{typ bool})
$ dummy_tuple $ term))
(* Pull apart the "split" at the beginning of the term, then apply
* to our dummy variables *)
val t = Raw_Simplifier.rewrite_term (Proof_Context.theory_of ctxt)
(map mk_meta_eq @{thms split_def fst_conv snd_conv mem_Collect_eq}) [] t
(*
* Pull out any references to any other variables into a lambda
* function.
*
* We pull out the globals variable first, because we want it to end
* up inner-most compared to all the other lambdas we generate.
*)
val globals_getter = #globals_getter prog_info
val t = Utils.abs_over "t" (globals_getter $ dummy_t) t
|> Utils.abs_over "s" (globals_getter $ dummy_s)
|> HOLogic.mk_case_prod
val t_collect = @{mk_term "Collect :: (?'s \<Rightarrow> bool) \<Rightarrow> ?'s set" ('s)}
(domain_type (fastype_of t))
in
(* Determine if there are any references left to the dummy state
* variable. If so, give up on the translation. *)
if Utils.contains_subterm dummy_s t
orelse Utils.contains_subterm dummy_t t then
(warning ("Can't parse spec term: "
^ (Utils.term_to_string ctxt term)); NONE)
else
SOME (t_collect $ t)
end
(*
* Parse an L1 expression containing references to the global state.
*
* We assume that the input term is in the "abstracted" form "%s. f s" where
* "s" is the global state variable.
*
* Our return value is a list of variables abstracted, whether the global
* variable was used, and the abstracted term itself.
*
* The function will fail (and return NONE) if the input expression performs
* arbitrary transformations on the state. For example:
*
* "%s. a_' s" => ([a], False, SOME @{term "%a s. a"})
* "%s. globals s" => ([], True, SOME @{term "%s. s"})
* "%s. a_' s + b_' s" => ([a, b], False, SOME @{term "%a b s. a + b"})
* "%s. False" => ([], False, SOME @{term "%s. False"})
* "%s. bot s" => ([], False, NONE)
*)
fun parse_expr ctxt prog_info name_map term =
let
val dummy_state = Free ("_dummy_state", #state_type prog_info)
(* Apply a dummy state variable to the term. This makes our later analysis
* easier. *)
val term = Envir.beta_eta_contract (term $ dummy_state)
(*
* Pull out any references to any other variables into a lambda
* function.
*
* We pull out the globals variable first, because we want it to end
* up inner-most compared to all the other lambdas we generate.
*)
val globals_getter = #globals_getter prog_info $ dummy_state
val globals_used = Utils.contains_subterm globals_getter term
val t = Utils.abs_over "s" globals_getter term
(* Pull out local variables. *)
val all_getters = #var_getters prog_info |> Symtab.dest |> map (fn (a,b) => (a, b $ dummy_state))
val (v1, t) = convert_local_vars name_map t all_getters
(*
* Determine if there are any references left to the dummy state
* variable.
*
* If so, we are stuck: we aren't pulling out a part of the state
* record, but instead performing an arbitrary transformation on it.
* The most likely reason for this is the C parser's dummy function
* "lvar_init", which attempts to set an uninitialised local
* variable to an invalid state. Other possibilities include "bot",
* the always-false guard.
*)
val t = if Utils.contains_subterm dummy_state t then
(warning ("Can't parse expression: "
^ (Utils.term_to_string ctxt term)); NONE)
else
SOME t;
in
(v1, globals_used, t)
end
(*
* Parse an "L1_modify" expression.
*)
local
fun parse_modify' ctxt prog_info name_map term =
let
val dummy_state = Free ("_dummy_state", #state_type prog_info)
(*
* We expect modify clauses in two forms: both "%x. (foo x) x" and just
* "foo". We apply a state variable to the function and beta/eta contract
* to normalise our output for the next steps.
*)
val modify_clause = Envir.beta_eta_contract (term $ dummy_state)
(*
* Extract "xxx" from "foo_'_update xxx".
*
* If the user has written custom "modifies" clauses (presumably
* using "AUXUPD" directives), this may fail.
*)
val (setter, modify_val, s) = case modify_clause of
(Const var $ value $ s) => (Const var, value, s)
| other => Utils.invalid_term "Const (x,y) $ z" other;
val (var_name, var_type) = ProgramInfo.guess_var_name_type_from_setter_term setter
(*
* At this stage we have assume we have an update function "f" of
* type "'a => 'a" which expects the old value of the variable
* being updated, and returns a new value.
*
* We now want to convert this into a value of type "'a", returning
* the new value. We do this by applying "(field_' s)" to the
* function f, followed by normalisation.
*)
val getter =
case (Symtab.lookup (#var_getters prog_info) var_name) of
SOME v => v
| NONE => Utils.invalid_input "valid local variable getter" var_name
val modify_val = betapply (modify_val, getter $ dummy_state)
|> Envir.beta_eta_contract
(*
* We are now in the form of "foo dummy_state". Pull out
* our dummy state variable, and parse the expression.
*)
fun remove_dummy_state_var t = Utils.abs_over "s" dummy_state t
val (vars, globals_used, modify_val) = parse_expr ctxt prog_info name_map (remove_dummy_state_var modify_val)
in
((var_name, var_type), vars, globals_used, modify_val, remove_dummy_state_var s)
end
in
fun parse_modify ctxt prog_info name_map term =
let
val dummy_state = Free ("_dummy_state", #state_type prog_info)
in
if Envir.beta_eta_contract (term $ dummy_state) = dummy_state then
[]
else
let
val (updated_var, read_vars, reads_globals, term, residual) = parse_modify' ctxt prog_info name_map term
in
(updated_var, read_vars, reads_globals, term) :: parse_modify ctxt prog_info name_map residual
end
end
end
(*
* Construct precondition from variable set.
*
* These preconditions are of the form:
*
* "(%s. n_' s = n) and (%s. i_' s = i) and ..."
*)
fun mk_precond prog_info name_map vars =
let
val myvarsT = #state_type prog_info
val dummy_state = Free ("_dummy_state", myvarsT)
(* Fetch a variable getter, such as "n_'" from a variable's name. *)
fun var_getter var =
case Symtab.lookup (#var_getters prog_info) var of
SOME x => (x $ dummy_state)
| NONE => Utils.invalid_input "valid local variable name" var
in
Utils.chain_preds myvarsT
(map (fn (var_name, var_type) => Utils.abs_over "s" dummy_state
(HOLogic.mk_eq (var_getter var_name, name_map (var_name, var_type))))
(dest_set vars))
end
(*
* Construct extraction functions, of the form:
*
* "%s. (a_' s, b_' s, c_' s)"
*)
fun mk_xf (prog_info : ProgramInfo.prog_info) vars =
let
val dummy_state = Free ("_dummy_state", #state_type prog_info)
fun var_getter var =
((Symtab.lookup (#var_getters prog_info) var |> the) $ dummy_state)
handle Option => (Utils.invalid_input "valid local variable name" var)
in
Utils.abs_over "s" dummy_state
(HOLogic.mk_tuple (dest_set vars |> map fst |> map var_getter))
end
(*
* Construct a correspondence lemma between a given L2 and L1 terms.
*)
fun mk_corresXF_prop thy prog_info name_map return_vars except_vars precond_vars l2_term l1_term =
let
(* Construct precondition and extraction functions. *)
val precond = mk_precond prog_info name_map precond_vars
val return_xf = mk_xf prog_info return_vars
val except_xf = mk_xf prog_info except_vars
in
Utils.mk_term thy @{term L2corres} [#globals_getter prog_info, return_xf,
except_xf, precond, l2_term, l1_term]
|> HOLogic.mk_Trueprop
end
(*
* Prove correspondence between L1 and L2.
*
* ctxt: Local theory context
*
* return_vars: Variables that are returned by the abstract spec's monad.
*
* except_vars: Variables that are thrown by the abstract spec's monad.
*
* precond_vars: Variables that must match between abstract and concrete.
*
* l2_term / l1_term: Abstract and concrete specs.
*)
fun mk_corresXF_thm ctxt prog_info name_map return_vars except_vars precond_vars l2_term l1_term tac =
let
val free_vars = precond_vars |> dest_set |> map name_map
val free_names = map (dest_Free #> fst) free_vars
in
mk_corresXF_prop (Proof_Context.theory_of ctxt) prog_info name_map
return_vars except_vars precond_vars l2_term l1_term
|> (fn x => Goal.prove ctxt free_names [] x (fn _ => tac))
end
fun mk_corresXF_thm' ctxt prog_info name_map return_vars except_vars precond_vars l2_term l1_term thm =
mk_corresXF_thm ctxt prog_info name_map return_vars except_vars precond_vars l2_term l1_term (
(rewrite_goal_tac ctxt [mk_meta_eq @{thm split_def}] 1)
THEN
(resolve_tac ctxt [rewrite_rule ctxt [mk_meta_eq @{thm split_def}] thm] 1)
THEN
(REPEAT (CHANGED (asm_full_simp_tac (setup_l2_ss ctxt) 1)))
)
fun l1call_function_const t = case strip_comb t |> apsnd rev of
(Const c, (Const c' :: _)) => if String.isSuffix "_'proc" (fst c')
then Const c' else Const c
| (Const c, _) => Const c
| (Abs (_, _, t), []) => l1call_function_const t
| _ => raise TERM ("l1call_function_const", [t])
(*
* Parse an L1 term.
*
* In particular, we break down the structure of the program and parse the
* usage of local variables in all expressions and modifies clauses.
*)
fun parse_l1 ctxt prog_info l1_infos l1_call_info name_map term =
case term of
(Const (@{const_name "L1_skip"}, _)) =>
Modify (term,
(SOME (Abs ("s", #globals_type prog_info, @{term "()"})), empty_set, false), NONE)
| (Const (@{const_name "L1_modify"}, _) $ m) =>
let
val parsed_clause = parse_modify ctxt prog_info name_map m
val (updated_var, read_vars, is_globals_reader, parsed_expr) =
case parsed_clause of
[x] => x
| _ => Utils.invalid_term "Modifies clause too complex." m
in
Modify (term, (parsed_expr, make_set read_vars, is_globals_reader), SOME updated_var)
end
| (Const (@{const_name "L1_seq"}, _) $ lhs $ rhs) =>
Seq (term, parse_l1 ctxt prog_info l1_infos l1_call_info name_map lhs,
parse_l1 ctxt prog_info l1_infos l1_call_info name_map rhs)
| (Const (@{const_name "L1_catch"}, _) $ lhs $ rhs) =>
Catch (term, parse_l1 ctxt prog_info l1_infos l1_call_info name_map lhs,
parse_l1 ctxt prog_info l1_infos l1_call_info name_map rhs)
| (Const (@{const_name "L1_guard"}, _) $ c) =>
let
val (read_vars, is_globals_reader, parsed_expr) = parse_expr ctxt prog_info name_map c
in
Guard (term, (parsed_expr, make_set read_vars, is_globals_reader))
end
| (Const (@{const_name "L1_throw"}, _)) =>
Throw term
| (Const (@{const_name "L1_condition"}, _) $ cond $ lhs $ rhs) =>
let
(* Parse the conditional. *)
val (read_vars, is_globals_reader, parsed_expr) = parse_expr ctxt prog_info name_map cond
in
Condition (term, (parsed_expr, make_set read_vars, is_globals_reader),
parse_l1 ctxt prog_info l1_infos l1_call_info name_map lhs,
parse_l1 ctxt prog_info l1_infos l1_call_info name_map rhs)
end
| (Const (@{const_name "L1_call"}, L1_call_type)
$ arg_setup $ dest_fn_term $ globals_extract $ ret_extract) =>
let
(* Parse arg setup. We treat this not as a modify, but as several
* expressions, as the modified variables are only in the scope of
* this L1_call command. *)
val arg_setup_exprs = parse_modify ctxt prog_info name_map arg_setup
|> map (fn (_, read_vars, is_globals_reader, term) =>
(term, make_set read_vars, is_globals_reader))
val dest_fn_term = case dest_fn_term of
Const (@{const_name "measure_call"}, _) $ f => f
| _ => dest_fn_term
(* Get the name of the variable the return value of the function will
* be placed into. *)
val dest_fn = Termtab.lookup (#const_to_function l1_call_info) (l1call_function_const dest_fn_term)
|> Utils.the' ("Unknown function " ^ quote (@{make_string} dest_fn_term))
|> Symtab.lookup l1_infos |> the
(* Parse the return arguments. *)
val ret_var = get_ret_var prog_info l1_infos (#name dest_fn)
val parsed_clause =
parse_modify ctxt prog_info name_map (betapply (ret_extract, Free ("_dummy_state", #state_type prog_info)))
|> map (fn (target_var, read_vars, globals_read, expr) =>
(target_var, (make_set read_vars) MINUS (make_set [ret_var]),
globals_read, Option.map (Utils.abs_over "ret" (name_map ret_var)) expr))
val (ret_expr, updated_var) =
case parsed_clause of
[(target_var, read_vars, globals_read, expr)] =>
((expr, read_vars, globals_read), SOME target_var)
| [] => ((NONE, empty_set, false), NONE)
| x => Utils.invalid_input "single return param" (@{make_string} x)
in
Call (term, arg_setup_exprs, ret_expr, updated_var, ())
end
| (Const (@{const_name "L1_while"}, _) $ cond $ body) =>
let
(* Parse conditional. *)
val (read_vars, is_globals_reader, parsed_expr) = parse_expr ctxt prog_info name_map cond;
in
While (term, (parsed_expr, make_set read_vars, is_globals_reader),
parse_l1 ctxt prog_info l1_infos l1_call_info name_map body)
end
| (Const (@{const_name "L1_init"}, _) $ setter) =>
let
val updated_var = ProgramInfo.guess_var_name_type_from_setter_term setter
in
Init (term, SOME updated_var)
end
| (Const (@{const_name "L1_spec"}, _) $ c) =>
(case parse_spec ctxt prog_info c of
SOME x =>
Spec (term, (SOME x, empty_set, true))
| NONE =>
Spec (term, (NONE, empty_set, true)))
| (Const (@{const_name "L1_fail"}, _)) =>
Fail term
| (Const (@{const_name "L1_recguard"}, _) $ var $ body) =>
RecGuard (term, parse_l1 ctxt prog_info l1_infos l1_call_info name_map body)
| other => Utils.invalid_term "a L1 term" other
(*
* Generate a proof showing that a particular variables "var" is not modified
* over the given input L1 term.
*)
fun mk_preservation_proof ctxt prog_info name_map var term =
let
val thy = Proof_Context.theory_of ctxt
(* Apply a tactic then simplify all remaining subgoals. *)
fun s tac =
tac THEN (TRY (REPEAT (CHANGED (asm_full_simp_tac (setup_l2_ss ctxt) 1))))
(* Apply a rule then simplify all remaining subgoals. *)
fun r thm = s (resolve_tac ctxt [thm] 1)
(* Generate the predicate. *)
val var_set = make_set [var]
val precond = mk_precond prog_info name_map var_set
val postcond_ret = absdummy @{typ unit} (mk_precond prog_info name_map var_set)
val postcond_ex = absdummy @{typ unit} (mk_precond prog_info name_map var_set)
val goal =
Utils.mk_term thy @{term validE} [precond, term, postcond_ret, postcond_ex]
|> HOLogic.mk_Trueprop
(* Construct a tactic that solves the problem. *)
val tac =
(case term of
(Const (@{const_name "L1_skip"}, _)) =>
r @{thm L1_skip_lp}
| (Const (@{const_name "L1_init"}, _) $ _) =>
r @{thm L1_init_lp}
| (Const (@{const_name "L1_modify"}, _) $ _) =>
r @{thm L1_modify_lp}
| (Const (@{const_name "L1_call"}, _) $ _ $ _ $ _ $ _) =>
r @{thm L1_call_lp}
| (Const (@{const_name "L1_guard"}, _) $ _) =>
r @{thm L1_guard_lp}
| (Const (@{const_name "L1_throw"}, _)) =>
r @{thm L1_throw_lp}
| (Const (@{const_name "L1_spec"}, _) $ _) =>
r @{thm hoareE_TrueI}
| (Const (@{const_name "L1_fail"}, _)) =>
r @{thm L1_fail_lp}
| (Const (@{const_name "L1_while"}, _) $ _ $ body) =>
let
val body' = mk_preservation_proof ctxt prog_info name_map var body
in
s (resolve_tac ctxt @{thms L1_while_lp} 1 THEN resolve_tac ctxt [body'] 1)
end
| (Const (@{const_name "L1_condition"}, _) $ _ $ lhs $ rhs) =>
let
val lhs' = mk_preservation_proof ctxt prog_info name_map var lhs
val rhs' = mk_preservation_proof ctxt prog_info name_map var rhs
in
s (resolve_tac ctxt @{thms L1_condition_lp} 1 THEN resolve_tac ctxt [lhs'] 1 THEN resolve_tac ctxt [rhs'] 1)
end
| (Const (@{const_name "L1_seq"}, _) $ lhs $ rhs) =>
let
val lhs' = mk_preservation_proof ctxt prog_info name_map var lhs
val rhs' = mk_preservation_proof ctxt prog_info name_map var rhs
in
s (resolve_tac ctxt @{thms L1_seq_lp} 1 THEN resolve_tac ctxt [lhs'] 1 THEN resolve_tac ctxt [rhs'] 1)
end
| (Const (@{const_name "L1_catch"}, _) $ lhs $ rhs) =>
let
val lhs' = mk_preservation_proof ctxt prog_info name_map var lhs
val rhs' = mk_preservation_proof ctxt prog_info name_map var rhs
in
s (resolve_tac ctxt @{thms L1_catch_lp} 1 THEN resolve_tac ctxt [lhs'] 1 THEN resolve_tac ctxt [rhs'] 1)
end
| (Const (@{const_name "L1_recguard"}, _) $ _ $ body) =>
let
val body' = mk_preservation_proof ctxt prog_info name_map var body
in
s (resolve_tac ctxt @{thms L1_recguard_lp} 1 THEN resolve_tac ctxt [body'] 1)
end
| other => Utils.invalid_term "a L1 term" other)
in
(* Generate proof. *)
Thm.cterm_of ctxt goal
|> Goal.init
|> Utils.apply_tac ("proving variable preservation for var '" ^ (fst var) ^ "'") tac
|> Goal.finish ctxt
end
(* Generate a preservation proof for multiple variables. *)
fun mk_multivar_preservation_proof ctxt prog_info name_map term var_set =
let
val proofs = map (fn x =>
mk_preservation_proof ctxt prog_info name_map x term)
(dest_set var_set)
val result = fold (fn x => fn y => @{thm combine_validE} OF [x,y])
proofs @{thm hoareE_TrueI}
in
result
end
handle Option => error ("Preservation proof failed for " ^ quote (@{make_string} var_set))
(*
* Generate a well-typed L2 monad expression.
*
* "const" is the name of the monadic function (e.g., @{const_name "L2_gets"})
*
* "ret"/"throw" are the variables being returned or thrown by this monadic
* expression. This is used only for determining the type of the output
* monad.
*
* "params" are the expressions to be beta applied to the monad.
*)
fun mk_l2monad (prog_info : ProgramInfo.prog_info) const ret throw params =
let
val retT = HOLogic.mk_tupleT (dest_set ret |> map snd)
val exT = HOLogic.mk_tupleT (dest_set throw |> map snd)
val monadT = mk_l2monadT (#globals_type prog_info) retT exT
in
betapplys ((Const (const, (map fastype_of params) ---> monadT)), params)
end
(* Abstract over a tuple using the given name map. *)
fun abs_over_tuple_vars (name_map : (string * typ) -> term) (vars : varset) =
Utils.abs_over_tuple (map (fn (a, b) => (a, name_map (a, b))) (dest_set vars))
(*
* Take an L2corres theorem of the form:
*
* L2corres st ret_xf ex_xf P (foo a b c) X
*
* and convert it into the form:
*
* L2corres st ret_xf ex_xf P ((%(a, b, c). foo a b c) (a, b, c)) X
*
* This is used to ease unification in proofs where the abstract monad is
* expected to be of the form "A x", where "x" is the return value of another
* monad.
*)
fun abs_over_thm ctxt (name_map : (string * typ) -> term) (thm : thm) (vars : varset) =
let
fun convert_var_to_free x =
case x of
Var ((a, _), t) => Free (a, t)
| x => x
fun convert_free_to_var x =
case x of
Free (a, t) => Var ((a, 0), t)
| x => x
val @{term_pat "?head \<comment> \<open>L2corres\<close> ?st ?ret_xf ?ex_xf ?precond ?l2_term ?l1_term"} =
map_aterms convert_var_to_free (Thm.concl_of thm) |> HOLogic.dest_Trueprop
val new_l2_term = (abs_over_tuple_vars name_map vars l2_term
$ Free ("r'", HOLogic.mk_tupleT (dest_set vars |> map snd)))
val new_concl =
head $ st $ ret_xf $ ex_xf $ precond $ new_l2_term $ l1_term
|> map_aterms convert_free_to_var
|> HOLogic.mk_Trueprop
val new_thm = list_implies (cprems_of thm, Thm.cterm_of ctxt new_concl)
in
Goal.init new_thm
|> asm_full_simp_tac (put_simpset HOL_basic_ss ctxt addsimps [mk_meta_eq @{thm split_def}]) 1 |> Seq.hd
|> resolve_tac ctxt [rewrite_rule ctxt [mk_meta_eq @{thm split_def}] thm] 1 |> Seq.hd
|> REPEAT (assume_tac ctxt 1) |> Seq.hd
|> Goal.finish ctxt
end
(*
* Given a L2 monad that returns the variables "vars_returned", convert it into
* an L2 monad that returns "needed_returns".
*
* This is frequently needed when a particular monad is only capable of returning
* a particular variable (or set of variables), but needs to return a different set
* of these variables. For example, both branches in an "condition" block need
* to return the same set of variables.
*
* The injection is done by (if necessary) appending an additional "L2_seq" to
* the input monad, returning the desired set of variables.
*
* "allow_excess" is the output monad is allowed to return a superset of
* "needed_returns". By allowing such excess variables to be returned, the
* generated output can be neater than if we were more strict.
*)
fun inject_return_vals ctxt prog_info name_map needed_returns allow_excess throw_vars fn_vars
term (vars_read, vars_returned, output_monad, thm) =
if needed_returns = vars_returned then
(* We already have precisely what is needed --- no more to do. *)
(vars_read, vars_returned, output_monad, thm)
else if (allow_excess andalso Varset.subset (needed_returns, vars_returned)) then
(* We already provide a superset of what is needed, and this is allowed. *)
(vars_read, vars_returned, output_monad, thm)
else
let
val (l1_term, _, _) = get_node_data term
(* Generate the return statement. *)
val injected_return =
mk_l2monad prog_info @{const_name L2_gets} needed_returns throw_vars
[absdummy (#globals_type prog_info) (HOLogic.mk_tuple (dest_set needed_returns |> map name_map)),
var_set_to_isa_list prog_info needed_returns]
|> abs_over_tuple_vars name_map vars_returned
(* Append the return statement to the input term. *)
val generated_term = mk_l2monad prog_info @{const_name L2_seq}
needed_returns throw_vars [output_monad, injected_return]
val preserved_vals = needed_returns MINUS vars_returned
(* Generate a proof of correctness. *)
val generated_thm =
let
val preserve_proof = mk_multivar_preservation_proof ctxt prog_info name_map l1_term preserved_vals
in
mk_corresXF_thm' ctxt prog_info name_map needed_returns throw_vars (vars_read UNION preserved_vals)
generated_term l1_term
(@{thm L2corres_inject_return} OF [thm, @{thm hoare_chainE} OF [preserve_proof]])
end
in
(vars_read UNION preserved_vals, needed_returns, generated_term, generated_thm)
end
(*
* Convert an L1 function into an L2 function.
*
* We assume that our input term has come out of the L1 conversion functions.
*
* We have inputs of the following:
*
* ctxt: Isabelle context
*
* needed_vars:
*
* Variables that are read in later executions.
*
* These are passed into the conversion so that we know what variables
* we need to track for later execution, and what variables we can
* just discard on the spot.
*
* If we didn't know what we actually needed to track, then the
* converted code would be significantly bloated due to returning
* variables that aren't actually used.
*
* allow_excess:
*
* Are we allowed to return _more_ variables than otherwise needed
* according to needed_vars? By setting this to true, more efficient
* code can be generated.
*
* throw_vars:
*
* Variables that must be thrown in the event we decide to emit an
* "L2_throw" call. These are calculated as we enter a try/catch block
* to ensure that all sites are consistent in the values they throw.
*
* term: The L1 term to convert.
*
* The return value of this function is a tuple:
*
* (<vars read by block>, <vars returned>, <term>, <proof>)
*
* The "vars returned" is the variables that are returned through the "bind"
* combinator.
*)
fun do_conv
(ctxt : Proof.context)
prog_info
(l1_infos : FunctionInfo.function_info Symtab.table)
(l1_call_info : FunctionInfo.call_graph_info)
name_map
(fn_vars : varset)
(callee_proofs : (bool * term * thm) Symtab.table)
(needed_vars : varset)
(allow_excess : bool)
(throw_vars : varset)
(term : (term * varset * varset, term option * varset * bool, (string * typ) option, unit) prog)
: (varset * varset * term * thm) =
let
val l1_term = get_node_data term |> #1
val live_vars = get_node_data term |> #2
val modified_vars = get_node_data term |> #3
val inject =
inject_return_vals ctxt prog_info name_map needed_vars allow_excess throw_vars fn_vars term
fun mkthm read_vars ret_vars generated_term thm =
mk_corresXF_thm' ctxt prog_info name_map ret_vars throw_vars read_vars generated_term l1_term thm
val mk_monad = mk_l2monad prog_info
in
case term of
Init (_, SOME output_var) =>
let
val out_vars = make_set [output_var]
val generated_term = mk_monad @{const_name L2_unknown} out_vars throw_vars
[Utils.ml_str_list_to_isa [fst output_var]]
val thm = mkthm empty_set out_vars generated_term @{thm L2corres_spec_unknown}
in
inject (empty_set, out_vars, generated_term, thm)
end
(* L1_skip. *)
| Modify (_, (SOME expr, _, _), NONE) =>
let
val generated_term = mk_monad @{const_name L2_gets}
empty_set throw_vars [expr, var_set_to_isa_list prog_info empty_set]
val thm = mkthm empty_set empty_set generated_term @{thm L2corres_gets_skip}
in
inject (empty_set, empty_set, generated_term, thm)
end
(* L1_modify with unparsable expression. *)
| Modify (_, (NONE, _, _), SOME output_var) =>
let
val out_vars = make_set [output_var]
val generated_term = mk_monad @{const_name L2_unknown} out_vars throw_vars []
val thm = mkthm empty_set out_vars generated_term @{thm L2corres_modify_unknown}
in
inject (empty_set, out_vars, generated_term, thm)
end
(* L1_modify that only modifies globals. *)
| Modify (_, (SOME expr, read_vars, _), SOME ("globals'", _)) =>
let
val generated_term = mk_monad @{const_name L2_modify} empty_set throw_vars [expr]
val thm = mkthm read_vars empty_set generated_term @{thm L2corres_modify_global}
in
inject (read_vars, empty_set, generated_term, thm)
end
(* L1_modify that only modifies a local and also reads globals. *)
| Modify (_, (SOME expr, read_vars, _), SOME output_var) =>
let
val generated_term = mk_monad @{const_name L2_gets}
(make_set [output_var]) throw_vars [expr, var_set_to_isa_list prog_info (make_set [output_var])]
val thm = mkthm read_vars (make_set [output_var]) generated_term @{thm L2corres_modify_gets}
in
inject (read_vars, make_set [output_var], generated_term, thm)
end
| Throw _ =>
let
val generated_term = mk_monad @{const_name L2_throw} needed_vars throw_vars
[HOLogic.mk_tuple (dest_set throw_vars |> map name_map),
var_set_to_isa_list prog_info throw_vars]
val thm = mkthm throw_vars needed_vars generated_term @{thm L2corres_throw}
in
(throw_vars, needed_vars, generated_term, thm)
end
| Spec (_, (SOME expr, read_vars, _)) =>
let
val generated_term = mk_monad @{const_name "L2_spec"} needed_vars throw_vars [expr]
val thm = mkthm read_vars needed_vars generated_term @{thm L2corres_spec}
in
inject (read_vars, needed_vars, generated_term, thm)
end
| Spec (_, (NONE, _, _)) =>
let
val generated_term = mk_monad @{const_name "L2_fail"} needed_vars throw_vars []
val thm = mkthm empty_set needed_vars generated_term @{thm L2corres_fail}
in
inject (empty_set, needed_vars, generated_term, thm)
end
| Guard (_, (SOME expr, read_vars, _)) =>
let
val generated_term = mk_monad @{const_name "L2_guard"} empty_set throw_vars [expr]
val thm = mkthm read_vars empty_set generated_term @{thm L2corres_guard}
in
inject (read_vars, empty_set, generated_term, thm)
end
| Guard (_, (NONE, _, _)) =>
let
val generated_term = mk_monad @{const_name "L2_fail"} needed_vars throw_vars []
val thm = mkthm empty_set needed_vars generated_term @{thm L2corres_fail}
in
(empty_set, needed_vars, generated_term, thm)
end
| Fail _ =>
let
val generated_term = mk_monad @{const_name "L2_fail"} needed_vars throw_vars []
val thm = mkthm empty_set needed_vars generated_term @{thm L2corres_fail}
in
(empty_set, needed_vars, generated_term, thm)
end
| Seq (_, lhs, rhs) =>
let
val (_, rhs_live, rhs_modified) = get_node_data rhs
val (lhs_term, _, lhs_modified) = get_node_data lhs
(* Convert LHS and RHS. *)
val ret_vars = rhs_live INTER lhs_modified
val (lhs_reads, lhs_rets, new_lhs, lhs_thm)
= do_conv ctxt prog_info l1_infos l1_call_info name_map
fn_vars callee_proofs ret_vars true throw_vars lhs
val (rhs_reads, rhs_rets, new_rhs, rhs_thm)
= do_conv ctxt prog_info l1_infos l1_call_info name_map
fn_vars callee_proofs needed_vars allow_excess throw_vars rhs
val block_reads = lhs_reads UNION (rhs_reads MINUS lhs_modified)
(* Reconstruct body to support our input tuple. *)
val rhs_thm = abs_over_thm ctxt name_map rhs_thm lhs_rets
val new_rhs = abs_over_tuple_vars name_map lhs_rets new_rhs
(* Generate the final term. *)
val generated_term = mk_monad @{const_name L2_seq} rhs_rets throw_vars [new_lhs, new_rhs]
(* Generate a proof. *)
val thm =
let
(* Show that certain variables are preserved by the LHS. *)
val needed_preserves = (rhs_reads MINUS lhs_modified)
val preserve_proof = mk_multivar_preservation_proof ctxt prog_info name_map lhs_term needed_preserves
in
mkthm block_reads rhs_rets generated_term
(@{thm L2corres_seq} OF [lhs_thm, rhs_thm,
@{thm hoare_chainE} OF [preserve_proof]])
end
in
inject (block_reads, rhs_rets, generated_term, thm)
end
| Catch (_, lhs, rhs) =>
let
val (lhs_term, _, lhs_modified) = get_node_data lhs
val (_, rhs_live, _) = get_node_data rhs
(* Convert LHS and RHS. *)
val lhs_throws = rhs_live INTER lhs_modified
val (lhs_reads, lhs_rets, new_lhs, lhs_thm)
= do_conv ctxt prog_info l1_infos l1_call_info name_map
fn_vars callee_proofs (needed_vars) false lhs_throws lhs
val (rhs_reads, _, new_rhs, rhs_thm)
= do_conv ctxt prog_info l1_infos l1_call_info name_map
fn_vars callee_proofs (needed_vars) false throw_vars rhs
val block_reads = lhs_reads UNION (rhs_reads MINUS lhs_throws)
(* Reconstruct body to support our input tuple. *)
val rhs_thm = abs_over_thm ctxt name_map rhs_thm lhs_throws
val new_rhs = abs_over_tuple_vars name_map lhs_throws new_rhs
(* Generate the final term. *)
val generated_term = mk_monad @{const_name L2_catch} needed_vars throw_vars [new_lhs, new_rhs]
(* Generate a proof. *)
val thm =
let
(* Show that certain variables are preserved by the LHS. *)
val needed_preserves = (rhs_reads MINUS lhs_modified)
val preserve_proof = mk_multivar_preservation_proof ctxt prog_info name_map lhs_term needed_preserves
in
mkthm block_reads needed_vars generated_term
(@{thm L2corres_catch} OF [lhs_thm, rhs_thm, @{thm hoare_chainE} OF [preserve_proof]])
end
in
inject (block_reads, needed_vars, generated_term, thm)
end
| RecGuard (_, body) =>
let
(* Convert body. *)
val (body_reads, vars_returned, new_body, body_thm) =
do_conv ctxt prog_info l1_infos l1_call_info name_map
fn_vars callee_proofs needed_vars false throw_vars body