-
Notifications
You must be signed in to change notification settings - Fork 72
Expand file tree
/
Copy pathAstToMiniRust.ml
More file actions
1927 lines (1719 loc) · 75.1 KB
/
AstToMiniRust.ml
File metadata and controls
1927 lines (1719 loc) · 75.1 KB
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
(* Low* to Rust backend *)
module LidMap = Idents.LidMap
module LidSet = Idents.LidSet
(* Location information *)
type location = {
curr_func: Ast.lident;
}
let ploc b { curr_func } = PrintAst.Ops.plid b curr_func
let empty_loc = { curr_func = [], "" }
(* Helpers *)
module H = struct
let add (e1: MiniRust.expr) (e2: MiniRust.expr): MiniRust.expr =
match e1, e2 with
| Constant (_, "0"), _ -> e2
| _, Constant (_, "0") -> e1
| _, _ -> Call (Operator Add, [], [ e1; e2 ])
let sub (e1: MiniRust.expr) (e2: MiniRust.expr): MiniRust.expr =
match e1, e2 with
| _, Constant (_, "0") -> e1
| _, _ -> Call (Operator Sub, [], [ e1; e2 ])
let usize i: MiniRust.expr =
Constant (SizeT, string_of_int i)
let range_with_len start len: MiniRust.expr =
Range (Some start, Some (add start len), false)
let wrapping_operator_opt = function
| Constant.Add -> Some "wrapping_add"
| Div -> Some "wrapping_div"
| Mult -> Some "wrapping_mul"
| Neg -> Some "wrapping_neg"
| Mod -> Some "wrapping_rem"
| BShiftL -> Some "wrapping_shl"
| BShiftR -> Some "wrapping_shr"
| Sub -> Some "wrapping_sub"
| _ -> None
let wrapping_operator o =
Option.get (wrapping_operator_opt o)
let is_wrapping_operator o =
wrapping_operator_opt o <> None
let is_const (e: MiniRust.expr) =
match e with
| MiniRust.Constant _ -> true
| _ -> false
let assert_const (e: MiniRust.expr) =
match e with
| MiniRust.Constant (_, s) -> int_of_string s
| _ -> failwith "unexpected: assert_const not const"
end
(* Rust's ownership system forbids raw pointer manipulations, meaning that Low*'s "EBufSub"
operation cannot be compiled as a pointer addition. Instead, we choose to compile it as
`split_at_mut`, a compilation scheme that entails several limitations.
- Since the length information is not available to us (it's erased by the time we get here), we
split on the offset and retain the history of how a given variable was split. Consider the
following sample program, which sub-divides an array of length 16 into four equal parts -- a
typical usage in Low*.
```
let x0 = x + 0
let x1 = x + 8
let x2 = x + 4
let x3 = x + 12
```
we compile the first line as:
```
let xr0 = x.split_at_mut(0)
```
and retain the following split tree:
xr0, 0
at that point, a *usage* of x0 compiles to `xr0.1` (the right component of xr0).
Then, we compile xr1 as follows:
```
let xr1 = xr0.1.split_at_mut(8)
```
This updates the split tree of x as follows
xr0, 0
\
xr1, 8
At that stage, note that a *usage* of x0 compiles to `xr1.0`, while a usage of x1 compiles to
`xr1.1`. We continue with the third split
```
let xr2 = xr1.0.split_at_mut(4)
```
This updates the split tree of `x`:
xr0, 0
\
xr1, 8
/
xr2, 4
At that stage, x0 compiles to xr2.0, x1 compiles to xr1.1, and x2 compiles to xr2.1. The final
split updates the split tree of x as follows:
xr0, 0
\
xr1, 8
/ \
xr2, 4 xr3, 12
- In order to avoid keeping downwards pointers in the environment from the split tree of `x` down
to the various xri, the split tree contains only indices, and each one of the xri remembers its
own position in the tree in the form of a `path`.
- In order to compute a usage of xri, it suffices to:
- Lookup xri's path in the tree
- Find the nearest variable whose path is of the form path(xri) ++ (r ++ l* )? -- i.e. the path
of xri, followed by a right component, followed by a series of left components.
- Select the right or left component of that variable, according to the search path above.
- This compilation scheme obviously does not work if the original program does something like:
```
let x0 = x + 4
x[4] = ...
```
this will throw a runtime exception in Rust -- in a sense, our compilation scheme is optimistic
- This compilation scheme has been enhanced to deal with a very simple language of indices, which
includes constants and addition of constants to terms. This could definitely be improved.
- In case this does not work, the programmer can always modify the original Low* code.
*)
module Splits = struct
type leveled_expr = int * MiniRust.expr
[@@deriving show]
let equalize (l1, e1) (l2, e2) =
let l = max l1 l2 in
let e1 = MiniRust.lift (l - l1) e1 in
let e2 = MiniRust.lift (l - l2) e2 in
l, e1, e2
(* We retain the depth of the environment at the time non-constant indices were recorded, in order
to be able to compare them properly. *)
let term_eq e1 e2 =
let _, e1, e2 = equalize e1 e2 in
e1 = e2
(* A super simple grammar of symbolic terms to be compared abstractly *)
type index =
| Constant of int
| Add of leveled_expr * int
[@@deriving show]
(* l current length of the environment *)
let index_of_expr (l: int) (e_ofs: MiniRust.expr) (t_ofs: MiniRust.typ): index =
match e_ofs with
| Constant (_, n) -> Constant (int_of_string n)
| MethodCall (e1, [ "wrapping_add" ], [ Constant (_, n) ])
| MethodCall (Constant (_, n), [ "wrapping_add" ], [ e1 ]) ->
if t_ofs = Constant SizeT then
Add ((l, e1), int_of_string n)
else
Add ((l, As (e1, Constant SizeT)), int_of_string n)
| _ ->
if t_ofs = Constant SizeT then
Add ((l, e_ofs), 0)
else
Add ((l, As (e_ofs, Constant SizeT)), 0)
(* l current length of the environment *)
let expr_of_index (l: int) (e: index): MiniRust.expr =
match e with
| Constant i ->
Constant (SizeT, string_of_int i)
| Add ((l', e), 0) ->
let e = MiniRust.lift (l - l') e in
e
| Add ((l', e), i) ->
let e = MiniRust.lift (l - l') e in
Call (Operator Add, [], [ e; Constant (SizeT, string_of_int i) ])
let gte (loc: location) i1 i2 =
match i1, i2 with
| Constant i1, Constant i2 ->
i1 >= i2
| Add (e1, i1), Add (e2, i2) when term_eq e1 e2 ->
i1 >= i2
| Add (_e1, i1), Constant i2 ->
(* operations are homogenous, meaning e1 must have type usize/u32, meaning e1 >= 0 *)
i1 >= i2
| Constant i1, Add (e2, i2) ->
(* TODO: proper warning system *)
KPrint.bprintf "%a: WARN: cannot compare constant %d and %a + %d\n"
ploc loc i1 PrintMiniRust.pexpr (snd e2) i2;
true
| Add (e1, i1), Add (e2, i2) ->
(* TODO: proper warning system *)
KPrint.bprintf "%a: WARN: Cannot compare %a@%d + %d and %a@%d + %d\n"
ploc loc
PrintMiniRust.pexpr (snd e1) (fst e1) i1
PrintMiniRust.pexpr (snd e2) (fst e2) i2;
true
let sub (loc: location) i1 i2 =
match i1, i2 with
| Constant i1, Constant i2 ->
Constant (i1 - i2)
| Add (e1, i1), Add (e2, i2) when term_eq e1 e2 ->
Constant (i1 - i2)
| Add (e1, i1), Constant i2 ->
Add (e1, i1 - i2)
| Constant i1, Add ((l2, e2), i2) ->
KPrint.bprintf "%a: WARN: Cannot subtract constant %d and %a + %d\n\
assuming monotonically increasing indices, this may cause runtime failures\n"
ploc loc i1 PrintMiniRust.pexpr e2 i2;
(* i1 - (e2 + i2) *)
Add ((l2, H.sub (H.usize i1) (H.add e2 (H.usize i2))), 0)
| Add (e1, i1), Add (e2, i2) ->
let l, e1, e2 = equalize e1 e2 in
KPrint.bprintf "%a: WARN: Cannot subtract constant %a@%d + %d and %a@%d + %d\n\
assuming monotonically increasing indices, this may cause runtime failures\n"
ploc loc
PrintMiniRust.pexpr e1 l i1
PrintMiniRust.pexpr e2 l i2;
(* (e1 + i1) - (e2 + i2) *)
Add ((l, H.sub (H.add e1 (H.usize i1)) (H.add e2 (H.usize i2))), 0)
(* A binary search tree that records the splits that have occured *)
type tree =
| Node of index * tree * tree
| Leaf
[@@deriving show]
type path_elem = Left | Right
[@@deriving show]
let string_of_path_elem = function
| Left -> "0"
| Right -> "1"
(* A path from the root into a node of the tree *)
type path = path_elem list
[@@deriving show]
type root_or_path =
| Root
| Path of path
[@@deriving show]
type info = {
tree: tree;
(* This variable (originally a "buffer") has been successively split at those indices *)
path: (MiniRust.db_index * path) option;
(* This variable "splits" db_index after path consecutive splits *)
}
let empty = { tree = Leaf; path = None }
(* Unlocks better comparisons in many cases *)
let norm e =
let exception NotConstant in
let rec is_zero e =
try norm e = 0
with NotConstant -> false
and norm (e: MiniRust.expr) =
(* This function assumes no overflow on index comparisons... FIXME *)
match e with
| As (e, _) ->
norm e
| MethodCall (e1, [ "wrapping_add" ], [ e2 ])
| Call (Operator (Add | AddW), _, [ e1; e2 ]) when is_zero e1 ->
norm e2
| MethodCall (e1, [ "wrapping_add" ], [ e2 ])
| Call (Operator (Add | AddW), _, [ e1; e2 ]) when is_zero e2 ->
norm e1
| MethodCall (e1, [ "wrapping_add" ], [ e2 ])
| Call (Operator (Add | AddW), _, [ e1; e2 ]) ->
norm e1 + norm e2
| MethodCall (e1, [ "wrapping_mul" ], [ e2 ])
| Call (Operator (Mult | MultW), _, [ e1; e2 ]) when is_zero e1 || is_zero e2 ->
0
| MethodCall (e1, [ "wrapping_mul" ], [ e2 ])
| Call (Operator (Mult | MultW), _, [ e1; e2 ]) ->
norm e1 * norm e2
| MethodCall (e1, [ "wrapping_sub" ], [ e2 ])
| Call (Operator (Sub | SubW), _, [ e1; e2 ]) ->
norm e1 - norm e2
| MethodCall (e1, [ "wrapping_div" ], [ e2 ])
| Call (Operator (Div | DivW), _, [ e1; e2 ]) ->
norm e1 / norm e2
| Constant (_, c) ->
int_of_string c
| _ ->
raise NotConstant
in
try
match e with
| Constant i -> Constant i
| Add ((_, e), i) ->
Constant (norm e + i)
with NotConstant ->
e
(* The variable with `info` is being split at `index` *)
let split loc l info index: info * path * MiniRust.expr =
let rec split tree index ofs =
match tree with
| Leaf ->
Node (index, Leaf, Leaf), [], ofs
| Node (index', t1, t2) ->
let index = norm index in
let index' = norm index' in
if gte loc index index' then
let t2, path, ofs = split t2 index (sub loc index index') in
Node (index', t1, t2), Right :: path, ofs
else
let t1, path, ofs = split t1 index ofs in
Node (index', t1, t2), Left :: path, ofs
in
let tree, path, ofs = split info.tree index index in
{ info with tree }, path, expr_of_index l ofs
let find_zero tree =
let rec find_zero path tree =
match tree with
| Leaf -> None
| Node (Constant 0, _, _) ->
Some path
| Node (_, t1, _) ->
find_zero (Left :: path) t1
in
find_zero [] tree
(* We are trying to access a variable whose position in the tree was originally p1 -- however,
many more splits may have occurred since this variable was defined. Does p2 provide a way to
access p1, and if so, via which side? *)
let accessible_via p1 p2: path_elem option =
match p1 with
| Root ->
if List.for_all ((=) Left) p2 then
Some Left
else
None
| Path p1 ->
let l1 = List.length p1 in
let l2 = List.length p2 in
if l2 < l1 then
None
else
let p2_prefix, p2_suffix = KList.split l1 p2 in
if p2_prefix <> p1 then
None
else
match p2_suffix with
| [] ->
Some Right
| Right :: p2_suffix when List.for_all ((=) Left) p2_suffix ->
Some Left
| _ ->
None
end
(* Environments *)
module NameSet = Set.Make(struct
type t = string list
let compare = compare
end)
module DataTypeMap = Map.Make(struct
type t = [ `Struct of Ast.lident | `Variant of Ast.lident * string ]
let compare = compare
end)
type env = {
decls: (MiniRust.name * MiniRust.typ) LidMap.t;
global_scope: NameSet.t;
types: MiniRust.name LidMap.t;
vars: (MiniRust.binding * Splits.info) list;
prefix: string list;
heap_structs: Idents.LidSet.t;
pointer_holding_structs: Idents.LidSet.t;
struct_fields: MiniRust.struct_field list DataTypeMap.t;
location: location;
}
let empty heap_structs pointer_holding_structs = {
decls = LidMap.empty;
global_scope = NameSet.empty;
types = LidMap.empty;
vars = [];
prefix = [];
struct_fields = DataTypeMap.empty;
heap_structs;
pointer_holding_structs;
location = empty_loc;
}
let locate env curr_func = { env with location = { curr_func } }
let push env (b: MiniRust.binding) =
(* KPrint.bprintf "Pushing %s to environment at type %a\n" b.name PrintMiniRust.ptyp b.typ; *)
{ env with vars = (b, Splits.empty) :: env.vars }
let push_with_info env b = { env with vars = b :: env.vars }
let push_decl env name t =
assert (not (LidMap.mem name env.decls));
{ env with decls = LidMap.add name t env.decls }
let push_type env name t =
assert (not (LidMap.mem name env.types));
{ env with types = LidMap.add name t env.types }
let lookup env v = List.nth env.vars v
let lookup_decl env name =
LidMap.find name env.decls
let lookup_type env name =
LidMap.find name env.types
let debug env =
List.iteri (fun i ((b: MiniRust.binding), info) ->
let open MiniRust in
let open Splits in
KPrint.bprintf "%d\t %s: %s\n tree = %s\n path = %s\n"
i b.name (show_typ b.typ)
(show_tree info.tree)
(match info.path with
| None -> ""
| Some (v, p) -> KPrint.bsprintf "%d -- %s" v (show_path p))
) env.vars
let compress_prefix prefix =
let depth = !Options.depth in
let prefix, last = KList.split_at_last prefix in
if List.length prefix <= depth then
prefix @ [ last ]
else
let path, prefix = KList.split depth prefix in
path @ [ String.concat "_" (prefix @ [ last ]) ]
(* Types *)
(* This is the name translation facility for things that are missing from the scope; typically,
external definitions that are to be implemented by hand.
Some background (current as of Feb 2025): things that *do* have a definition end up with Rust
name crate::foo::bar where `foo` is the file that the definition got assigned to, and `bar` is
the last component of the `lident`. So, for instance, if Hacl.Streaming.Keccak.hash_len ends up
in Rust *file* hash_sha3.rs, then it becomes crate::hash_sha3::hash_len and is emitted as `fn
hash_len` within that file. The environment knows about this. Notably, this means that
rename-prefix is completely ignored for Rust, for good reason -- there is a better namespacing
story.
Now for things that are completely external (e.g. assumed library, like LowStar.Endianness), this
is a different story -- how do we meaningfully steer the name generation towards the naming
scheme we *would like*?
So, here, for things that do not get the nice Rust namespacing, we *do* honor rename-prefix to
give the user a little more control over how things get emitted when they don't have definitions
in scope.*)
let translate_unknown_lid (m, n) =
let m =
match GlobalNames.rename_prefix (m, n) with
| Some m -> [ m ]
| None -> compress_prefix m
in
List.map String.lowercase_ascii m @ [ n ]
let borrow_kind_of_bool b: MiniRust.borrow_kind =
if b then Shared (* Constant pointer case *)
else Mut
type config = {
box: bool;
lifetime: MiniRust.lifetime option;
(* Rely on the Ast type to set borrow mutability.
Should always be set to false to correctly infer
mutability in a later pass, except when translating
external (assumed) declarations *)
keep_mut: bool;
}
let default_config = {
box = false;
lifetime = None;
keep_mut = false;
}
let is_contained t =
let t = KList.last t in
List.exists (fun t' -> KString.starts_with t t') !Options.contained
let has_pointer env =
(object
inherit [_] Ast.reduce as super
method zero = false
method plus = (||)
method! visit_TBuf _ _ _ = true
method! visit_TQualified _ lid = Idents.LidSet.mem lid env.pointer_holding_structs
method! visit_TApp env lid ts =
if lid = (["Pulse"; "Lib"; "Slice"], "slice") then
true
else
super#visit_TApp env lid ts
end)#visit_typ ()
let rec translate_type_with_config (env: env) (config: config) (t: Ast.typ): MiniRust.typ =
match t with
| TInt w -> Constant w
| TBool -> Bool
| TUnit -> Unit
| TAny -> failwith "unexpected: [type] no casts in Low* -> Rust"
| TBuf (t, b) ->
if config.box then
MiniRust.box (Slice (translate_type_with_config env config t))
(* Vec (translate_type_with_config env config t) *)
else
Ref (config.lifetime, (if config.keep_mut then borrow_kind_of_bool b else Shared), Slice (translate_type_with_config env config t))
| TArray (t, c) -> Array (translate_type_with_config env config t, int_of_string (snd c))
| TQualified lid ->
let generic_params =
if Idents.LidSet.mem lid env.pointer_holding_structs && Option.is_some config.lifetime then
(* We are within a type declaration *)
[ MiniRust.Lifetime (Option.get config.lifetime) ]
else
[]
in
begin try
Name (lookup_type env lid, generic_params)
with Not_found ->
(* KPrint.bprintf "Type name not found: %a\n" PrintAst.plid lid; *)
Name (translate_unknown_lid lid, generic_params)
end
| TArrow _ ->
let t, ts = Helpers.flatten_arrow t in
let ts = match ts with [ TUnit ] -> [] | _ -> ts in
let needs_lifetime = has_pointer env t in
(* We need to find a fresh lifetime -- struct foo<'a> { a: for<'a> ... } is an error. *)
(* Function pointers refer to global functions so they can't refer to the enclosing lifetime anyhow. *)
let new_lifetime =
MiniRust.Label (match config.lifetime with
| Some (MiniRust.Label n) -> n ^ "1"
| None -> "a") in
let config = { config with lifetime = if needs_lifetime then Some new_lifetime else None } in
let translate_parameter_type t =
match translate_type_with_config env config t with
| Ref (_, m, (Slice (Name (t, _ :: _)) as slice_t)) when is_contained t ->
(* Unlike top-level declarations (where we use 'b), we omit the lifetime name here.
Writing an explicit lifetime name here would also require adding a for<'b>.
By omitting the lifetime name, Rust will automatically do this. *)
MiniRust.Ref (None, m, slice_t)
| t ->
t
in
Function (0, Option.to_list config.lifetime,
List.map translate_parameter_type ts, translate_type_with_config env config t)
| TApp ((["Pulse"; "Lib"; "Slice"], "slice"), [ t ]) ->
Ref (config.lifetime, Shared, Slice (translate_type_with_config env config t))
| TApp _ -> failwith (KPrint.bsprintf "TODO: TApp %a" PrintAst.ptyp t)
| TBound i -> Bound i
| TTuple ts -> Tuple (List.map (translate_type_with_config env config) ts)
| TAnonymous _ -> failwith "unexpected: we don't compile data types going to Rust"
| TCgArray _ -> failwith "Impossible: TCgArray"
| TCgApp _ -> failwith "Impossible: TCgApp"
| TPoly _ -> failwith "Impossible: TPoly"
let translate_type env = translate_type_with_config env default_config
(* Expressions *)
(* Allocate a target Rust name for a definition named `lid` pertaining to file
(i.e. future rust module) `env.prefix`.
We do something super simple: we only retain the last component of `lid` and
prefix it with the current namespace. This ensures that at Rust
pretty-printing time, all the definitions in module m have their fully
qualified names starting with m (and ending up with a single path component).
As a further refinement, we at least make sure that the function is injective. The
pretty-printing phase will take care of dealing with lexical conventions and the like (e.g. the
fact that we oftentimes have single quotes in names but that's not ok in Rust -- not our problem
here). *)
let translate_lid env lid =
let rec translate_lid last i =
let name = env.prefix @ [ last ^ string_of_int i ] in
if NameSet.mem name env.global_scope then
translate_lid last (i + 1)
else
allocate name
and allocate name =
{ env with global_scope = NameSet.add name env.global_scope }, name
in
let name = env.prefix @ [ snd lid ] in
if NameSet.mem name env.global_scope then
translate_lid (snd lid) 0
else
allocate name
(* let translate_lid env lid = *)
(* let env, name = translate_lid env lid in *)
(* KPrint.bprintf "%a --> %s\n" PrintAst.plid lid (String.concat "::" name); *)
(* env, name *)
let translate_binder_name (b: Ast.binder) =
let open Ast in
if snd !(b.node.mark) = AtMost 0 then "_" ^ b.node.name else b.node.name
(* Trying to compile a *reference* to variable, who originates from a split of `v_base`, and whose
original path in the tree is `path`. *)
let lookup_split (env: env) (v_base: MiniRust.db_index) (path: Splits.root_or_path): MiniRust.expr =
if Options.debug "rs-splits" then
KPrint.bprintf "looking up from base = %d path %s\n" v_base (Splits.show_root_or_path path);
let rec find ofs bs =
match bs with
| (_, info) :: bs ->
(* KPrint.bprintf "looking for %d\n" (v_base - ofs - 1); *)
begin match info.Splits.path with
| Some (v_base', path') when v_base' = v_base - ofs - 1 ->
begin match Splits.accessible_via path path' with
| Some pe ->
MiniRust.(Field (Var ofs, Splits.string_of_path_elem pe, None))
| None ->
find (ofs + 1) bs
end
| _ ->
find (ofs + 1) bs
end
| [] ->
failwith "unexpected: path not found"
in
find 0 env.vars
(* Only valid for a struct. *)
let field_type env (e: Ast.expr) f =
let t_lid = Helpers.assert_tlid e.typ in
let struct_fields = DataTypeMap.find (`Struct t_lid) env.struct_fields in
(List.find (fun (sf: MiniRust.struct_field) -> sf.name = f) struct_fields).typ
(* Translate an expression, and take the annotated original type to be the
expected type.
[fn_t_ret] corresponds to the return type of the function we are currently
translating, and is used for EReturn nodes.
*)
let rec translate_expr (env: env) ?context (fn_t_ret: MiniRust.typ) (e: Ast.expr) : env * MiniRust.expr =
(* KPrint.bprintf "translate_expr: %a : %a\n" PrintAst.Ops.pexpr e PrintAst.Ops.ptyp e.typ; *)
translate_expr_with_type env ?context fn_t_ret e (translate_type env e.typ)
and translate_expr_list (env: env) (fn_t_ret: MiniRust.typ) (es: Ast.expr list) : env * MiniRust.expr list =
let env, acc =
List.fold_left (fun (env, acc) e -> let env, e = translate_expr env fn_t_ret e in env, e :: acc) (env, []) es
in
env, List.rev acc
and translate_expr_list_with_types (env: env) (fn_t_ret: MiniRust.typ) (es: Ast.expr list) ts : env * MiniRust.expr list =
let env, acc =
List.fold_left2 (fun (env, acc) e t -> let env, e = translate_expr_with_type env fn_t_ret e t in env, e :: acc) (env, []) es ts
in
env, List.rev acc
and translate_array (env: env) (fn_t_ret: MiniRust.typ) is_toplevel (init: Ast.expr): env * MiniRust.expr * MiniRust.typ =
let to_array = function
| Common.Stack -> true
| Eternal -> is_toplevel
| Heap -> false
in
match init.node with
| EBufCreate (lifetime, e_init, len) ->
let t = translate_type env (Helpers.assert_tbuf_or_tarray init.typ) in
let env, len = translate_expr_with_type env fn_t_ret len (Constant SizeT) in
let env, e_init = translate_expr env fn_t_ret e_init in
let e_init = MiniRust.Repeat (e_init, len) in
if to_array lifetime && H.is_const len then
env, Array e_init, Array (t, H.assert_const len)
else
MiniRust.(env, box_new e_init, box (Slice t))
| EBufCreateL (lifetime, es) ->
let t = translate_type env (Helpers.assert_tbuf_or_tarray init.typ) in
let l = List.length es in
let env, es = translate_expr_list env fn_t_ret es in
let e_init = MiniRust.List es in
if to_array lifetime then
env, Array e_init, Array (t, l)
else
MiniRust.(env, box_new e_init, box (Slice t))
| _ ->
Warn.fatal_error "unexpected: non-bufcreate expression, got %a" PrintAst.Ops.pexpr init
(* However, generally, we will have a type provided by the context that may
necessitate the insertion of conversions *)
and translate_expr_with_type (env: env) ?(context=`Other) (fn_t_ret: MiniRust.typ) (e: Ast.expr) (t_ret: MiniRust.typ): env * MiniRust.expr =
(* KPrint.bprintf "translate_expr_with_type: %a @@ %a\n" PrintMiniRust.ptyp t_ret PrintAst.Ops.pexpr e; *)
let erase_borrow_kind_info = (object(self)
inherit [_] MiniRust.DeBruijn.map
method! visit_Ref env a _ t = Ref (a, Shared, self#visit_typ env t)
end)#visit_typ ()
in
let erase_lifetime_info = (object(self)
inherit [_] MiniRust.DeBruijn.map
method! visit_Ref env _ bk t = Ref (None, bk, self#visit_typ env t)
method! visit_Function env _ _ ts t = Function (0, [], List.map (self#visit_typ env) ts, self#visit_typ env t)
method! visit_tname _ n _ = Name (n, [])
end)#visit_typ ()
in
(* Possibly convert expression x, known to have type t (per Rust), into
expected type t_ret (captured variable). *)
let possibly_convert (x: MiniRust.expr) t: MiniRust.expr =
(* KPrint.bprintf "possibly_convert: %a: %a <: %a\n" *)
(* PrintMiniRust.pexpr x *)
(* PrintMiniRust.ptyp t *)
(* PrintMiniRust.ptyp t_ret; *)
(* Mutable borrows were only included for external definitions.
We erase them here; they will be handled during mutability inference, which will
be rechecked by the Rust compiler *)
begin match x, erase_borrow_kind_info t, erase_borrow_kind_info t_ret with
| _, (MiniRust.App (Name (["Box"], _), [Slice _]) | MiniRust.Vec _ | Array _), Ref (_, k, Slice _) ->
Borrow (k, x)
| Constant (w, x), Constant UInt32, Constant SizeT ->
assert (w = Constant.UInt32);
Constant (SizeT, x)
| _, Constant UInt32, Constant SizeT ->
As (x, Constant SizeT)
| _, Function (_, _, ts, t), Function (_, _, ts', t') when ts = ts' && t = t' ->
(* The type annotations coming from Ast do not feature polymorphic binders in types, but we
do retain them in our Function type -- so we need to relax the comparison here *)
begin match e.node with
| EQualified _ when context <> `CallHead ->
As (x, t_ret)
| _ ->
x
end
(* More conversions due to box-ing types. *)
| _, Ref (_, _, Slice _), App (Name (["Box"], _), [Slice _]) ->
(* COPY *)
MethodCall (Borrow (Shared, Deref x), ["into"], [])
| _, Vec _, App (Name (["Box"], _), [Slice _]) ->
(* DEAD CODE -- no method try_into on Vec *)
MethodCall (MethodCall (x, ["try_into"], []), ["unwrap"], [])
| _, Array _, App (Name (["Box"], _), [Slice _]) ->
(* COPY (remember that Box::new does not take any type argument) *)
Call (Name ["Box"; "new"], [], [x])
(* More conversions due to vec-ing types *)
| _, Ref (_, _, Slice _), Vec _ ->
(* COPY *)
MethodCall (x, ["to_vec"], [])
| _, Array _, Vec _ ->
(* COPY *)
Call (Name ["Vec"; "from"], [], [x])
| _, Name (n, _), Name (n', _) when n = n' ->
x
| _, Ref (_, b, t), Ref (_, b', t') when b = b' && t = t' ->
(* TODO: if field types are annotated with lifetimes, then we get a
comparison where one side has a lifetime and the other doesn't -- we
should probably run the arguments to this function through a
strip_lifetimes function or something. *)
x
(* More conversions due to borrowing structs with pointers *)
| _, Ref (_, _, t), t' when t = t' ->
Deref x
| Borrow (k, e) , Ref (_, _, t), Ref (_, _, Slice t') when t = t' ->
Borrow (k, Array (List [ e ]))
| _, App (Name (["Aligned"], _), _), Ref (_, k, Slice _) ->
Borrow (k, Index (x, Range (None, None, false)))
| _, App (Name (["Aligned"], _), _), _ ->
x
| _ ->
(* If we reach this case, we perform one last try by erasing the lifetime
information in both terms. This is useful to handle, e.g., implicit lifetime
annotations or annotations up to alpha-conversion.
Note, this is sound as lifetime mismatches will be caught by the Rust compiler. *)
if erase_lifetime_info t = erase_lifetime_info t_ret then
x
else
Warn.failwith "type mismatch;\n e=%a\n t=%a (verbose: %s)\n t_ret=%a (verbose: %s)\n x=%a"
PrintAst.Ops.pexpr e
PrintMiniRust.ptyp t (MiniRust.show_typ t)
PrintMiniRust.ptyp t_ret (MiniRust.show_typ t_ret)
PrintMiniRust.pexpr x;
end
in
match e.node with
| EOpen _ ->
failwith "unexpected: EOpen"
| EBound v ->
if Options.debug "rs-splits" then begin
KPrint.bprintf "Translating: %a\n" PrintAst.Ops.pexpr e;
debug env
end;
let ({ MiniRust.typ; _ }: MiniRust.binding), info = lookup env v in
let e =
begin match info.path with
| None -> possibly_convert (Var v) typ
| Some (v_base, p) ->
let etyp = translate_type env e.typ in
possibly_convert (lookup_split env (v_base + v + 1) (Path p)) etyp
end
in
(* Reset the tree for variable v, as accessing it invalidates previous
splits on it.
TODO: Should probably do this recursively *)
let env = { env with vars =
List.mapi (fun i (b, info) ->
b, if i = v then {info with Splits.tree = Splits.Leaf } else info
) env.vars
} in
env, e
| EOp (o, _) ->
env, Operator o
| EQualified lid ->
begin try
match lid with
| [ "Pulse"; "Lib"; "Pervasives" ], "_zero_for_deref"
| [ "C" ], "_zero_for_deref" ->
(* CInt for Rust means no suffix -- rustc will convert to usize or u32 *)
env, Constant (CInt, "0")
| _ ->
let name, t = lookup_decl env lid in
env, possibly_convert (Name name) t
with Not_found ->
(* External -- TODO: make sure external definitions are properly added
to the scope *)
env, Name (translate_unknown_lid lid)
end
| EConstant c ->
env, possibly_convert (Constant c) (Constant (fst c))
| EUnit ->
env, Unit
| EBool b ->
env, Bool b
| EString s ->
env, ConstantString s
| EAny -> failwith "Unexpected EAny"
| EAbort (_, s) ->
env, Panic (Stdlib.Option.value ~default:"" s)
| EIgnore e ->
let t = translate_type env e.typ in
let env, e = translate_expr env fn_t_ret e in
let binding: MiniRust.binding = { name = "_"; typ = t; mut = false; ref = false } in
env, Let (binding, Some e, Unit)
| EApp ({ node = EOp (o, TInt w); _ }, es) when H.is_wrapping_operator o && not (Constant.is_float w) ->
let w = Helpers.assert_tint (List.hd es).typ in
let env, es = translate_expr_list env fn_t_ret es in
env, possibly_convert (MethodCall (List.hd es, [ H.wrapping_operator o ], List.tl es)) (Constant w)
(* BEGIN PULSE BUILTINS *)
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "from_array"); _ }, [], [], [ t ]); _ }, [e1; _e2]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
let t = translate_type env t in
env, possibly_convert e1 (Ref (None, Shared, Slice t))
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "op_Array_Access"); _ }, [], [], _); _ }, [e1; e2]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
let env, e2 = translate_expr env fn_t_ret e2 in
env, Index (e1, e2)
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "op_Array_Assignment"); _ }, [], [], t); _ }, [ e1; e2; e3]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
let env, e2 = translate_expr env fn_t_ret e2 in
let env, e3 = translate_expr env fn_t_ret e3 in
env, Assign (Index (e1, e2), e3, translate_type env (KList.one t))
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "split"); _ }, [], [], [_]); _ }, [e1; e2]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
let env, e2 = translate_expr env fn_t_ret e2 in
env, MethodCall (e1, ["split_at"], [ e2 ])
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "subslice"); _ }, [], [], [_]); _ }, [e1; e2; e3]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
let env, e2 = translate_expr env fn_t_ret e2 in
let env, e3 = translate_expr env fn_t_ret e3 in
env, Borrow (Shared, Index (e1, Range (Some e2, Some e3, false)))
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "copy"); _ }, [], [], _); _ }, [e1; e2]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
let env, e2 = translate_expr env fn_t_ret e2 in
env, MethodCall (e1, ["copy_from_slice"], [ e2 ])
| EApp ({ node = ETApp ({ node = EQualified (["Pulse"; "Lib"; "Slice"], "len"); _ }, [], [], _); _ }, [e1]) ->
let env, e1 = translate_expr env fn_t_ret e1 in
env, MethodCall (e1, ["len"], [])
(* END PULSE BUILTINS *)
| EApp ({ node = EQualified ([ "LowStar"; "BufferOps" ], s); _ }, e1 :: _) when KString.starts_with s "op_Bang_Star__" ->
let env, e1 = translate_expr env fn_t_ret e1 in
(* env, Deref e1 *)
env, Index (e1, MiniRust.zero_usize)
| EApp ({ node = EQualified ([ "LowStar"; "BufferOps" ], s); _ }, e1 :: e2 :: _ ) when KString.starts_with s "op_Star_Equals__" ->
let env, e1 = translate_expr env fn_t_ret e1 in
let t2 = translate_type env e2.typ in
let env, e2 = translate_expr env fn_t_ret e2 in
env, Assign (Index (e1, MiniRust.zero_usize), e2, t2)
| EApp ({ node = ETApp (e, cgs, cgs', ts); _ }, es) ->
assert (cgs @ cgs' = []);
let es =
match es with
| [ { typ = TUnit; node; _ } ] -> assert (node = EUnit); []
| _ -> es
in
let env, e = translate_expr env ~context:`CallHead fn_t_ret e in
let ts = List.map (translate_type env) ts in
let env, es = translate_expr_list env fn_t_ret es in
env, Call (e, ts, es)
| EApp ({ node = EPolyComp (op, TBuf _); _ }, ([ { node = EBufNull; _ }; _ ] | [ _; { node = EBufNull; _ }])) ->
(* No null-checks in Rust -- function will panic. *)
begin match op with
| PEq -> env, Bool false (* nothing is ever null *)
| PNeq -> env, Bool true (* everything is always non-null *)
end
| EApp (e0, es) ->
let es, ts =
match es with
| [ { typ = TUnit; node; _ } ] -> assert (node = EUnit); [], []
| _ -> es, snd (Helpers.flatten_arrow e0.typ)
in
(* KPrint.bprintf "Translating arguments to call %a\n" PrintAst.Ops.pexpr e0; *)
let env, e0 = translate_expr env ~context:`CallHead fn_t_ret e0 in
let env, es = translate_expr_list_with_types env fn_t_ret es (List.map (translate_type env) ts) in
env, possibly_convert (Call (e0, [], es)) (translate_type env e.typ)
| ETApp (_, _, _, _) ->
failwith "TODO: ETApp"
| EPolyComp (op, _t) ->
(* All that is left here is enumerations, which *do* derive eq. *)
begin match op with
| PEq -> env, Operator Eq
| PNeq -> env, Operator Neq
end
(* failwith (KPrint.bsprintf "unexpected: EPolyComp at type %a" PrintAst.Ops.ptyp t) *)
| ELet (b, ({ node = EBufSub ({ node = EBound v_base; _ } as e_base, e_ofs); _ } as e1), e2) ->
(* Keep initial environment to return after translation *)
let env0 = env in
if Options.debug "rs-splits" then begin
KPrint.bprintf "Translating: let %a = %a\n" PrintAst.Ops.pbind b PrintAst.Ops.pexpr e1;
debug env
end;
let l = List.length env.vars in
let env, e_ofs' = translate_expr env fn_t_ret e_ofs in
let index = Splits.index_of_expr l e_ofs' (translate_type env e_ofs.typ) in
(* We're splitting a variable x_base. *)
let _, info_base = lookup env v_base in
(* At the end of `path` is the variable we want to split. *)
let info_base, path, index = Splits.split env.location l info_base index in