Skip to content

Commit

Permalink
COMPILER: improve approximation
Browse files Browse the repository at this point in the history
  • Loading branch information
hhugo committed Nov 9, 2013
1 parent e077895 commit 361252d
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 51 deletions.
30 changes: 29 additions & 1 deletion compiler/eval.ml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ let eval_prim x =
| _ -> fun _ -> None in
let float_binop_bool f = float_binop_aux (fun i j -> Int (if f i j then 1 else 0)) in
(match name, l with
| "caml_ml_string_length", [String s] -> Some (Int (String.length s))
(* int *)
| "%int_add", _ -> int_binop (Int.add)
| "%int_sub", _ -> int_binop (Int.sub)
Expand Down Expand Up @@ -108,6 +107,23 @@ let eval_prim x =

exception Not_constant


let the_length_of info x =
get_approx info
(fun x ->
match info.info_defs.(Var.idx x) with
| Expr (Constant (String s))
| Expr (Constant (IString s)) -> Val (String.length s)
| _ -> Top)
Top Bottom
(fun u v -> match u,v with
| Val l,Val l' when l = l' -> Val l
| Bottom,x | x,Bottom -> x
| _ -> Top
)
x


let eval_instr info i =
match i with
| Let (x, Prim (Extern ("caml_js_equals"|"caml_equal"), [y;z])) ->
Expand All @@ -120,6 +136,18 @@ let eval_instr info i =
Let (x , Constant (Int c))
| _ -> i
end
| Let (x,Prim (Extern "caml_ml_string_length", [s])) ->
let c = match s with
| Pc (String s)
| Pc (IString s) -> Some (String.length s)
| Pv v -> begin match the_length_of info v with
| Val i -> Some i
| _ -> None end
| _ -> None
in
(match c with
| None -> i
| Some c -> Let(x,Constant (Int c)))
| Let (x,Prim (prim, prim_args)) ->
begin
let prim_args' = List.map (fun x ->
Expand Down
159 changes: 118 additions & 41 deletions compiler/flow.ml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,23 @@ open Code

let add_var = VarISet.add

type def = Phi of VarSet.t | Expr of Code.expr | Param
type def = Phi of VarSet.t | Expr of Code.expr | Param of (Var.t * int) option


type 'a v =
| Bottom
| Val of 'a
| Top

let string_of_approx f = function
| Bottom -> "Bottom"
| Top -> "Top"
| Val i -> Printf.sprintf "Val %s" (f i)


type info = {
info_defs:def array;
info_params: VarSet.t array array;
info_known_origins : Code.VarSet.t Code.VarTbl.t;
info_maybe_unknown : bool Code.VarTbl.t;
info_possibly_mutable : bool array
Expand All @@ -50,16 +63,32 @@ let add_assign_def vars defs x y =
add_var vars x;
let idx = Var.idx x in
match defs.(idx) with
Expr _ | Param ->
Expr _ | Param _ ->
assert false
| Phi s ->
defs.(idx) <- Phi (VarSet.add y s)

let add_param_def vars defs x =

let add_params_origin state f args =
let fx = Var.idx f in
let argsl = List.length args in
let a =
if Array.length state.(fx) < argsl
then
let a = Array.make argsl VarSet.empty in
Array.blit state.(fx) 0 a 0 (Array.length state.(fx));
state.(fx) <- a;
a
else state.(fx) in
List.iteri (fun i ar -> a.(i) <- VarSet.add ar a.(i) ) args

let add_param_def vars defs x extra =
add_var vars x;
let idx = Var.idx x in
assert (is_undefined defs.(idx) || defs.(idx) = Param);
defs.(idx) <- Param
match defs.(idx) with
| x when is_undefined x -> defs.(idx) <- Param extra
| Param x' when x' = extra -> ()
| _ -> assert false

(* x depends on y *)
let add_dep deps x y =
Expand All @@ -79,38 +108,41 @@ let cont_deps blocks vars deps defs (pc, args) =
let block = AddrMap.find pc blocks in
arg_deps vars deps defs block.params args

let expr_deps blocks vars deps defs x e =
let expr_deps blocks vars deps defs params x e =
match e with
Const _ | Constant _ | Apply _ | Prim _ ->
| Const _ | Constant _ | Prim _ ->
()
| Closure (l, cont) ->
List.iter (fun x -> add_param_def vars defs x) l;
| Apply (f,args,_) ->
add_params_origin params f args
| Closure (l, cont) ->
List.iteri (fun i v -> add_param_def vars defs v (Some (x,i))) l;
cont_deps blocks vars deps defs cont
| Block (_, a) ->
| Block (_, a) ->
Array.iter (fun y -> add_dep deps x y) a
| Field (y, _) ->
| Field (y, _) ->
add_dep deps x y

let program_deps (_, blocks, _) =
let nv = Var.count () in
let vars = VarISet.empty () in
let deps = Array.make nv VarSet.empty in
let defs = Array.make nv undefined in
let params = Array.make nv [||] in
AddrMap.iter
(fun pc block ->
List.iter
(fun i ->
match i with
Let (x, e) ->
| Let (x, e) ->
add_var vars x;
add_expr_def defs x e;
expr_deps blocks vars deps defs x e
| Set_field _ | Array_set _ | Offset_ref _ ->
expr_deps blocks vars deps defs params x e
| Set_field _ | Array_set _ | Offset_ref _ ->
())
block.body;
Util.opt_iter
(fun (x, cont) ->
add_param_def vars defs x;
add_param_def vars defs x None;
cont_deps blocks vars deps defs cont)
block.handler;
match block.branch with
Expand All @@ -127,14 +159,14 @@ let program_deps (_, blocks, _) =
| Pushtrap (cont, _, _, _) ->
cont_deps blocks vars deps defs cont)
blocks;
(vars, deps, defs)
(vars, deps, defs,params)

let var_set_lift f s =
VarSet.fold (fun y s -> VarSet.union (f y) s) s VarSet.empty

let propagate1 deps defs st x =
match defs.(Var.idx x) with
Param ->
Param _ ->
VarSet.singleton x
| Phi s ->
var_set_lift (fun y -> VarTbl.get st y) s
Expand All @@ -151,7 +183,7 @@ let propagate1 deps defs st x =
let t = a.(n) in
add_dep deps x t;
VarTbl.get st t
| Phi _ | Param | Expr _ ->
| Phi _ | Param _ | Expr _ ->
VarSet.empty)
(VarTbl.get st y)

Expand Down Expand Up @@ -253,7 +285,7 @@ let approx_lift f s = VarSet.fold (fun y u -> a_max (f y) u) s Known

let propagate2 ?(skip_param=false) defs known_origins possibly_mutable st x =
match defs.(Var.idx x) with
Param -> skip_param
Param _ -> skip_param
| Phi s ->
VarSet.exists (fun y -> VarTbl.get st y) s
| Expr e ->
Expand All @@ -272,7 +304,7 @@ let propagate2 ?(skip_param=false) defs known_origins possibly_mutable st x =
possibly_mutable.(Var.idx z)
||
VarTbl.get st a.(n)
| Phi _ | Param | Expr _ ->
| Phi _ | Param _| Expr _ ->
true)
(VarTbl.get known_origins y)

