ocaml/bytecomp/simplif.ml

445 lines
16 KiB
OCaml
Raw Normal View History

(***********************************************************************)
(* *)
(* Objective Caml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the Q Public License version 1.0. *)
(* *)
(***********************************************************************)
(* $Id$ *)
(* Elimination of useless Llet(Alias) bindings.
Also transform let-bound references into variables. *)
open Asttypes
open Lambda
(* To transform let-bound references into variables *)
exception Real_reference
let rec eliminate_ref id = function
Lvar v as lam ->
if Ident.same v id then raise Real_reference else lam
| Lconst cst as lam -> lam
| Lapply(e1, el, loc) ->
Lapply(eliminate_ref id e1, List.map (eliminate_ref id) el, loc)
| Lfunction(kind, params, body) as lam ->
if IdentSet.mem id (free_variables lam)
then raise Real_reference
else lam
| Llet(str, v, e1, e2) ->
Llet(str, v, eliminate_ref id e1, eliminate_ref id e2)
| Lletrec(idel, e2) ->
Lletrec(List.map (fun (v, e) -> (v, eliminate_ref id e)) idel,
eliminate_ref id e2)
| Lprim(Pfield 0, [Lvar v]) when Ident.same v id ->
Lvar id
| Lprim(Psetfield(0, _), [Lvar v; e]) when Ident.same v id ->
Lassign(id, eliminate_ref id e)
| Lprim(Poffsetref delta, [Lvar v]) when Ident.same v id ->
Lassign(id, Lprim(Poffsetint delta, [Lvar id]))
| Lprim(p, el) ->
Lprim(p, List.map (eliminate_ref id) el)
| Lswitch(e, sw) ->
Lswitch(eliminate_ref id e,
{sw_numconsts = sw.sw_numconsts;
sw_consts =
List.map (fun (n, e) -> (n, eliminate_ref id e)) sw.sw_consts;
sw_numblocks = sw.sw_numblocks;
sw_blocks =
List.map (fun (n, e) -> (n, eliminate_ref id e)) sw.sw_blocks;
sw_failaction = match sw.sw_failaction with
| None -> None
| Some l -> Some (eliminate_ref id l)})
| Lstaticraise (i,args) ->
Lstaticraise (i,List.map (eliminate_ref id) args)
| Lstaticcatch(e1, i, e2) ->
Lstaticcatch(eliminate_ref id e1, i, eliminate_ref id e2)
| Ltrywith(e1, v, e2) ->
Ltrywith(eliminate_ref id e1, v, eliminate_ref id e2)
| Lifthenelse(e1, e2, e3) ->
Lifthenelse(eliminate_ref id e1,
eliminate_ref id e2,
eliminate_ref id e3)
| Lsequence(e1, e2) ->
Lsequence(eliminate_ref id e1, eliminate_ref id e2)
| Lwhile(e1, e2) ->
Lwhile(eliminate_ref id e1, eliminate_ref id e2)
| Lfor(v, e1, e2, dir, e3) ->
Lfor(v, eliminate_ref id e1, eliminate_ref id e2,
dir, eliminate_ref id e3)
| Lassign(v, e) ->
Lassign(v, eliminate_ref id e)
| Lsend(k, m, o, el) ->
Lsend(k, eliminate_ref id m, eliminate_ref id o,
List.map (eliminate_ref id) el)
| Levent(l, ev) ->
Levent(eliminate_ref id l, ev)
| Lifused(v, e) ->
Lifused(v, eliminate_ref id e)
(* Simplification of exits *)
let simplify_exits lam =
(* Count occurrences of (exit n ...) statements *)
let exits = Hashtbl.create 17 in
let count_exit i =
try
!(Hashtbl.find exits i)
with
| Not_found -> 0
and incr_exit i =
try
incr (Hashtbl.find exits i)
with
| Not_found -> Hashtbl.add exits i (ref 1) in
let rec count = function
| (Lvar _| Lconst _) -> ()
| Lapply(l1, ll, _) -> count l1; List.iter count ll
| Lfunction(kind, params, l) -> count l
| Llet(str, v, l1, l2) ->
count l2; count l1
| Lletrec(bindings, body) ->
List.iter (fun (v, l) -> count l) bindings;
count body
| Lprim(p, ll) -> List.iter count ll
| Lswitch(l, sw) ->
count_default sw ;
count l;
List.iter (fun (_, l) -> count l) sw.sw_consts;
List.iter (fun (_, l) -> count l) sw.sw_blocks
| Lstaticraise (i,ls) -> incr_exit i ; List.iter count ls
| Lstaticcatch (l1,(i,[]),Lstaticraise (j,[])) ->
(* i will be replaced by j in l1, so each occurence of i in l1
increases j's ref count *)
count l1 ;
let ic = count_exit i in
begin try
let r = Hashtbl.find exits j in r := !r + ic
with
| Not_found ->
Hashtbl.add exits j (ref ic)
end
| Lstaticcatch(l1, (i,_), l2) ->
count l1;
(* If l1 does not contain (exit i),
l2 will be removed, so don't count its exits *)
if count_exit i > 0 then
count l2
| Ltrywith(l1, v, l2) -> count l1; count l2
| Lifthenelse(l1, l2, l3) -> count l1; count l2; count l3
| Lsequence(l1, l2) -> count l1; count l2
| Lwhile(l1, l2) -> count l1; count l2
| Lfor(_, l1, l2, dir, l3) -> count l1; count l2; count l3
| Lassign(v, l) ->
(* Lalias-bound variables are never assigned, so don't increase
v's refcount *)
count l
| Lsend(k, m, o, ll) -> List.iter count (m::o::ll)
| Levent(l, _) -> count l
| Lifused(v, l) -> count l
and count_default sw = match sw.sw_failaction with
| None -> ()
| Some al ->
let nconsts = List.length sw.sw_consts
and nblocks = List.length sw.sw_blocks in
if
nconsts < sw.sw_numconsts && nblocks < sw.sw_numblocks
then begin (* default action will occur twice in native code *)
count al ; count al
end else begin (* default action will occur once *)
assert (nconsts < sw.sw_numconsts || nblocks < sw.sw_numblocks) ;
count al
end
in
count lam;
(*
Second pass simplify ``catch body with (i ...) handler''
- if (exit i ...) does not occur in body, suppress catch
- if (exit i ...) occurs exactly once in body,
substitute it with handler
- If handler is a single variable, replace (exit i ..) with it
Note:
In ``catch body with (i x1 .. xn) handler''
Substituted expression is
let y1 = x1 and ... yn = xn in
handler[x1 <- y1 ; ... ; xn <- yn]
For the sake of preserving the uniqueness of bound variables.
(No alpha conversion of ``handler'' is presently needed, since
substitution of several ``(exit i ...)''
occurs only when ``handler'' is a variable.)
*)
let subst = Hashtbl.create 17 in
let rec simplif = function
| (Lvar _|Lconst _) as l -> l
| Lapply(l1, ll, loc) -> Lapply(simplif l1, List.map simplif ll, loc)
| Lfunction(kind, params, l) -> Lfunction(kind, params, simplif l)
| Llet(kind, v, l1, l2) -> Llet(kind, v, simplif l1, simplif l2)
| Lletrec(bindings, body) ->
Lletrec(List.map (fun (v, l) -> (v, simplif l)) bindings, simplif body)
| Lprim(p, ll) -> Lprim(p, List.map simplif ll)
| Lswitch(l, sw) ->
let new_l = simplif l
and new_consts = List.map (fun (n, e) -> (n, simplif e)) sw.sw_consts
and new_blocks = List.map (fun (n, e) -> (n, simplif e)) sw.sw_blocks
and new_fail = match sw.sw_failaction with
| None -> None
| Some l -> Some (simplif l) in
Lswitch
(new_l,
{sw with sw_consts = new_consts ; sw_blocks = new_blocks;
sw_failaction = new_fail})
| Lstaticraise (i,[]) as l ->
begin try
let _,handler = Hashtbl.find subst i in
handler
with
| Not_found -> l
end
| Lstaticraise (i,ls) ->
let ls = List.map simplif ls in
begin try
let xs,handler = Hashtbl.find subst i in
let ys = List.map Ident.rename xs in
let env =
List.fold_right2
(fun x y t -> Ident.add x (Lvar y) t)
xs ys Ident.empty in
List.fold_right2
(fun y l r -> Llet (Alias, y, l, r))
ys ls (Lambda.subst_lambda env handler)
with
| Not_found -> Lstaticraise (i,ls)
end
| Lstaticcatch (l1,(i,[]),(Lstaticraise (j,[]) as l2)) ->
Hashtbl.add subst i ([],simplif l2) ;
simplif l1
| Lstaticcatch (l1,(i,xs), (Lvar _ as l2)) ->
begin match count_exit i with
| 0 -> simplif l1
| _ ->
Hashtbl.add subst i (xs,l2) ;
simplif l1
end
| Lstaticcatch (l1,(i,xs),l2) ->
begin match count_exit i with
| 0 -> simplif l1
| 1 ->
Hashtbl.add subst i (xs,simplif l2) ;
simplif l1
| _ ->
Lstaticcatch (simplif l1, (i,xs), simplif l2)
end
| Ltrywith(l1, v, l2) -> Ltrywith(simplif l1, v, simplif l2)
| Lifthenelse(l1, l2, l3) -> Lifthenelse(simplif l1, simplif l2, simplif l3)
| Lsequence(l1, l2) -> Lsequence(simplif l1, simplif l2)
| Lwhile(l1, l2) -> Lwhile(simplif l1, simplif l2)
| Lfor(v, l1, l2, dir, l3) ->
Lfor(v, simplif l1, simplif l2, dir, simplif l3)
| Lassign(v, l) -> Lassign(v, simplif l)
| Lsend(k, m, o, ll) -> Lsend(k, simplif m, simplif o, List.map simplif ll)
| Levent(l, ev) -> Levent(simplif l, ev)
| Lifused(v, l) -> Lifused (v,simplif l)
in
simplif lam
(* Simplification of lets *)
let simplify_lets lam =
(* First pass: count the occurrences of all let-bound identifiers *)
let occ = (Hashtbl.create 83: (Ident.t, int ref) Hashtbl.t) in
(* The global table [occ] associates to each let-bound identifier
the number of its uses (as a reference):
- 0 if never used
- 1 if used exactly once in and not under a lambda or within a loop
- > 1 if used several times or under a lambda or within a loop.
The local table [bv] associates to each locally-let-bound variable
its reference count, as above. [bv] is enriched at let bindings
but emptied when crossing lambdas and loops. *)
(* Current use count of a variable. *)
let count_var v =
try
!(Hashtbl.find occ v)
with Not_found ->
0
(* Entering a [let]. Returns updated [bv]. *)
and bind_var bv v =
let r = ref 0 in
Hashtbl.add occ v r;
Tbl.add v r bv
(* Record a use of a variable *)
and use_var bv v n =
try
let r = Tbl.find v bv in r := !r + n
with Not_found ->
(* v is not locally bound, therefore this is a use under a lambda
or within a loop. Increase use count by 2 -- enough so
that single-use optimizations will not apply. *)
try
let r = Hashtbl.find occ v in r := !r + 2
with Not_found ->
(* Not a let-bound variable, ignore *)
() in
let rec count bv = function
| Lconst cst -> ()
| Lvar v ->
use_var bv v 1
| Lapply(l1, ll, _) ->
count bv l1; List.iter (count bv) ll
| Lfunction(kind, params, l) ->
count Tbl.empty l
| Llet(str, v, Lvar w, l2) when not !Clflags.debug ->
(* v will be replaced by w in l2, so each occurrence of v in l2
increases w's refcount *)
count (bind_var bv v) l2;
use_var bv w (count_var v)
| Llet(str, v, l1, l2) ->
count (bind_var bv v) l2;
(* If v is unused, l1 will be removed, so don't count its variables *)
if str = Strict || count_var v > 0 then count bv l1
| Lletrec(bindings, body) ->
List.iter (fun (v, l) -> count bv l) bindings;
count bv body
| Lprim(p, ll) -> List.iter (count bv) ll
| Lswitch(l, sw) ->
count_default bv sw ;
count bv l;
List.iter (fun (_, l) -> count bv l) sw.sw_consts;
List.iter (fun (_, l) -> count bv l) sw.sw_blocks
| Lstaticraise (i,ls) -> List.iter (count bv) ls
| Lstaticcatch(l1, (i,_), l2) -> count bv l1; count bv l2
| Ltrywith(l1, v, l2) -> count bv l1; count bv l2
| Lifthenelse(l1, l2, l3) -> count bv l1; count bv l2; count bv l3
| Lsequence(l1, l2) -> count bv l1; count bv l2
| Lwhile(l1, l2) -> count Tbl.empty l1; count Tbl.empty l2
| Lfor(_, l1, l2, dir, l3) -> count bv l1; count bv l2; count Tbl.empty l3
| Lassign(v, l) ->
(* Lalias-bound variables are never assigned, so don't increase
v's refcount *)
count bv l
| Lsend(_, m, o, ll) -> List.iter (count bv) (m::o::ll)
| Levent(l, _) -> count bv l
| Lifused(v, l) ->
if count_var v > 0 then count bv l
and count_default bv sw = match sw.sw_failaction with
| None -> ()
| Some al ->
let nconsts = List.length sw.sw_consts
and nblocks = List.length sw.sw_blocks in
if
nconsts < sw.sw_numconsts && nblocks < sw.sw_numblocks
then begin (* default action will occur twice in native code *)
count bv al ; count bv al
end else begin (* default action will occur once *)
assert (nconsts < sw.sw_numconsts || nblocks < sw.sw_numblocks) ;
count bv al
end
in
count Tbl.empty lam;
(* Second pass: remove Lalias bindings of unused variables,
and substitute the bindings of variables used exactly once. *)
let subst = Hashtbl.create 83 in
let optimize = !Clflags.native_code || not !Clflags.debug in
(* This (small) optimisation is always legal, it may uncover some
tail call later on. *)
let mklet (kind,v,e1,e2) = match e2 with
| Lvar w when optimize && Ident.same v w -> e1
| _ -> Llet (kind,v,e1,e2) in
let rec simplif = function
Lvar v as l ->
begin try
Hashtbl.find subst v
with Not_found ->
l
end
| Lconst cst as l -> l
| Lapply(l1, ll, loc) -> Lapply(simplif l1, List.map simplif ll, loc)
| Lfunction(kind, params, l) -> Lfunction(kind, params, simplif l)
| Llet(str, v, Lvar w, l2) when optimize ->
Hashtbl.add subst v (simplif (Lvar w));
simplif l2
| Llet(Strict, v, Lprim(Pmakeblock(0, Mutable), [linit]), lbody)
when optimize ->
let slinit = simplif linit in
let slbody = simplif lbody in
begin try
mklet (Variable, v, slinit, eliminate_ref v slbody)
with Real_reference ->
mklet(Strict, v, Lprim(Pmakeblock(0, Mutable), [slinit]), slbody)
end
| Llet(Alias, v, l1, l2) ->
begin match count_var v with
0 -> simplif l2
| 1 when optimize -> Hashtbl.add subst v (simplif l1); simplif l2
| n -> Llet(Alias, v, simplif l1, simplif l2)
end
| Llet(StrictOpt, v, l1, l2) ->
begin match count_var v with
0 -> simplif l2
| n -> mklet(Alias, v, simplif l1, simplif l2)
end
| Llet(kind, v, l1, l2) -> mklet(kind, v, simplif l1, simplif l2)
| Lletrec(bindings, body) ->
Lletrec(List.map (fun (v, l) -> (v, simplif l)) bindings, simplif body)
| Lprim(p, ll) -> Lprim(p, List.map simplif ll)
| Lswitch(l, sw) ->
let new_l = simplif l
and new_consts = List.map (fun (n, e) -> (n, simplif e)) sw.sw_consts
and new_blocks = List.map (fun (n, e) -> (n, simplif e)) sw.sw_blocks
and new_fail = match sw.sw_failaction with
| None -> None
| Some l -> Some (simplif l) in
Lswitch
(new_l,
{sw with sw_consts = new_consts ; sw_blocks = new_blocks;
sw_failaction = new_fail})
| Lstaticraise (i,ls) ->
Lstaticraise (i, List.map simplif ls)
| Lstaticcatch(l1, (i,args), l2) ->
Lstaticcatch (simplif l1, (i,args), simplif l2)
| Ltrywith(l1, v, l2) -> Ltrywith(simplif l1, v, simplif l2)
| Lifthenelse(l1, l2, l3) -> Lifthenelse(simplif l1, simplif l2, simplif l3)
| Lsequence(Lifused(v, l1), l2) ->
if count_var v > 0
then Lsequence(simplif l1, simplif l2)
else simplif l2
| Lsequence(l1, l2) -> Lsequence(simplif l1, simplif l2)
| Lwhile(l1, l2) -> Lwhile(simplif l1, simplif l2)
| Lfor(v, l1, l2, dir, l3) ->
Lfor(v, simplif l1, simplif l2, dir, simplif l3)
| Lassign(v, l) -> Lassign(v, simplif l)
| Lsend(k, m, o, ll) -> Lsend(k, simplif m, simplif o, List.map simplif ll)
| Levent(l, ev) -> Levent(simplif l, ev)
| Lifused(v, l) ->
if count_var v > 0 then simplif l else lambda_unit
in
simplif lam
let simplify_lambda lam = simplify_lets (simplify_exits lam)