ocaml/asmcomp/closure.ml

458 lines
17 KiB
OCaml

(***********************************************************************)
(* *)
(* Objective Caml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* Automatique. Distributed only by permission. *)
(* *)
(***********************************************************************)
(* $Id$ *)
(* Introduction of closures, uncurrying, recognition of direct calls *)
open Misc
open Asttypes
open Primitive
open Lambda
open Clambda
(* Auxiliaries for compiling functions *)
let rec split_list n l =
if n <= 0 then ([], l) else begin
match l with
[] -> fatal_error "Closure.split_list"
| a::l -> let (l1, l2) = split_list (n-1) l in (a::l1, l2)
end
let rec build_closure_env env_param pos = function
[] -> Tbl.empty
| id :: rem ->
Tbl.add id (Uprim(Pfield pos, [Uvar env_param]))
(build_closure_env env_param (pos+1) rem)
(* Check if a variable occurs in a [clambda] term. *)
let occurs_var var u =
let rec occurs = function
Uvar v -> v = var
| Uconst cst -> false
| Udirect_apply(lbl, args) -> List.exists occurs args
| Ugeneric_apply(funct, args) -> occurs funct or List.exists occurs args
| Uclosure(fundecls, clos) -> List.exists occurs clos
| Uoffset(u, ofs) -> occurs u
| Ulet(id, def, body) -> occurs def or occurs body
| Uletrec(decls, body) ->
List.exists (fun (id, u) -> occurs u) decls or occurs body
| Uprim(p, args) -> List.exists occurs args
| Uswitch(arg, s) ->
occurs arg or occurs_array s.us_cases_consts
or occurs_array s.us_cases_blocks
| Ustaticfail -> false
| Ucatch(body, hdlr) -> occurs body or occurs hdlr
| Utrywith(body, exn, hdlr) -> occurs body or occurs hdlr
| Uifthenelse(cond, ifso, ifnot) ->
occurs cond or occurs ifso or occurs ifnot
| Usequence(u1, u2) -> occurs u1 or occurs u2
| Uwhile(cond, body) -> occurs cond or occurs body
| Ufor(id, lo, hi, dir, body) -> occurs lo or occurs hi or occurs body
| Uassign(id, u) -> id = var or occurs u
| Usend(met, obj, args) ->
occurs met or occurs obj or List.exists occurs args
and occurs_array a =
try
for i = 0 to Array.length a - 1 do
if occurs a.(i) then raise Exit
done;
false
with Exit ->
true
in occurs u
(* Determine whether the estimated size of a clambda term is below
some threshold *)
let prim_size prim args =
match prim with
Pidentity -> 0
| Pgetglobal id -> 1
| Psetglobal id -> 1
| Pmakeblock(tag, mut) -> 5 + List.length args
| Pfield f -> 1
| Psetfield(f, isptr) -> if isptr then 4 else 1
| Pfloatfield f -> 1
| Psetfloatfield f -> 1
| Pccall p -> (if p.prim_alloc then 10 else 4) + List.length args
| Praise -> 4
| Pstringlength -> 5
| Pstringrefs | Pstringsets -> 6
| Pmakearray kind -> 5 + List.length args
| Parraylength kind -> if kind = Pgenarray then 6 else 2
| Parrayrefu kind -> if kind = Pgenarray then 12 else 2
| Parraysetu kind -> if kind = Pgenarray then 16 else 4
| Parrayrefs kind -> if kind = Pgenarray then 18 else 8
| Parraysets kind -> if kind = Pgenarray then 22 else 10
| Pbittest -> 3
| _ -> 2 (* arithmetic and comparisons *)
let lambda_smaller lam threshold =
let size = ref 0 in
let rec lambda_size lam =
if !size > threshold then raise Exit;
match lam with
Uvar v -> ()
| Uconst(Const_base(Const_int _ | Const_char _ | Const_float _) |
Const_pointer _) -> incr size
| Uconst _ ->
raise Exit (* avoid duplication of structured constants *)
| Udirect_apply(fn, args) ->
size := !size + 4; lambda_list_size args
| Ugeneric_apply(fn, args) ->
size := !size + 6; lambda_size fn; lambda_list_size args
| Uclosure(defs, vars) ->
raise Exit (* inlining would duplicate function definitions *)
| Uoffset(lam, ofs) ->
incr size; lambda_size lam
| Ulet(id, lam, body) ->
lambda_size lam; lambda_size body
| Uletrec(bindings, body) ->
raise Exit (* usually too large *)
| Uprim(prim, args) ->
size := !size + prim_size prim args;
lambda_list_size args
| Uswitch(lam, cases) ->
if Array.length cases.us_cases_consts > 0 then size := !size + 5;
if Array.length cases.us_cases_blocks > 0 then size := !size + 5;
if cases.us_checked then size := !size + 2;
lambda_size lam;
lambda_array_size cases.us_cases_consts;
lambda_array_size cases.us_cases_blocks
| Ustaticfail -> ()
| Ucatch(body, handler) ->
incr size; lambda_size body; lambda_size handler
| Utrywith(body, id, handler) ->
size := !size + 8; lambda_size body; lambda_size handler
| Uifthenelse(cond, ifso, ifnot) ->
size := !size + 2;
lambda_size cond; lambda_size ifso; lambda_size ifnot
| Usequence(lam1, lam2) ->
lambda_size lam1; lambda_size lam2
| Uwhile(cond, body) ->
size := !size + 2; lambda_size cond; lambda_size body
| Ufor(id, low, high, dir, body) ->
size := !size + 4; lambda_size low; lambda_size high; lambda_size body
| Uassign(id, lam) ->
incr size; lambda_size lam
| Usend(met, obj, args) ->
size := !size + 8;
lambda_size met; lambda_size obj; lambda_list_size args
and lambda_list_size l = List.iter lambda_size l
and lambda_array_size a = Array.iter lambda_size a in
try
lambda_size lam; !size <= threshold
with Exit ->
false
(* Check if a lambda term denoting a function is ``pure'',
that is without side-effects *and* not containing function definitions *)
let rec is_pure = function
Lvar v -> true
| Lprim(Pgetglobal id, _) -> true
| Lprim(Pfield n, [arg]) -> is_pure arg
| _ -> false
(* Generate a direct application *)
let direct_apply fundesc funct ufunct uargs =
let app_args =
if fundesc.fun_closed then uargs else uargs @ [ufunct] in
let app =
match fundesc.fun_inline with
None -> Udirect_apply(fundesc.fun_label, app_args)
| Some(params, body) ->
List.fold_right2
(fun param arg body -> Ulet(param, arg, body))
params app_args body in
(if is_pure funct then app else Usequence(ufunct, app))
(* Maintain the approximation of the global structure being defined *)
let global_approx = ref([||] : value_approximation array)
(* Uncurry an expression and explicitate closures.
Also return the approximation of the expression.
The approximation environment [fenv] maps idents to approximations.
Idents not bound in [fenv] approximate to [Value_unknown].
The closure environment [cenv] maps idents to [ulambda] terms.
It is used to substitute environment accesses for free identifiers. *)
let close_var cenv id =
try Tbl.find id cenv with Not_found -> Uvar id
let approx_var fenv id =
try Tbl.find id fenv with Not_found -> Value_unknown
let rec close fenv cenv = function
Lvar id ->
(close_var cenv id, approx_var fenv id)
| Lconst cst ->
(Uconst cst, Value_unknown)
| Lfunction(kind, params, body) as funct ->
close_one_function fenv cenv (Ident.create "fun") funct
| Lapply(funct, args) ->
let nargs = List.length args in
begin match (close fenv cenv funct, close_list fenv cenv args) with
((ufunct, Value_closure(fundesc, approx_res)),
[Uprim(Pmakeblock(_, _), uargs)])
when List.length uargs = - fundesc.fun_arity ->
(direct_apply fundesc funct ufunct uargs, approx_res)
| ((ufunct, Value_closure(fundesc, approx_res)), uargs)
when nargs = fundesc.fun_arity ->
(direct_apply fundesc funct ufunct uargs, approx_res)
| ((ufunct, Value_closure(fundesc, approx_res)), uargs)
when fundesc.fun_arity > 0 && nargs > fundesc.fun_arity ->
let (first_args, rem_args) = split_list fundesc.fun_arity uargs in
(Ugeneric_apply(direct_apply fundesc funct ufunct first_args,
rem_args),
Value_unknown)
| ((ufunct, _), uargs) ->
(Ugeneric_apply(ufunct, uargs), Value_unknown)
end
| Lsend(met, obj, args) ->
let (umet, _) = close fenv cenv met in
let (uobj, _) = close fenv cenv obj in
(Usend(umet, uobj, close_list fenv cenv args), Value_unknown)
| Llet(str, id, lam, body) ->
let (ulam, alam) = close_named fenv cenv id lam in
let (ubody, abody) = close (Tbl.add id alam fenv) cenv body in
(Ulet(id, ulam, ubody), abody)
| Lletrec(defs, body) ->
if List.for_all
(function (id, Lfunction(_, _, _)) -> true | _ -> false)
defs
then begin
(* Simple case: only function definitions *)
let (clos, infos) = close_functions fenv cenv defs in
let clos_ident = Ident.create "clos" in
let fenv_body =
List.fold_right
(fun (id, pos, approx) fenv -> Tbl.add id approx fenv)
infos fenv in
let (ubody, approx) = close fenv_body cenv body in
(Ulet(clos_ident, clos,
List.fold_right
(fun (id, pos, approx) body ->
Ulet(id, Uoffset(Uvar clos_ident, pos), body))
infos ubody),
approx)
end else begin
(* General case: recursive definition of values *)
let rec clos_defs = function
[] -> ([], fenv)
| (id, lam) :: rem ->
let (udefs, fenv_body) = clos_defs rem in
let (ulam, approx) = close fenv cenv lam in
((id, ulam) :: udefs, Tbl.add id approx fenv_body) in
let (udefs, fenv_body) = clos_defs defs in
let (ubody, approx) = close fenv_body cenv body in
(Uletrec(udefs, ubody), approx)
end
| Lprim(Pgetglobal id, []) ->
(Uprim(Pgetglobal id, []), Compilenv.global_approx id)
| Lprim(Pmakeblock(tag, mut) as prim, lams) ->
let (ulams, approxs) = List.split (List.map (close fenv cenv) lams) in
(Uprim(prim, ulams),
begin match mut with
Immutable -> Value_tuple(Array.of_list approxs)
| Mutable -> Value_unknown
end)
| Lprim(Pfield n, [lam]) ->
let (ulam, approx) = close fenv cenv lam in
(Uprim(Pfield n, [ulam]),
match approx with
Value_tuple a when n < Array.length a -> a.(n)
| _ -> Value_unknown)
| Lprim(Psetfield(n, _), [Lprim(Pgetglobal id, []); lam]) ->
let (ulam, approx) = close fenv cenv lam in
(!global_approx).(n) <- approx;
(Uprim(Psetfield(n, false), [Uprim(Pgetglobal id, []); ulam]),
Value_unknown)
| Lprim(p, args) ->
(Uprim(p, close_list fenv cenv args), Value_unknown)
| Lswitch(arg, sw) ->
let (uarg, _) = close fenv cenv arg in
let (const_index, const_cases) =
close_switch fenv cenv sw.sw_numconsts sw.sw_consts in
let (block_index, block_cases) =
close_switch fenv cenv sw.sw_numblocks sw.sw_blocks in
(Uswitch(uarg,
{us_index_consts = const_index;
us_cases_consts = const_cases;
us_index_blocks = block_index;
us_cases_blocks = block_cases;
us_checked = sw.sw_checked}),
Value_unknown)
| Lstaticfail ->
(Ustaticfail, Value_unknown)
| Lcatch(body, handler) ->
let (ubody, _) = close fenv cenv body in
let (uhandler, _) = close fenv cenv handler in
(Ucatch(ubody, uhandler), Value_unknown)
| Ltrywith(body, id, handler) ->
let (ubody, _) = close fenv cenv body in
let (uhandler, _) = close fenv cenv handler in
(Utrywith(ubody, id, uhandler), Value_unknown)
| Lifthenelse(arg, ifso, ifnot) ->
let (uarg, _) = close fenv cenv arg in
let (uifso, _) = close fenv cenv ifso in
let (uifnot, _) = close fenv cenv ifnot in
(Uifthenelse(uarg, uifso, uifnot), Value_unknown)
| Lsequence(lam1, lam2) ->
let (ulam1, _) = close fenv cenv lam1 in
let (ulam2, approx) = close fenv cenv lam2 in
(Usequence(ulam1, ulam2), approx)
| Lwhile(cond, body) ->
let (ucond, _) = close fenv cenv cond in
let (ubody, _) = close fenv cenv body in
(Uwhile(ucond, ubody), Value_unknown)
| Lfor(id, lo, hi, dir, body) ->
let (ulo, _) = close fenv cenv lo in
let (uhi, _) = close fenv cenv hi in
let (ubody, _) = close fenv cenv body in
(Ufor(id, ulo, uhi, dir, ubody), Value_unknown)
| Lassign(id, lam) ->
let (ulam, _) = close fenv cenv lam in
(Uassign(id, ulam), Value_unknown)
| Levent _ -> assert false
and close_list fenv cenv = function
[] -> []
| lam :: rem ->
let (ulam, _) = close fenv cenv lam in
ulam :: close_list fenv cenv rem
and close_named fenv cenv id = function
Lfunction(kind, params, body) as funct ->
close_one_function fenv cenv id funct
| lam ->
close fenv cenv lam
(* Build a shared closure for a set of mutually recursive functions *)
and close_functions fenv cenv fun_defs =
(* Determine the free variables of the functions *)
let fv =
IdentSet.elements (free_variables (Lletrec(fun_defs, lambda_unit))) in
(* Build the function descriptors for the functions.
Initially all functions are assumed not to need their environment
parameter. *)
let uncurried_defs =
List.map
(function
(id, (Lfunction(kind, params, body) as def)) ->
let label =
Compilenv.current_unit_name() ^ "_" ^ Ident.unique_name id in
let arity = List.length params in
let fundesc =
{fun_label = label;
fun_arity = (if kind = Tupled then -arity else arity);
fun_closed = true;
fun_inline = None } in
(id, params, body, fundesc)
| (_, _) -> fatal_error "Closure.close_functions")
fun_defs in
(* Build an approximate fenv for compiling the functions *)
let fenv_rec =
List.fold_right
(fun (id, params, body, fundesc) fenv ->
Tbl.add id (Value_closure(fundesc, Value_unknown)) fenv)
uncurried_defs fenv in
(* Determine the offsets of each function's closure in the shared block *)
let env_pos = ref (-1) in
let clos_offsets =
List.map
(fun (id, params, body, fundesc) ->
let pos = !env_pos + 1 in
env_pos := !env_pos + 1 + (if fundesc.fun_arity <> 1 then 3 else 2);
pos)
uncurried_defs in
let fv_pos = !env_pos in
(* This reference will be set to false if the hypothesis that a function
does not use its environment parameter is invalidated. *)
let useless_env = ref true in
(* Translate each function definition *)
let clos_fundef (id, params, body, fundesc) env_pos =
let env_param = Ident.create "env" in
let cenv_fv =
build_closure_env env_param (fv_pos - env_pos) fv in
let cenv_body =
List.fold_right2
(fun (id, params, arity, body) pos env ->
Tbl.add id (Uoffset(Uvar env_param, pos - env_pos)) env)
uncurried_defs clos_offsets cenv_fv in
let (ubody, approx) = close fenv_rec cenv_body body in
if !useless_env & occurs_var env_param ubody then useless_env := false;
let fun_params = if !useless_env then params else params @ [env_param] in
((fundesc.fun_label, fundesc.fun_arity, fun_params, ubody),
(id, env_pos, Value_closure(fundesc, approx))) in
(* Translate all function definitions. *)
let clos_info_list =
let cl = List.map2 clos_fundef uncurried_defs clos_offsets in
(* If the hypothesis that the environment parameters are useless has been
invalidated, then set [fun_closed] to false in all descriptions and
recompile *)
if !useless_env then cl else begin
List.iter
(fun (id, params, body, fundesc) -> fundesc.fun_closed <- false)
uncurried_defs;
List.map2 clos_fundef uncurried_defs clos_offsets
end in
(* Return the Uclosure node and the list of all identifiers defined,
with offsets and approximations. *)
let (clos, infos) = List.split clos_info_list in
(Uclosure(clos, List.map (close_var cenv) fv), infos)
(* Same, for one non-recursive function *)
and close_one_function fenv cenv id funct =
match close_functions fenv cenv [id, funct] with
((Uclosure([_, _, params, body], _) as clos),
[_, _, (Value_closure(fundesc, _) as approx)]) ->
(* See if the function can be inlined *)
if lambda_smaller body (!Clflags.inline_threshold + List.length params)
then fundesc.fun_inline <- Some(params, body);
(clos, approx)
| _ -> fatal_error "Closure.close_one_function"
(* Close a switch *)
and close_switch fenv cenv num_keys cases =
let index = Array.create num_keys 0 in
let ucases = ref []
and num_cases = ref 0 in
if List.length cases < num_keys then begin
num_cases := 1;
ucases := [Ustaticfail]
end;
List.iter
(function (key, lam) ->
let (ulam, _) = close fenv cenv lam in
ucases := ulam :: !ucases;
index.(key) <- !num_cases;
incr num_cases)
cases;
(index, Array.of_list(List.rev !ucases))
(* The entry point *)
let intro size lam =
global_approx := Array.create size Value_unknown;
let (ulam, approx) = close Tbl.empty Tbl.empty lam in
Compilenv.set_global_approx(Value_tuple !global_approx);
global_approx := [||];
ulam