Expand All @@ -291,33 +323,77 @@ let solver2 ?skip_param vars deps defs known_origins possibly_mutable =
in
Solver2.f () g (propagate2 ?skip_param defs known_origins possibly_mutable)

let get_approx {info_defs; info_known_origins;info_maybe_unknown} f top join x =
let s = VarTbl.get info_known_origins x in
if VarTbl.get info_maybe_unknown x then top else
match VarSet.cardinal s with
0 -> top
| 1 -> f (VarSet.choose s)
| _ -> VarSet.fold (fun x u -> join (f x) u) s (f (VarSet.choose s))
let get_approx {info_defs; info_known_origins;info_maybe_unknown;info_possibly_mutable;info_params} f top bottom join x =
let rec aux visited x =
let visited = VarSet.add x visited in
let s = VarTbl.get info_known_origins x in
if VarTbl.get info_maybe_unknown x then top,visited else
match VarSet.cardinal s with
| 0 -> bottom,visited
| 1 ->
begin
let x = VarSet.choose s in
match info_defs.(Var.idx x) with
| Param (Some (fct,i)) ->
let idx_f = Var.idx fct in
(* check if fct can escape *)
if info_possibly_mutable.(idx_f)
then top,visited
else
let a = info_params.(idx_f) in
if Array.length a > i
then
let s = a.(i) in
let s = VarSet.elements s in
let rec loop visited res = function
| [] -> res,visited
| x::xs ->
if VarSet.mem x visited
then loop visited res xs
else
let visited = VarSet.add x visited in
let r,visited = aux visited x in
let res = join r res in
loop visited res xs
in loop visited bottom s
else top,visited
| _ -> f x,visited
end
| _ -> VarSet.fold (fun x (u,visited) -> join (f x) u, visited) s (f (VarSet.choose s), visited)
in
fst(aux VarSet.empty x)


let the_def_of info x =
match x with
| Pv x ->
get_approx info
(fun x -> match info.info_defs.(Var.idx x) with Expr e -> Some e | _ -> None)
None (fun u v -> None) x
| Pc c -> Some (Constant c)
| Pv x ->
let v = get_approx info
(fun x -> match info.info_defs.(Var.idx x) with
| Expr e -> Val e
| _ -> Top)
Top Bottom (fun u v -> Top) x in
match v with
| Val v -> Some v
| _ -> None

let the_int info x =
match x with
| Pv x ->
get_approx info
| Pv x -> begin
let v = get_approx info
(fun x -> match info.info_defs.(Var.idx x) with
| Expr (Const i) -> Some i
| Expr (Constant (Int i)) -> Some i
| _ -> None)
None
(fun u v -> match u, v with Some i, Some j when i = j -> u | _ -> None)
x
| Expr (Const i) -> Val i
| Expr (Constant (Int i)) -> Val i
| _ -> Top)
Top Bottom
(fun u v -> match u, v with
| Val i, Val j when i = j -> u
| Bottom, x | x, Bottom -> x
| _ -> Top) x in
match v with
| Val v -> Some v
| _ -> None
end
| Pc (Int i) -> Some i
| _ -> None

