-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathSimplify.ml
2117 lines (1826 loc) · 78.7 KB
/
Simplify.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 (c) INRIA and Microsoft Corporation. All rights reserved. *)
(* Licensed under the Apache 2.0 and MIT Licenses. *)
(** This module rewrites the original AST to send it into Low*, the subset we
* know how to translate to C. *)
open Ast
open DeBruijn
open Warn
open PrintAst.Ops
open Helpers
(* A visitor that determines whether it is safe to substitute bound variable 0
* with its definition, assuming that:
* - said definition is read-only
* - there is a single use of it in the continuation.
* The notion of "safe" can be customized; here, we let EBufRead be a safe node,
* so by default, safe means read-only. *)
type use = Safe | SafeUse | Unsafe
class ['self] safe_use = object (self: 'self)
inherit [_] reduce as super
(* By default, everything is safe. We override several cases below. *)
method private zero = Safe
(* The default composition rule; it only applies if the expression itself is
* safe. (For instance, this is not a valid composition rule for an
* EBufWrite node.) *)
method private plus x y =
match x, y with
| Safe, SafeUse
| SafeUse, Safe ->
SafeUse
| Safe, Safe ->
Safe
| SafeUse, SafeUse ->
failwith "this contradicts the use-analysis (1)"
| _ ->
Unsafe
method private expr_plus_typ = self#plus
method! extend j _ =
j + 1
(* The composition rule for [es] expressions, whose evaluation order is
* unspecified, but is known to happen before an unsafe expression. The only
* safe case is if the use is found among safe nodes. *)
method private unordered (j, _) es =
let es = List.map (self#visit_expr_w j) es in
let the_use, the_rest = List.partition ((=) SafeUse) es in
match List.length the_use, List.for_all ((=) Safe) the_rest with
| 1, true -> SafeUse
| x, _ when x > 1 -> failwith "this contradicts the use-analysis (2)"
| _ -> Unsafe
(* The sequential composition rule, where [e1] is known be to be
* sequentialized before [e2]. Two cases: the use is found in [e1] (and we
* don't care what happens in [e2]), otherwise, just a regular composition.
* Note: e2 = Some _ if and only if this is a let-node (hence the + 1). *)
method private sequential (j, _) e1 e2 =
match self#visit_expr_w j e1, e2 with
| SafeUse, _ -> SafeUse
| x, Some e2 ->
self#plus x (self#visit_expr_w (j + 1) e2)
| _, None ->
(* We don't know what comes after; can't conclude anything. *)
Unsafe
method! visit_EBound (j, _) i = if i = j then SafeUse else Safe
method! visit_EBufWrite env e1 e2 e3 = self#unordered env [ e1; e2; e3 ]
method! visit_EBufFill env e1 e2 e3 = self#unordered env [ e1; e2; e3 ]
method! visit_EBufBlit env e1 e2 e3 e4 e5 = self#unordered env [ e1; e2; e3; e4; e5 ]
method! visit_EBufFree env e = self#sequential env e None
method! visit_EAssign env e1 e2 = self#unordered env [ e1; e2 ]
method! visit_EApp env e es =
match e.node with
| EOp _ -> super#visit_EApp env e es
| EQualified _ when Helpers.is_readonly_builtin_lid e -> super#visit_EApp env e es
| ETApp ({ node = EQualified _; _ } as e, _, _, _) when Helpers.is_readonly_builtin_lid e -> super#visit_EApp env e es
| _ -> self#unordered env (e :: es)
method! visit_ELet env _ e1 e2 = self#sequential env e1 (Some e2)
(* All of the cases below could be refined with a more precise analysis that makes sure that
*every* path in the control-flow is safe. *)
method! visit_EIfThenElse env e _ _ = self#sequential env e None
method! visit_ESwitch env e _ = self#sequential env e None
method! visit_EWhile env e _ = self#sequential env e None
method! visit_EFor env _ e _ _ _ = self#sequential env e None
method! visit_EMatch env _ e _ = self#sequential env e None
method! visit_ESequence env es = self#sequential env (List.hd es) None
end
let safe_readonly_use e =
match (new safe_use)#visit_expr_w 0 e with
| SafeUse -> true
| Unsafe -> false
| Safe -> failwith "F* isn't supposed to nest uu__'s this deep, how did we miss it?"
class ['self] safe_pure_use = object (self: 'self)
inherit [_] reduce as super
inherit [_] safe_use
method! visit_EBufRead env e1 e2 = self#unordered env [ e1; e2 ]
method! visit_EApp env e es =
match e.node with
| EOp _ -> super#visit_EApp env e es
| _ -> self#unordered env (e :: es)
end
let safe_pure_use e =
match (new safe_pure_use)#visit_expr_w 0 e with
| SafeUse -> true
| Unsafe -> false
| Safe -> failwith "F* isn't supposed to nest uu__'s this deep, how did we miss it?"
(* This phase tries to substitute away variables that are temporary (named
uu____ in F*, or "scrut" if inserted by the pattern matches compilation
phase), or that are of a type that cannot be copied (to hopefully skirt
future failures). This phase assumes the mark field of each binder contains a
conservative approximation of the number of uses of that binder. *)
let use_mark_to_inline_temporaries = object (self)
inherit [_] map
method! visit_ELet _ b e1 e2 =
let e1 = self#visit_expr_w () e1 in
let e2 = self#visit_expr_w () e2 in
let _, v = !(b.node.mark) in
if (b.node.attempt_inline ||
Helpers.is_uu b.node.name ||
b.node.name = "scrut" ||
Structs.should_rewrite b.typ = NoCopies
) &&
v = AtMost 1 && (
is_readonly_c_expression e1 &&
safe_readonly_use e2 ||
safe_pure_use e2
) (* || is_readonly_and_variable_free_c_expression e1 && b.node.mut *)
then
(* Don't drop a potentially useful comment into the ether *)
let e1 = { e1 with meta = e1.meta @ b.meta } in
(DeBruijn.subst e1 0 e2).node
else
ELet (b, e1, e2)
end
(* PPrint.(Print.(print (PrintAst.print_files files ^^ hardline))); *)
let optimize_lets ?ifdefs files =
let open UseAnalysis in
let _ = (build_usage_map_and_mark ifdefs)#visit_files [] files in
let files = (use_mark_to_remove_or_ignore (not (ifdefs = None)))#visit_files () files in
let files = use_mark_to_inline_temporaries#visit_files () files in
files
(* Unused parameter elimination ***********************************************)
(* Done by typing (in general), and based on usage information (strictly for
* static, first-order functions. Relies on an accurate count from
* count_and_remove_locals. *)
(* Build a table that maps each lid to a list of booleans, where true indicates
* that the parameter is unused in the body. *)
let unused_parameter_table = object (_)
inherit [_] iter
method! visit_DFunction parameter_table _ flags _ _ _ name binders _ =
let is_private = List.mem Common.Private flags && not (Helpers.is_static_header name) in
if is_private then
let unused_vector = List.map (fun b -> !(b.node.mark)) binders in
Hashtbl.add parameter_table name unused_vector
end
(* Remove entries in the table if they appear anywhere other than the head of an
* application. *)
let ignore_non_first_order = object (self)
inherit [_] iter
method! visit_EApp env e es =
List.iter (self#visit_expr env) es;
let parameter_table, _ = env in
let _, ts = Helpers.flatten_arrow e.typ in
match e.node with
| EQualified lid ->
(* partial applications are not first-order... may be overly
* conservative with higher-order code that really means to return a
* function pointer but that's not the end of the world *)
if List.length es <> List.length ts then
Hashtbl.remove parameter_table lid
| _ -> self#visit_expr env e
method! visit_EQualified (parameter_table, _) lid =
if Hashtbl.mem parameter_table lid then
Hashtbl.remove parameter_table lid
end
(* Now we are ready to effectively remove unused parameters *)
let implies x y =
not x || y
(* Subtlety: we decline to remove the first parameter if all others are removed,
* since we don't have zero-argument functions at this stage. *)
let unused private_count_table lid ts (i: int) =
(* Is the parameter at index i unused? *)
let unused_i i =
(* First case: private function (i.e. in the table) that is also unused *)
Hashtbl.mem private_count_table lid && (
let l = Hashtbl.find private_count_table lid in
i < List.length l &&
snd (List.nth l i) = Mark.AtMost 0
) ||
(* Second case: it's a unit, so here type-based elimination *)
List.nth ts i = TUnit
in
unused_i i &&
implies (i = 0) (List.exists not (List.init (List.length ts) unused_i))
(* TODO: this is not a fixed point computation, meaning some opportunities for
unused parameter elimination are missed *)
let remove_unused_parameters = object (self)
inherit [_] map
val mutable current_lid = [], ""
method! extend (table, j) _ =
table, j + 1
method! visit_DFunction (parameter_table, _) cc flags n_cgs n ret name binders body =
current_lid <- name;
(* Visit first: this may create more unused parameters and modify
* parameter_table. *)
let body = self#visit_expr_w (parameter_table, 0) body in
(* Then eliminate stuff. *)
let binders = self#visit_binders_w (parameter_table, 0) binders in
let ret = self#visit_typ (parameter_table, 0) ret in
let n_binders = List.length binders in
let ts = List.map (fun b -> b.typ) binders in
let unused = unused parameter_table name ts in
let body = List.fold_left (fun body i ->
if unused i then begin
DeBruijn.subst eunit (n_binders - 1 - i) body
end else
body
) body (List.init n_binders (fun i -> i)) in
let binders = KList.filter_mapi (fun i b -> if unused i then None else Some b) binders in
DFunction (cc, flags, n_cgs, n, ret, name, binders, body)
method! visit_DExternal (parameter_table, _ as env) cc flags n_cg n name t hints =
let ret, args = flatten_arrow t in
let hints = KList.filter_mapi (fun i arg ->
if unused parameter_table dummy_lid args i then
None
else
Some arg
) hints in
let args = KList.filter_mapi (fun i arg ->
if unused parameter_table dummy_lid args i then
None
else
Some (self#visit_typ env arg)
) args in
let ret = self#visit_typ env ret in
let t = fold_arrow args ret in
DExternal (cc, flags, n_cg, n, name, t, hints)
method! visit_TArrow (parameter_table, _ as env) t1 t2 =
(* Important: the only entries in `parameter_table` are those which are
* first order, i.e. for which the only occurrence is under an EApp, which
* does *not* recurse into visit_TArrow! *)
let t1 = self#visit_typ env t1 in
let t2 = self#visit_typ env t2 in
let ret, args = flatten_arrow (TArrow (t1, t2)) in
let args = KList.filter_mapi (fun i arg ->
if unused parameter_table dummy_lid args i then
None
else
Some arg
) args in
fold_arrow args ret
method private update_table_current private_use_table unused i es =
(* We are currently rewriting the body of [f], which is eligible for
* use-based unused parameter elimination (because it's private). *)
if Hashtbl.mem private_use_table current_lid then
let l = List.length (Hashtbl.find private_use_table current_lid) in
List.iteri (fun arg_i e ->
match e.node with
(* We are currently looking at a function call. The arguments are [es]
* and each [unused] contains a matching boolean for each one of the [es]. *)
| EBound j when j >= i && unused arg_i ->
(* We're about to eliminate [EBound j], which refers to a formal
* parameter of [f]. This is an opportunity to update the entry for
* [f]. *)
Hashtbl.replace private_use_table current_lid
(List.mapi (fun k (o, Mark.AtMost count) ->
(* 0 is the last parameter, l - 1 the first *)
if k == l - 1 - (j - i) then begin
assert (count > 0);
o, Mark.AtMost (count - 1)
end else
o, Mark.AtMost count
) (Hashtbl.find private_use_table current_lid))
| _ ->
(* TODO: we could be smarter here *)
()
) es
method! visit_EApp ((parameter_table, i), _) e es =
let t, ts = flatten_arrow e.typ in
let lid = match e.node with
| EQualified lid -> lid
| _ -> [], ""
in
let unused = unused parameter_table lid ts in
let es = List.map (self#visit_expr_w (parameter_table, i)) es in
self#update_table_current parameter_table unused i es;
(* Three cases:
* - more arguments than the type indicates; it's fairly bad, but happens,
* e.g. because the function is not in scope (no extract, polymorphic
* assume, etc.) -- ignore
* - as many arguments as the type indicates; perform the transformation
* - less arguments than the type indicates: perform the transformation on
* the arguments we have, transform the type nonetheless *)
if List.length es <= List.length ts then
(* Transform the type of the head *)
let used = List.init (List.length ts) (fun i -> not (unused i)) in
let ts = KList.filter_mask used ts in
let ts = List.map (self#visit_typ (parameter_table, 0)) ts in
let e = { e with typ = fold_arrow ts t } in
(* Then transform the arguments, on a possible prefix of used when there's
* a partial application. *)
let used, _ = KList.split (List.length es) used in
let es, to_evaluate = List.fold_left2 (fun (es, to_evaluate) used arg ->
if not used then
if is_readonly_c_expression arg then
es, to_evaluate
else
let x, _atom = mk_binding "unused" arg.typ in
es, (x, arg) :: to_evaluate
else
arg :: es, to_evaluate
) ([], []) used es in
let es = List.rev es in
let to_evaluate = List.rev to_evaluate in
(* Special case: we allow a partial application over an eliminated
* argument to become a reference to a function pointer. Useful for
* functions that take regions but that we still want to use as function
* pointers. *)
let e = self#visit_expr_w (parameter_table, i) e in
let app = if List.length es > 0 then with_type t (EApp (e, es)) else e in
(nest to_evaluate t app).node
else
EApp (self#visit_expr_w (parameter_table, i) e, es)
end
(* Get wraparound semantics for arithmetic operations using casts to uint_* ***)
let wrapping_arithmetic = object (self)
inherit [_] map
(* TODO: this is no longer exposed by F*; check and remove this pass? *)
method! visit_EApp env e es =
match e.node, es with
| EOp (((K.AddW | K.SubW | K.MultW | K.DivW) as op), w), [ e1; e2 ] when K.is_signed w ->
let unsigned_w = K.unsigned_of_signed w in
let e = mk_op (K.without_wrap op) unsigned_w in
let e1 = self#visit_expr env e1 in
let e2 = self#visit_expr env e2 in
let c e = { node = ECast (e, TInt unsigned_w); typ = TInt unsigned_w; meta = [] } in
(** TODO: the second call to [c] is optional per the C semantics, but in
* order to preserve typing, we have to insert it... maybe recognize
* that pattern later on at the C emission level? *)
let unsigned_app = { node = EApp (e, [ c e1; c e2 ]); typ = TInt unsigned_w; meta = [] } in
ECast (unsigned_app, TInt w)
| EOp (((K.AddW | K.SubW | K.MultW | K.DivW) as op), w), [ e1; e2 ] when K.is_unsigned w ->
let e = mk_op (K.without_wrap op) w in
let e1 = self#visit_expr env e1 in
let e2 = self#visit_expr env e2 in
EApp (e, [ e1; e2 ])
| _, es ->
EApp (self#visit_expr env e, List.map (self#visit_expr env) es)
end
let remove_ignore_unit = object
inherit [_] map as super
method! visit_EApp env hd args =
match hd.node, args with
| ETApp ({ node = EQualified (["LowStar"; "Ignore"], "ignore"); _}, _, _, [ TUnit ]), [ { node = EUnit; _ } ] ->
EUnit
| _ ->
super#visit_EApp env hd args
end
let remove_proj_is = object
inherit [_] map
method! visit_DFunction _ cc flags n_cgs n t name binders body =
if KString.starts_with (snd name) "__proj__" || KString.starts_with (snd name) "__is__" then
DFunction (cc, Private :: flags, n_cgs, n, t, name, binders, body)
else
DFunction (cc, flags, n_cgs, n, t, name, binders, body)
end
(* Convert back and forth between [e1; e2] and [let _ = e1 in e2]. ************)
let sequence_to_let = object (self)
inherit [_] map
method! visit_ESequence env es =
let es = List.map (self#visit_expr env) es in
match List.rev es with
| last :: first_fews ->
(List.fold_left (fun cont e ->
{ node = ELet (sequence_binding (), e, lift 1 cont); typ = last.typ; meta = [] }
) last first_fews).node
| [] ->
failwith "[sequence_to_let]: impossible (empty sequence)"
method! visit_EIgnore (env, t) e =
(nest_in_return_pos t (fun _ e -> with_type t (EIgnore (self#visit_expr_w env e))) e).node
end
let let_to_sequence = object (self)
inherit [_] map
method! visit_ELet env b e1 e2 =
match b.node.meta with
| Some MetaSequence ->
let e1 = self#visit_expr env e1 in
let _, e2 = open_binder b e2 in
let e2 = self#visit_expr env e2 in
begin match e1.node, e2.node with
| _, EUnit ->
(* let _ = e1 in () *)
e1.node
| ECast ({ node = EUnit; _ }, _), _
| EUnit, _ ->
(* let _ = () in e2 *)
e2.node
| _, ESequence es ->
ESequence (e1 :: es)
| _ ->
ESequence [e1; e2]
end
| None ->
let e2 = self#visit_expr env e2 in
ELet (b, e1, e2)
end
(* This pass rewrites:
* let x = if ... then e else e'
* into:
* let x = any;
* if ... then
* x <- e
* else
* x <- e'
*
* The code is prettier if we push the assignment under lets, ifs and switches.
* We also rewrite:
* x <- if ... then ...
* the same way. *)
let let_if_to_assign = object (self)
inherit [_] map
method private make_assignment lhs e1 =
let nest_assign = nest_in_return_pos TUnit (fun i innermost -> {
node = EAssign (DeBruijn.lift i lhs, innermost);
typ = TUnit; meta = []
}) in
match e1.node with
| EIfThenElse (cond, e_then, e_else) ->
let e_then = nest_assign (self#visit_expr_w () e_then) in
let e_else = nest_assign (self#visit_expr_w () e_else) in
with_unit (EIfThenElse (cond, e_then, e_else))
| ESwitch (e, branches) ->
let branches = List.map (fun (tag, e) ->
tag, nest_assign (self#visit_expr_w () e)
) branches in
with_unit (ESwitch (e, branches))
| _ ->
invalid_arg "make_assignment"
method! visit_ELet (_, t) b e1 e2 =
match e1.node, b.node.meta with
| (EIfThenElse _ | ESwitch _), None ->
(* [b] holds the return value of the conditional *)
let b = mark_mut b in
let e = self#make_assignment (with_type b.typ (EBound 0)) (DeBruijn.lift 1 e1) in
ELet (b, any, with_type t (
ELet (sequence_binding (), e, DeBruijn.lift 1 (self#visit_expr_w () e2))))
| _ ->
(* This may be a statement-let; visit both. *)
ELet (b, self#visit_expr_w () e1, self#visit_expr_w () e2)
method! visit_EAssign _ e1 e2 =
match e2.node with
| (EIfThenElse _ | ESwitch _) ->
(self#make_assignment e1 e2).node
| _ ->
EAssign (e1, e2)
end
(* Transform functional updates of the form x.(i) <- { x.(i) with f = e } into
* in-place field updates. *)
let functional_updates = object (self)
inherit [_] map as super
val mutable make_mut = []
(* TODO: there are many combinations of operators or not (both for reading and writing), a single
assignment or not... we don't cover everything *)
method private gen_assignments env e_read exprfields =
ESequence (List.map (fun (the_field, the_expr) ->
let the_field = Option.get the_field in
let the_expr = self#visit_expr env the_expr in
make_mut <- (assert_tlid e_read.typ, the_field) :: make_mut;
with_unit (EAssign (with_type the_expr.typ (EField (e_read, the_field)), the_expr))
) exprfields)
method! visit_EApp env e es =
(* Without temporary, any position, support for multiple fields updated
e1 *= { fi = ei } with ei = e if i = k, e1[0].fi otherwise
-->
e1 *= e
e1 *= { fi = ei } with ei = e if i = k, !*e1.fi otherwise
-->
e1 *= e
*)
match e.node, es with
| EQualified (["LowStar"; "BufferOps"], op), [ e1; { node = EFlat fields; _ }] when KString.starts_with op "op_Star_Equal" ->
let updated_fields, untouched_fields = List.partition (function
| Some f, { node = EField ({ node = EBufRead (e1', e2); _ }, f'); _ } when
f = f' && e1 = e1' && Helpers.is_zero e2 ->
false
| Some f, { node = EField ({
node = EApp ({
node = EQualified (["LowStar"; "BufferOps"], op); _
},
[ e1' ]); _
}, f'); _ } when
(* we match: !*e_deref.f' and request e_read = BufRead (e_deref, 0) *)
f = f' && e1 = e1' && KString.starts_with op "op_Bang_Star" ->
false
| _ ->
true
) fields
in
if List.length untouched_fields > 0 then
(* TODO: catch the monomorphized name of the *= operator above and use that for prettier
code-gen *)
let e_read = with_type (assert_tbuf e1.typ) (EBufRead (e1, Helpers.zerou32)) in
self#gen_assignments env e_read updated_fields
else
super#visit_EApp env e es
| _ ->
super#visit_EApp env e es
method! visit_EBufWrite env e1 e2 e3 =
(* Without temporary, any position, support for multiple fields updated
e1[e2] <- { fi = ei } with ei = e if i = k, e1[e2].fi otherwise
-->
e1[e2].fk <- e
e1[0] <- { fi = ei } with ei = e if i = k, !*e1.fi otherwise
-->
e1[0].fk <- e
*)
let e_read = EBufRead (e1, e2) in
match e3.node with
| EFlat fields ->
let updated_fields, untouched_fields = List.partition (function
| Some f, { node = EField (e_read', f'); _ } when f = f' && e_read = e_read'.node ->
false
| Some f, { node = EField ({
node = EApp ({
node = EQualified (["LowStar"; "BufferOps"], op); _
},
[ e_deref ]); _
}, f'); _ } when
(* we match: !*e_deref.f' and request e_read = BufRead (e_deref, 0) *)
f = f' && e1 = e_deref && Helpers.is_zero e2 && KString.starts_with op "op_Bang_Star" ->
false
| _ ->
true
) fields
in
if List.length untouched_fields > 0 then
self#gen_assignments env (with_type (assert_tbuf e1.typ) e_read) updated_fields
else
super#visit_EBufWrite env e1 e2 e3
| _ ->
super#visit_EBufWrite env e1 e2 e3
method! visit_ELet env b e1 e2 =
let b = self#visit_binder env b in
let e1 = self#visit_expr env e1 in
let make_assignment fields k =
let updated_fields, untouched_fields = List.partition (function
| Some f, { node = EField ({ node = EBound 0; _ }, f'); _ } when f = f' ->
false
| _ ->
true
) fields
in
if List.length untouched_fields > 0 then
let updated_fields = List.map (fun (f, e) -> f, snd (open_binder b e)) updated_fields in
k (self#gen_assignments env e1 updated_fields)
else
ELet (b, e1, self#visit_expr env e2)
in
(* TODO: operators *)
match e1.node, e2.node with
| EBufRead ({ node = EBound i; _ }, j),
EBufWrite ({ node = EBound iplusone; _ }, j', { node = EFlat fields; _ })
when j = j' && iplusone = i + 1 ->
(* With temporary, in terminal position:
let uu = (Bound i)[j] in
(Bound (i + 1))[j] <- { fi = ei } with ei = e if i = k, (Bound 0).fi otherwise
-->
(Bound i)[j].fk <- (unlift 1 e)
*)
make_assignment fields (fun x -> x)
| EBufRead ({ node = EBound i; _ }, j),
ELet (b,
{ node = EBufWrite ({ node = EBound iplusone; _ }, j', { node = EFlat fields; _ }); _ },
e3)
when j = j' && iplusone = i + 1 ->
(* With temporary, NOT in terminal position:
let uu = (Bound i)[j];
let _ = (Bound (i + 1))[j] <- { fi = ei } with ei = e if i = k, * (Bound 0).fi otherwise in
e3
-->
let _ = (Bound i)[j].fk <- (unlift 1 e) in
e3
*)
make_assignment fields (fun x ->
let e3 = self#visit_expr env (snd (open_binder b e3)) in
ELet (b, with_unit x, e3))
| _ ->
ELet (b, e1, self#visit_expr env e2)
(* The same object is called a second time with the mark_mut flag set to true
* to mark those fields that now ought to be mutable *)
method! visit_DType mark_mut name flags n_cgs n def =
match def with
| Flat fields when mark_mut ->
let fields = List.map (fun (f, (t, m)) ->
if List.exists (fun (name', f') -> Some f' = f && name' = name) make_mut then
f, (t, true)
else
f, (t, m)
) fields in
DType (name, flags, n_cgs, n, Flat fields)
| _ ->
DType (name, flags, n_cgs, n, def)
end
let mutated_types = Hashtbl.create 41
let misc_cosmetic = object (self)
inherit [_] map as super
val mutable count = 0
method! visit_ELet env b e1 e2 =
let b = self#visit_binder env b in
let e1 = self#visit_expr env e1 in
(* We cannot optimize aligned types, because CStarToC11 is only inserting alignment directives
on arrays, not local variables. *)
let is_aligned = match e1.typ with
| TQualified lid when Helpers.is_aligned_type lid ->
true
| _ ->
false
in
match e1.node with
| EBufCreate (Common.Stack, e1, { node = EConstant (_, "1"); _ }) when not (Options.wasm () || Options.rust ()) && not is_aligned ->
(* int x[1]; x[0] = e; x
* -->
* int x; x = e; &x *)
let t = assert_tbuf_or_tarray b.typ in
let b = with_type t { b.node with mut = true } in
let ref = with_type (TBuf (t, false)) (EAddrOf (with_type t (EBound 0))) in
ELet (b, e1, self#visit_expr env (DeBruijn.subst_no_open ref 0 e2))
| _ ->
(* let x = $any in
^^ ^^^
b e1
let _ = f (&x) in // sequence
^^ ^^
b' e3
p[k] <- { ... f: x ... }
^^ ^^^^^^^^^^^^^
e4 fs
-->
f (&p[k].f);
p[k].f' <- e'
*)
match e1.node, e2.node with
| EAny, ELet (b', { node = EApp (e3, [ { node = EAddrOf ({ node = EBound 0; _ }); _ } ]); _ },
{ node = EBufWrite (e4, e4_index, { node = EFlat fields; _ }); _ })
when b'.node.meta = Some MetaSequence &&
List.exists (fun (f, x) -> f <> None && x.node = EBound 1) fields &&
Mark.is_atmost 2 (snd !(b.node.mark)) ->
(* if true then Warn.fatal_error "MATCHED: %a" pexpr (with_unit (ELet (b, e1, e2))); *)
let f, { typ = x_typ; _ } = List.find (fun (_, x) -> x.node = EBound 1) fields in
let f = Option.get f in
let e3 = snd (DeBruijn.open_binder b e3) in
let e4 = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e4))) in
let e4_index = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e4_index))) in
let fields = List.map (fun (f, e) -> f, snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e)))) fields in
let e4_typ = assert_tbuf e4.typ in
Hashtbl.add mutated_types (assert_tlid e4_typ) ();
let e4 = with_type e4_typ (EBufRead (e4, e4_index)) in
ESequence (
with_unit (EApp (e3, [
with_type (TBuf (x_typ, false)) (EAddrOf (
with_type x_typ (EField (e4, f))))])) ::
List.filter_map (fun (f', e) ->
let f' = Option.get f' in
if f = f' then
None
else
Some (with_unit (EAssign (
with_type e.typ (EField (e4, f')),
e)))
) fields)
(* let x;
f(&x);
p[k] <- { ... f: x ... };
...
-->
f (&p[k].f);
p.f' <- e';
...
(Same as above, but in non-terminal position)
*)
| EAny, ELet (b', { node = EApp (e3, [ { node = EAddrOf ({ node = EBound 0; _ }); _ } ]); _ },
{ node = ELet (b'', { node = EBufWrite (e4, e4_index, { node = EFlat fields; _ }); _ }, e5); _ })
when b'.node.meta = Some MetaSequence &&
b''.node.meta = Some MetaSequence &&
List.exists (fun (f, x) -> f <> None && x.node = EBound 1) fields &&
Mark.is_atmost 2 (snd !(b.node.mark)) ->
(* if true then Warn.fatal_error "MATCHED: %a" pexpr (with_unit (ELet (b, e1, e2))); *)
let f, { typ = x_typ; _ } = List.find (fun (_, x) -> x.node = EBound 1) fields in
let f = Option.get f in
let e3 = snd (DeBruijn.open_binder b e3) in
let e4 = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e4))) in
let e4_index = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e4_index))) in
let fields = List.map (fun (f, e) -> f, snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e)))) fields in
let e4_typ = assert_tbuf e4.typ in
Hashtbl.add mutated_types (assert_tlid e4_typ) ();
let e4 = with_type e4_typ (EBufRead (e4, e4_index)) in
let e5 = self#visit_expr env e5 in
ESequence (
with_unit (EApp (e3, [
with_type (TBuf (x_typ, false)) (EAddrOf (
with_type x_typ (EField (e4, f))))])) ::
List.filter_map (fun (f', e) ->
let f' = Option.get f' in
if f = f' then
None
else
Some (with_unit (EAssign (
with_type e.typ (EField (e4, f')),
e)))
) fields @
[ e5 ])
(* let x = $any in
^^ ^^^
b e1
let _ = f (&x) in // sequence
^^ ^^
b' e3
p := x
^^
e4
-->
f (&p);
...
*)
| EAny, ELet (b', { node = EApp (e3, [ { node = EAddrOf ({ node = EBound 0; _ }); _ } ]); _ },
{ node = EAssign (e4, { node = EBound 1; _ }); _ })
when b'.node.meta = Some MetaSequence &&
Mark.is_atmost 2 (snd !(b.node.mark)) ->
(* KPrint.bprintf "MATCHED: %a" pexpr (with_unit (ELet (b, e1, e2))); *)
let e3 = snd (DeBruijn.open_binder b e3) in
let e4 = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e4))) in
EApp (e3, [ with_type (TBuf (e4.typ, false)) (EAddrOf e4)])
(* let x = $any in
let _ = f(&x) in
let _ = p := x in
...
(same as above but not in non-terminal position) *)
| EAny, ELet (b',
{ node = EApp (e3, [ { node = EAddrOf ({ node = EBound 0; _ }); _ } ]); _ },
{ node = ELet (b'', { node = EAssign (e4, { node = EBound 1; _ }); _ }, e5); _ })
when b'.node.meta = Some MetaSequence &&
Mark.is_atmost 2 (snd !(b.node.mark)) ->
(* KPrint.bprintf "MATCHED: %a" pexpr (with_unit (ELet (b, e1, e2))); *)
let e3 = snd (DeBruijn.open_binder b e3) in
let e4 = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e4))) in
let e5 = snd (DeBruijn.open_binder b (snd (DeBruijn.open_binder b' e5))) in
ELet (b'', with_unit (EApp (e3, [ with_type (TBuf (e4.typ, false)) (EAddrOf e4)])), self#visit_expr env e5)
| _, _ ->
ELet (b, e1, self#visit_expr env e2)
(* Turn empty then branches into empty else branches to get prettier syntax
* later on. *)
method! visit_EIfThenElse env e1 e2 e3 =
let e1 = self#visit_expr env e1 in
let e2 = self#visit_expr env e2 in
let e3 = self#visit_expr env e3 in
match e2.node with
| EUnit when e3.node <> EUnit ->
(* TODO: if e1 is an equality, make it a != *)
EIfThenElse (Helpers.mk_not e1, e3, e2)
| _ ->
EIfThenElse (e1, e2, e3)
(* &x[0] --> x *)
method! visit_EAddrOf env e =
let e = self#visit_expr env e in
match e.node with
| EBufRead (e, { node = EConstant (_, "0"); _ }) ->
e.node
| _ ->
EAddrOf e
method! visit_EBufRead env e1 e2 =
let e1 = self#visit_expr env e1 in
let e2 = self#visit_expr env e2 in
match e1.node, e2.node with
| EAddrOf e, EConstant (_, "0") ->
e.node
| _ ->
EBufRead (e1, e2)
method! visit_EBufWrite env e1 e2 e3 =
let e1 = self#visit_expr env e1 in
let e2 = self#visit_expr env e2 in
let e3 = self#visit_expr env e3 in
match e1.node, e2.node with
| EAddrOf e, EConstant (_, "0") ->
EAssign (e, e3)
| _ ->
EBufWrite (e1, e2, e3)
method! visit_EBufSub env e1 e2 =
(* AstToCStar emits BufSub (e, 0) as just e, so we need the value
* check to be in agreement on both sides. *)
match e2.node with
| EConstant (_, "0") when not (Options.rust ()) ->
(self#visit_expr env e1).node
| _ ->
EBufSub (self#visit_expr env e1, self#visit_expr env e2)
(* renumber uu's to have a stable numbering scheme that minimizes the diff
* from one code generation to another *)
method! visit_decl env decl =
count <- 0;
super#visit_decl env decl
method! visit_binder _ binder =
if Helpers.is_uu binder.node.name then
let c = count in
count <- count + 1;
{ binder with node = { binder.node with name = "uu____" ^ string_of_int c }}
else
binder
end
let misc_cosmetic2 = object
inherit [_] map
method! visit_DType () name flags n_cgs n def =
match def with
| Flat fields when Hashtbl.mem mutated_types name ->
DType (name, flags, n_cgs, n, Flat (List.map (fun (f, (t, _)) -> f, (t, true)) fields))
| _ ->
DType (name, flags, n_cgs, n, def)
end
(* No left-nested let-bindings ************************************************)
(* This function returns an expression that can successfully be translated as a
* C* statement, after going through let-if-to-assign conversion.
* - This function shall be called wherever statements are expected (function
* bodies; then/else branches; branches of switches).
* - It returns a series of let-bindings nested over an expression in terminal
* position.
* - It guarantees that if-then-else nodes appear either in statement position,
* or immediately under a let-binding, meaning they will undergo
* let-if-to-assign conversion. *)
type pos =
| UnderStmtLet
| UnderConditional
| Unspecified
(* Enforce short-circuiting semantics for boolean operators; in C, this means
* erroring out, and in Wasm, this means nesting let-bindings for the rhs
* underneath. *)
let rec flag_short_circuit loc t e0 es =
let lhs0, e0 = hoist_expr loc Unspecified e0 in
let lhss, es = List.split (List.map (hoist_expr loc Unspecified) es) in
match e0.node, es, lhss with
| EOp ((K.And | K.Or) as op, K.Bool), [ e1; e2 ], [ lhs1; lhs2 ] ->
(* In Wasm, we automatically inline functions based on their size, so we
* can't ask the user to rewrite, but it's ok, because it's an expression
* language, so we can have let-bindings anywhere. *)
if List.length lhs2 > 0 && not (Options.wasm ()) then begin
Warn.(maybe_fatal_error (KPrint.bsprintf "%a" Loc.ploc loc,
GeneratesLetBindings (
KPrint.bsprintf "%a, a short-circuiting boolean operator" pexpr e0,
with_type t (EApp (e0, es)),
lhs2)));
match op with
| K.And ->
lhs1, with_type t (EIfThenElse (e1, nest lhs2 e2.typ e2, efalse))
| K.Or ->
lhs1, with_type t (EIfThenElse (e1, etrue, nest lhs2 e2.typ e2))
| _ ->
assert false
end else
lhs1, with_type t (EApp (e0, [ e1; nest lhs2 e2.typ e2 ]))
| _ ->