ocaml/middle_end/freshening.ml

445 lines
15 KiB
OCaml

(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Pierre Chambart, OCamlPro *)
(* Mark Shinwell and Leo White, Jane Street Europe *)
(* *)
(* Copyright 2013--2016 OCamlPro SAS *)
(* Copyright 2014--2016 Jane Street Group LLC *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
[@@@ocaml.warning "+a-4-9-30-40-41-42"]
type tbl = {
sb_var : Variable.t Variable.Map.t;
sb_mutable_var : Mutable_variable.t Mutable_variable.Map.t;
sb_exn : Static_exception.t Static_exception.Map.t;
(* Used to handle substitution sequences: we cannot call the substitution
recursively because there can be name clashes. *)
back_var : Variable.t list Variable.Map.t;
back_mutable_var : Mutable_variable.t list Mutable_variable.Map.t;
}
type t =
| Inactive
| Active of tbl
type subst = t
let empty_tbl = {
sb_var = Variable.Map.empty;
sb_mutable_var = Mutable_variable.Map.empty;
sb_exn = Static_exception.Map.empty;
back_var = Variable.Map.empty;
back_mutable_var = Mutable_variable.Map.empty;
}
let print ppf = function
| Inactive -> Format.fprintf ppf "Inactive"
| Active tbl ->
Format.fprintf ppf "Active:@ ";
Variable.Map.iter (fun var1 var2 ->
Format.fprintf ppf "%a -> %a@ "
Variable.print var1
Variable.print var2)
tbl.sb_var;
Mutable_variable.Map.iter (fun mut_var1 mut_var2 ->
Format.fprintf ppf "(mutable) %a -> %a@ "
Mutable_variable.print mut_var1
Mutable_variable.print mut_var2)
tbl.sb_mutable_var;
Variable.Map.iter (fun var vars ->
Format.fprintf ppf "%a -> %a@ "
Variable.print var
Variable.Set.print (Variable.Set.of_list vars))
tbl.back_var;
Mutable_variable.Map.iter (fun mut_var mut_vars ->
Format.fprintf ppf "(mutable) %a -> %a@ "
Mutable_variable.print mut_var
Mutable_variable.Set.print (Mutable_variable.Set.of_list mut_vars))
tbl.back_mutable_var
let empty = Inactive
let empty_preserving_activation_state = function
| Inactive -> Inactive
| Active _ -> Active empty_tbl
let activate = function
| Inactive -> Active empty_tbl
| Active _ as t -> t
let rec add_sb_var sb id id' =
let sb = { sb with sb_var = Variable.Map.add id id' sb.sb_var } in
let sb =
try let pre_vars = Variable.Map.find id sb.back_var in
List.fold_left (fun sb pre_id -> add_sb_var sb pre_id id') sb pre_vars
with Not_found -> sb in
let back_var =
let l = try Variable.Map.find id' sb.back_var with Not_found -> [] in
Variable.Map.add id' (id :: l) sb.back_var in
{ sb with back_var }
let rec add_sb_mutable_var sb id id' =
let sb =
{ sb with
sb_mutable_var = Mutable_variable.Map.add id id' sb.sb_mutable_var;
}
in
let sb =
try
let pre_vars = Mutable_variable.Map.find id sb.back_mutable_var in
List.fold_left (fun sb pre_id -> add_sb_mutable_var sb pre_id id')
sb pre_vars
with Not_found -> sb in
let back_mutable_var =
let l =
try Mutable_variable.Map.find id' sb.back_mutable_var
with Not_found -> []
in
Mutable_variable.Map.add id' (id :: l) sb.back_mutable_var
in
{ sb with back_mutable_var }
let apply_static_exception t i =
match t with
| Inactive ->
i
| Active t ->
try Static_exception.Map.find i t.sb_exn
with Not_found -> i
let add_static_exception t i =
match t with
| Inactive -> i, t
| Active t ->
let i' = Static_exception.create () in
let sb_exn =
Static_exception.Map.add i i' t.sb_exn
in
i', Active { t with sb_exn; }
let active_add_variable t id =
let id' = Variable.rename id in
let t = add_sb_var t id id' in
id', t
let add_variable t id =
match t with
| Inactive -> id, t
| Active t ->
let id', t = active_add_variable t id in
id', Active t
let active_add_variables' t ids =
List.fold_right (fun id (ids, t) ->
let id', t = active_add_variable t id in
id' :: ids, t) ids ([], t)
let add_variables t defs =
List.fold_right (fun (id, data) (defs, t) ->
let id', t = add_variable t id in
(id', data) :: defs, t) defs ([], t)
let add_variables' t ids =
List.fold_right (fun id (ids, t) ->
let id', t = add_variable t id in
id' :: ids, t) ids ([], t)
let active_add_mutable_variable t id =
let id' = Mutable_variable.freshen id in
let t = add_sb_mutable_var t id id' in
id', t
let add_mutable_variable t id =
match t with
| Inactive -> id, t
| Active t ->
let id', t = active_add_mutable_variable t id in
id', Active t
let active_find_var_exn t id =
try Variable.Map.find id t.sb_var with
| Not_found ->
Misc.fatal_error (Format.asprintf "find_var: can't find %a@."
Variable.print id)
let apply_variable t var =
match t with
| Inactive -> var
| Active t ->
try Variable.Map.find var t.sb_var with
| Not_found -> var
let apply_mutable_variable t mut_var =
match t with
| Inactive -> mut_var
| Active t ->
try Mutable_variable.Map.find mut_var t.sb_mutable_var with
| Not_found -> mut_var
let rewrite_recursive_calls_with_symbols t
(function_declarations : Flambda.function_declarations)
~make_closure_symbol =
match t with
| Inactive -> function_declarations
| Active _ ->
let all_free_symbols =
Flambda_utils.all_free_symbols function_declarations
in
let closure_symbols_used = ref false in
let closure_symbols =
Variable.Map.fold (fun var _ map ->
let closure_id = Closure_id.wrap var in
let sym = make_closure_symbol closure_id in
if Symbol.Set.mem sym all_free_symbols then begin
closure_symbols_used := true;
Symbol.Map.add sym var map
end else begin
map
end)
function_declarations.funs Symbol.Map.empty
in
if not !closure_symbols_used then begin
(* Don't waste time rewriting the function declaration(s) if there
are no occurrences of any of the closure symbols. *)
function_declarations
end else begin
let funs =
Variable.Map.map (fun (ffun : Flambda.function_declaration) ->
let body =
Flambda_iterators.map_toplevel_named
(* CR-someday pchambart: This may be worth deep substituting
below the closures, but that means that we need to take care
of functions' free variables. *)
(function
| Symbol sym when Symbol.Map.mem sym closure_symbols ->
Expr (Var (Symbol.Map.find sym closure_symbols))
| e -> e)
ffun.body
in
Flambda.create_function_declaration ~params:ffun.params
~body ~stub:ffun.stub ~dbg:ffun.dbg ~inline:ffun.inline
~specialise:ffun.specialise ~is_a_functor:ffun.is_a_functor)
function_declarations.funs
in
Flambda.update_function_declarations function_declarations ~funs
end
module Project_var = struct
type t =
{ vars_within_closure : Var_within_closure.t Var_within_closure.Map.t;
closure_id : Closure_id.t Closure_id.Map.t }
let empty =
{ vars_within_closure = Var_within_closure.Map.empty;
closure_id = Closure_id.Map.empty;
}
let print ppf t =
Format.fprintf ppf "{ vars_within_closure %a, closure_id %a }"
(Var_within_closure.Map.print Var_within_closure.print)
t.vars_within_closure
(Closure_id.Map.print Closure_id.print)
t.closure_id
let new_subst_fv t id subst =
match subst with
| Inactive -> id, subst, t
| Active subst ->
let id' = Variable.rename id in
let subst = add_sb_var subst id id' in
let off = Var_within_closure.wrap id in
let off' = Var_within_closure.wrap id' in
let off_sb = Var_within_closure.Map.add off off' t.vars_within_closure in
id', Active subst, { t with vars_within_closure = off_sb; }
let new_subst_fun t id subst =
let id' = Variable.rename id in
let subst = add_sb_var subst id id' in
let off = Closure_id.wrap id in
let off' = Closure_id.wrap id' in
let off_sb = Closure_id.Map.add off off' t.closure_id in
id', subst, { t with closure_id = off_sb; }
(** Returns :
* The map of new_identifiers -> expression
* The new environment with added substitution
* a fresh ffunction_subst with only the substitution of free variables
*)
let subst_free_vars fv subst ~only_freshen_parameters
: (Flambda.specialised_to * _) Variable.Map.t * _ * _ =
Variable.Map.fold (fun id lam (fv, subst, t) ->
let id, subst, t =
if only_freshen_parameters then
id, subst, t
else
new_subst_fv t id subst
in
Variable.Map.add id lam fv, subst, t)
fv
(Variable.Map.empty, subst, empty)
(** Returns :
* The function_declaration with renamed function identifiers
* The new environment with added substitution
* The ffunction_subst completed with function substitution
subst_free_vars must have been used to build off_sb
*)
let func_decls_subst t (subst : subst)
(func_decls : Flambda.function_declarations)
~only_freshen_parameters =
match subst with
| Inactive -> func_decls, subst, t
| Active subst ->
let subst_func_decl _fun_id (func_decl : Flambda.function_declaration)
subst =
let params, subst = active_add_variables' subst func_decl.params in
(* Since all parameters are distinct, even between functions, we can
just use a single substitution. *)
let body =
Flambda_utils.toplevel_substitution subst.sb_var func_decl.body
in
let function_decl =
Flambda.create_function_declaration ~params
~body ~stub:func_decl.stub ~dbg:func_decl.dbg
~inline:func_decl.inline ~specialise:func_decl.specialise
~is_a_functor:func_decl.is_a_functor
in
function_decl, subst
in
let subst, t =
if only_freshen_parameters then
subst, t
else
Variable.Map.fold (fun orig_id _func_decl (subst, t) ->
let _id, subst, t = new_subst_fun t orig_id subst in
subst, t)
func_decls.funs
(subst, t)
in
let funs, subst =
Variable.Map.fold (fun orig_id func_decl (funs, subst) ->
let func_decl, subst = subst_func_decl orig_id func_decl subst in
let id =
if only_freshen_parameters then orig_id
else active_find_var_exn subst orig_id
in
let funs = Variable.Map.add id func_decl funs in
funs, subst)
func_decls.funs
(Variable.Map.empty, subst)
in
let function_decls =
Flambda.update_function_declarations func_decls ~funs
in
function_decls, Active subst, t
let apply_closure_id t closure_id =
try Closure_id.Map.find closure_id t.closure_id
with Not_found -> closure_id
let apply_var_within_closure t var_in_closure =
try Var_within_closure.Map.find var_in_closure t.vars_within_closure
with Not_found -> var_in_closure
module Compose (T : Identifiable.S) = struct
let compose ~earlier ~later =
if (T.Map.equal T.equal) earlier later
|| T.Map.cardinal later = 0
then
earlier
else
T.Map.mapi (fun src_var var ->
if T.Map.mem src_var later then begin
Misc.fatal_errorf "Freshening.Project_var.compose: domains \
of substitutions must be disjoint. earlier=%a later=%a"
(T.Map.print T.print) earlier
(T.Map.print T.print) later
end;
match T.Map.find var later with
| exception Not_found -> var
| var -> var)
earlier
end
module V = Compose (Var_within_closure)
module C = Compose (Closure_id)
let compose ~earlier ~later : t =
{ vars_within_closure =
V.compose ~earlier:earlier.vars_within_closure
~later:later.vars_within_closure;
closure_id =
C.compose ~earlier:earlier.closure_id
~later:later.closure_id;
}
end
let apply_function_decls_and_free_vars t fv func_decls
~only_freshen_parameters =
let module I = Project_var in
let fv, t, of_closures = I.subst_free_vars fv t ~only_freshen_parameters in
let func_decls, t, of_closures =
I.func_decls_subst of_closures t func_decls ~only_freshen_parameters
in
fv, func_decls, t, of_closures
let does_not_freshen t vars =
match t with
| Inactive -> true
| Active subst ->
not (List.exists (fun var -> Variable.Map.mem var subst.sb_var) vars)
let freshen_projection (projection : Projection.t) ~freshening
~closure_freshening : Projection.t =
match projection with
| Project_var { closure; closure_id; var; } ->
Project_var {
closure = apply_variable freshening closure;
closure_id = Project_var.apply_closure_id closure_freshening closure_id;
var = Project_var.apply_var_within_closure closure_freshening var;
}
| Project_closure { set_of_closures; closure_id; } ->
Project_closure {
set_of_closures = apply_variable freshening set_of_closures;
closure_id = Project_var.apply_closure_id closure_freshening closure_id;
}
| Move_within_set_of_closures { closure; start_from; move_to; } ->
Move_within_set_of_closures {
closure = apply_variable freshening closure;
start_from = Project_var.apply_closure_id closure_freshening start_from;
move_to = Project_var.apply_closure_id closure_freshening move_to;
}
| Field (field_index, var) ->
Field (field_index, apply_variable freshening var)
let freshen_projection_relation relation ~freshening ~closure_freshening =
Variable.Map.map (fun (spec_to : Flambda.specialised_to) ->
let projection =
match spec_to.projection with
| None -> None
| Some projection ->
Some (freshen_projection projection ~freshening ~closure_freshening)
in
{ spec_to with projection; })
relation
let freshen_projection_relation' relation ~freshening ~closure_freshening =
Variable.Map.map (fun ((spec_to : Flambda.specialised_to), data) ->
let projection =
match spec_to.projection with
| None -> None
| Some projection ->
Some (freshen_projection projection ~freshening ~closure_freshening)
in
{ spec_to with projection; }, data)
relation