495 lines
18 KiB
OCaml
495 lines
18 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"]
|
|
|
|
(* This cannot be done in a single simple pass due to expressions like:
|
|
|
|
let rec ... =
|
|
...
|
|
let rec f1 x =
|
|
let f2 y =
|
|
f1 rec_list
|
|
in
|
|
f2 v
|
|
and rec_list = f1 :: rec_list in
|
|
...
|
|
|
|
and v = ...
|
|
|
|
f1, f2 and rec_list are constants iff v is a constant.
|
|
|
|
To handle this we populate both a 'not constant' set NC and a set of
|
|
implications between variables.
|
|
|
|
For example, the above code would generate the implications:
|
|
|
|
f1 in NC => rec_list in NC
|
|
f2 in NC => f1 in NC
|
|
rec_list in NC => f2 in NC
|
|
v in NC => f1 in NC
|
|
|
|
then if v is found to be in NC this will be propagated to place
|
|
f1, f2 and rec_list in NC as well.
|
|
|
|
*)
|
|
|
|
(* CR-someday lwhite: I think this pass could be combined with
|
|
alias_analysis and other parts of lift_constants into a single
|
|
type-based anaylsis which infers a "type" for each variable that is
|
|
either an allocated_constant expression or "not constant". Recursion
|
|
would be handled with unification variables. *)
|
|
|
|
module Int = Numbers.Int
|
|
module Symbol_field = struct
|
|
type t = Symbol.t * Int.t
|
|
include Identifiable.Make (Identifiable.Pair (Symbol) (Int))
|
|
end
|
|
|
|
type dep =
|
|
| Closure of Set_of_closures_id.t
|
|
| Var of Variable.t
|
|
| Symbol of Symbol.t
|
|
| Symbol_field of Symbol_field.t
|
|
|
|
type state =
|
|
| Not_constant
|
|
| Implication of dep list
|
|
|
|
type result = {
|
|
id : state Variable.Tbl.t;
|
|
closure : state Set_of_closures_id.Tbl.t;
|
|
}
|
|
|
|
module type Param = sig
|
|
val program : Flambda.program
|
|
val compilation_unit : Compilation_unit.t
|
|
end
|
|
|
|
(* CR-soon mshinwell: consider removing functor *)
|
|
module Inconstants (P:Param) (Backend:Backend_intf.S) = struct
|
|
let program = P.program
|
|
let compilation_unit = P.compilation_unit
|
|
let imported_symbols = Flambda_utils.imported_symbols program
|
|
|
|
(* Sets representing NC *)
|
|
let variables : state Variable.Tbl.t = Variable.Tbl.create 42
|
|
let closures : state Set_of_closures_id.Tbl.t =
|
|
Set_of_closures_id.Tbl.create 42
|
|
let symbols : state Symbol.Tbl.t = Symbol.Tbl.create 42
|
|
let symbol_fields : state Symbol_field.Tbl.t = Symbol_field.Tbl.create 42
|
|
|
|
let mark_queue = Queue.create ()
|
|
|
|
(* CR-soon pchambart: We could probably improve that quite a lot by adding
|
|
(the future annotation) [@unrolled] at the right call sites. Or more
|
|
directly mark mark_dep as [@inline] and call it instead of mark_curr in
|
|
some situations.
|
|
*)
|
|
|
|
(* adds 'dep in NC' *)
|
|
let rec mark_dep = function
|
|
| Var id -> begin
|
|
match Variable.Tbl.find variables id with
|
|
| Not_constant -> ()
|
|
| Implication deps ->
|
|
Variable.Tbl.replace variables id Not_constant;
|
|
Queue.push deps mark_queue
|
|
| exception Not_found ->
|
|
Variable.Tbl.add variables id Not_constant
|
|
end
|
|
| Closure cl -> begin
|
|
match Set_of_closures_id.Tbl.find closures cl with
|
|
| Not_constant -> ()
|
|
| Implication deps ->
|
|
Set_of_closures_id.Tbl.replace closures cl Not_constant;
|
|
Queue.push deps mark_queue
|
|
| exception Not_found ->
|
|
Set_of_closures_id.Tbl.add closures cl Not_constant
|
|
end
|
|
| Symbol s -> begin
|
|
match Symbol.Tbl.find symbols s with
|
|
| Not_constant -> ()
|
|
| Implication deps ->
|
|
Symbol.Tbl.replace symbols s Not_constant;
|
|
Queue.push deps mark_queue
|
|
| exception Not_found ->
|
|
Symbol.Tbl.add symbols s Not_constant
|
|
end
|
|
| Symbol_field s -> begin
|
|
match Symbol_field.Tbl.find symbol_fields s with
|
|
| Not_constant -> ()
|
|
| Implication deps ->
|
|
Symbol_field.Tbl.replace symbol_fields s Not_constant;
|
|
Queue.push deps mark_queue
|
|
| exception Not_found ->
|
|
Symbol_field.Tbl.add symbol_fields s Not_constant
|
|
end
|
|
|
|
and mark_deps deps =
|
|
List.iter mark_dep deps
|
|
|
|
and complete_marking () =
|
|
while not (Queue.is_empty mark_queue) do
|
|
let deps =
|
|
try
|
|
Queue.take mark_queue
|
|
with Not_found -> []
|
|
in
|
|
mark_deps deps;
|
|
done
|
|
|
|
(* adds 'curr in NC' *)
|
|
let mark_curr curr =
|
|
mark_deps curr;
|
|
complete_marking ()
|
|
|
|
(* adds in the tables 'dep in NC => curr in NC' *)
|
|
let register_implication ~in_nc:dep ~implies_in_nc:curr =
|
|
match dep with
|
|
| Var id -> begin
|
|
match Variable.Tbl.find variables id with
|
|
| Not_constant ->
|
|
mark_deps curr;
|
|
complete_marking ();
|
|
| Implication deps ->
|
|
let deps = List.rev_append curr deps in
|
|
Variable.Tbl.replace variables id (Implication deps)
|
|
| exception Not_found ->
|
|
Variable.Tbl.add variables id (Implication curr);
|
|
end
|
|
| Closure cl -> begin
|
|
match Set_of_closures_id.Tbl.find closures cl with
|
|
| Not_constant ->
|
|
mark_deps curr;
|
|
complete_marking ();
|
|
| Implication deps ->
|
|
let deps = List.rev_append curr deps in
|
|
Set_of_closures_id.Tbl.replace closures cl (Implication deps)
|
|
| exception Not_found ->
|
|
Set_of_closures_id.Tbl.add closures cl (Implication curr);
|
|
end
|
|
| Symbol symbol -> begin
|
|
match Symbol.Tbl.find symbols symbol with
|
|
| Not_constant ->
|
|
mark_deps curr;
|
|
complete_marking ();
|
|
| Implication deps ->
|
|
let deps = List.rev_append curr deps in
|
|
Symbol.Tbl.replace symbols symbol (Implication deps)
|
|
| exception Not_found ->
|
|
Symbol.Tbl.add symbols symbol (Implication curr);
|
|
end
|
|
| Symbol_field ((symbol, _) as field) -> begin
|
|
match Symbol_field.Tbl.find symbol_fields field with
|
|
| Not_constant ->
|
|
mark_deps curr;
|
|
complete_marking ();
|
|
| Implication deps ->
|
|
let deps = List.rev_append curr deps in
|
|
Symbol_field.Tbl.replace symbol_fields field (Implication deps)
|
|
| exception Not_found ->
|
|
(* There is no information available about the contents of imported
|
|
symbols, so we must consider all their fields as inconstant. *)
|
|
(* CR-someday pchambart: recover that from the cmx information *)
|
|
if Symbol.Set.mem symbol imported_symbols then begin
|
|
Symbol_field.Tbl.add symbol_fields field Not_constant;
|
|
mark_deps curr;
|
|
complete_marking ();
|
|
end else begin
|
|
Symbol_field.Tbl.add symbol_fields field (Implication curr)
|
|
end
|
|
end
|
|
|
|
(* First loop: iterates on the tree to mark dependencies.
|
|
|
|
curr is the variables or closures to wich we add constraints like
|
|
'... in NC => curr in NC' or 'curr in NC'
|
|
|
|
It can be empty when no constraint can be added like in the toplevel
|
|
expression or in the body of a function.
|
|
*)
|
|
let rec mark_loop ~toplevel (curr : dep list) (flam : Flambda.t) =
|
|
match flam with
|
|
| Let { var; defining_expr = lam; body; _ } ->
|
|
mark_named ~toplevel [Var var] lam;
|
|
(* adds 'var in NC => curr in NC'
|
|
This is not really necessary, but compiling this correctly is
|
|
trickier than eliminating that earlier. *)
|
|
mark_var var curr;
|
|
mark_loop ~toplevel curr body
|
|
| Let_mutable (_mut_var, var, body) ->
|
|
mark_var var curr;
|
|
mark_loop ~toplevel curr body
|
|
| Let_rec(defs, body) ->
|
|
List.iter (fun (var, def) ->
|
|
mark_named ~toplevel [Var var] def;
|
|
(* adds 'var in NC => curr in NC' same remark as let case *)
|
|
mark_var var curr)
|
|
defs;
|
|
mark_loop ~toplevel curr body
|
|
| Var var -> mark_var var curr
|
|
(* Not constant cases: we mark directly 'curr in NC' and mark
|
|
bound variables as in NC also *)
|
|
| Assign _ ->
|
|
mark_curr curr
|
|
| Try_with (f1,id,f2) ->
|
|
mark_curr [Var id];
|
|
mark_curr curr;
|
|
mark_loop ~toplevel [] f1;
|
|
mark_loop ~toplevel [] f2
|
|
| Static_catch (_,ids,f1,f2) ->
|
|
List.iter (fun id -> mark_curr [Var id]) ids;
|
|
mark_curr curr;
|
|
mark_loop ~toplevel [] f1;
|
|
mark_loop ~toplevel [] f2
|
|
(* CR-someday pchambart: If recursive staticcatch is introduced:
|
|
this becomes ~toplevel:false *)
|
|
| For { bound_var; from_value; to_value; direction = _; body; } ->
|
|
mark_curr [Var bound_var];
|
|
mark_var from_value curr;
|
|
mark_var to_value curr;
|
|
mark_curr curr;
|
|
mark_loop ~toplevel:false [] body
|
|
| While (f1,body) ->
|
|
mark_curr curr;
|
|
mark_loop ~toplevel [] f1;
|
|
mark_loop ~toplevel:false [] body
|
|
| If_then_else (f1,f2,f3) ->
|
|
mark_curr curr;
|
|
mark_curr [Var f1];
|
|
mark_loop ~toplevel [] f2;
|
|
mark_loop ~toplevel [] f3
|
|
| Static_raise (_,l) ->
|
|
mark_curr curr;
|
|
List.iter (fun v -> mark_var v curr) l
|
|
| Apply ({func; args; _ }) ->
|
|
mark_curr curr;
|
|
mark_var func curr;
|
|
mark_vars args curr;
|
|
| Switch (arg,sw) ->
|
|
mark_curr curr;
|
|
mark_var arg curr;
|
|
List.iter (fun (_,l) -> mark_loop ~toplevel [] l) sw.consts;
|
|
List.iter (fun (_,l) -> mark_loop ~toplevel [] l) sw.blocks;
|
|
Misc.may (fun l -> mark_loop ~toplevel [] l) sw.failaction
|
|
| String_switch (arg,sw,def) ->
|
|
mark_curr curr;
|
|
mark_var arg curr;
|
|
List.iter (fun (_,l) -> mark_loop ~toplevel [] l) sw;
|
|
Misc.may (fun l -> mark_loop ~toplevel [] l) def
|
|
| Send { kind = _; meth; obj; args; dbg = _; } ->
|
|
mark_curr curr;
|
|
mark_var meth curr;
|
|
mark_var obj curr;
|
|
List.iter (fun arg -> mark_var arg curr) args
|
|
| Proved_unreachable ->
|
|
mark_curr curr
|
|
|
|
and mark_named ~toplevel curr (named : Flambda.named) =
|
|
match named with
|
|
| Set_of_closures (set_of_closures) ->
|
|
mark_loop_set_of_closures ~toplevel curr set_of_closures
|
|
| Const _ | Allocated_const _ -> ()
|
|
| Read_mutable _ -> mark_curr curr
|
|
| Symbol symbol -> begin
|
|
let current_unit = Compilation_unit.get_current_exn () in
|
|
if Compilation_unit.equal current_unit (Symbol.compilation_unit symbol)
|
|
then
|
|
()
|
|
else
|
|
match (Backend.import_symbol symbol).descr with
|
|
| Value_unresolved _ ->
|
|
(* Constant when 'for_clambda' means: can be a symbol (which is
|
|
obviously the case here) with a known approximation. If this
|
|
condition is not satisfied we mark as inconstant to reflect
|
|
the fact that the symbol's contents are unknown and thus
|
|
prevent attempts to examine it. (This is a bit of a hack.) *)
|
|
mark_curr curr
|
|
| _ ->
|
|
()
|
|
end
|
|
| Read_symbol_field (symbol, index) ->
|
|
register_implication ~in_nc:(Symbol_field (symbol, index))
|
|
~implies_in_nc:curr
|
|
(* Globals are symbols: handle like symbols *)
|
|
| Prim (Lambda.Pgetglobal _id, [], _) -> ()
|
|
(* Constant constructors: those expressions are constant if all their
|
|
parameters are:
|
|
- makeblock is compiled to a constant block
|
|
- offset is compiled to a pointer inside a constant closure.
|
|
See Cmmgen for the details
|
|
|
|
makeblock(Mutable) can be a 'constant' if it is allocated at
|
|
toplevel: if this expression is evaluated only once.
|
|
*)
|
|
| Prim (Lambda.Pmakeblock (_tag, Asttypes.Immutable), args, _dbg) ->
|
|
mark_vars args curr
|
|
(* (* CR-someday pchambart: If global mutables are allowed: *)
|
|
| Prim(Lambda.Pmakeblock(_tag, Asttypes.Mutable), args, _dbg, _)
|
|
when toplevel ->
|
|
List.iter (mark_loop ~toplevel curr) args
|
|
*)
|
|
| Prim (Pmakearray (Pfloatarray, Immutable), args, _) ->
|
|
mark_vars args curr
|
|
| Prim (Pmakearray (Pfloatarray, Mutable), args, _) ->
|
|
if toplevel then mark_vars args curr
|
|
else mark_curr curr
|
|
| Prim (Pduparray (Pfloatarray, Immutable), [arg], _) ->
|
|
mark_var arg curr
|
|
| Prim (Pduparray (Pfloatarray, Mutable), [arg], _) ->
|
|
if toplevel then mark_var arg curr
|
|
else mark_curr curr
|
|
| Prim (Pduparray _, _, _) ->
|
|
(* See Lift_constants *)
|
|
mark_curr curr
|
|
| Project_closure ({ set_of_closures; closure_id; }) ->
|
|
if Closure_id.in_compilation_unit closure_id compilation_unit then
|
|
mark_var set_of_closures curr
|
|
else
|
|
mark_curr curr
|
|
| Move_within_set_of_closures ({ closure; start_from; move_to; }) ->
|
|
(* CR-someday mshinwell: We should be able to deem these projections
|
|
(same for the cases below) as constant when from another
|
|
compilation unit, but there isn't code to handle this yet. (Note
|
|
that for Project_var we cannot yet generate a projection from a
|
|
closure in another compilation unit, since we only lift closed
|
|
closures.) *)
|
|
if Closure_id.in_compilation_unit start_from compilation_unit then begin
|
|
assert (Closure_id.in_compilation_unit move_to compilation_unit);
|
|
mark_var closure curr
|
|
end else begin
|
|
mark_curr curr
|
|
end
|
|
| Project_var ({ closure; closure_id; var = _ }) ->
|
|
if Closure_id.in_compilation_unit closure_id compilation_unit then
|
|
mark_var closure curr
|
|
else
|
|
mark_curr curr
|
|
| Prim (Lambda.Pfield _, [f1], _) ->
|
|
mark_curr curr;
|
|
mark_var f1 curr
|
|
| Prim (_, args, _) ->
|
|
mark_curr curr;
|
|
mark_vars args curr
|
|
| Expr flam ->
|
|
mark_loop ~toplevel curr flam
|
|
|
|
and mark_var var curr =
|
|
(* adds 'id in NC => curr in NC' *)
|
|
register_implication ~in_nc:(Var var) ~implies_in_nc:curr
|
|
|
|
and mark_vars vars curr =
|
|
(* adds 'id in NC => curr in NC' *)
|
|
List.iter (fun var -> mark_var var curr) vars
|
|
|
|
(* [toplevel] is intended for allowing static allocations of mutable
|
|
blocks. This feature should be available in a future release once the
|
|
necessary GC changes have been merged. (See GPR#178.) *)
|
|
and mark_loop_set_of_closures ~toplevel:_ curr
|
|
{ Flambda. function_decls; free_vars; specialised_args } =
|
|
(* If a function in the set of closures is specialised, do not consider
|
|
it constant, unless all specialised args are also constant. *)
|
|
Variable.Map.iter (fun _ (spec_arg : Flambda.specialised_to) ->
|
|
register_implication
|
|
~in_nc:(Var spec_arg.var)
|
|
~implies_in_nc:[Closure function_decls.set_of_closures_id])
|
|
specialised_args;
|
|
(* adds 'function_decls in NC => curr in NC' *)
|
|
register_implication ~in_nc:(Closure function_decls.set_of_closures_id)
|
|
~implies_in_nc:curr;
|
|
(* a closure is constant if its free variables are constants. *)
|
|
Variable.Map.iter (fun inner_id (var : Flambda.specialised_to) ->
|
|
register_implication ~in_nc:(Var var.var)
|
|
~implies_in_nc:[
|
|
Var inner_id;
|
|
Closure function_decls.set_of_closures_id
|
|
])
|
|
free_vars;
|
|
Variable.Map.iter (fun fun_id (ffunc : Flambda.function_declaration) ->
|
|
(* for each function f in a closure c 'c in NC => f' *)
|
|
register_implication ~in_nc:(Closure function_decls.set_of_closures_id)
|
|
~implies_in_nc:[Var fun_id];
|
|
(* function parameters are in NC unless specialised *)
|
|
List.iter (fun param ->
|
|
match Variable.Map.find param specialised_args with
|
|
| exception Not_found -> mark_curr [Var param]
|
|
| outer_var ->
|
|
register_implication ~in_nc:(Var outer_var.var)
|
|
~implies_in_nc:[Var param])
|
|
ffunc.params;
|
|
mark_loop ~toplevel:false [] ffunc.body)
|
|
function_decls.funs
|
|
|
|
let mark_constant_defining_value (const:Flambda.constant_defining_value) =
|
|
match const with
|
|
| Allocated_const _
|
|
| Block _
|
|
| Project_closure _ -> ()
|
|
| Set_of_closures set_of_closure ->
|
|
mark_loop_set_of_closures ~toplevel:true [] set_of_closure
|
|
|
|
let mark_program (program : Flambda.program) =
|
|
let rec loop (program : Flambda.program_body) =
|
|
match program with
|
|
| End _ -> ()
|
|
| Initialize_symbol (symbol,_tag,fields,program) ->
|
|
List.iteri (fun i field ->
|
|
mark_loop ~toplevel:true
|
|
[Symbol symbol; Symbol_field (symbol,i)] field)
|
|
fields;
|
|
loop program
|
|
| Effect (expr, program) ->
|
|
mark_loop ~toplevel:true [] expr;
|
|
loop program
|
|
| Let_symbol (_, def, program) ->
|
|
mark_constant_defining_value def;
|
|
loop program
|
|
| Let_rec_symbol (defs, program) ->
|
|
List.iter (fun (_, def) -> mark_constant_defining_value def) defs;
|
|
loop program
|
|
in
|
|
loop program.program_body
|
|
|
|
let res =
|
|
mark_program program;
|
|
{ id = variables;
|
|
closure = closures;
|
|
}
|
|
end
|
|
|
|
let inconstants_on_program ~compilation_unit ~backend
|
|
(program : Flambda.program) =
|
|
let module P = struct
|
|
let program = program
|
|
let compilation_unit = compilation_unit
|
|
end in
|
|
let module Backend = (val backend : Backend_intf.S) in
|
|
let module I = Inconstants (P) (Backend) in
|
|
I.res
|
|
|
|
let variable var { id; _ } =
|
|
match Variable.Tbl.find id var with
|
|
| Not_constant -> true
|
|
| Implication _ -> false
|
|
| exception Not_found -> false
|
|
|
|
let closure cl { closure; _ } =
|
|
match Set_of_closures_id.Tbl.find closure cl with
|
|
| Not_constant -> true
|
|
| Implication _ -> false
|
|
| exception Not_found -> false
|