Skip to content

Commit d7a8f9e

Browse files
authored
Merge pull request #679 from FStarLang/issue_676
Fix issue 676
2 parents d6607b9 + 2370041 commit d7a8f9e

File tree

10 files changed

+348
-129
lines changed

10 files changed

+348
-129
lines changed

lib/DataTypes.ml

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -687,22 +687,14 @@ let remove_unit_buffers = object (self)
687687

688688
end
689689

690-
let remove_unit_fields = object (self)
690+
let build_unit_field_table erasable_fields = object (self)
691691

692692
inherit [_] map
693693

694-
val erasable_fields = Hashtbl.create 41
695-
val mutable atoms = []
696-
697694
method private is_erasable = function
698695
| TUnit -> true
699696
| _ -> false
700697

701-
method private default_value = function
702-
| TUnit -> EUnit
703-
| TAny -> EAny
704-
| t -> Warn.fatal_error "default_value: %a" ptyp t
705-
706698
method! visit_DType _ lid flags n_cgs n type_def =
707699
match type_def with
708700
| Variant branches ->
@@ -737,6 +729,19 @@ let remove_unit_fields = object (self)
737729
Some (f, (t, m))
738730
) fields
739731

732+
end
733+
734+
let remove_unit_fields erasable_fields = object (self)
735+
736+
inherit [_] map
737+
738+
val mutable atoms = []
739+
740+
method private default_value = function
741+
| TUnit -> EUnit
742+
| TAny -> EAny
743+
| t -> Warn.fatal_error "default_value: %a" ptyp t
744+
740745
(* As we're about to visit a pattern, we collect binders for now-defunct
741746
* fields, and replace them with default values in the corresponding branch. *)
742747
method! visit_branch _ (binders, pat, expr) =
@@ -1369,7 +1374,9 @@ let simplify files =
13691374
(* Unit elimination, after monomorphization *)
13701375
let optimize files =
13711376
let files = remove_unit_buffers#visit_files () files in
1372-
let files = remove_unit_fields#visit_files () files in
1377+
let tbl = Hashtbl.create 41 in
1378+
let files = (build_unit_field_table tbl)#visit_files () files in
1379+
let files = (remove_unit_fields tbl)#visit_files () files in
13731380
files
13741381