Expand All @@ -333,7 +409,7 @@ let direct_approx info x =
Some a.(n)
| _ ->
None)
None
None None
(fun u v ->
match u, v with
Some n, Some m when Var.compare n m = 0 -> u
Expand Down Expand Up @@ -364,7 +440,7 @@ let build_subst info vars =
let f ?skip_param ((pc, blocks, free_pc) as p) =
let t = Util.Timer.make () in
let t1 = Util.Timer.make () in
let (vars, deps, defs) = program_deps p in
let (vars, deps, defs,params) = program_deps p in
if times () then Format.eprintf " flow analysis 1: %a@." Util.Timer.print t1;
let t2 = Util.Timer.make () in
let known_origins = solver1 vars deps defs in
Expand All @@ -391,6 +467,7 @@ let f ?skip_param ((pc, blocks, free_pc) as p) =
let t5 = Util.Timer.make () in
let info = {
info_defs = defs;
info_params = params;
info_known_origins = known_origins;
info_maybe_unknown = maybe_unknown;
info_possibly_mutable = possibly_mutable;
Expand Down
12 changes: 10 additions & 2 deletions compiler/flow.mli
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,25 @@ val get_label : t -> Code.Var.t option
*)

type def = Phi of Code.VarSet.t | Expr of Code.expr | Param
type def = Phi of Code.VarSet.t | Expr of Code.expr | Param of (Code.Var.t * int) option

type 'a v =
| Bottom
| Val of 'a
| Top

val string_of_approx : ('a -> string ) -> 'a v -> string

type info = {
info_defs:def array;
info_params: Code.VarSet.t array array;
info_known_origins : Code.VarSet.t Code.VarTbl.t;
info_maybe_unknown : bool Code.VarTbl.t;
info_possibly_mutable : bool array;
}

val get_approx : info -> (Code.VarSet.elt -> 'b) ->
'b -> ('b -> 'b -> 'b) -> Code.VarTbl.key -> 'b
'b -> 'b -> ('b -> 'b -> 'b) -> Code.VarTbl.key -> 'b

val the_def_of : info -> Code.prim_arg -> Code.expr option

Expand Down
21 changes: 14 additions & 7 deletions compiler/specialize.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,24 @@ open Flow
let function_cardinality info x =
get_approx info
(fun x ->
match info.info_defs.(Var.idx x) with
Expr (Closure (l, _)) -> Some (List.length l)
| _ -> None)
None
(fun u v -> match u, v with Some n, Some m when n = m -> u | _ -> None)
match info.info_defs.(Var.idx x) with
| Expr (Closure (l, _)) ->
Val (List.length l)
| _ -> Top)
Top Bottom
(fun u v -> match u, v with
| Val n, Val m when n = m -> u
| Bottom, x | x, Bottom -> x
| x -> Top)
x

let specialize_instr info i =
match i with
| Let (x, Apply (f, l, _)) when Option.Optim.optcall () ->
Let (x, Apply (f, l, function_cardinality info f))
| Let (x, Apply (f, l, None)) when Option.Optim.optcall () ->
let nopt = match function_cardinality info f with
| Val n -> Some n
| _ -> None in
Let (x, Apply (f, l, nopt))
| _ ->
i

Expand Down

1 comment on commit 361252d

@vouillon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm struggling to follow all your commits :-).

I suspect you do not deal correctly with partial application: if you have a function with two parameters and apply it to only one argument, then you don't know what the second argument will be bound to.

It seems to me that with your changes, get_approx duplicates somewhat what is done in solver1.

So, it seems to me it would be much better to refine the analyses:

  • function propagate1 should return the set of known arguments when applied to a function parameter; when applied to Expr (Apply _), it should record the dependency between the arguments and the parameters (both in a table of type VarSet.t VarTbl.t and by calling add_dep;
  • function propagate2 should be modified as well: the value of a parameter may be unknown if the function escapes, one of the corresponding arguments may be unknown, or the function is partially applied (and there is no argument corresponding to this parameter).

But we then have a problem with build_subst, which cannot any longer rely on these analyses. In particular, we cannot substitute variable across function boundaries : in the piece of code below, you cannot replace xby z which is not in scope:

function f (x) { return 1+x; }
z = 1;
y = f(z);

So, I think we need a specific analysis for variable renaming. The way this is implemented at the moment is a bit of a hack anyway. This is related to computing dominators (see "A Simple, Fast Dominance Algorithm"): basically, what we want is to replace each variable by the root of the corresponding dominator tree. I need to think about how to compute that.

So, I think the first step is to change build_subst not to depend on previous analyses, and then we can improve these analyses.

Please sign in to comment.