814 lines
36 KiB
OCaml
814 lines
36 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-30-40-41-42"]
|
|
|
|
type flambda_kind =
|
|
| Normal
|
|
| Lifted
|
|
|
|
(* Explicit "ignore" functions. We name every pattern variable, avoiding
|
|
underscores, to try to avoid accidentally failing to handle (for example)
|
|
a particular variable.
|
|
We also avoid explicit record field access during the checking functions,
|
|
preferring instead to use exhaustive record matches.
|
|
*)
|
|
(* CR-someday pchambart: for sum types, we should probably add an exhaustive
|
|
pattern in ignores functions to be reminded if a type change *)
|
|
let already_added_bound_variable_to_env (_ : Variable.t) = ()
|
|
let will_traverse_named_expression_later (_ : Flambda.named) = ()
|
|
let ignore_variable (_ : Variable.t) = ()
|
|
let ignore_call_kind (_ : Flambda.call_kind) = ()
|
|
let ignore_debuginfo (_ : Debuginfo.t) = ()
|
|
let ignore_meth_kind (_ : Lambda.meth_kind) = ()
|
|
let ignore_int (_ : int) = ()
|
|
let ignore_int_set (_ : Numbers.Int.Set.t) = ()
|
|
let ignore_bool (_ : bool) = ()
|
|
let ignore_string (_ : string) = ()
|
|
let ignore_static_exception (_ : Static_exception.t) = ()
|
|
let ignore_direction_flag (_ : Asttypes.direction_flag) = ()
|
|
let ignore_primitive ( _ : Lambda.primitive) = ()
|
|
let ignore_const (_ : Flambda.const) = ()
|
|
let ignore_allocated_const (_ : Allocated_const.t) = ()
|
|
let ignore_set_of_closures_id (_ : Set_of_closures_id.t) = ()
|
|
let ignore_set_of_closures_origin (_ : Set_of_closures_origin.t) = ()
|
|
let ignore_closure_id (_ : Closure_id.t) = ()
|
|
let ignore_var_within_closure (_ : Var_within_closure.t) = ()
|
|
let ignore_tag (_ : Tag.t) = ()
|
|
let ignore_inline_attribute (_ : Lambda.inline_attribute) = ()
|
|
let ignore_specialise_attribute (_ : Lambda.specialise_attribute) = ()
|
|
|
|
exception Binding_occurrence_not_from_current_compilation_unit of Variable.t
|
|
exception Mutable_binding_occurrence_not_from_current_compilation_unit of
|
|
Mutable_variable.t
|
|
exception Binding_occurrence_of_variable_already_bound of Variable.t
|
|
exception Binding_occurrence_of_mutable_variable_already_bound of
|
|
Mutable_variable.t
|
|
exception Binding_occurrence_of_symbol_already_bound of Symbol.t
|
|
exception Unbound_variable of Variable.t
|
|
exception Unbound_mutable_variable of Mutable_variable.t
|
|
exception Unbound_symbol of Symbol.t
|
|
exception Vars_in_function_body_not_bound_by_closure_or_params of
|
|
Variable.Set.t * Flambda.set_of_closures * Variable.t
|
|
exception Function_decls_have_overlapping_parameters of Variable.Set.t
|
|
exception Specialised_arg_that_is_not_a_parameter of Variable.t
|
|
exception Projection_must_be_a_free_var of Projection.t
|
|
exception Projection_must_be_a_specialised_arg of Projection.t
|
|
exception Free_variables_set_is_lying of
|
|
Variable.t * Variable.Set.t * Variable.Set.t * Flambda.function_declaration
|
|
exception Set_of_closures_free_vars_map_has_wrong_range of Variable.Set.t
|
|
exception Static_exception_not_caught of Static_exception.t
|
|
exception Static_exception_caught_in_multiple_places of Static_exception.t
|
|
exception Access_to_global_module_identifier of Lambda.primitive
|
|
exception Pidentity_should_not_occur
|
|
exception Pdirapply_should_be_expanded
|
|
exception Prevapply_should_be_expanded
|
|
exception Sequential_logical_operator_primitives_must_be_expanded of
|
|
Lambda.primitive
|
|
exception Var_within_closure_bound_multiple_times of Var_within_closure.t
|
|
exception Declared_closure_from_another_unit of Compilation_unit.t
|
|
exception Closure_id_is_bound_multiple_times of Closure_id.t
|
|
exception Set_of_closures_id_is_bound_multiple_times of Set_of_closures_id.t
|
|
exception Unbound_closure_ids of Closure_id.Set.t
|
|
exception Unbound_vars_within_closures of Var_within_closure.Set.t
|
|
exception Move_to_a_closure_not_in_the_free_variables of Variable.t * Variable.Set.t
|
|
|
|
exception Flambda_invariants_failed
|
|
|
|
(* CR-someday mshinwell: We should make "direct applications should not have
|
|
overapplication" be an invariant throughout. At the moment I think this is
|
|
only true after [Inline_and_simplify] has split overapplications. *)
|
|
|
|
(* CR-someday mshinwell: What about checks for shadowed variables and symbols? *)
|
|
|
|
let variable_and_symbol_invariants (program : Flambda.program) =
|
|
let all_declared_variables = ref Variable.Set.empty in
|
|
let declare_variable var =
|
|
if Variable.Set.mem var !all_declared_variables then
|
|
raise (Binding_occurrence_of_variable_already_bound var);
|
|
all_declared_variables := Variable.Set.add var !all_declared_variables
|
|
in
|
|
let declare_variables vars =
|
|
Variable.Set.iter declare_variable vars
|
|
in
|
|
let all_declared_mutable_variables = ref Mutable_variable.Set.empty in
|
|
let declare_mutable_variable mut_var =
|
|
if Mutable_variable.Set.mem mut_var !all_declared_mutable_variables then
|
|
raise (Binding_occurrence_of_mutable_variable_already_bound mut_var);
|
|
all_declared_mutable_variables :=
|
|
Mutable_variable.Set.add mut_var !all_declared_mutable_variables
|
|
in
|
|
let add_binding_occurrence (var_env, mut_var_env, sym_env) var =
|
|
let compilation_unit = Compilation_unit.get_current_exn () in
|
|
if not (Variable.in_compilation_unit var compilation_unit) then
|
|
raise (Binding_occurrence_not_from_current_compilation_unit var);
|
|
declare_variable var;
|
|
Variable.Set.add var var_env, mut_var_env, sym_env
|
|
in
|
|
let add_mutable_binding_occurrence (var_env, mut_var_env, sym_env) mut_var =
|
|
let compilation_unit = Compilation_unit.get_current_exn () in
|
|
if not (Mutable_variable.in_compilation_unit mut_var compilation_unit) then
|
|
raise (Mutable_binding_occurrence_not_from_current_compilation_unit
|
|
mut_var);
|
|
declare_mutable_variable mut_var;
|
|
var_env, Mutable_variable.Set.add mut_var mut_var_env, sym_env
|
|
in
|
|
let add_binding_occurrence_of_symbol (var_env, mut_var_env, sym_env) sym =
|
|
if Symbol.Set.mem sym sym_env then
|
|
raise (Binding_occurrence_of_symbol_already_bound sym)
|
|
else
|
|
var_env, mut_var_env, Symbol.Set.add sym sym_env
|
|
in
|
|
let add_binding_occurrences env vars =
|
|
List.fold_left (fun env var -> add_binding_occurrence env var) env vars
|
|
in
|
|
let check_variable_is_bound (var_env, _, _) var =
|
|
if not (Variable.Set.mem var var_env) then raise (Unbound_variable var)
|
|
in
|
|
let check_symbol_is_bound (_, _, sym_env) sym =
|
|
if not (Symbol.Set.mem sym sym_env) then raise (Unbound_symbol sym)
|
|
in
|
|
let check_variables_are_bound env vars =
|
|
List.iter (check_variable_is_bound env) vars
|
|
in
|
|
let check_mutable_variable_is_bound (_, mut_var_env, _) mut_var =
|
|
if not (Mutable_variable.Set.mem mut_var mut_var_env) then begin
|
|
raise (Unbound_mutable_variable mut_var)
|
|
end
|
|
in
|
|
let rec loop env (flam : Flambda.t) =
|
|
match flam with
|
|
(* Expressions that can bind [Variable.t]s: *)
|
|
| Let { var; defining_expr; body; _ } ->
|
|
loop_named env defining_expr;
|
|
loop (add_binding_occurrence env var) body
|
|
| Let_mutable (mut_var, var, body) ->
|
|
check_variable_is_bound env var;
|
|
loop (add_mutable_binding_occurrence env mut_var) body
|
|
| Let_rec (defs, body) ->
|
|
let env =
|
|
List.fold_left (fun env (var, def) ->
|
|
will_traverse_named_expression_later def;
|
|
add_binding_occurrence env var)
|
|
env defs
|
|
in
|
|
List.iter (fun (var, def) ->
|
|
already_added_bound_variable_to_env var;
|
|
loop_named env def) defs;
|
|
loop env body
|
|
| For { bound_var; from_value; to_value; direction; body; } ->
|
|
ignore_direction_flag direction;
|
|
check_variable_is_bound env from_value;
|
|
check_variable_is_bound env to_value;
|
|
loop (add_binding_occurrence env bound_var) body
|
|
| Static_catch (static_exn, vars, body, handler) ->
|
|
ignore_static_exception static_exn;
|
|
loop env body;
|
|
loop (add_binding_occurrences env vars) handler
|
|
| Try_with (body, var, handler) ->
|
|
loop env body;
|
|
loop (add_binding_occurrence env var) handler
|
|
(* Everything else: *)
|
|
| Var var -> check_variable_is_bound env var
|
|
| Apply { func; args; kind; dbg; inline; specialise; } ->
|
|
check_variable_is_bound env func;
|
|
check_variables_are_bound env args;
|
|
ignore_call_kind kind;
|
|
ignore_debuginfo dbg;
|
|
ignore_inline_attribute inline;
|
|
ignore_specialise_attribute specialise
|
|
| Assign { being_assigned; new_value; } ->
|
|
check_mutable_variable_is_bound env being_assigned;
|
|
check_variable_is_bound env new_value
|
|
| Send { kind; meth; obj; args; dbg; } ->
|
|
ignore_meth_kind kind;
|
|
check_variable_is_bound env meth;
|
|
check_variable_is_bound env obj;
|
|
check_variables_are_bound env args;
|
|
ignore_debuginfo dbg
|
|
| If_then_else (cond, ifso, ifnot) ->
|
|
check_variable_is_bound env cond;
|
|
loop env ifso;
|
|
loop env ifnot
|
|
| Switch (arg, { numconsts; consts; numblocks; blocks; failaction; }) ->
|
|
check_variable_is_bound env arg;
|
|
ignore_int_set numconsts;
|
|
ignore_int_set numblocks;
|
|
List.iter (fun (n, e) ->
|
|
ignore_int n;
|
|
loop env e)
|
|
(consts @ blocks);
|
|
Misc.may (loop env) failaction
|
|
| String_switch (arg, cases, e_opt) ->
|
|
check_variable_is_bound env arg;
|
|
List.iter (fun (label, case) ->
|
|
ignore_string label;
|
|
loop env case)
|
|
cases;
|
|
Misc.may (loop env) e_opt
|
|
| Static_raise (static_exn, es) ->
|
|
ignore_static_exception static_exn;
|
|
List.iter (check_variable_is_bound env) es
|
|
| While (e1, e2) ->
|
|
loop env e1;
|
|
loop env e2
|
|
| Proved_unreachable -> ()
|
|
and loop_named env (named : Flambda.named) =
|
|
match named with
|
|
| Symbol symbol -> check_symbol_is_bound env symbol
|
|
| Const const -> ignore_const const
|
|
| Allocated_const const -> ignore_allocated_const const
|
|
| Read_mutable mut_var ->
|
|
check_mutable_variable_is_bound env mut_var
|
|
| Read_symbol_field (symbol, index) ->
|
|
check_symbol_is_bound env symbol;
|
|
assert (index >= 0) (* CR-someday mshinwell: add proper error *)
|
|
| Set_of_closures set_of_closures ->
|
|
loop_set_of_closures env set_of_closures
|
|
| Project_closure { set_of_closures; closure_id; } ->
|
|
check_variable_is_bound env set_of_closures;
|
|
ignore_closure_id closure_id
|
|
| Move_within_set_of_closures { closure; start_from; move_to; } ->
|
|
check_variable_is_bound env closure;
|
|
ignore_closure_id start_from;
|
|
ignore_closure_id move_to;
|
|
| Project_var { closure; closure_id; var; } ->
|
|
check_variable_is_bound env closure;
|
|
ignore_closure_id closure_id;
|
|
ignore_var_within_closure var
|
|
| Prim (prim, args, dbg) ->
|
|
ignore_primitive prim;
|
|
check_variables_are_bound env args;
|
|
ignore_debuginfo dbg
|
|
| Expr expr ->
|
|
loop env expr
|
|
and loop_set_of_closures env
|
|
({ Flambda.function_decls; free_vars; specialised_args; }
|
|
as set_of_closures) =
|
|
let { Flambda.set_of_closures_id; set_of_closures_origin; funs; } =
|
|
function_decls
|
|
in
|
|
ignore_set_of_closures_id set_of_closures_id;
|
|
ignore_set_of_closures_origin set_of_closures_origin;
|
|
let functions_in_closure = Variable.Map.keys funs in
|
|
let variables_in_closure =
|
|
Variable.Map.fold (fun var (var_in_closure : Flambda.specialised_to)
|
|
variables_in_closure ->
|
|
(* [var] may occur in the body, but will effectively be renamed
|
|
to [var_in_closure], so the latter is what we check to make
|
|
sure it's bound. *)
|
|
ignore_variable var;
|
|
check_variable_is_bound env var_in_closure.var;
|
|
Variable.Set.add var variables_in_closure)
|
|
free_vars Variable.Set.empty
|
|
in
|
|
let all_params, all_free_vars =
|
|
Variable.Map.fold (fun fun_var function_decl acc ->
|
|
let all_params, all_free_vars = acc in
|
|
(* CR-soon mshinwell: check function_decl.all_symbols *)
|
|
let { Flambda.params; body; free_variables; stub; dbg; _ } =
|
|
function_decl
|
|
in
|
|
assert (Variable.Set.mem fun_var functions_in_closure);
|
|
ignore_bool stub;
|
|
ignore_debuginfo dbg;
|
|
(* Check that [free_variables], which is only present as an
|
|
optimization, is not lying. *)
|
|
let free_variables' = Flambda.free_variables body in
|
|
if not (Variable.Set.subset free_variables' free_variables) then
|
|
raise (Free_variables_set_is_lying (fun_var,
|
|
free_variables, free_variables', function_decl));
|
|
(* Check that every variable free in the body of the function is
|
|
bound by either the set of closures or the parameter list. *)
|
|
let acceptable_free_variables =
|
|
Variable.Set.union
|
|
(Variable.Set.union variables_in_closure functions_in_closure)
|
|
(Variable.Set.of_list params)
|
|
in
|
|
let bad =
|
|
Variable.Set.diff free_variables acceptable_free_variables
|
|
in
|
|
if not (Variable.Set.is_empty bad) then begin
|
|
raise (Vars_in_function_body_not_bound_by_closure_or_params
|
|
(bad, set_of_closures, fun_var))
|
|
end;
|
|
(* Check that parameters are unique across all functions in the
|
|
declaration. *)
|
|
let old_all_params_size = Variable.Set.cardinal all_params in
|
|
let params = Variable.Set.of_list params in
|
|
let params_size = Variable.Set.cardinal params in
|
|
let all_params = Variable.Set.union all_params params in
|
|
let all_params_size = Variable.Set.cardinal all_params in
|
|
if all_params_size <> old_all_params_size + params_size then begin
|
|
raise (Function_decls_have_overlapping_parameters all_params)
|
|
end;
|
|
(* Check that parameters and function variables are not
|
|
bound somewhere else in the program *)
|
|
declare_variables params;
|
|
declare_variable fun_var;
|
|
(* Check that the body of the functions is correctly structured *)
|
|
let body_env =
|
|
let (var_env, _, sym_env) = env in
|
|
let var_env =
|
|
Variable.Set.fold (fun var -> Variable.Set.add var)
|
|
free_variables var_env
|
|
in
|
|
(* Mutable variables cannot be captured by closures *)
|
|
let mut_env = Mutable_variable.Set.empty in
|
|
(var_env, mut_env, sym_env)
|
|
in
|
|
loop body_env body;
|
|
all_params, Variable.Set.union free_variables all_free_vars)
|
|
funs (Variable.Set.empty, Variable.Set.empty)
|
|
in
|
|
(* CR-soon pchambart: This is not a property that we can certainly
|
|
ensure.
|
|
If the function get inlined, it is possible for the inlined version
|
|
to still use that variable. To be able to ensure that, we need to
|
|
also ensure that the inlined version will certainly be transformed
|
|
in a same way that can drop the dependency.
|
|
mshinwell: This should get some thought after the first release to
|
|
decide for sure what to do. *)
|
|
(* Check that the free variables rewriting map in the set of closures
|
|
does not contain variables in its domain that are not actually free
|
|
variables of any of the function bodies. *)
|
|
let bad_free_vars =
|
|
Variable.Set.diff (Variable.Map.keys free_vars) all_free_vars
|
|
in
|
|
(*
|
|
if not (Variable.Set.is_empty bad_free_vars) then begin
|
|
raise (Set_of_closures_free_vars_map_has_wrong_range bad_free_vars)
|
|
end;
|
|
*)
|
|
(* Ignore it to avoid the warning: TODO get rid of that when the
|
|
case is settled *)
|
|
ignore (Set_of_closures_free_vars_map_has_wrong_range bad_free_vars);
|
|
|
|
(* Check that free variables variables are not bound somewhere
|
|
else in the program *)
|
|
declare_variables (Variable.Map.keys free_vars);
|
|
(* Check that every "specialised arg" is a parameter of one of the
|
|
functions being declared, and that the variable to which the
|
|
parameter is being specialised is bound. *)
|
|
(* CR mshinwell: also check [projectee]---must always be another
|
|
specialised arg in the same set of closures *)
|
|
Variable.Map.iter (fun _inner_var
|
|
(specialised_to : Flambda.specialised_to) ->
|
|
check_variable_is_bound env specialised_to.var;
|
|
match specialised_to.projection with
|
|
| None -> ()
|
|
| Some projection ->
|
|
let projecting_from = Projection.projecting_from projection in
|
|
if not (Variable.Map.mem projecting_from free_vars)
|
|
then begin
|
|
raise (Projection_must_be_a_free_var projection)
|
|
end)
|
|
free_vars;
|
|
Variable.Map.iter (fun being_specialised
|
|
(specialised_to : Flambda.specialised_to) ->
|
|
if not (Variable.Set.mem being_specialised all_params) then begin
|
|
raise (Specialised_arg_that_is_not_a_parameter being_specialised)
|
|
end;
|
|
check_variable_is_bound env specialised_to.var;
|
|
match specialised_to.projection with
|
|
| None -> ()
|
|
| Some projection ->
|
|
let projecting_from = Projection.projecting_from projection in
|
|
if not (Variable.Map.mem projecting_from specialised_args)
|
|
then begin
|
|
raise (Projection_must_be_a_specialised_arg projection)
|
|
end)
|
|
specialised_args
|
|
in
|
|
let loop_constant_defining_value env (const : Flambda.constant_defining_value) =
|
|
match const with
|
|
| Flambda.Allocated_const c ->
|
|
ignore_allocated_const c
|
|
| Flambda.Block (tag,fields) ->
|
|
ignore_tag tag;
|
|
List.iter (fun (fields : Flambda.constant_defining_value_block_field) ->
|
|
match fields with
|
|
| Const c -> ignore_const c
|
|
| Symbol s -> check_symbol_is_bound env s)
|
|
fields
|
|
| Flambda.Set_of_closures set_of_closures ->
|
|
loop_set_of_closures env set_of_closures;
|
|
(* Constant set of closures must not have free variables *)
|
|
if not (Variable.Map.is_empty set_of_closures.free_vars) then
|
|
assert false; (* TODO: correct error *)
|
|
if not (Variable.Map.is_empty set_of_closures.specialised_args) then
|
|
assert false; (* TODO: correct error *)
|
|
| Flambda.Project_closure (symbol,closure_id) ->
|
|
ignore_closure_id closure_id;
|
|
check_symbol_is_bound env symbol
|
|
in
|
|
let rec loop_program_body env (program : Flambda.program_body) =
|
|
match program with
|
|
| Let_rec_symbol (defs, program) ->
|
|
let env =
|
|
List.fold_left (fun env (symbol, _) ->
|
|
add_binding_occurrence_of_symbol env symbol)
|
|
env defs
|
|
in
|
|
List.iter (fun (_, def) ->
|
|
loop_constant_defining_value env def)
|
|
defs;
|
|
loop_program_body env program
|
|
| Let_symbol (symbol, def, program) ->
|
|
loop_constant_defining_value env def;
|
|
let env = add_binding_occurrence_of_symbol env symbol in
|
|
loop_program_body env program
|
|
| Initialize_symbol (symbol, _tag, fields, program) ->
|
|
List.iter (loop env) fields;
|
|
let env = add_binding_occurrence_of_symbol env symbol in
|
|
loop_program_body env program
|
|
| Effect (expr, program) ->
|
|
loop env expr;
|
|
loop_program_body env program
|
|
| End root ->
|
|
check_symbol_is_bound env root
|
|
in
|
|
let env =
|
|
Symbol.Set.fold (fun symbol env ->
|
|
add_binding_occurrence_of_symbol env symbol)
|
|
program.imported_symbols
|
|
(Variable.Set.empty, Mutable_variable.Set.empty, Symbol.Set.empty)
|
|
in
|
|
loop_program_body env program.program_body
|
|
|
|
let primitive_invariants flam ~no_access_to_global_module_identifiers =
|
|
Flambda_iterators.iter_named (function
|
|
| Prim (prim, _, _) ->
|
|
begin match prim with
|
|
| Psequand | Psequor ->
|
|
raise (Sequential_logical_operator_primitives_must_be_expanded prim)
|
|
| Pgetglobal id ->
|
|
if no_access_to_global_module_identifiers
|
|
&& not (Ident.is_predef_exn id) then
|
|
begin
|
|
raise (Access_to_global_module_identifier prim)
|
|
end
|
|
| Pidentity -> raise Pidentity_should_not_occur
|
|
| Pdirapply _ -> raise Pdirapply_should_be_expanded
|
|
| Prevapply _ -> raise Prevapply_should_be_expanded
|
|
| _ -> ()
|
|
end
|
|
| _ -> ())
|
|
flam
|
|
|
|
let declared_var_within_closure (flam:Flambda.program) =
|
|
let bound = ref Var_within_closure.Set.empty in
|
|
let bound_multiple_times = ref None in
|
|
let add_and_check var =
|
|
if Var_within_closure.Set.mem var !bound then begin
|
|
bound_multiple_times := Some var
|
|
end;
|
|
bound := Var_within_closure.Set.add var !bound
|
|
in
|
|
Flambda_iterators.iter_on_set_of_closures_of_program
|
|
~f:(fun ~constant:_ { Flambda. free_vars; _ } ->
|
|
Variable.Map.iter (fun id _ ->
|
|
let var = Var_within_closure.wrap id in
|
|
add_and_check var)
|
|
free_vars)
|
|
flam;
|
|
!bound, !bound_multiple_times
|
|
|
|
let no_var_within_closure_is_bound_multiple_times (flam:Flambda.program) =
|
|
match declared_var_within_closure flam with
|
|
| _, Some var -> raise (Var_within_closure_bound_multiple_times var)
|
|
| _, None -> ()
|
|
|
|
let every_declared_closure_is_from_current_compilation_unit flam =
|
|
let current_compilation_unit = Compilation_unit.get_current_exn () in
|
|
Flambda_iterators.iter_on_sets_of_closures (fun
|
|
{ Flambda. function_decls; _ } ->
|
|
let compilation_unit =
|
|
Set_of_closures_id.get_compilation_unit
|
|
function_decls.set_of_closures_id
|
|
in
|
|
if not (Compilation_unit.equal compilation_unit current_compilation_unit)
|
|
then raise (Declared_closure_from_another_unit compilation_unit))
|
|
flam
|
|
|
|
let declared_closure_ids program =
|
|
let bound = ref Closure_id.Set.empty in
|
|
let bound_multiple_times = ref None in
|
|
let add_and_check var =
|
|
if Closure_id.Set.mem var !bound
|
|
then bound_multiple_times := Some var;
|
|
bound := Closure_id.Set.add var !bound
|
|
in
|
|
Flambda_iterators.iter_on_set_of_closures_of_program program
|
|
~f:(fun ~constant:_ { Flambda. function_decls; _; } ->
|
|
Variable.Map.iter (fun id _ ->
|
|
let var = Closure_id.wrap id in
|
|
add_and_check var)
|
|
function_decls.funs);
|
|
!bound, !bound_multiple_times
|
|
|
|
let no_closure_id_is_bound_multiple_times program =
|
|
match declared_closure_ids program with
|
|
| _, Some closure_id ->
|
|
raise (Closure_id_is_bound_multiple_times closure_id)
|
|
| _, None -> ()
|
|
|
|
let declared_set_of_closures_ids program =
|
|
let bound = ref Set_of_closures_id.Set.empty in
|
|
let bound_multiple_times = ref None in
|
|
let add_and_check var =
|
|
if Set_of_closures_id.Set.mem var !bound
|
|
then bound_multiple_times := Some var;
|
|
bound := Set_of_closures_id.Set.add var !bound
|
|
in
|
|
Flambda_iterators.iter_on_set_of_closures_of_program program
|
|
~f:(fun ~constant:_ { Flambda. function_decls; _; } ->
|
|
add_and_check function_decls.set_of_closures_id);
|
|
!bound, !bound_multiple_times
|
|
|
|
let no_set_of_closures_id_is_bound_multiple_times program =
|
|
match declared_set_of_closures_ids program with
|
|
| _, Some set_of_closures_id ->
|
|
raise (Set_of_closures_id_is_bound_multiple_times set_of_closures_id)
|
|
| _, None -> ()
|
|
|
|
let used_closure_ids (program:Flambda.program) =
|
|
let used = ref Closure_id.Set.empty in
|
|
let f (flam : Flambda.named) =
|
|
match flam with
|
|
| Project_closure { closure_id; _} ->
|
|
used := Closure_id.Set.add closure_id !used;
|
|
| Move_within_set_of_closures { closure = _; start_from; move_to; } ->
|
|
used := Closure_id.Set.add start_from !used;
|
|
used := Closure_id.Set.add move_to !used
|
|
| Project_var { closure = _; closure_id; var = _ } ->
|
|
used := Closure_id.Set.add closure_id !used
|
|
| Set_of_closures _ | Symbol _ | Const _ | Allocated_const _
|
|
| Prim _ | Expr _ | Read_mutable _ | Read_symbol_field _ -> ()
|
|
in
|
|
(* TODO: check closure_ids of constant_defining_values project_closures *)
|
|
Flambda_iterators.iter_named_of_program ~f program;
|
|
!used
|
|
|
|
let used_vars_within_closures (flam:Flambda.program) =
|
|
let used = ref Var_within_closure.Set.empty in
|
|
let f (flam : Flambda.named) =
|
|
match flam with
|
|
| Project_var { closure = _; closure_id = _; var; } ->
|
|
used := Var_within_closure.Set.add var !used
|
|
| _ -> ()
|
|
in
|
|
Flambda_iterators.iter_named_of_program ~f flam;
|
|
!used
|
|
|
|
let every_used_function_from_current_compilation_unit_is_declared (program:Flambda.program) =
|
|
let current_compilation_unit = Compilation_unit.get_current_exn () in
|
|
let declared, _ = declared_closure_ids program in
|
|
let used = used_closure_ids program in
|
|
let used_from_current_unit =
|
|
Closure_id.Set.filter (fun cu ->
|
|
Closure_id.in_compilation_unit cu current_compilation_unit)
|
|
used
|
|
in
|
|
let counter_examples =
|
|
Closure_id.Set.diff used_from_current_unit declared
|
|
in
|
|
if Closure_id.Set.is_empty counter_examples
|
|
then ()
|
|
else raise (Unbound_closure_ids counter_examples)
|
|
|
|
let every_used_var_within_closure_from_current_compilation_unit_is_declared
|
|
(flam:Flambda.program) =
|
|
let current_compilation_unit = Compilation_unit.get_current_exn () in
|
|
let declared, _ = declared_var_within_closure flam in
|
|
let used = used_vars_within_closures flam in
|
|
let used_from_current_unit =
|
|
Var_within_closure.Set.filter (fun cu ->
|
|
Var_within_closure.in_compilation_unit cu current_compilation_unit)
|
|
used
|
|
in
|
|
let counter_examples =
|
|
Var_within_closure.Set.diff used_from_current_unit declared in
|
|
if Var_within_closure.Set.is_empty counter_examples
|
|
then ()
|
|
else raise (Unbound_vars_within_closures counter_examples)
|
|
|
|
let every_static_exception_is_caught flam =
|
|
let check env (flam : Flambda.t) =
|
|
match flam with
|
|
| Static_raise (exn, _) ->
|
|
if not (Static_exception.Set.mem exn env)
|
|
then raise (Static_exception_not_caught exn)
|
|
| _ -> ()
|
|
in
|
|
let rec loop env (flam : Flambda.t) =
|
|
match flam with
|
|
| Static_catch (i, _, body, handler) ->
|
|
let env = Static_exception.Set.add i env in
|
|
loop env handler;
|
|
loop env body
|
|
| exp ->
|
|
check env exp;
|
|
Flambda_iterators.apply_on_subexpressions (loop env)
|
|
(fun (_ : Flambda.named) -> ()) exp
|
|
in
|
|
loop Static_exception.Set.empty flam
|
|
|
|
let every_static_exception_is_caught_at_a_single_position flam =
|
|
let caught = ref Static_exception.Set.empty in
|
|
let f (flam : Flambda.t) =
|
|
match flam with
|
|
| Static_catch (i, _, _body, _handler) ->
|
|
if Static_exception.Set.mem i !caught then
|
|
raise (Static_exception_caught_in_multiple_places i);
|
|
caught := Static_exception.Set.add i !caught
|
|
| _ -> ()
|
|
in
|
|
Flambda_iterators.iter f (fun (_ : Flambda.named) -> ()) flam
|
|
|
|
let _every_move_within_set_of_closures_is_to_a_function_in_the_free_vars program =
|
|
let moves = ref Closure_id.Map.empty in
|
|
Flambda_iterators.iter_named_of_program program
|
|
~f:(function
|
|
| Move_within_set_of_closures { start_from; move_to; _ } ->
|
|
let moved_to =
|
|
try Closure_id.Map.find start_from !moves with
|
|
| Not_found -> Closure_id.Set.empty
|
|
in
|
|
moves :=
|
|
Closure_id.Map.add start_from
|
|
(Closure_id.Set.add move_to moved_to)
|
|
!moves
|
|
| _ -> ());
|
|
Flambda_iterators.iter_on_set_of_closures_of_program program
|
|
~f:(fun ~constant:_ { Flambda.function_decls = { funs; _ }; _ } ->
|
|
Variable.Map.iter (fun fun_var { Flambda.free_variables; _ } ->
|
|
match Closure_id.Map.find (Closure_id.wrap fun_var) !moves with
|
|
| exception Not_found -> ()
|
|
| moved_to ->
|
|
let missing_dependencies =
|
|
Variable.Set.diff (Closure_id.unwrap_set moved_to)
|
|
free_variables
|
|
in
|
|
if not (Variable.Set.is_empty missing_dependencies) then
|
|
raise (Move_to_a_closure_not_in_the_free_variables
|
|
(fun_var, missing_dependencies)))
|
|
funs)
|
|
|
|
let check_exn ?(kind=Normal) ?(cmxfile=false) (flam:Flambda.program) =
|
|
ignore kind;
|
|
try
|
|
variable_and_symbol_invariants flam;
|
|
no_closure_id_is_bound_multiple_times flam;
|
|
no_set_of_closures_id_is_bound_multiple_times flam;
|
|
every_used_function_from_current_compilation_unit_is_declared flam;
|
|
no_var_within_closure_is_bound_multiple_times flam;
|
|
every_used_var_within_closure_from_current_compilation_unit_is_declared flam;
|
|
(* CR pchambart: This invariant is not maintained. It should be either relaxed
|
|
or reformulated. Currently, it is safe to disable it as the potential related
|
|
errors would result in fatal errors, not in miscompilations *)
|
|
(* every_move_within_set_of_closures_is_to_a_function_in_the_free_vars flam; *)
|
|
Flambda_iterators.iter_exprs_at_toplevel_of_program flam ~f:(fun flam ->
|
|
primitive_invariants flam ~no_access_to_global_module_identifiers:cmxfile;
|
|
every_static_exception_is_caught flam;
|
|
every_static_exception_is_caught_at_a_single_position flam;
|
|
every_declared_closure_is_from_current_compilation_unit flam)
|
|
with exn -> begin
|
|
(* CR-someday split printing code into its own function *)
|
|
begin match exn with
|
|
| Binding_occurrence_not_from_current_compilation_unit var ->
|
|
Format.eprintf ">> Binding occurrence of variable marked as not being \
|
|
from the current compilation unit: %a"
|
|
Variable.print var
|
|
| Mutable_binding_occurrence_not_from_current_compilation_unit mut_var ->
|
|
Format.eprintf ">> Binding occurrence of mutable variable marked as not \
|
|
being from the current compilation unit: %a"
|
|
Mutable_variable.print mut_var
|
|
| Binding_occurrence_of_variable_already_bound var ->
|
|
Format.eprintf ">> Binding occurrence of variable that was already \
|
|
bound: %a"
|
|
Variable.print var
|
|
| Binding_occurrence_of_mutable_variable_already_bound mut_var ->
|
|
Format.eprintf ">> Binding occurrence of mutable variable that was already \
|
|
bound: %a"
|
|
Mutable_variable.print mut_var
|
|
| Binding_occurrence_of_symbol_already_bound sym ->
|
|
Format.eprintf ">> Binding occurrence of symbol that was already \
|
|
bound: %a"
|
|
Symbol.print sym
|
|
| Unbound_variable var ->
|
|
Format.eprintf ">> Unbound variable: %a" Variable.print var
|
|
| Unbound_mutable_variable mut_var ->
|
|
Format.eprintf ">> Unbound mutable variable: %a"
|
|
Mutable_variable.print mut_var
|
|
| Unbound_symbol sym ->
|
|
Format.eprintf ">> Unbound symbol: %a %s" Symbol.print sym (Printexc.raw_backtrace_to_string (Printexc.get_callstack 100))
|
|
| Vars_in_function_body_not_bound_by_closure_or_params
|
|
(vars, set_of_closures, fun_var) ->
|
|
Format.eprintf ">> Variable(s) (%a) in the body of a function declaration \
|
|
(fun_var = %a) that is not bound by either the closure or the function's \
|
|
parameter list. Set of closures: %a"
|
|
Variable.Set.print vars
|
|
Variable.print fun_var
|
|
Flambda.print_set_of_closures set_of_closures
|
|
| Function_decls_have_overlapping_parameters vars ->
|
|
Format.eprintf ">> Function declarations whose parameters overlap: \
|
|
%a"
|
|
Variable.Set.print vars
|
|
| Specialised_arg_that_is_not_a_parameter var ->
|
|
Format.eprintf ">> Variable in [specialised_args] that is not a \
|
|
parameter of any of the function(s) in the corresponding \
|
|
declaration(s): %a"
|
|
Variable.print var
|
|
| Projection_must_be_a_free_var var ->
|
|
Format.eprintf ">> Projection %a in [free_vars] from a variable that is \
|
|
not a (inner) free variable of the set of closures"
|
|
Projection.print var
|
|
| Projection_must_be_a_specialised_arg var ->
|
|
Format.eprintf ">> Projection %a in [specialised_args] from a variable \
|
|
that is not a (inner) specialised argument variable of the set of \
|
|
closures"
|
|
Projection.print var
|
|
| Free_variables_set_is_lying (var, claimed, calculated, function_decl) ->
|
|
Format.eprintf ">> Function declaration whose [free_variables] set (%a) \
|
|
is not a superset of the result of [Flambda.free_variables] \
|
|
applied to the body of the function (%a). Declaration: %a"
|
|
Variable.Set.print claimed
|
|
Variable.Set.print calculated
|
|
Flambda.print_function_declaration (var, function_decl)
|
|
| Set_of_closures_free_vars_map_has_wrong_range vars ->
|
|
Format.eprintf ">> [free_vars] map in set of closures has in its range \
|
|
variables that are not free variables of the corresponding \
|
|
functions: %a"
|
|
Variable.Set.print vars
|
|
| Sequential_logical_operator_primitives_must_be_expanded prim ->
|
|
Format.eprintf ">> Sequential logical operator primitives must be \
|
|
expanded (see closure_conversion.ml): %a"
|
|
Printlambda.primitive prim
|
|
| Var_within_closure_bound_multiple_times var ->
|
|
Format.eprintf ">> Variable within a closure is bound multiple times: \
|
|
%a"
|
|
Var_within_closure.print var
|
|
| Closure_id_is_bound_multiple_times closure_id ->
|
|
Format.eprintf ">> Closure ID is bound multiple times: %a"
|
|
Closure_id.print closure_id
|
|
| Set_of_closures_id_is_bound_multiple_times set_of_closures_id ->
|
|
Format.eprintf ">> Set of closures ID is bound multiple times: %a"
|
|
Set_of_closures_id.print set_of_closures_id
|
|
| Declared_closure_from_another_unit compilation_unit ->
|
|
Format.eprintf ">> Closure declared as being from another compilation \
|
|
unit: %a"
|
|
Compilation_unit.print compilation_unit
|
|
| Unbound_closure_ids closure_ids ->
|
|
Format.eprintf ">> Unbound closure ID(s) from the current compilation \
|
|
unit: %a"
|
|
Closure_id.Set.print closure_ids
|
|
| Unbound_vars_within_closures vars_within_closures ->
|
|
Format.eprintf ">> Unbound variable(s) within closure(s) from the \
|
|
current compilation_unit: %a"
|
|
Var_within_closure.Set.print vars_within_closures
|
|
| Static_exception_not_caught static_exn ->
|
|
Format.eprintf ">> Uncaught static exception: %a"
|
|
Static_exception.print static_exn
|
|
| Static_exception_caught_in_multiple_places static_exn ->
|
|
Format.eprintf ">> Static exception caught in multiple places: %a"
|
|
Static_exception.print static_exn
|
|
| Access_to_global_module_identifier prim ->
|
|
(* CR-someday mshinwell: backend-specific checks should move to another
|
|
module, in the asmcomp/ directory. *)
|
|
Format.eprintf ">> Forbidden access to a global module identifier (not \
|
|
allowed in Flambda that will be exported to a .cmx file): %a"
|
|
Printlambda.primitive prim
|
|
| Pidentity_should_not_occur ->
|
|
Format.eprintf ">> The Pidentity primitive should never occur in an \
|
|
Flambda expression (see closure_conversion.ml)"
|
|
| Pdirapply_should_be_expanded ->
|
|
Format.eprintf ">> The Pdirapply primitive should never occur in an \
|
|
Flambda expression (see closure_conversion.ml); use Apply instead"
|
|
| Prevapply_should_be_expanded ->
|
|
Format.eprintf ">> The Prevapply primitive should never occur in an \
|
|
Flambda expression (see closure_conversion.ml); use Apply instead"
|
|
| Move_to_a_closure_not_in_the_free_variables (start_from, move_to) ->
|
|
Format.eprintf ">> A Move_within_set_of_closures from the closure %a \
|
|
to closures that are not parts of its free variables: %a"
|
|
Variable.print start_from
|
|
Variable.Set.print move_to
|
|
| exn -> raise exn
|
|
end;
|
|
Format.eprintf "\n@?";
|
|
raise Flambda_invariants_failed
|
|
end
|