13751382
(* For Rust, we leave `match` nodes in the AST -- Rust *does* have

lib/Monomorphization.ml

Lines changed: 143 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -332,114 +332,145 @@ let monomorphize_data_types map = object(self)
332332
* order declarations as they are needed, including that of the node we are
333333
* visiting. *)
334334
method private visit_node (under_ref: bool) (n: node) =
335-
(* Because we have no equirecursive types, this should not loop *)
336-
let n = fst3 n, List.map (self#visit_typ under_ref) (snd3 n), thd3 n in
337-
let lid, args, cgs = n in
338-
(* White, gray or black? *)
339-
match Hashtbl.find state n with
340-
| exception Not_found ->
341-
let chosen_lid, flag = self#lid_of n in
342-
if Options.debug "data-types-traversal" then
343-
KPrint.bprintf "visiting %a: Not_found --> %a\n" ptyp (fold_tapp n) plid chosen_lid;
344-
let flag = if fst3 best_hint = n then thd3 best_hint @ flag else flag in
345-
if lid = tuple_lid then begin
346-
Hashtbl.add state n (Gray, chosen_lid);
347-
(* For tuples, we immediately know how to generate a definition. *)
348-
let fields = List.mapi (fun i arg -> Some (self#field_at i), (arg, false)) args in
349-
self#record (DType (chosen_lid, [ Common.Private ] @ flag, 0, 0, Flat fields));
350-
Hashtbl.replace state n (Black, chosen_lid)
351-
end else begin
352-
(* This specific node has not been visited yet. *)
353-
Hashtbl.add state n (Gray, chosen_lid);
354-
355-
let subst fields = List.map (fun (field, (t, m)) ->
356-
field, (DeBruijn.subst_tn args (DeBruijn.subst_ctn' cgs t), m)
357-
) fields in
358-
assert (not (Hashtbl.mem map lid) || not (has_variables args) && not (has_cg_variables cgs));
359-
begin match Hashtbl.find map lid with
360-
| exception Not_found ->
361-
(* Unknown, external non-polymorphic lid, e.g. Prims.int *)
362-
Hashtbl.replace state n (Black, chosen_lid)
363-
| flags, ((Variant _ | Flat _ | Union _) as def) when under_ref && not (Hashtbl.mem seen_declarations lid) ->
364-
(* FORWARD DECLARATION: this is unrelated to monomorphization (but we do it
365-
here anyway). An occurrence of `t<ts>*` appears before the definition of `t`; we
366-
pick a name, insert a forward declaration for `t<ts>`, then remember that *once we
367-
have see the definition of `t`*, we will insert the monomorphized `t___ts` at that
368-
specific location.
369-
370-
The comment below furthermore tries to explain why we do things this way (as
371-
opposed to inserting the monomorphized `t___ts` immediately).
372-
373-
Because this looks up a definition in the global map, the
374-
definitions are reordered according to the traversal order, which
375-
is generally a good idea (we accept more programs!), EXCEPT
376-
when the user relies on mutual recursion behind a reference
377-
(pointer) type. In that case, following the type dependency graph is
378-
generally not a good idea, since we may go from a valid
379-
ordering to an invalid one (see tests/MutualStruct.fst). So,
380-
the intent here (i.e., when under a ref type) is that:
381-
- tuple types ALWAYS get monomorphized on-demand (see
382-
above)
383-
- abbreviations are fine and won't cause further issues
384-
- data types, however, need to have their names allocated and a
385-
forward reference inserted (TODO: at most once), then the
386-
specific choice of type arguments need to be recorded as
387-
something we want to visit later (once we're done with this
388-
particular traversal)... *)
389-
if Options.debug "data-types-traversal" then
390-
KPrint.bprintf "DEFERRING %a\n" ptyp (fold_tapp n);
391-
if match def with Union _ -> true | _ -> false then
392-
self#record (DType (chosen_lid, flags, 0, 0, Forward FUnion))
393-
else
394-
self#record (DType (chosen_lid, flags, 0, 0, Forward FStruct));
395-
Hashtbl.add pending_monomorphizations lid (args, cgs);
396-
(* FORWARD DECLARATIONS: remove us from the state to make sure future occurences of the
397-
same situation send us through this codepath again *)
398-
Hashtbl.remove state n
399-
| flags, Variant branches ->
400-
let branches = List.map (fun (cons, fields) -> cons, subst fields) branches in
401-
let branches = self#visit_branches_t under_ref branches in
402-
self#record (DType (chosen_lid, flag @ flags, 0, 0, Variant branches));
403-
Hashtbl.replace state n (Black, chosen_lid)
404-
| flags, Flat fields ->
405-
let fields = self#visit_fields_t_opt under_ref (subst fields) in
406-
self#record (DType (chosen_lid, flag @ flags, 0, 0, Flat fields));
407-
Hashtbl.replace state n (Black, chosen_lid)
408-
| flags, Union fields ->
409-
let fields = List.map (fun (f, t) ->
335+
(* Fast-path:
336+
- there are no type arguments (so: nothing to monomorphize here)
337+
- we are not under a ref (so: no forward declarations to insert here),
338+
- we are not visiting ourselves either (so: no hopeful forward declaration to insert here)
339+
--> we do not visit this lid, and do not self#record it.
340+
341+
1. If we saw this lid, great.
342+
2. If we haven't seen this lid yet, no problem, its contents will be
343+
visited through self#visit_decl in the case where n = 0 && n_cgs = 0,
344+
and it will be inserted in its original spot, which has the nice
345+
side-effect of preserving the source order.*)
346+
if snd3 n = [] && thd3 n = [] && not under_ref && Option.map fst @@ Hashtbl.find_opt state n <> Some Gray then
347+
fst3 n
348+
else
349+
(* We recursively visit the arguments. This avoids inconsistencies where
350+
the map would previously record both t<u<v>> and t<u__v> which then
351+
required renormalizations and didn't work anyhow.
352+
353+
Because we have no equirecursive types, this should not loop *)
354+
let n = fst3 n, List.map (self#visit_typ under_ref) (snd3 n), thd3 n in
355+
let lid, args, cgs = n in
356+
(* White, gray or black? *)
357+
match Hashtbl.find state n with
358+
| exception Not_found ->
359+
let chosen_lid, flag = self#lid_of n in
360+
if Options.debug "data-types-traversal" then
361+
KPrint.bprintf "visiting %a: Not_found --> %a\n" ptyp (fold_tapp n) plid chosen_lid;
362+
let flag = if fst3 best_hint = n then thd3 best_hint @ flag else flag in
363+
if lid = tuple_lid then begin
364+
assert (args <> []);
365+
Hashtbl.add state n (Gray, chosen_lid);
366+
(* For tuples, we immediately know how to generate a definition. *)
367+
let fields = List.mapi (fun i arg -> Some (self#field_at i), (arg, false)) args in
368+
self#record (DType (chosen_lid, [ Common.Private ] @ flag, 0, 0, Flat fields));
369+
Hashtbl.replace state n (Black, chosen_lid)
370+
end else begin
371+
(* This specific node has not been visited yet. *)
372+
Hashtbl.add state n (Gray, chosen_lid);
373+
374+
let subst fields = List.map (fun (field, (t, m)) ->
375+
field, (DeBruijn.subst_tn args (DeBruijn.subst_ctn' cgs t), m)
376+
) fields in
377+
assert (not (Hashtbl.mem map lid) || not (has_variables args) && not (has_cg_variables cgs));
378+
begin match Hashtbl.find map lid with
379+
| exception Not_found ->
380+
(* Unknown, external non-polymorphic lid, e.g. Prims.int *)
381+
Hashtbl.replace state n (Black, chosen_lid)
382+
| flags, ((Variant _ | Flat _ | Union _) as def) when under_ref && not (Hashtbl.mem seen_declarations lid) ->
383+
(* FORWARD DECLARATION: this is unrelated to monomorphization (but we do it
384+
here anyway). An occurrence of `t<ts>*` appears before the definition of `t`; we
385+
pick a name, insert a forward declaration for `t<ts>`, then remember that *once we
386+
have see the definition of `t`, we will insert the monomorphized `t___ts` at that
387+
specific location.
388+
389+
The comment below furthermore tries to explain why we do things this way (as
390+
opposed to inserting the monomorphized `t___ts` immediately).
391+
392+
Because this looks up a definition in the global map, the
393+
definitions are reordered according to the traversal order, which
394+
is generally a good idea (we accept more programs!), EXCEPT
395+
when the user relies on mutual recursion behind a reference
396+
(pointer) type. In that case, following the type dependency graph is
397+
generally not a good idea, since we may go from a valid
398+
ordering to an invalid one (see tests/MutualStruct.fst). So,
399+
the intent here (i.e., when under a ref type) is that:
400+
- tuple types ALWAYS get monomorphized on-demand (see
401+
above)
402+
- abbreviations are fine and won't cause further issues
403+
- data types, however, need to have their names allocated and a
404+
forward reference inserted (TODO: at most once), then the
405+
specific choice of type arguments need to be recorded as
406+
something we want to visit later (once we're done with this
407+
particular traversal)... *)
408+
if Options.debug "data-types-traversal" then
409+
KPrint.bprintf "DEFERRING %a\n" ptyp (fold_tapp n);
410+
if match def with Union _ -> true | _ -> false then
411+
self#record (DType (chosen_lid, flags, 0, 0, Forward FUnion))
412+
else
413+
self#record (DType (chosen_lid, flags, 0, 0, Forward FStruct));
414+
Hashtbl.add pending_monomorphizations lid (args, cgs);
415+
(* FORWARD DECLARATIONS: remove us from the state to make sure future occurences of the
416+
same situation send us through this codepath again *)
417+
Hashtbl.remove state n
418+
| flags, Variant branches ->
419+
let branches = List.map (fun (cons, fields) -> cons, subst fields) branches in
420+
let branches = self#visit_branches_t under_ref branches in
421+
if args <> [] || cgs <> [] then
422+
self#record (DType (chosen_lid, flag @ flags, 0, 0, Variant branches));
423+
Hashtbl.replace state n (Black, chosen_lid)
424+
| flags, Flat fields ->
425+
let fields = self#visit_fields_t_opt under_ref (subst fields) in
426+
if args <> [] || cgs <> [] then
427+
self#record (DType (chosen_lid, flag @ flags, 0, 0, Flat fields));
428+
Hashtbl.replace state n (Black, chosen_lid)
429+
| flags, Union fields ->
430+
let fields = List.map (fun (f, t) ->
431+
let t = DeBruijn.subst_tn args t in
432+
let t = self#visit_typ under_ref t in
433+
f, t
434+
) fields in
435+
if args <> [] || cgs <> [] then
436+
self#record (DType (chosen_lid, flag @ flags, 0, 0, Union fields));
437+
Hashtbl.replace state n (Black, chosen_lid)
438+
| flags, Abbrev t ->
410439
let t = DeBruijn.subst_tn args t in
411440
let t = self#visit_typ under_ref t in
412-
f, t
413-
) fields in
414-
self#record (DType (chosen_lid, flag @ flags, 0, 0, Union fields));
415-
Hashtbl.replace state n (Black, chosen_lid)
416-
| flags, Abbrev t ->
417-
let t = DeBruijn.subst_tn args t in
418-
let t = self#visit_typ under_ref t in
419-
self#record (DType (chosen_lid, flag @ flags, 0, 0, Abbrev t));
420-
Hashtbl.replace state n (Black, chosen_lid)
421-
| _ ->
422-
Hashtbl.replace state n (Black, chosen_lid)
423-
end
424-
end;
425-
chosen_lid
426-
| Gray, chosen_lid ->
427-
(* FORWARD DECLARATION: simple case of a recursive type that needs a forward declaration *)
428-
if Options.debug "data-types-traversal" then
429-
KPrint.bprintf "visiting %a: Gray\n" ptyp (fold_tapp n);
430-
begin match Hashtbl.find map lid with
431-
| exception Not_found ->
432-
()
433-
| flags, Union _ ->
434-
self#record (DType (chosen_lid, flags, 0, 0, Forward FUnion))
435-
| flags, _ ->
436-
self#record (DType (chosen_lid, flags, 0, 0, Forward FStruct))
437-
end;
438-
chosen_lid
439-
| Black, chosen_lid ->
440-
if Options.debug "data-types-traversal" then
441-
KPrint.bprintf "visiting %a: Black\n" ptyp (fold_tapp n);
442-
chosen_lid
441+
if args <> [] || cgs <> [] then
442+
self#record (DType (chosen_lid, flag @ flags, 0, 0, Abbrev t));
443+
Hashtbl.replace state n (Black, chosen_lid)
444+
| _ ->
445+
Hashtbl.replace state n (Black, chosen_lid)
446+
end
447+
end;
448+
chosen_lid
449+
| Gray, chosen_lid ->
450+
(* FORWARD DECLARATION: simple case of a recursive type that needs a
451+
forward declaration.
452+
453+
We still insert something to deal with cases like
454+
455+
typedef struct s {
456+
void f(t x);
457+
} t;
458+
*)
459+
if Options.debug "data-types-traversal" then
460+
KPrint.bprintf "visiting %a: Gray\n" ptyp (fold_tapp n);
461+
begin match Hashtbl.find map lid with
462+
| exception Not_found ->
463+
()
464+
| flags, Union _ ->
465+
self#record (DType (chosen_lid, flags, 0, 0, Forward FUnion))
466+
| flags, _ ->
467+
self#record (DType (chosen_lid, flags, 0, 0, Forward FStruct))
468+
end;
469+
chosen_lid
470+
| Black, chosen_lid ->
471+
if Options.debug "data-types-traversal" then
472+
KPrint.bprintf "visiting %a: Black\n" ptyp (fold_tapp n);
473+
chosen_lid
443474

444475
(* Top-level, non-parameterized declarations are root of our graph traversal.
445476
* This also visits, via occurrences in code, applications of parameterized
@@ -514,18 +545,13 @@ let monomorphize_data_types map = object(self)
514545

515546
| DType (lid, _, n_cgs, n, (Flat _ | Variant _ | Abbrev _ | Union _)) ->
516547
assert (n = 0 && n_cgs = 0);
517-
(* Regular traversal logic, as below. *)
518-
ignore (self#visit_decl false d);
519-
(* FORWARD DECLARATIONS: we force a visit of this (non-polymorphic) type definition, for
520-
the sole side-effect of remembering that we have seen it, and that further occurrences
521-
of it need not generate a forward declaration (i.e. mark it Black, because
522-
under_ref=false).
523-
524-
Note that `visit_node` will insert our own definition in the pending list, flushed by
525-
`clear` -- ignore and don't insert twice. *)
526-
ignore (self#visit_node false (lid, [], []));
548+
(* This was not inserted earlier (see comment at the beginning of
549+
visit_node, so we visit it here *)
550+
Hashtbl.add state (lid, [], []) (Gray, lid);
551+
let d = self#visit_decl false d in
552+
Hashtbl.replace state (lid, [], []) (Black, lid);
527553
Hashtbl.add seen_declarations lid ();
528-
self#clear ()
554+
self#clear () @ [ d ]
529555

530556
| _ ->
531557
(* Not a type, e.g. a global; needs to be retained. *)

test/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ FSTAR = $(FSTAR_EXE) \
8585
--trivial_pre_for_unannotated_effectful_fns false \
8686
--cmi --warn_error -274
8787

88-
all: $(FILES) rust $(WASM_FILES) $(CUSTOM) ctypes-test sepcomp-test rust-val-test bundle-test
88+
all: $(FILES) rust $(WASM_FILES) $(CUSTOM) ctypes-test sepcomp-test rust-val-test bundle-test rust-propererasure-bundle-test
8989

9090
# Needs node
9191
wasm: $(WASM_FILES)
@@ -299,6 +299,10 @@ rust-val-test:
299299
+$(MAKE) -C rust-val
300300
.PHONY: rust-val-test
301301

302+
rust-propererasure-bundle-test:
303+
+$(MAKE) -C rust-propererasure-bundle
304+
.PHONY: rust-propererasure-bundle-test
305+
302306
CTYPES_HAND_WRITTEN_FILES=Client.ml Makefile
303307

304308
.PHONY: $(LOWLEVEL_DIR)/Client.native

0 commit comments

Comments
 (0)