Skip to content

Commit 3d901f7

Browse files
authored
flambda-backend: New library for computing instance names after substitution (#1186)
Defines `Global_module.t` and related types and their behavior under substitution. These represent the names of global modules, including instances, which carry arguments for parameterized modules. In particular, a `Global_module.Name.t` will be contained by the `Ident.t` for an instance identifier.
1 parent d63b736 commit 3d901f7

File tree

5 files changed

+349
-1
lines changed

5 files changed

+349
-1
lines changed

dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989

9090
;; TYPING
9191
ident path jkind primitive shape shape_reduce types btype oprint subst predef datarepr
92-
cmi_format persistent_env env errortrace mode jkind_types jkind_intf
92+
global_module cmi_format persistent_env env errortrace mode jkind_types jkind_intf
9393
typedtree printtyped ctype printtyp includeclass mtype envaux includecore
9494
tast_iterator tast_mapper signature_group cmt_format cms_format untypeast
9595
includemod includemod_errorprinter

typing/global_module.ml

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
[@@@ocaml.warning "+a-40-41-42"]
2+
3+
let pp_concat pp ppf list =
4+
Format.pp_print_list ~pp_sep:Format.pp_print_cut pp ppf list
5+
6+
module Name : sig
7+
type t = private {
8+
head : string;
9+
args : (t * t) list;
10+
}
11+
12+
val create : string -> (t * t) list -> t
13+
14+
val unsafe_create_unchecked : string -> (t * t) list -> t
15+
16+
include Identifiable.S with type t := t
17+
end = struct
18+
type t = {
19+
head : string;
20+
args : (t * t) list;
21+
}
22+
23+
include Identifiable.Make (struct
24+
type nonrec t = t
25+
26+
let rec compare
27+
({ head = head1; args = args1 } as t1)
28+
({ head = head2; args = args2 } as t2) =
29+
if t1 == t2 then 0
30+
else
31+
match String.compare head1 head2 with
32+
| 0 -> List.compare compare_arg args1 args2
33+
| c -> c
34+
and compare_arg (name1, arg1) (name2, arg2) =
35+
match compare name1 name2 with
36+
| 0 -> compare arg1 arg2
37+
| c -> c
38+
39+
let equal t1 t2 = compare t1 t2 = 0
40+
41+
let rec print ppf ({ head; args } : t) =
42+
match args with
43+
| [] ->
44+
(* Preserve simple non-wrapping behaviour in atomic case *)
45+
Format.fprintf ppf "%s" head
46+
| _ ->
47+
Format.fprintf ppf "@[<hov 1>%s%a@]"
48+
head
49+
(pp_concat print_arg_pair) args
50+
and print_arg_pair ppf (name, arg) =
51+
Format.fprintf ppf "[%a:%a]" print name print arg
52+
53+
let output = print |> Misc.output_of_print
54+
55+
let hash = Hashtbl.hash
56+
end)
57+
58+
let create head args =
59+
let sorted_args =
60+
List.sort_uniq (fun (name1, _) (name2, _) -> compare name1 name2) args
61+
in
62+
let t = { head; args = sorted_args } in
63+
if List.length args != List.length sorted_args then
64+
Misc.fatal_errorf "Names of instance arguments must be unique:@ %a"
65+
print t;
66+
t
67+
68+
let unsafe_create_unchecked head args = { head; args }
69+
end
70+
71+
let compare_arg_name (name1, _) (name2, _) = Name.compare name1 name2
72+
73+
let rec list_similar f list1 list2 =
74+
match list1, list2 with
75+
| [], [] -> true
76+
| a :: list1, b :: list2 -> f a b && list_similar f list1 list2
77+
| (_ :: _), [] | [], (_ :: _) -> false
78+
79+
module T0 : sig
80+
type t = private {
81+
head : string;
82+
visible_args : (Name.t * t) list;
83+
hidden_args : (Name.t * t) list;
84+
}
85+
86+
include Identifiable.S with type t := t
87+
88+
val create : string -> (Name.t * t) list -> hidden_args:(Name.t * t) list -> t
89+
90+
val to_name : t -> Name.t
91+
end = struct
92+
type t = {
93+
head : string;
94+
visible_args : (Name.t * t) list;
95+
hidden_args : (Name.t * t) list;
96+
}
97+
98+
include Identifiable.Make (struct
99+
type nonrec t = t
100+
101+
let rec compare
102+
({ head = head1; visible_args = visible_args1; hidden_args = hidden_args1 } as t1)
103+
({ head = head2; visible_args = visible_args2; hidden_args = hidden_args2 } as t2) =
104+
if t1 == t2 then 0
105+
else
106+
match String.compare head1 head2 with
107+
| 0 -> begin
108+
match List.compare compare_pairs visible_args1 visible_args2 with
109+
| 0 -> List.compare compare_pairs hidden_args1 hidden_args2
110+
| c -> c
111+
end
112+
| c -> c
113+
and compare_pairs (param1, value1) (param2, value2) =
114+
match Name.compare param1 param2 with
115+
| 0 -> compare value1 value2
116+
| c -> c
117+
118+
let equal t1 t2 = compare t1 t2 = 0
119+
120+
let rec equal_looking t name =
121+
let { head; visible_args; hidden_args } = t in
122+
let { Name.head = name_head; args = name_args } = name in
123+
hidden_args = []
124+
&& String.equal head name_head
125+
&& list_similar equal_looking_args visible_args name_args
126+
and equal_looking_args (name1, value1) (name2, value2) =
127+
Name.equal name1 name2 && equal_looking value1 value2
128+
129+
let rec print ppf { head; visible_args; hidden_args } =
130+
Format.fprintf ppf "@[<hov 1>%s%a%a@]"
131+
head
132+
(pp_concat print_visible_pair) visible_args
133+
(pp_concat print_hidden_pair) hidden_args
134+
and print_visible_pair ppf (name, value) =
135+
Format.fprintf ppf "[%a:%a]" Name.print name print value
136+
and print_hidden_pair ppf (name, value) =
137+
if equal_looking value name then
138+
Format.fprintf ppf "{%a}" Name.print name
139+
else
140+
Format.fprintf ppf "{%a:%a}" Name.print name print value
141+
142+
let output = print |> Misc.output_of_print
143+
144+
let hash = Hashtbl.hash
145+
end)
146+
147+
let create head visible_args ~hidden_args =
148+
let visible_args_sorted = List.sort compare_arg_name visible_args in
149+
let hidden_args_sorted = List.sort compare_arg_name hidden_args in
150+
let t =
151+
{
152+
head;
153+
visible_args = visible_args_sorted;
154+
hidden_args = hidden_args_sorted;
155+
}
156+
in
157+
if
158+
List.length visible_args != List.length visible_args_sorted
159+
|| List.length hidden_args != List.length hidden_args_sorted
160+
then
161+
Misc.fatal_errorf "Names of arguments and parameters must be unique:@ %a"
162+
print t;
163+
t
164+
165+
(* CR-someday lmaurer: Should try and make this unnecessary or at least cheap.
166+
Could do it by making [Name.t] an unboxed existential so that converting from
167+
[t] is the identity. Or just have [Name.t] wrap [t] and ignore [hidden_args]. *)
168+
let rec to_name ({ head; visible_args; hidden_args = _ }) : Name.t =
169+
(* Safe because we already checked the names in this exact argument list *)
170+
Name.unsafe_create_unchecked head (List.map arg_to_name visible_args)
171+
and arg_to_name (name, value) =
172+
name, to_name value
173+
end
174+
175+
include T0
176+
177+
let all_args t = t.visible_args @ t.hidden_args
178+
179+
module Subst = Name.Map
180+
type subst = t Subst.t
181+
182+
let rec subst0 (t : t) (s : subst) ~changed =
183+
match Subst.find_opt (to_name t) s with
184+
| Some rhs -> changed := true; rhs
185+
| None -> subst0_inside t s ~changed
186+
and subst0_inside { head; visible_args; hidden_args } s ~changed =
187+
let matching_hidden_args, non_matching_hidden_args =
188+
List.partition_map
189+
(fun ((name, value) as pair) ->
190+
match Subst.find_opt (to_name value) s with
191+
| Some rhs -> changed := true; Left (name, rhs)
192+
| None -> Right pair)
193+
hidden_args
194+
in
195+
let visible_args = subst0_alist visible_args s ~changed in
196+
let hidden_args = subst0_alist non_matching_hidden_args s ~changed in
197+
let visible_args =
198+
List.merge compare_arg_name visible_args matching_hidden_args
199+
in
200+
create head visible_args ~hidden_args
201+
and subst0_alist l s ~changed =
202+
List.map (fun (name, value) -> name, subst0 value s ~changed) l
203+
204+
let subst t s =
205+
let changed = ref false in
206+
let new_t = subst0 t s ~changed in
207+
if !changed then new_t, `Changed else t, `Did_not_change
208+
209+
let subst_inside t s =
210+
let changed = ref false in
211+
let new_t = subst0_inside t s ~changed in
212+
if !changed then new_t else t
213+
214+
let check s args =
215+
(* This could do more - say, check that the replacement (the argument) has
216+
all the parameters of the original (the parameter). (The subset rule
217+
requires this, since an argument has to refer to the parameter it
218+
implements, and thus the parameter's parameters must include the
219+
argument's parameters.) It would be redundant with the checks
220+
implemented elsewhere but could still be helpful. *)
221+
let param_set = List.map to_name args |> Name.Set.of_list in
222+
Name.Set.subset (Name.Map.keys s) param_set
223+
224+
let rec is_complete t =
225+
match t.hidden_args with
226+
| [] -> List.for_all (fun (_, value) -> is_complete value) t.visible_args
227+
| _ -> false
228+
229+
let has_arguments t =
230+
match t with
231+
| { head = _; visible_args = []; hidden_args = [] } -> false
232+
| _ -> true

typing/global_module.mli

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
[@@@ocaml.warning "+a-9-40-41-42"]
2+
3+
module Name : sig
4+
type t = private {
5+
head : string;
6+
args : (t * t) list;
7+
}
8+
9+
include Identifiable.S with type t := t
10+
11+
val create : string -> (t * t) list -> t
12+
end
13+
14+
(** An elaborated form of name in which all arguments are expressed, including
15+
those being passed implicitly from one module to another by the subset rule
16+
for parameterised modules. Normally, these "hidden" arguments simply say to
17+
pass [X] as [X] for some module [X], but if there are parameterised
18+
parameters, the hidden arguments can get more complex.
19+
20+
Suppose [M] takes parameters [X] and [Y], neither of which is itself
21+
parameterised. If someone is passing [Foo] as the value of [X], then, we
22+
will have (abbreviating nested records):
23+
24+
{v
25+
{ head: M; visible_args: [ X, Foo ]; hidden_args: [ Y, Y ] }
26+
v}
27+
28+
This represents that [X] is explicitly being given the value [Foo] and [Y]
29+
(the parameter) is implicitly getting the value [Y] (the argument currently
30+
in scope).
31+
32+
However, suppose instead [Y] is parameterised by [X]. Then [M] still takes
33+
two parameters [X] and [Y], but now once [X] has the value [Foo], [Y]
34+
requires _that particular_ [X]:
35+
36+
{v
37+
{ head: M; visible_args: [ X, Foo ]; hidden_args: [ Y, Y[X:Foo] ] }
38+
v}
39+
40+
Importantly, the _parameters_ [X] and [Y] never change: they are names that
41+
appear in [m.ml] and [m.cmi]. But further specialisation requires passing
42+
specifically a [Y[X:Foo]] rather than a [Y]. (Here, [Y[X:Foo]] stands for
43+
the record [{ head = Y; visible_args = [ X, Foo ]; hidden_args = [] }] of
44+
type [t].)
45+
*)
46+
type t = private {
47+
head : string;
48+
visible_args : (Name.t * t) list;
49+
hidden_args : (Name.t * t) list;
50+
}
51+
52+
include Identifiable.S with type t := t
53+
54+
val create : string -> (Name.t * t) list -> hidden_args:(Name.t * t) list -> t
55+
56+
val to_name : t -> Name.t
57+
58+
val all_args : t -> (Name.t * t) list
59+
60+
(** A map from parameter names to their values. Hidden arguments aren't relevant
61+
in the parameter names, so they're represented by [Name.t]s here. *)
62+
type subst = t Name.Map.t
63+
64+
(** Apply a substitution to the given global. If it appears in the substitution
65+
directly (that is, its [Name.t] form is a key in the map), this simply
66+
performs a lookup. Otherwise, we perform a _revealing substitution_: if the
67+
value of a hidden argument is a key in the substitution, the argument becomes
68+
visible. Otherwise, substitution recurses into arguments (both hidden and
69+
visible) as usual. See [global_test.ml] for examples. *)
70+
val subst : t -> subst -> t * [ `Changed | `Did_not_change ]
71+
72+
(** Apply a substitution to the arguments and parameters in [t] but not to [t]
73+
itself. Useful if [subst] is constructed from some parameter-argument pairs
74+
and [t] is one of the parameters, since we want to handle any
75+
interdependencies but the substitution applied to [t] itself is
76+
uninterestingly just the corresponding value. *)
77+
val subst_inside : t -> subst -> t
78+
79+
(** Check that a substitution is a valid (possibly partial) instantiation of
80+
a module with the given parameter list. Each name being substituted must
81+
appear in the list. *)
82+
val check : subst -> t list -> bool
83+
84+
(** Returns [true] if [hidden_args] is empty and all argument values (if any)
85+
are also complete. This is a stronger condition than full application, and
86+
(unless the whole global is itself a parameter) it's equivalent to the
87+
global being a static constant, since any parameters being used would have
88+
to show up in a [hidden_args] somewhere. (Importantly, it's not possible
89+
that a parameter is being used as an argument to a different parameter,
90+
since a module can be declared to be an argument for up to one parameter.)
91+
92+
CR lmaurer: Make sure we're checking for the user redundantly passing an
93+
parameter as an argument. This should be accepted and ignored, lest we
94+
count the parameter as filled and consider something completely
95+
instantiated. *)
96+
val is_complete : t -> bool
97+
98+
(** Returns [true] if this name has at least one argument (either hidden or
99+
visible). *)
100+
val has_arguments : t -> bool

utils/misc.ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,16 @@ let print_see_manual ppf manual_section =
10891089
(pp_print_list ~pp_sep:(fun f () -> pp_print_char f '.') pp_print_int)
10901090
manual_section
10911091

1092+
let output_of_print print =
1093+
let output out_channel t =
1094+
let ppf = Format.formatter_of_out_channel out_channel in
1095+
print ppf t;
1096+
(* Must flush the formatter immediately because it has a buffer separate
1097+
from the output channel's buffer *)
1098+
Format.pp_print_flush ppf ()
1099+
in
1100+
output
1101+
10921102

10931103
type filepath = string
10941104

utils/misc.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,12 @@ val pp_two_columns :
624624
val print_see_manual : Format.formatter -> int list -> unit
625625
(** See manual section *)
626626

627+
val output_of_print :
628+
(Format.formatter -> 'a -> unit) -> out_channel -> 'a -> unit
629+
(** [output_of_print print] produces an output function from a pretty printer.
630+
Note that naively using [Format.formatter_of_out_channel] typechecks but
631+
doesn't work because it fails to flush the formatter. *)
632+
627633
(** {1 Displaying configuration variables} *)
628634

629635
val show_config_and_exit : unit -> unit

0 commit comments

Comments
 (0)