1638 lines
67 KiB
OCaml
1638 lines
67 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"]
|
|
|
|
module A = Simple_value_approx
|
|
module B = Inlining_cost.Benefit
|
|
module E = Inline_and_simplify_aux.Env
|
|
module R = Inline_and_simplify_aux.Result
|
|
|
|
(** Values of two types hold the information propagated during simplification:
|
|
- [E.t] "environments", top-down, almost always called "env";
|
|
- [R.t] "results", bottom-up approximately following the evaluation order,
|
|
almost always called "r". These results come along with rewritten
|
|
Flambda terms.
|
|
The environments map variables to approximations, which enable various
|
|
simplifications to be performed; for example, some variable may be known
|
|
to always hold a particular constant.
|
|
*)
|
|
|
|
(* CR mshinwell: make sure "simplify_free_variable" (and not
|
|
"simplify_var_to_var_using_approx") is always used where necessary. *)
|
|
|
|
let ret = R.set_approx
|
|
|
|
type simplify_variable_result =
|
|
| No_binding of Variable.t
|
|
| Binding of Variable.t * (Flambda.named Flambda.With_free_variables.t)
|
|
|
|
let simplify_free_variable_internal env original_var =
|
|
let var = Freshening.apply_variable (E.freshening env) original_var in
|
|
let original_var = var in
|
|
(* In the case where an approximation is useful, we introduce a [let]
|
|
to bind (e.g.) the constant or symbol replacing [var], unless this
|
|
would introduce a useless [let] as a consequence of [var] already being
|
|
in the current scope.
|
|
|
|
Even when the approximation is not useful, this simplification helps.
|
|
In particular, it squashes aliases of the form:
|
|
let var1 = var2 in ... var2 ...
|
|
by replacing [var2] in the body with [var1]. Simplification can then
|
|
eliminate the [let].
|
|
*)
|
|
let var =
|
|
let approx = E.find_exn env var in
|
|
match approx.var with
|
|
| Some var when E.mem env var -> var
|
|
| Some _ | None -> var
|
|
in
|
|
(* CR-soon mshinwell: Should we update [r] when we *add* code?
|
|
Aside from that, it looks like maybe we don't need [r] in this function,
|
|
because the approximation within it wouldn't be used by any of the
|
|
call sites. *)
|
|
match E.find_with_scope_exn env var with
|
|
| Current, approx -> No_binding var, approx (* avoid useless [let] *)
|
|
| Outer, approx ->
|
|
match A.simplify_var approx with
|
|
| None -> No_binding var, approx
|
|
| Some (named, approx) ->
|
|
let module W = Flambda.With_free_variables in
|
|
Binding (original_var, W.of_named named), approx
|
|
|
|
let simplify_free_variable env var ~f : Flambda.t * R.t =
|
|
match simplify_free_variable_internal env var with
|
|
| No_binding var, approx -> f env var approx
|
|
| Binding (var, named), approx ->
|
|
let module W = Flambda.With_free_variables in
|
|
let var = Variable.rename var in
|
|
let env = E.add env var approx in
|
|
let body, r = f env var approx in
|
|
(W.create_let_reusing_defining_expr var named body), r
|
|
|
|
let simplify_free_variables env vars ~f : Flambda.t * R.t =
|
|
let rec collect_bindings vars env bound_vars approxs : Flambda.t * R.t =
|
|
match vars with
|
|
| [] -> f env (List.rev bound_vars) (List.rev approxs)
|
|
| var::vars ->
|
|
match simplify_free_variable_internal env var with
|
|
| No_binding var, approx ->
|
|
collect_bindings vars env (var::bound_vars) (approx::approxs)
|
|
| Binding (var, named), approx ->
|
|
let module W = Flambda.With_free_variables in
|
|
let var = Variable.rename var in
|
|
let env = E.add env var approx in
|
|
let body, r =
|
|
collect_bindings vars env (var::bound_vars) (approx::approxs)
|
|
in
|
|
(W.create_let_reusing_defining_expr var named body), r
|
|
in
|
|
collect_bindings vars env [] []
|
|
|
|
let simplify_free_variables_named env vars ~f : Flambda.named * R.t =
|
|
let rec collect_bindings vars env bound_vars approxs
|
|
: Flambda.maybe_named * R.t =
|
|
match vars with
|
|
| [] ->
|
|
let named, r = f env (List.rev bound_vars) (List.rev approxs) in
|
|
Is_named named, r
|
|
| var::vars ->
|
|
match simplify_free_variable_internal env var with
|
|
| No_binding var, approx ->
|
|
collect_bindings vars env (var::bound_vars) (approx::approxs)
|
|
| Binding (var, named), approx ->
|
|
let module W = Flambda.With_free_variables in
|
|
let var = Variable.rename var in
|
|
let env = E.add env var approx in
|
|
let body, r =
|
|
collect_bindings vars env (var::bound_vars) (approx::approxs)
|
|
in
|
|
let body =
|
|
match body with
|
|
| Is_named body -> Flambda_utils.name_expr body ~name:"simplify_fv"
|
|
| Is_expr body -> body
|
|
in
|
|
Is_expr (W.create_let_reusing_defining_expr var named body), r
|
|
in
|
|
let named_or_expr, r = collect_bindings vars env [] [] in
|
|
match named_or_expr with
|
|
| Is_named named -> named, r
|
|
| Is_expr expr -> Expr expr, r
|
|
|
|
(* CR-soon mshinwell: tidy this up *)
|
|
let simplify_free_variable_named env var ~f : Flambda.named * R.t =
|
|
simplify_free_variables_named env [var] ~f:(fun env vars vars_approxs ->
|
|
match vars, vars_approxs with
|
|
| [var], [approx] -> f env var approx
|
|
| _ -> assert false)
|
|
|
|
let simplify_named_using_approx r lam approx =
|
|
let lam, _summary, approx = A.simplify_named approx lam in
|
|
lam, R.set_approx r approx
|
|
|
|
let simplify_using_approx_and_env env r original_lam approx =
|
|
let lam, summary, approx =
|
|
A.simplify_using_env approx ~is_present_in_env:(E.mem env) original_lam
|
|
in
|
|
let r =
|
|
let r = ret r approx in
|
|
match summary with
|
|
(* CR-soon mshinwell: Why is [r] not updated with the cost of adding the
|
|
new code?
|
|
mshinwell: similar to CR above *)
|
|
| Replaced_term -> R.map_benefit r (B.remove_code original_lam)
|
|
| Nothing_done -> r
|
|
in
|
|
lam, r
|
|
|
|
let simplify_named_using_approx_and_env env r original_named approx =
|
|
let named, summary, approx =
|
|
A.simplify_named_using_env approx ~is_present_in_env:(E.mem env)
|
|
original_named
|
|
in
|
|
let r =
|
|
let r = ret r approx in
|
|
match summary with
|
|
| Replaced_term -> R.map_benefit r (B.remove_code_named original_named)
|
|
| Nothing_done -> r
|
|
in
|
|
named, r
|
|
|
|
(* This adds only the minimal set of approximations to the closures.
|
|
It is not strictly necessary to have this restriction, but it helps
|
|
to catch potential substitution bugs. *)
|
|
let populate_closure_approximations
|
|
~(function_decl : Flambda.function_declaration)
|
|
~(free_vars : (_ * A.t) Variable.Map.t)
|
|
~(parameter_approximations : A.t Variable.Map.t)
|
|
~set_of_closures_env =
|
|
(* Add approximations of free variables *)
|
|
let env =
|
|
Variable.Map.fold (fun id (_, desc) env ->
|
|
E.add_outer_scope env id desc)
|
|
free_vars set_of_closures_env in
|
|
|
|
(* Add known approximations of function parameters *)
|
|
let env =
|
|
List.fold_left (fun env id ->
|
|
let approx = try Variable.Map.find id parameter_approximations
|
|
with Not_found -> (A.value_unknown Other) in
|
|
E.add env id approx)
|
|
env function_decl.params in
|
|
env
|
|
|
|
let simplify_const (const : Flambda.const) =
|
|
match const with
|
|
| Int i -> A.value_int i
|
|
| Char c -> A.value_char c
|
|
| Const_pointer i -> A.value_constptr i
|
|
|
|
let approx_for_allocated_const (const : Allocated_const.t) =
|
|
match const with
|
|
| String s -> A.value_string (String.length s) None
|
|
| Immutable_string s -> A.value_string (String.length s) (Some s)
|
|
| Int32 i -> A.value_boxed_int Int32 i
|
|
| Int64 i -> A.value_boxed_int Int64 i
|
|
| Nativeint i -> A.value_boxed_int Nativeint i
|
|
| Float f -> A.value_float f
|
|
| Float_array a -> A.value_mutable_float_array ~size:(List.length a)
|
|
| Immutable_float_array a ->
|
|
A.value_immutable_float_array
|
|
(Array.map (fun x -> Some x) (Array.of_list a))
|
|
|
|
(* Determine whether a given closure ID corresponds directly to a variable
|
|
(bound to a closure) in the given environment. This happens when the body
|
|
of a [let rec]-bound function refers to another in the same set of closures.
|
|
If we succeed in this process, we can change [Project_closure]
|
|
expressions into [Var] expressions, thus sharing closure projections. *)
|
|
let reference_recursive_function_directly env closure_id =
|
|
let closure_id = Closure_id.unwrap closure_id in
|
|
match E.find_opt env closure_id with
|
|
| None -> None
|
|
| Some approx -> Some (Flambda.Expr (Var closure_id), approx)
|
|
|
|
(* Simplify an expression that takes a set of closures and projects an
|
|
individual closure from it. *)
|
|
let simplify_project_closure env r ~(project_closure : Flambda.project_closure)
|
|
: Flambda.named * R.t =
|
|
simplify_free_variable_named env project_closure.set_of_closures
|
|
~f:(fun _env set_of_closures set_of_closures_approx ->
|
|
match A.check_approx_for_set_of_closures set_of_closures_approx with
|
|
| Wrong ->
|
|
Misc.fatal_errorf "Wrong approximation when projecting closure: %a"
|
|
Flambda.print_project_closure project_closure
|
|
| Unresolved symbol ->
|
|
(* A set of closures coming from another compilation unit, whose .cmx is
|
|
missing; as such, we cannot have rewritten the function and don't
|
|
need to do any freshening. *)
|
|
Project_closure {
|
|
set_of_closures;
|
|
closure_id = project_closure.closure_id;
|
|
}, ret r (A.value_unresolved symbol)
|
|
| Unknown ->
|
|
(* CR-soon mshinwell: see CR comment in e.g. simple_value_approx.ml
|
|
[check_approx_for_closure_allowing_unresolved] *)
|
|
Project_closure {
|
|
set_of_closures;
|
|
closure_id = project_closure.closure_id;
|
|
}, ret r (A.value_unknown Other)
|
|
| Unknown_because_of_unresolved_symbol symbol ->
|
|
Project_closure {
|
|
set_of_closures;
|
|
closure_id = project_closure.closure_id;
|
|
}, ret r (A.value_unknown (Unresolved_symbol symbol))
|
|
| Ok (set_of_closures_var, value_set_of_closures) ->
|
|
let closure_id =
|
|
A.freshen_and_check_closure_id value_set_of_closures
|
|
project_closure.closure_id
|
|
in
|
|
let projecting_from =
|
|
match set_of_closures_var with
|
|
| None -> None
|
|
| Some set_of_closures_var ->
|
|
let projection : Projection.t =
|
|
Project_closure {
|
|
set_of_closures = set_of_closures_var;
|
|
closure_id;
|
|
}
|
|
in
|
|
match E.find_projection env ~projection with
|
|
| None -> None
|
|
| Some var -> Some (var, projection)
|
|
in
|
|
match projecting_from with
|
|
| Some (var, projection) ->
|
|
simplify_free_variable_named env var ~f:(fun _env var var_approx ->
|
|
let r = R.map_benefit r (B.remove_projection projection) in
|
|
Expr (Var var), ret r var_approx)
|
|
| None ->
|
|
match reference_recursive_function_directly env closure_id with
|
|
| Some (flam, approx) -> flam, ret r approx
|
|
| None ->
|
|
let set_of_closures_var =
|
|
match set_of_closures_var with
|
|
| Some set_of_closures_var' when E.mem env set_of_closures_var' ->
|
|
set_of_closures_var
|
|
| Some _ | None -> None
|
|
in
|
|
let approx =
|
|
A.value_closure ?set_of_closures_var value_set_of_closures
|
|
closure_id
|
|
in
|
|
Project_closure { set_of_closures; closure_id; }, ret r approx)
|
|
|
|
(* Simplify an expression that, given one closure within some set of
|
|
closures, returns another closure (possibly the same one) within the
|
|
same set. *)
|
|
let simplify_move_within_set_of_closures env r
|
|
~(move_within_set_of_closures : Flambda.move_within_set_of_closures)
|
|
: Flambda.named * R.t =
|
|
simplify_free_variable_named env move_within_set_of_closures.closure
|
|
~f:(fun _env closure closure_approx ->
|
|
match A.check_approx_for_closure_allowing_unresolved closure_approx with
|
|
| Wrong ->
|
|
Misc.fatal_errorf "Wrong approximation when moving within set of \
|
|
closures. Approximation: %a Term: %a"
|
|
A.print closure_approx
|
|
Flambda.print_move_within_set_of_closures move_within_set_of_closures
|
|
| Unresolved sym ->
|
|
Move_within_set_of_closures {
|
|
closure;
|
|
start_from = move_within_set_of_closures.start_from;
|
|
move_to = move_within_set_of_closures.move_to;
|
|
},
|
|
ret r (A.value_unresolved sym)
|
|
| Unknown ->
|
|
Move_within_set_of_closures {
|
|
closure;
|
|
start_from = move_within_set_of_closures.start_from;
|
|
move_to = move_within_set_of_closures.move_to;
|
|
},
|
|
ret r (A.value_unknown Other)
|
|
| Unknown_because_of_unresolved_symbol sym ->
|
|
(* For example: a move upon a (move upon a closure whose .cmx file
|
|
is missing). *)
|
|
Move_within_set_of_closures {
|
|
closure;
|
|
start_from = move_within_set_of_closures.start_from;
|
|
move_to = move_within_set_of_closures.move_to;
|
|
},
|
|
ret r (A.value_unknown (Unresolved_symbol sym))
|
|
| Ok (_value_closure, set_of_closures_var, set_of_closures_symbol,
|
|
value_set_of_closures) ->
|
|
let freshen =
|
|
(* CR-soon mshinwell: potentially misleading name---not freshening with
|
|
new names, but with previously fresh names *)
|
|
A.freshen_and_check_closure_id value_set_of_closures
|
|
in
|
|
let move_to = freshen move_within_set_of_closures.move_to in
|
|
let start_from = freshen move_within_set_of_closures.start_from in
|
|
let projection : Projection.t =
|
|
Move_within_set_of_closures {
|
|
closure;
|
|
start_from;
|
|
move_to;
|
|
}
|
|
in
|
|
match E.find_projection env ~projection with
|
|
| Some var ->
|
|
simplify_free_variable_named env var ~f:(fun _env var var_approx ->
|
|
let r = R.map_benefit r (B.remove_projection projection) in
|
|
Expr (Var var), ret r var_approx)
|
|
| None ->
|
|
match reference_recursive_function_directly env move_to with
|
|
| Some (flam, approx) -> flam, ret r approx
|
|
| None ->
|
|
if Closure_id.equal start_from move_to then
|
|
(* Moving from one closure to itself is a no-op. We can return an
|
|
[Var] since we already have a variable bound to the closure. *)
|
|
Expr (Var closure), ret r closure_approx
|
|
else
|
|
match set_of_closures_var with
|
|
| Some set_of_closures_var when E.mem env set_of_closures_var ->
|
|
(* A variable bound to the set of closures is in scope,
|
|
meaning we can rewrite the [Move_within_set_of_closures] to a
|
|
[Project_closure]. *)
|
|
let project_closure : Flambda.project_closure =
|
|
{ set_of_closures = set_of_closures_var;
|
|
closure_id = move_to;
|
|
}
|
|
in
|
|
let approx =
|
|
A.value_closure ~set_of_closures_var value_set_of_closures
|
|
move_to
|
|
in
|
|
Project_closure project_closure, ret r approx
|
|
| Some _ | None ->
|
|
match set_of_closures_symbol with
|
|
| Some set_of_closures_symbol ->
|
|
let set_of_closures_var = Variable.create "symbol" in
|
|
let project_closure : Flambda.project_closure =
|
|
{ set_of_closures = set_of_closures_var;
|
|
closure_id = move_to;
|
|
}
|
|
in
|
|
let project_closure_var = Variable.create "project_closure" in
|
|
let let1 =
|
|
Flambda.create_let project_closure_var
|
|
(Project_closure project_closure)
|
|
(Var project_closure_var)
|
|
in
|
|
let expr =
|
|
Flambda.create_let set_of_closures_var
|
|
(Symbol set_of_closures_symbol)
|
|
let1
|
|
in
|
|
let approx =
|
|
A.value_closure ~set_of_closures_var ~set_of_closures_symbol
|
|
value_set_of_closures move_to
|
|
in
|
|
Expr expr, ret r approx
|
|
| None ->
|
|
(* The set of closures is not available in scope, and we
|
|
have no other information by which to simplify the move. *)
|
|
let move_within : Flambda.move_within_set_of_closures =
|
|
{ closure; start_from; move_to; }
|
|
in
|
|
let approx = A.value_closure value_set_of_closures move_to in
|
|
Move_within_set_of_closures move_within, ret r approx)
|
|
|
|
(* Transform an expression denoting an access to a variable bound in
|
|
a closure. Variables in the closure ([project_var.closure]) may
|
|
have been freshened since [expr] was constructed; as such, we
|
|
must ensure the same happens to [expr]. The renaming information is
|
|
contained within the approximation deduced from [closure] (as
|
|
such, that approximation *must* identify which closure it is).
|
|
|
|
For instance in some imaginary syntax for flambda:
|
|
|
|
[let f x =
|
|
let g y ~closure:{a} = a + y in
|
|
let closure = { a = x } in
|
|
g 12 ~closure]
|
|
|
|
when [f] is traversed, [g] can be inlined, resulting in the
|
|
expression
|
|
|
|
[let f z =
|
|
let g y ~closure:{a} = a + y in
|
|
let closure = { a = x } in
|
|
closure.a + 12]
|
|
|
|
[closure.a] being a notation for:
|
|
|
|
[Project_var{closure = closure; closure_id = g; var = a}]
|
|
|
|
If [f] is inlined later, the resulting code will be
|
|
|
|
[let x = ... in
|
|
let g' y' ~closure':{a'} = a' + y' in
|
|
let closure' = { a' = x } in
|
|
closure'.a' + 12]
|
|
|
|
in particular the field [a] of the closure has been alpha renamed to [a'].
|
|
This information must be carried from the declaration to the use.
|
|
|
|
If the function is declared outside of the alpha renamed part, there is
|
|
no need for renaming in the [Ffunction] and [Project_var].
|
|
This is not usualy the case, except when the closure declaration is a
|
|
symbol.
|
|
|
|
What ensures that this information is available at [Project_var]
|
|
point is that those constructions can only be introduced by inlining,
|
|
which requires that same information. For this to still be valid,
|
|
other transformation must avoid transforming the information flow in
|
|
a way that the inline function can't propagate it.
|
|
*)
|
|
let rec simplify_project_var env r ~(project_var : Flambda.project_var)
|
|
: Flambda.named * R.t =
|
|
simplify_free_variable_named env project_var.closure
|
|
~f:(fun _env closure approx ->
|
|
match A.check_approx_for_closure_allowing_unresolved approx with
|
|
| Ok (value_closure, _set_of_closures_var, _set_of_closures_symbol,
|
|
value_set_of_closures) ->
|
|
let module F = Freshening.Project_var in
|
|
let freshening = value_set_of_closures.freshening in
|
|
let var = F.apply_var_within_closure freshening project_var.var in
|
|
let closure_id = F.apply_closure_id freshening project_var.closure_id in
|
|
let closure_id_in_approx = value_closure.closure_id in
|
|
if not (Closure_id.equal closure_id closure_id_in_approx) then begin
|
|
Misc.fatal_errorf "When simplifying [Project_var], the closure ID %a \
|
|
in the approximation of the set of closures did not match the \
|
|
closure ID %a in the [Project_var] term. Approximation: %a@. \
|
|
Var-within-closure being projected: %a@."
|
|
Closure_id.print closure_id_in_approx
|
|
Closure_id.print closure_id
|
|
Simple_value_approx.print approx
|
|
Var_within_closure.print var
|
|
end;
|
|
let projection : Projection.t =
|
|
Project_var {
|
|
closure;
|
|
closure_id;
|
|
var;
|
|
}
|
|
in
|
|
begin match E.find_projection env ~projection with
|
|
| Some var ->
|
|
simplify_free_variable_named env var ~f:(fun _env var var_approx ->
|
|
let r = R.map_benefit r (B.remove_projection projection) in
|
|
Expr (Var var), ret r var_approx)
|
|
| None ->
|
|
let approx = A.approx_for_bound_var value_set_of_closures var in
|
|
let expr : Flambda.named = Project_var { closure; closure_id; var; } in
|
|
let unwrapped = Var_within_closure.unwrap var in
|
|
let expr =
|
|
if E.mem env unwrapped then
|
|
Flambda.Expr (Var unwrapped)
|
|
else
|
|
expr
|
|
in
|
|
simplify_named_using_approx_and_env env r expr approx
|
|
end
|
|
| Unresolved symbol ->
|
|
(* This value comes from a symbol for which we couldn't find any
|
|
approximation, telling us that names within the closure couldn't
|
|
have been renamed. So we don't need to change the variable or
|
|
closure ID in the [Project_var] expression. *)
|
|
Project_var { project_var with closure },
|
|
ret r (A.value_unresolved symbol)
|
|
| Unknown ->
|
|
Project_var { project_var with closure },
|
|
ret r (A.value_unknown Other)
|
|
| Unknown_because_of_unresolved_symbol symbol ->
|
|
Project_var { project_var with closure },
|
|
ret r (A.value_unknown (Unresolved_symbol symbol))
|
|
| Wrong ->
|
|
(* We must have the correct approximation of the value to ensure
|
|
we take account of all freshenings. *)
|
|
Misc.fatal_errorf "[Project_var] from a value with wrong \
|
|
approximation: %a@.closure=%a@.approx of closure=%a@."
|
|
Flambda.print_project_var project_var
|
|
Variable.print closure
|
|
Simple_value_approx.print approx)
|
|
|
|
(* Transforms closure definitions by applying [loop] on the code of every
|
|
one of the set and on the expressions of the free variables.
|
|
If the substitution is activated, alpha renaming also occur on everything
|
|
defined by the set of closures:
|
|
* Variables bound by a closure of the set
|
|
* closure identifiers
|
|
* parameters
|
|
|
|
The rewriting occurs in a clean environment without any of the variables
|
|
defined outside reachable. This helps increase robustness against
|
|
accidental, potentially unsound simplification of variable accesses by
|
|
[simplify_using_approx_and_env].
|
|
|
|
The rewriting occurs in an environment filled with:
|
|
* The approximation of the free variables
|
|
* An explicitely unknown approximation for function parameters,
|
|
except for those where it is known to be safe: those present in the
|
|
[specialised_args] set.
|
|
* An approximation for the closures in the set. It contains the code of
|
|
the functions before rewriting.
|
|
|
|
The approximation of the currently defined closures is available to
|
|
allow marking recursives calls as direct and in some cases, allow
|
|
inlining of one closure from the set inside another one. For this to
|
|
be correct an alpha renaming is first applied on the expressions by
|
|
[apply_function_decls_and_free_vars].
|
|
|
|
For instance when rewriting the declaration
|
|
|
|
[let rec f_1 x_1 =
|
|
let y_1 = x_1 + 1 in
|
|
g_1 y_1
|
|
and g_1 z_1 = f_1 (f_1 z_1)]
|
|
|
|
When rewriting this function, the first substitution will contain
|
|
some mapping:
|
|
{ f_1 -> f_2;
|
|
g_1 -> g_2;
|
|
x_1 -> x_2;
|
|
z_1 -> z_2 }
|
|
|
|
And the approximation for the closure will contain
|
|
|
|
{ f_2:
|
|
fun x_2 ->
|
|
let y_1 = x_2 + 1 in
|
|
g_2 y_1
|
|
g_2:
|
|
fun z_2 -> f_2 (f_2 z_2) }
|
|
|
|
Note that no substitution is applied to the let-bound variable [y_1].
|
|
If [f_2] where to be inlined inside [g_2], we known that a new substitution
|
|
will be introduced in the current scope for [y_1] each time.
|
|
|
|
|
|
If the function where a recursive one comming from another compilation
|
|
unit, the code already went through [Flambdasym] that could have
|
|
replaced the function variable by the symbol identifying the function
|
|
(this occur if the function contains only constants in its closure).
|
|
To handle that case, we first replace those symbols by the original
|
|
variable.
|
|
*)
|
|
and simplify_set_of_closures original_env r
|
|
(set_of_closures : Flambda.set_of_closures)
|
|
: Flambda.set_of_closures * R.t * Freshening.Project_var.t =
|
|
let function_decls =
|
|
let module Backend = (val (E.backend original_env) : Backend_intf.S) in
|
|
(* CR-soon mshinwell: Does this affect
|
|
[reference_recursive_function_directly]?
|
|
mshinwell: This should be thought about as part of the wider issue of
|
|
references to functions via symbols or variables. *)
|
|
Freshening.rewrite_recursive_calls_with_symbols (E.freshening original_env)
|
|
set_of_closures.function_decls
|
|
~make_closure_symbol:Backend.closure_symbol
|
|
in
|
|
let env = E.increase_closure_depth original_env in
|
|
let free_vars =
|
|
Variable.Map.map (fun (external_var : Flambda.specialised_to) ->
|
|
let var =
|
|
let var =
|
|
Freshening.apply_variable (E.freshening env) external_var.var
|
|
in
|
|
match
|
|
A.simplify_var_to_var_using_env (E.find_exn env var)
|
|
~is_present_in_env:(fun var -> E.mem env var)
|
|
with
|
|
| None -> var
|
|
| Some var -> var
|
|
in
|
|
let approx = E.find_exn env var in
|
|
(* The projections are freshened below in one step, once we know
|
|
the closure freshening substitution. *)
|
|
let projection = external_var.projection in
|
|
({ var; projection; } : Flambda.specialised_to), approx)
|
|
set_of_closures.free_vars
|
|
in
|
|
let specialised_args =
|
|
Variable.Map.map (fun (spec_to : Flambda.specialised_to) ->
|
|
let external_var = spec_to.var in
|
|
let var = Freshening.apply_variable (E.freshening env) external_var in
|
|
let var =
|
|
match
|
|
A.simplify_var_to_var_using_env (E.find_exn env var)
|
|
~is_present_in_env:(fun var -> E.mem env var)
|
|
with
|
|
| None -> var
|
|
| Some var -> var
|
|
in
|
|
let projection = spec_to.projection in
|
|
({ var; projection; } : Flambda.specialised_to))
|
|
set_of_closures.specialised_args
|
|
in
|
|
let environment_before_cleaning = env in
|
|
(* [E.local] helps us to catch bugs whereby variables escape their scope. *)
|
|
let env = E.local env in
|
|
let free_vars, function_decls, sb, freshening =
|
|
Freshening.apply_function_decls_and_free_vars (E.freshening env) free_vars
|
|
function_decls
|
|
in
|
|
let env = E.set_freshening env sb in
|
|
let free_vars =
|
|
Freshening.freshen_projection_relation' free_vars
|
|
~freshening:(E.freshening env)
|
|
~closure_freshening:freshening
|
|
in
|
|
let specialised_args =
|
|
let specialised_args =
|
|
Variable.Map.map_keys (Freshening.apply_variable (E.freshening env))
|
|
specialised_args
|
|
in
|
|
Freshening.freshen_projection_relation specialised_args
|
|
~freshening:(E.freshening env)
|
|
~closure_freshening:freshening
|
|
in
|
|
let parameter_approximations =
|
|
(* Approximations of parameters that are known to always hold the same
|
|
argument throughout the body of the function. *)
|
|
Variable.Map.map_keys (Freshening.apply_variable (E.freshening env))
|
|
(Variable.Map.mapi (fun _id' (spec_to : Flambda.specialised_to) ->
|
|
E.find_exn environment_before_cleaning spec_to.var)
|
|
specialised_args)
|
|
in
|
|
let env =
|
|
E.enter_set_of_closures_declaration
|
|
function_decls.set_of_closures_origin env
|
|
in
|
|
(* we use the previous closure for evaluating the functions *)
|
|
let internal_value_set_of_closures =
|
|
let bound_vars =
|
|
Variable.Map.fold (fun id (_, desc) map ->
|
|
Var_within_closure.Map.add (Var_within_closure.wrap id) desc map)
|
|
free_vars Var_within_closure.Map.empty
|
|
in
|
|
A.create_value_set_of_closures ~function_decls ~bound_vars
|
|
~invariant_params:(lazy Variable.Map.empty) ~specialised_args
|
|
~freshening
|
|
in
|
|
(* Populate the environment with the approximation of each closure.
|
|
This part of the environment is shared between all of the closures in
|
|
the set of closures. *)
|
|
let set_of_closures_env =
|
|
Variable.Map.fold (fun closure _ env ->
|
|
let approx =
|
|
A.value_closure ~closure_var:closure internal_value_set_of_closures
|
|
(Closure_id.wrap closure)
|
|
in
|
|
E.add env closure approx
|
|
)
|
|
function_decls.funs env
|
|
in
|
|
let simplify_function fid (function_decl : Flambda.function_declaration)
|
|
(funs, used_params, r)
|
|
: Flambda.function_declaration Variable.Map.t * Variable.Set.t * R.t =
|
|
let closure_env =
|
|
populate_closure_approximations ~function_decl ~free_vars
|
|
~parameter_approximations ~set_of_closures_env
|
|
in
|
|
(* Add definitions of known projections to the environment. *)
|
|
let add_projections ~closure_env ~which_variables ~map =
|
|
Variable.Map.fold (fun inner_var spec_arg env ->
|
|
let (spec_arg : Flambda.specialised_to) = map spec_arg in
|
|
match spec_arg.projection with
|
|
| None -> env
|
|
| Some projection ->
|
|
let from = Projection.projecting_from projection in
|
|
if Variable.Set.mem from function_decl.free_variables then
|
|
E.add_projection env ~projection ~bound_to:inner_var
|
|
else
|
|
env)
|
|
which_variables
|
|
closure_env
|
|
in
|
|
let closure_env =
|
|
add_projections ~closure_env ~which_variables:specialised_args
|
|
~map:(fun spec_to -> spec_to)
|
|
in
|
|
let closure_env =
|
|
add_projections ~closure_env ~which_variables:free_vars
|
|
~map:(fun (spec_to, _approx) -> spec_to)
|
|
in
|
|
let body, r =
|
|
E.enter_closure closure_env ~closure_id:(Closure_id.wrap fid)
|
|
~inline_inside:
|
|
(Inlining_decision.should_inline_inside_declaration function_decl)
|
|
~debuginfo:function_decl.dbg
|
|
~f:(fun body_env -> simplify body_env r function_decl.body)
|
|
in
|
|
let inline : Lambda.inline_attribute =
|
|
match function_decl.inline with
|
|
| Default_inline ->
|
|
if !Clflags.classic_inlining && not function_decl.stub then
|
|
(* In classic-inlining mode, the inlining decision is taken at
|
|
definition site (here). If the function is small enough
|
|
(below the -inline threshold) it will always be inlined. *)
|
|
let inlining_threshold =
|
|
Inline_and_simplify_aux.initial_inlining_threshold
|
|
~round:(E.round env)
|
|
in
|
|
if Inlining_cost.can_inline body inlining_threshold ~bonus:0
|
|
then
|
|
Always_inline
|
|
else
|
|
Default_inline
|
|
else
|
|
Default_inline
|
|
| inline ->
|
|
inline
|
|
in
|
|
let function_decl =
|
|
Flambda.create_function_declaration ~params:function_decl.params
|
|
~body ~stub:function_decl.stub ~dbg:function_decl.dbg
|
|
~inline ~specialise:function_decl.specialise
|
|
~is_a_functor:function_decl.is_a_functor
|
|
in
|
|
let used_params' = Flambda.used_params function_decl in
|
|
Variable.Map.add fid function_decl funs,
|
|
Variable.Set.union used_params used_params', r
|
|
in
|
|
let funs, _used_params, r =
|
|
Variable.Map.fold simplify_function function_decls.funs
|
|
(Variable.Map.empty, Variable.Set.empty, r)
|
|
in
|
|
let function_decls =
|
|
Flambda.update_function_declarations function_decls ~funs
|
|
in
|
|
let invariant_params =
|
|
lazy (Invariant_params.invariant_params_in_recursion function_decls
|
|
~backend:(E.backend env))
|
|
in
|
|
let value_set_of_closures =
|
|
A.create_value_set_of_closures ~function_decls
|
|
~bound_vars:internal_value_set_of_closures.bound_vars
|
|
~invariant_params
|
|
~specialised_args:internal_value_set_of_closures.specialised_args
|
|
~freshening:internal_value_set_of_closures.freshening
|
|
in
|
|
let set_of_closures =
|
|
Flambda.create_set_of_closures ~function_decls
|
|
~free_vars:(Variable.Map.map fst free_vars)
|
|
~specialised_args
|
|
in
|
|
let r = ret r (A.value_set_of_closures value_set_of_closures) in
|
|
set_of_closures, r, value_set_of_closures.freshening
|
|
|
|
and simplify_apply env r ~(apply : Flambda.apply) : Flambda.t * R.t =
|
|
let {
|
|
Flambda. func = lhs_of_application; args; kind = _; dbg;
|
|
inline = inline_requested; specialise = specialise_requested;
|
|
} = apply in
|
|
simplify_free_variable env lhs_of_application
|
|
~f:(fun env lhs_of_application lhs_of_application_approx ->
|
|
simplify_free_variables env args ~f:(fun env args args_approxs ->
|
|
(* By using the approximation of the left-hand side of the
|
|
application, attempt to determine which function is being applied
|
|
(even if the application is currently [Indirect]). If
|
|
successful---in which case we then have a direct
|
|
application---consider inlining. *)
|
|
match A.check_approx_for_closure lhs_of_application_approx with
|
|
| Ok (value_closure, _set_of_closures_var,
|
|
_set_of_closures_symbol, value_set_of_closures) ->
|
|
let closure_id_being_applied = value_closure.closure_id in
|
|
let function_decls = value_set_of_closures.function_decls in
|
|
let function_decl =
|
|
try
|
|
Flambda_utils.find_declaration closure_id_being_applied
|
|
function_decls
|
|
with
|
|
| Not_found ->
|
|
Misc.fatal_errorf "When handling application expression, \
|
|
approximation references non-existent closure %a@."
|
|
Closure_id.print closure_id_being_applied
|
|
in
|
|
let r =
|
|
match apply.kind with
|
|
| Indirect ->
|
|
R.map_benefit r Inlining_cost.Benefit.direct_call_of_indirect
|
|
| Direct _ -> r
|
|
in
|
|
let nargs = List.length args in
|
|
let arity = Flambda_utils.function_arity function_decl in
|
|
if nargs = arity then
|
|
simplify_full_application env r ~function_decls ~lhs_of_application
|
|
~closure_id_being_applied ~function_decl ~value_set_of_closures
|
|
~args ~args_approxs ~dbg ~inline_requested ~specialise_requested
|
|
else if nargs > arity then
|
|
simplify_over_application env r ~args ~args_approxs ~function_decls
|
|
~lhs_of_application ~closure_id_being_applied ~function_decl
|
|
~value_set_of_closures ~dbg ~inline_requested
|
|
~specialise_requested
|
|
else if nargs > 0 && nargs < arity then
|
|
simplify_partial_application env r ~lhs_of_application
|
|
~closure_id_being_applied ~function_decl ~args ~dbg
|
|
~inline_requested ~specialise_requested
|
|
else
|
|
Misc.fatal_errorf "Function with arity %d when simplifying \
|
|
application expression: %a"
|
|
arity Flambda.print (Flambda.Apply apply)
|
|
| Wrong -> (* Insufficient approximation information to simplify. *)
|
|
Apply ({ func = lhs_of_application; args; kind = Indirect; dbg;
|
|
inline = inline_requested; specialise = specialise_requested; }),
|
|
ret r (A.value_unknown Other)))
|
|
|
|
and simplify_full_application env r ~function_decls ~lhs_of_application
|
|
~closure_id_being_applied ~function_decl ~value_set_of_closures ~args
|
|
~args_approxs ~dbg ~inline_requested ~specialise_requested =
|
|
Inlining_decision.for_call_site ~env ~r ~function_decls
|
|
~lhs_of_application ~closure_id_being_applied ~function_decl
|
|
~value_set_of_closures ~args ~args_approxs ~dbg ~simplify
|
|
~inline_requested ~specialise_requested
|
|
|
|
and simplify_partial_application env r ~lhs_of_application
|
|
~closure_id_being_applied ~function_decl ~args ~dbg
|
|
~inline_requested ~specialise_requested =
|
|
let arity = Flambda_utils.function_arity function_decl in
|
|
assert (arity > List.length args);
|
|
(* For simplicity, we disallow [@inline] attributes on partial
|
|
applications. The user may always write an explicit wrapper instead
|
|
with such an attribute. *)
|
|
(* CR-someday mshinwell: Pierre noted that we might like a function to be
|
|
inlined when applied to its first set of arguments, e.g. for some kind
|
|
of type class like thing. *)
|
|
begin match (inline_requested : Lambda.inline_attribute) with
|
|
| Always_inline | Never_inline ->
|
|
Location.prerr_warning (Debuginfo.to_location dbg)
|
|
(Warnings.Inlining_impossible "[@inlined] attributes may not be used \
|
|
on partial applications")
|
|
| Unroll _ ->
|
|
Location.prerr_warning (Debuginfo.to_location dbg)
|
|
(Warnings.Inlining_impossible "[@unroll] attributes may not be used \
|
|
on partial applications")
|
|
| Default_inline -> ()
|
|
end;
|
|
begin match (specialise_requested : Lambda.specialise_attribute) with
|
|
| Always_specialise | Never_specialise ->
|
|
Location.prerr_warning (Debuginfo.to_location dbg)
|
|
(Warnings.Inlining_impossible "[@specialised] attributes may not be used \
|
|
on partial applications")
|
|
| Default_specialise -> ()
|
|
end;
|
|
let freshened_params =
|
|
List.map (fun id -> Variable.rename id) function_decl.Flambda.params
|
|
in
|
|
let applied_args, remaining_args =
|
|
Misc.Stdlib.List.map2_prefix (fun arg id' -> id', arg)
|
|
args freshened_params
|
|
in
|
|
let wrapper_accepting_remaining_args =
|
|
let body : Flambda.t =
|
|
Apply {
|
|
func = lhs_of_application;
|
|
args = freshened_params;
|
|
kind = Direct closure_id_being_applied;
|
|
dbg;
|
|
inline = Default_inline;
|
|
specialise = Default_specialise;
|
|
}
|
|
in
|
|
let closure_variable =
|
|
Variable.rename
|
|
~append:"_partial_fun"
|
|
(Closure_id.unwrap closure_id_being_applied)
|
|
in
|
|
Flambda_utils.make_closure_declaration ~id:closure_variable
|
|
~body
|
|
~params:remaining_args
|
|
in
|
|
let with_known_args =
|
|
Flambda_utils.bind
|
|
~bindings:(List.map (fun (var, arg) ->
|
|
var, Flambda.Expr (Var arg)) applied_args)
|
|
~body:wrapper_accepting_remaining_args
|
|
in
|
|
simplify env r with_known_args
|
|
|
|
and simplify_over_application env r ~args ~args_approxs ~function_decls
|
|
~lhs_of_application ~closure_id_being_applied ~function_decl
|
|
~value_set_of_closures ~dbg ~inline_requested ~specialise_requested =
|
|
let arity = Flambda_utils.function_arity function_decl in
|
|
assert (arity < List.length args);
|
|
assert (List.length args = List.length args_approxs);
|
|
let full_app_args, remaining_args =
|
|
Misc.Stdlib.List.split_at arity args
|
|
in
|
|
let full_app_approxs, _ =
|
|
Misc.Stdlib.List.split_at arity args_approxs
|
|
in
|
|
let expr, r =
|
|
simplify_full_application env r ~function_decls ~lhs_of_application
|
|
~closure_id_being_applied ~function_decl ~value_set_of_closures
|
|
~args:full_app_args ~args_approxs:full_app_approxs ~dbg
|
|
~inline_requested ~specialise_requested
|
|
in
|
|
let func_var = Variable.create "full_apply" in
|
|
let expr : Flambda.t =
|
|
Flambda.create_let func_var (Expr expr)
|
|
(Apply { func = func_var; args = remaining_args; kind = Indirect; dbg;
|
|
inline = inline_requested; specialise = specialise_requested; })
|
|
in
|
|
let expr = Lift_code.lift_lets_expr expr ~toplevel:true in
|
|
expr, ret r (A.value_unknown Other)
|
|
(* CR mshinwell for lwhite: This causes camlp4 to fail to build with -O3.
|
|
Can you see what's going on? This pass gets stuck in an infinite loop.
|
|
Maybe it's something to do with the approximation of [func_var] being such
|
|
that the original over-application term appears again?
|
|
simplify env r expr
|
|
*)
|
|
|
|
and simplify_named env r (tree : Flambda.named) : Flambda.named * R.t =
|
|
match tree with
|
|
| Symbol sym ->
|
|
(* New Symbol construction could have been introduced during
|
|
transformation (by simplify_named_using_approx_and_env).
|
|
When this comes from another compilation unit, we must load it. *)
|
|
let approx = E.find_or_load_symbol env sym in
|
|
simplify_named_using_approx r tree approx
|
|
| Const cst -> tree, ret r (simplify_const cst)
|
|
| Allocated_const cst -> tree, ret r (approx_for_allocated_const cst)
|
|
| Read_mutable mut_var ->
|
|
(* See comment on the [Assign] case. *)
|
|
let mut_var =
|
|
Freshening.apply_mutable_variable (E.freshening env) mut_var
|
|
in
|
|
Read_mutable mut_var, ret r (A.value_unknown Other)
|
|
| Read_symbol_field (symbol, field_index) ->
|
|
let approx = E.find_or_load_symbol env symbol in
|
|
begin match A.get_field approx ~field_index with
|
|
(* CR-someday mshinwell: Think about [Unreachable] vs. [Value_bottom]. *)
|
|
| Unreachable -> (Flambda.Expr Proved_unreachable), r
|
|
| Ok approx ->
|
|
let approx = A.augment_with_symbol_field approx symbol field_index in
|
|
simplify_named_using_approx_and_env env r tree approx
|
|
end
|
|
| Set_of_closures set_of_closures -> begin
|
|
let backend = E.backend env in
|
|
let set_of_closures, r, first_freshening =
|
|
simplify_set_of_closures env r set_of_closures
|
|
in
|
|
let simplify env r expr ~pass_name : Flambda.named * R.t =
|
|
(* If simplifying a set of closures more than once during any given round
|
|
of simplification, the [Freshening.Project_var] substitutions arising
|
|
from each call to [simplify_set_of_closures] must be composed.
|
|
Note that this function only composes with [first_freshening] owing
|
|
to the structure of the code below (this new [simplify] is always
|
|
in tail position). *)
|
|
(* CR-someday mshinwell: It was mooted that maybe we could try
|
|
structurally-typed closures (i.e. where we would never rename the
|
|
closure elements), or something else, to try to remove
|
|
the "closure freshening" thing in the approximation which is hard
|
|
to deal with. *)
|
|
let expr, r = simplify (E.set_never_inline env) r expr in
|
|
let approx = R.approx r in
|
|
let value_set_of_closures =
|
|
match A.strict_check_approx_for_set_of_closures approx with
|
|
| Wrong ->
|
|
Misc.fatal_errorf "Unexpected approximation returned from \
|
|
simplification of [%s] result: %a"
|
|
pass_name A.print approx
|
|
| Ok (_var, value_set_of_closures) ->
|
|
let freshening =
|
|
Freshening.Project_var.compose ~earlier:first_freshening
|
|
~later:value_set_of_closures.freshening
|
|
in
|
|
A.update_freshening_of_value_set_of_closures value_set_of_closures
|
|
~freshening
|
|
in
|
|
Expr expr, (ret r (A.value_set_of_closures value_set_of_closures))
|
|
in
|
|
(* This does the actual substitutions of specialised args introduced
|
|
by [Unbox_closures] for free variables. (Apart from simplifying
|
|
the [Unbox_closures] output, this also prevents applying
|
|
[Unbox_closures] over and over.) *)
|
|
let set_of_closures =
|
|
match Remove_free_vars_equal_to_args.run set_of_closures with
|
|
| None -> set_of_closures
|
|
| Some set_of_closures -> set_of_closures
|
|
in
|
|
(* Do [Unbox_closures] next to try to decide which things are
|
|
free variables and which things are specialised arguments before
|
|
unboxing them. *)
|
|
match
|
|
Unbox_closures.rewrite_set_of_closures ~env ~set_of_closures
|
|
with
|
|
| Some (expr, benefit) ->
|
|
let r = R.add_benefit r benefit in
|
|
simplify env r expr ~pass_name:"Unbox_closures"
|
|
| None ->
|
|
match Unbox_free_vars_of_closures.run ~env ~set_of_closures with
|
|
| Some (expr, benefit) ->
|
|
let r = R.add_benefit r benefit in
|
|
simplify env r expr ~pass_name:"Unbox_free_vars_of_closures"
|
|
| None ->
|
|
(* CR-soon mshinwell: should maybe add one allocation for the stub *)
|
|
match
|
|
Unbox_specialised_args.rewrite_set_of_closures ~env
|
|
~set_of_closures
|
|
with
|
|
| Some (expr, benefit) ->
|
|
let r = R.add_benefit r benefit in
|
|
simplify env r expr ~pass_name:"Unbox_specialised_args"
|
|
| None ->
|
|
match
|
|
Remove_unused_arguments.
|
|
separate_unused_arguments_in_set_of_closures
|
|
set_of_closures ~backend
|
|
with
|
|
| Some set_of_closures ->
|
|
let expr =
|
|
Flambda_utils.name_expr (Set_of_closures set_of_closures)
|
|
~name:"remove_unused_arguments"
|
|
in
|
|
simplify env r expr ~pass_name:"Remove_unused_arguments"
|
|
| None ->
|
|
Set_of_closures set_of_closures, r
|
|
end
|
|
| Project_closure project_closure ->
|
|
simplify_project_closure env r ~project_closure
|
|
| Project_var project_var -> simplify_project_var env r ~project_var
|
|
| Move_within_set_of_closures move_within_set_of_closures ->
|
|
simplify_move_within_set_of_closures env r ~move_within_set_of_closures
|
|
| Prim (prim, args, dbg) ->
|
|
simplify_free_variables_named env args ~f:(fun env args args_approxs ->
|
|
let tree = Flambda.Prim (prim, args, dbg) in
|
|
begin match prim, args, args_approxs with
|
|
| Pgetglobal _, _, _ ->
|
|
Misc.fatal_error "Pgetglobal is forbidden in Inline_and_simplify"
|
|
| Pfield field_index, [arg], [arg_approx] ->
|
|
let projection : Projection.t = Field (field_index, arg) in
|
|
begin match E.find_projection env ~projection with
|
|
| Some var ->
|
|
simplify_free_variable_named env var ~f:(fun _env var var_approx ->
|
|
let r = R.map_benefit r (B.remove_projection projection) in
|
|
Expr (Var var), ret r var_approx)
|
|
| None ->
|
|
begin match A.get_field arg_approx ~field_index with
|
|
| Unreachable -> (Flambda.Expr Proved_unreachable, r)
|
|
| Ok approx ->
|
|
let tree, approx =
|
|
match arg_approx.symbol with
|
|
(* If the [Pfield] is projecting directly from a symbol, rewrite
|
|
the expression to [Read_symbol_field]. *)
|
|
| Some (symbol, None) ->
|
|
let approx =
|
|
A.augment_with_symbol_field approx symbol field_index
|
|
in
|
|
Flambda.Read_symbol_field (symbol, field_index), approx
|
|
| None | Some (_, Some _ ) ->
|
|
(* This [Pfield] is either not projecting from a symbol at all,
|
|
or it is the projection of a projection from a symbol. *)
|
|
let module Backend = (val (E.backend env) : Backend_intf.S) in
|
|
let approx' = Backend.really_import_approx approx in
|
|
tree, approx'
|
|
in
|
|
simplify_named_using_approx_and_env env r tree approx
|
|
end
|
|
end
|
|
| Pfield _, _, _ -> Misc.fatal_error "Pfield arity error"
|
|
| (Psetfield _ | Parraysetu _ | Parraysets _),
|
|
_block::_, block_approx::_ ->
|
|
if A.is_definitely_immutable block_approx then begin
|
|
Location.prerr_warning (Debuginfo.to_location dbg)
|
|
Warnings.Assignment_to_non_mutable_value
|
|
end;
|
|
tree, ret r (A.value_unknown Other)
|
|
| (Psetfield _ | Parraysetu _ | Parraysets _), _, _ ->
|
|
Misc.fatal_error "Psetfield / Parraysetu / Parraysets arity error"
|
|
| (Psequand | Psequor), _, _ ->
|
|
Misc.fatal_error "Psequand and Psequor must be expanded (see handling \
|
|
in closure_conversion.ml)"
|
|
| p, args, args_approxs ->
|
|
let expr, approx, benefit =
|
|
let module Backend = (val (E.backend env) : Backend_intf.S) in
|
|
Simplify_primitives.primitive p (args, args_approxs) tree dbg
|
|
~size_int:Backend.size_int ~big_endian:Backend.big_endian
|
|
in
|
|
let r = R.map_benefit r (B.(+) benefit) in
|
|
let approx =
|
|
match p with
|
|
| Popaque -> A.value_unknown Other
|
|
| _ -> approx
|
|
in
|
|
expr, ret r approx
|
|
end)
|
|
| Expr expr ->
|
|
let expr, r = simplify env r expr in
|
|
Expr expr, r
|
|
|
|
and simplify env r (tree : Flambda.t) : Flambda.t * R.t =
|
|
match tree with
|
|
| Var var ->
|
|
let var = Freshening.apply_variable (E.freshening env) var in
|
|
(* If from the approximations we can simplify [var], then we will be
|
|
forced to insert [let]-expressions (done using [name_expr], in
|
|
[Simple_value_approx]) to bind a [named]. This has an important
|
|
consequence: it brings bindings of constants closer to their use
|
|
points. *)
|
|
simplify_using_approx_and_env env r (Var var) (E.find_exn env var)
|
|
| Apply apply ->
|
|
simplify_apply env r ~apply
|
|
| Let _ ->
|
|
let for_defining_expr (env, r) var defining_expr =
|
|
let defining_expr, r = simplify_named env r defining_expr in
|
|
let var, sb = Freshening.add_variable (E.freshening env) var in
|
|
let env = E.set_freshening env sb in
|
|
let env = E.add env var (R.approx r) in
|
|
(env, r), var, defining_expr
|
|
in
|
|
let for_last_body (env, r) body =
|
|
simplify env r body
|
|
in
|
|
let filter_defining_expr r var defining_expr free_vars_of_body =
|
|
if Variable.Set.mem var free_vars_of_body then
|
|
r, var, Some defining_expr
|
|
else if Effect_analysis.no_effects_named defining_expr then
|
|
let r = R.map_benefit r (B.remove_code_named defining_expr) in
|
|
r, var, None
|
|
else
|
|
r, var, Some defining_expr
|
|
in
|
|
Flambda.fold_lets_option tree
|
|
~init:(env, r)
|
|
~for_defining_expr
|
|
~for_last_body
|
|
~filter_defining_expr
|
|
| Let_mutable (mut_var, var, body) ->
|
|
(* CR-someday mshinwell: add the dead let elimination, as above. *)
|
|
simplify_free_variable env var ~f:(fun env var _var_approx ->
|
|
let mut_var, sb =
|
|
Freshening.add_mutable_variable (E.freshening env) mut_var
|
|
in
|
|
let env = E.set_freshening env sb in
|
|
let body, r =
|
|
simplify (E.add_mutable env mut_var (A.value_unknown Other)) r body
|
|
in
|
|
Flambda.Let_mutable (mut_var, var, body), r)
|
|
| Let_rec (defs, body) ->
|
|
let defs, sb = Freshening.add_variables (E.freshening env) defs in
|
|
let env = E.set_freshening env sb in
|
|
let def_env =
|
|
List.fold_left (fun env_acc (id, _lam) ->
|
|
E.add env_acc id (A.value_unknown Other))
|
|
env defs
|
|
in
|
|
let defs, body_env, r =
|
|
List.fold_right (fun (id, lam) (defs, env_acc, r) ->
|
|
let lam, r = simplify_named def_env r lam in
|
|
let defs = (id, lam) :: defs in
|
|
let env_acc = E.add env_acc id (R.approx r) in
|
|
defs, env_acc, r)
|
|
defs ([], env, r)
|
|
in
|
|
let body, r = simplify body_env r body in
|
|
Let_rec (defs, body), r
|
|
| Static_raise (i, args) ->
|
|
let i = Freshening.apply_static_exception (E.freshening env) i in
|
|
simplify_free_variables env args ~f:(fun _env args _args_approxs ->
|
|
let r = R.use_static_exception r i in
|
|
Static_raise (i, args), ret r A.value_bottom)
|
|
| Static_catch (i, vars, body, handler) ->
|
|
begin
|
|
match body with
|
|
| Let { var; defining_expr = def; body; _ }
|
|
when not (Flambda_utils.might_raise_static_exn def i) ->
|
|
simplify env r
|
|
(Flambda.create_let var def (Static_catch (i, vars, body, handler)))
|
|
| _ ->
|
|
let i, sb = Freshening.add_static_exception (E.freshening env) i in
|
|
let env = E.set_freshening env sb in
|
|
let body, r = simplify env r body in
|
|
(* CR-soon mshinwell: for robustness, R.used_static_exceptions should
|
|
maybe be removed. *)
|
|
if not (Static_exception.Set.mem i (R.used_static_exceptions r)) then
|
|
(* If the static exception is not used, we can drop the declaration *)
|
|
body, r
|
|
else begin
|
|
match (body : Flambda.t) with
|
|
| Static_raise (j, args) ->
|
|
assert (Static_exception.equal i j);
|
|
let handler =
|
|
List.fold_left2 (fun body var arg ->
|
|
Flambda.create_let var (Expr (Var arg)) body)
|
|
handler vars args
|
|
in
|
|
let r = R.exit_scope_catch r i in
|
|
simplify env r handler
|
|
| _ ->
|
|
let vars, sb = Freshening.add_variables' (E.freshening env) vars in
|
|
let env =
|
|
List.fold_left (fun env id ->
|
|
E.add env id (A.value_unknown Other))
|
|
(E.set_freshening env sb) vars
|
|
in
|
|
let env = E.inside_branch env in
|
|
let handler, r = simplify env r handler in
|
|
let r = R.exit_scope_catch r i in
|
|
Static_catch (i, vars, body, handler),
|
|
ret r (A.value_unknown Other)
|
|
end
|
|
end
|
|
| Try_with (body, id, handler) ->
|
|
let body, r = simplify env r body in
|
|
let id, sb = Freshening.add_variable (E.freshening env) id in
|
|
let env = E.add (E.set_freshening env sb) id (A.value_unknown Other) in
|
|
let env = E.inside_branch env in
|
|
let handler, r = simplify env r handler in
|
|
Try_with (body, id, handler), ret r (A.value_unknown Other)
|
|
| If_then_else (arg, ifso, ifnot) ->
|
|
(* When arg is the constant false or true (or something considered
|
|
as true), we can drop the if and replace it by a sequence.
|
|
if arg is not effectful we can also drop it. *)
|
|
simplify_free_variable env arg ~f:(fun env arg arg_approx ->
|
|
begin match arg_approx.descr with
|
|
| Value_constptr 0 | Value_int 0 -> (* Constant [false]: keep [ifnot] *)
|
|
let ifnot, r = simplify env r ifnot in
|
|
ifnot, R.map_benefit r B.remove_branch
|
|
| Value_constptr _ | Value_int _
|
|
| Value_block _ -> (* Constant [true]: keep [ifso] *)
|
|
let ifso, r = simplify env r ifso in
|
|
ifso, R.map_benefit r B.remove_branch
|
|
| _ ->
|
|
let env = E.inside_branch env in
|
|
let ifso, r = simplify env r ifso in
|
|
let ifso_approx = R.approx r in
|
|
let ifnot, r = simplify env r ifnot in
|
|
let ifnot_approx = R.approx r in
|
|
If_then_else (arg, ifso, ifnot),
|
|
ret r (A.meet ifso_approx ifnot_approx)
|
|
end)
|
|
| While (cond, body) ->
|
|
let cond, r = simplify env r cond in
|
|
let body, r = simplify env r body in
|
|
While (cond, body), ret r (A.value_unknown Other)
|
|
| Send { kind; meth; obj; args; dbg; } ->
|
|
simplify_free_variable env meth ~f:(fun env meth _meth_approx ->
|
|
simplify_free_variable env obj ~f:(fun env obj _obj_approx ->
|
|
simplify_free_variables env args ~f:(fun _env args _args_approx ->
|
|
Send { kind; meth; obj; args; dbg; },
|
|
ret r (A.value_unknown Other))))
|
|
| For { bound_var; from_value; to_value; direction; body; } ->
|
|
simplify_free_variable env from_value ~f:(fun env from_value _approx ->
|
|
simplify_free_variable env to_value ~f:(fun env to_value _approx ->
|
|
let bound_var, sb =
|
|
Freshening.add_variable (E.freshening env) bound_var
|
|
in
|
|
let env =
|
|
E.add (E.set_freshening env sb) bound_var
|
|
(A.value_unknown Other)
|
|
in
|
|
let body, r = simplify env r body in
|
|
For { bound_var; from_value; to_value; direction; body; },
|
|
ret r (A.value_unknown Other)))
|
|
| Assign { being_assigned; new_value; } ->
|
|
(* No need to use something like [simplify_free_variable]: the
|
|
approximation of [being_assigned] is always unknown. *)
|
|
let being_assigned =
|
|
Freshening.apply_mutable_variable (E.freshening env) being_assigned
|
|
in
|
|
simplify_free_variable env new_value ~f:(fun _env new_value _approx ->
|
|
Assign { being_assigned; new_value; }, ret r (A.value_unknown Other))
|
|
| Switch (arg, sw) ->
|
|
(* When [arg] is known to be a variable whose approximation is that of a
|
|
block with a fixed tag or a fixed integer, we can eliminate the
|
|
[Switch]. (This should also make the [Let] that binds [arg] redundant,
|
|
meaning that it too can be eliminated.) *)
|
|
simplify_free_variable env arg ~f:(fun env arg arg_approx ->
|
|
let get_failaction () : Flambda.t =
|
|
(* If the switch is applied to a statically-known value that does not
|
|
match any case:
|
|
* if there is a default action take that case;
|
|
* otherwise this is something that is guaranteed not to
|
|
be reachable by the type checker. For example:
|
|
[type 'a t = Int : int -> int t | Float : float -> float t
|
|
match Int 1 with
|
|
| Int _ -> ...
|
|
| Float f as v ->
|
|
match v with <-- This match is unreachable
|
|
| Float f -> ...]
|
|
*)
|
|
match sw.failaction with
|
|
| None -> Proved_unreachable
|
|
| Some f -> f
|
|
in
|
|
begin match arg_approx.descr with
|
|
| Value_int i
|
|
| Value_constptr i ->
|
|
let lam =
|
|
try List.assoc i sw.consts
|
|
with Not_found -> get_failaction ()
|
|
in
|
|
let lam, r = simplify env r lam in
|
|
lam, R.map_benefit r B.remove_branch
|
|
| Value_block (tag, _) ->
|
|
let tag = Tag.to_int tag in
|
|
let lam =
|
|
try List.assoc tag sw.blocks
|
|
with Not_found -> get_failaction ()
|
|
in
|
|
let lam, r = simplify env r lam in
|
|
lam, R.map_benefit r B.remove_branch
|
|
| _ ->
|
|
let env = E.inside_branch env in
|
|
let f (i, v) (acc, r) =
|
|
let approx = R.approx r in
|
|
let lam, r = simplify env r v in
|
|
((i, lam)::acc, R.set_approx r (A.meet (R.approx r) approx))
|
|
in
|
|
let r = R.set_approx r A.value_bottom in
|
|
let consts, r = List.fold_right f sw.consts ([], r) in
|
|
let blocks, r = List.fold_right f sw.blocks ([], r) in
|
|
let failaction, r =
|
|
match sw.failaction with
|
|
| None -> None, r
|
|
| Some l ->
|
|
let approx = R.approx r in
|
|
let l, r = simplify env r l in
|
|
Some l, R.set_approx r (A.meet (R.approx r) approx)
|
|
in
|
|
let sw = { sw with failaction; consts; blocks; } in
|
|
Switch (arg, sw), r
|
|
end)
|
|
| String_switch (arg, sw, def) ->
|
|
simplify_free_variable env arg ~f:(fun env arg _arg_approx ->
|
|
let sw, r =
|
|
List.fold_right (fun (str, lam) (sw, r) ->
|
|
let lam, r = simplify env r lam in
|
|
(str, lam)::sw, r)
|
|
sw
|
|
([], r)
|
|
in
|
|
let def, r =
|
|
match def with
|
|
| None -> def, r
|
|
| Some def ->
|
|
let def, r = simplify env r def in
|
|
Some def, r
|
|
in
|
|
String_switch (arg, sw, def), ret r (A.value_unknown Other))
|
|
| Proved_unreachable -> tree, ret r A.value_bottom
|
|
|
|
and simplify_list env r l =
|
|
match l with
|
|
| [] -> [], [], r
|
|
| h::t ->
|
|
let t', approxs, r = simplify_list env r t in
|
|
let h', r = simplify env r h in
|
|
let approxs = (R.approx r) :: approxs in
|
|
if t' == t && h' == h
|
|
then l, approxs, r
|
|
else h' :: t', approxs, r
|
|
|
|
let constant_defining_value_approx
|
|
env
|
|
(constant_defining_value:Flambda.constant_defining_value) =
|
|
match constant_defining_value with
|
|
| Allocated_const const ->
|
|
approx_for_allocated_const const
|
|
| Block (tag, fields) ->
|
|
let fields =
|
|
List.map
|
|
(function
|
|
| Flambda.Symbol sym -> begin
|
|
match E.find_symbol_opt env sym with
|
|
| Some approx -> approx
|
|
| None -> A.value_unresolved sym
|
|
end
|
|
| Flambda.Const cst -> simplify_const cst)
|
|
fields
|
|
in
|
|
A.value_block tag (Array.of_list fields)
|
|
| Set_of_closures { function_decls; free_vars; specialised_args } ->
|
|
(* At toplevel, there is no freshening currently happening (this
|
|
cannot be the body of a currently inlined function), so we can
|
|
keep the original set_of_closures in the approximation. *)
|
|
assert(E.freshening env = Freshening.empty);
|
|
assert(Variable.Map.is_empty free_vars);
|
|
assert(Variable.Map.is_empty specialised_args);
|
|
let invariant_params =
|
|
lazy (Invariant_params.invariant_params_in_recursion function_decls
|
|
~backend:(E.backend env))
|
|
in
|
|
let value_set_of_closures =
|
|
A.create_value_set_of_closures ~function_decls
|
|
~bound_vars:Var_within_closure.Map.empty
|
|
~invariant_params
|
|
~specialised_args:Variable.Map.empty
|
|
~freshening:Freshening.Project_var.empty
|
|
in
|
|
A.value_set_of_closures value_set_of_closures
|
|
| Project_closure (set_of_closures_symbol, closure_id) -> begin
|
|
match E.find_symbol_opt env set_of_closures_symbol with
|
|
| None ->
|
|
A.value_unresolved set_of_closures_symbol
|
|
| Some set_of_closures_approx ->
|
|
let checked_approx =
|
|
A.check_approx_for_set_of_closures set_of_closures_approx
|
|
in
|
|
match checked_approx with
|
|
| Ok (_, value_set_of_closures) ->
|
|
let closure_id =
|
|
A.freshen_and_check_closure_id value_set_of_closures closure_id
|
|
in
|
|
A.value_closure value_set_of_closures closure_id
|
|
| Unresolved sym -> A.value_unresolved sym
|
|
| Unknown -> A.value_unknown Other
|
|
| Unknown_because_of_unresolved_symbol sym ->
|
|
A.value_unknown (Unresolved_symbol sym)
|
|
| Wrong ->
|
|
Misc.fatal_errorf "Wrong approximation for [Project_closure] \
|
|
when being used as a [constant_defining_value]: %a"
|
|
Flambda.print_constant_defining_value constant_defining_value
|
|
end
|
|
|
|
(* See documentation on [Let_rec_symbol] in flambda.mli. *)
|
|
let define_let_rec_symbol_approx env defs =
|
|
(* First declare an empty version of the symbols *)
|
|
let env =
|
|
List.fold_left (fun env (symbol, _) ->
|
|
E.add_symbol env symbol (A.value_unresolved symbol))
|
|
env defs
|
|
in
|
|
let rec loop times env =
|
|
if times <= 0 then
|
|
env
|
|
else
|
|
let env =
|
|
List.fold_left (fun env (symbol, constant_defining_value) ->
|
|
let approx =
|
|
constant_defining_value_approx env constant_defining_value
|
|
in
|
|
E.redefine_symbol env symbol approx)
|
|
env defs
|
|
in
|
|
loop (times-1) env
|
|
in
|
|
loop 2 env
|
|
|
|
let simplify_constant_defining_value
|
|
env r symbol
|
|
(constant_defining_value:Flambda.constant_defining_value) =
|
|
let r, constant_defining_value, approx =
|
|
match constant_defining_value with
|
|
(* No simplifications are possible for [Allocated_const] or [Block]. *)
|
|
| Allocated_const const ->
|
|
r, constant_defining_value, approx_for_allocated_const const
|
|
| Block (tag, fields) ->
|
|
let fields = List.map
|
|
(function
|
|
| Flambda.Symbol sym -> E.find_symbol_exn env sym
|
|
| Flambda.Const cst -> simplify_const cst)
|
|
fields
|
|
in
|
|
r, constant_defining_value, A.value_block tag (Array.of_list fields)
|
|
| Set_of_closures set_of_closures ->
|
|
if Variable.Map.cardinal set_of_closures.free_vars <> 0 then begin
|
|
Misc.fatal_errorf "Set of closures bound by [Let_symbol] is not \
|
|
closed: %a"
|
|
Flambda.print_set_of_closures set_of_closures
|
|
end;
|
|
let set_of_closures, r, _freshening =
|
|
simplify_set_of_closures env r set_of_closures
|
|
in
|
|
r, ((Set_of_closures set_of_closures) : Flambda.constant_defining_value),
|
|
R.approx r
|
|
| Project_closure (set_of_closures_symbol, closure_id) ->
|
|
(* No simplifications are necessary here. *)
|
|
let set_of_closures_approx =
|
|
E.find_symbol_exn env set_of_closures_symbol
|
|
in
|
|
let closure_approx =
|
|
match A.check_approx_for_set_of_closures set_of_closures_approx with
|
|
| Ok (_, value_set_of_closures) ->
|
|
let closure_id =
|
|
A.freshen_and_check_closure_id value_set_of_closures closure_id
|
|
in
|
|
A.value_closure value_set_of_closures closure_id
|
|
| Unresolved sym -> A.value_unresolved sym
|
|
| Unknown -> A.value_unknown Other
|
|
| Unknown_because_of_unresolved_symbol sym ->
|
|
A.value_unknown (Unresolved_symbol sym)
|
|
| Wrong ->
|
|
Misc.fatal_errorf "Wrong approximation for [Project_closure] \
|
|
when being used as a [constant_defining_value]: %a"
|
|
Flambda.print_constant_defining_value constant_defining_value
|
|
in
|
|
r, constant_defining_value, closure_approx
|
|
in
|
|
let approx = A.augment_with_symbol approx symbol in
|
|
let r = ret r approx in
|
|
r, constant_defining_value, approx
|
|
|
|
let rec simplify_program_body env r (program : Flambda.program_body)
|
|
: Flambda.program_body * R.t =
|
|
match program with
|
|
| Let_rec_symbol (defs, program) ->
|
|
let env = define_let_rec_symbol_approx env defs in
|
|
let env, r, defs =
|
|
List.fold_left (fun (env, r, defs) (symbol, def) ->
|
|
let r, def, approx =
|
|
simplify_constant_defining_value env r symbol def
|
|
in
|
|
let approx = A.augment_with_symbol approx symbol in
|
|
let env = E.redefine_symbol env symbol approx in
|
|
(env, r, (symbol, def) :: defs))
|
|
(env, r, []) defs
|
|
in
|
|
let program, r = simplify_program_body env r program in
|
|
Let_rec_symbol (defs, program), r
|
|
| Let_symbol (symbol, constant_defining_value, program) ->
|
|
let r, constant_defining_value, approx =
|
|
simplify_constant_defining_value env r symbol constant_defining_value
|
|
in
|
|
let approx = A.augment_with_symbol approx symbol in
|
|
let env = E.add_symbol env symbol approx in
|
|
let program, r = simplify_program_body env r program in
|
|
Let_symbol (symbol, constant_defining_value, program), r
|
|
| Initialize_symbol (symbol, tag, fields, program) ->
|
|
let fields, approxs, r = simplify_list env r fields in
|
|
let approx =
|
|
A.augment_with_symbol (A.value_block tag (Array.of_list approxs)) symbol
|
|
in
|
|
let module Backend = (val (E.backend env) : Backend_intf.S) in
|
|
let env = E.add_symbol env symbol approx in
|
|
let program, r = simplify_program_body env r program in
|
|
Initialize_symbol (symbol, tag, fields, program), r
|
|
| Effect (expr, program) ->
|
|
let expr, r = simplify env r expr in
|
|
let program, r = simplify_program_body env r program in
|
|
Effect (expr, program), r
|
|
| End root -> End root, r
|
|
|
|
let simplify_program env r (program : Flambda.program) =
|
|
let env, r =
|
|
Symbol.Set.fold (fun symbol (env, r) ->
|
|
let env, approx =
|
|
match E.find_symbol_exn env symbol with
|
|
| exception Not_found ->
|
|
let module Backend = (val (E.backend env) : Backend_intf.S) in
|
|
(* CR-someday mshinwell for mshinwell: Is there a reason we cannot
|
|
use [simplify_named_using_approx_and_env] here? *)
|
|
let approx = Backend.import_symbol symbol in
|
|
E.add_symbol env symbol approx, approx
|
|
| approx -> env, approx
|
|
in
|
|
env, ret r approx)
|
|
program.imported_symbols
|
|
(env, r)
|
|
in
|
|
let program_body, r = simplify_program_body env r program.program_body in
|
|
let program = { program with program_body; } in
|
|
program, r
|
|
|
|
let add_predef_exns_to_environment ~env ~backend =
|
|
let module Backend = (val backend : Backend_intf.S) in
|
|
List.fold_left (fun env predef_exn ->
|
|
assert (Ident.is_predef_exn predef_exn);
|
|
let symbol = Backend.symbol_for_global' predef_exn in
|
|
let name = Ident.name predef_exn in
|
|
let approx =
|
|
A.value_block Tag.object_tag
|
|
[| A.value_string (String.length name) (Some name);
|
|
A.value_unknown Other;
|
|
|]
|
|
in
|
|
E.add_symbol env symbol (A.augment_with_symbol approx symbol))
|
|
env
|
|
Predef.all_predef_exns
|
|
|
|
let run ~never_inline ~backend ~prefixname ~round program =
|
|
let r = R.create () in
|
|
let report = !Clflags.inlining_report in
|
|
if never_inline then Clflags.inlining_report := false;
|
|
let initial_env =
|
|
add_predef_exns_to_environment
|
|
~env:(E.create ~never_inline ~backend ~round)
|
|
~backend
|
|
in
|
|
let result, r = simplify_program initial_env r program in
|
|
let result = Flambda_utils.introduce_needed_import_symbols result in
|
|
if not (Static_exception.Set.is_empty (R.used_static_exceptions r))
|
|
then begin
|
|
Misc.fatal_error (Format.asprintf "remaining static exceptions: %a@.%a@."
|
|
Static_exception.Set.print (R.used_static_exceptions r)
|
|
Flambda.print_program result)
|
|
end;
|
|
assert (Static_exception.Set.is_empty (R.used_static_exceptions r));
|
|
if !Clflags.inlining_report then begin
|
|
let output_prefix = Printf.sprintf "%s.%d" prefixname round in
|
|
Inlining_stats.save_then_forget_decisions ~output_prefix
|
|
end;
|
|
Clflags.inlining_report := report;
|
|
result
|