From 890a6f9895057b6a97c11f55ed35cff98303514a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 19 Jan 2023 17:42:57 +0100 Subject: [PATCH 01/17] Stdlib: add List.map_last and List.iter_last --- compiler/lib/generate.ml | 11 +---------- compiler/lib/stdlib.ml | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/compiler/lib/generate.ml b/compiler/lib/generate.ml index f22f1a40a7..1dce09880c 100644 --- a/compiler/lib/generate.ml +++ b/compiler/lib/generate.ml @@ -61,15 +61,6 @@ let list_group f g l = | [] -> [] | a :: r -> list_group_rec f g r (f a) [ g a ] [] -(* like [List.map] except that it calls the function with - an additional argument to indicate whether we're mapping - over the last element of the list *) -let rec map_last f l = - match l with - | [] -> assert false - | [ x ] -> [ f true x ] - | x :: xs -> f false x :: map_last f xs - (****) type application_description = @@ -1821,7 +1812,7 @@ and compile_decision_tree st loop_stack backs frontier interm loc cx dtree = let l = List.flatten (List.map l ~f:(fun (ints, br) -> - map_last (fun last i -> int i, if last then br else []) ints)) + List.map_last ~f:(fun last i -> int i, if last then br else []) ints)) in !all_never, [ J.Switch_statement (cx, l, Some last, []), loc ] in diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index faf6bb01aa..6776e737fd 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -277,6 +277,26 @@ module List = struct | x :: xs -> aux (x :: acc) xs in aux [] xs + + (* like [List.map] except that it calls the function with + an additional argument to indicate whether we're mapping + over the last element of the list *) + let rec map_last ~f l = + match l with + | [] -> assert false + | [ x ] -> [ f true x ] + | x :: xs -> f false x :: map_last ~f xs + + (* like [List.iter] except that it calls the function with + an additional argument to indicate whether we're iterating + over the last element of the list *) + let rec iter_last ~f l = + match l with + | [] -> () + | [ a ] -> f true a + | a :: l -> + f false a; + iter_last ~f l end let ( @ ) = List.append From f5fab3c68eca8d4a7e0ef907aded756fec6eb7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 6 Jan 2023 17:57:18 +0100 Subject: [PATCH 02/17] Add function Code.fold_closures_innermost_first --- compiler/lib/code.ml | 19 +++++++++++++++++++ compiler/lib/code.mli | 3 +++ 2 files changed, 22 insertions(+) diff --git a/compiler/lib/code.ml b/compiler/lib/code.ml index 18505805e7..e6cb09cb33 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -633,6 +633,25 @@ let rec preorder_traverse' { fold } f pc visited blocks acc = let preorder_traverse fold f pc blocks acc = snd (preorder_traverse' fold f pc Addr.Set.empty blocks acc) +let fold_closures_innermost_first { start; blocks; _ } f accu = + let rec visit blocks pc f accu = + traverse + { fold = fold_children } + (fun pc accu -> + let block = Addr.Map.find pc blocks in + List.fold_left block.body ~init:accu ~f:(fun accu i -> + match i with + | Let (x, Closure (params, cont)) -> + let accu = visit blocks (fst cont) f accu in + f (Some x) params cont accu + | _ -> accu)) + pc + blocks + accu + in + let accu = visit blocks start f accu in + f None [] (start, []) accu + let eq p1 p2 = p1.start = p2.start && Addr.Map.cardinal p1.blocks = Addr.Map.cardinal p2.blocks diff --git a/compiler/lib/code.mli b/compiler/lib/code.mli index cdd27b6cba..4935eb9ab4 100644 --- a/compiler/lib/code.mli +++ b/compiler/lib/code.mli @@ -231,6 +231,9 @@ type fold_blocs_poly = { fold : 'a. 'a fold_blocs } [@@unboxed] val fold_closures : program -> (Var.t option -> Var.t list -> cont -> 'd -> 'd) -> 'd -> 'd +val fold_closures_innermost_first : + program -> (Var.t option -> Var.t list -> cont -> 'd -> 'd) -> 'd -> 'd + val fold_children : 'c fold_blocs val traverse : From d3956c558a8a3ff7fdd71c56717b12709734d197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 11 Jan 2023 17:38:12 +0100 Subject: [PATCH 03/17] Add function Code.Var.Tbl.iter --- compiler/lib/code.ml | 7 +++++++ compiler/lib/code.mli | 2 ++ 2 files changed, 9 insertions(+) diff --git a/compiler/lib/code.ml b/compiler/lib/code.ml index e6cb09cb33..8d0238fd47 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -107,6 +107,8 @@ module Var : sig val set : 'a t -> key -> 'a -> unit val make : size -> 'a -> 'a t + + val iter : (key -> 'a -> unit) -> 'a t -> unit end module ISet : sig @@ -213,6 +215,11 @@ end = struct let set t x v = t.(x) <- v let make () v = Array.make (count ()) v + + let iter f t = + for i = 0 to Array.length t - 1 do + f i t.(i) + done end module ISet = struct diff --git a/compiler/lib/code.mli b/compiler/lib/code.mli index 4935eb9ab4..bfad289662 100644 --- a/compiler/lib/code.mli +++ b/compiler/lib/code.mli @@ -100,6 +100,8 @@ module Var : sig val set : 'a t -> key -> 'a -> unit val make : size -> 'a -> 'a t + + val iter : (key -> 'a -> unit) -> 'a t -> unit end module ISet : sig From 58e0fed4ccd1c82f6e4d89e6a97b31c855cd9c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 6 Jan 2023 18:09:49 +0100 Subject: [PATCH 04/17] Dgraph: improved scheduling We start from a pretty good ordering (reverse postorder is optimal when there is no loop). Then we use a queue so that we process all other nodes before coming back to a node, resulting in less iterations. --- compiler/lib/dgraph.ml | 50 +++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/compiler/lib/dgraph.ml b/compiler/lib/dgraph.ml index a2db00119a..8993e69291 100644 --- a/compiler/lib/dgraph.ml +++ b/compiler/lib/dgraph.ml @@ -58,22 +58,22 @@ struct let m = ref 0 - type stack = - { stack : N.t Stack.t + type queue = + { queue : N.t Queue.t ; mutable set : NSet.t } - let is_empty st = Stack.is_empty st.stack + let is_empty st = Queue.is_empty st.queue let pop st = - let x = Stack.pop st.stack in + let x = Queue.pop st.queue in st.set <- NSet.remove x st.set; x let push x st = if not (NSet.mem x st.set) then ( - Stack.push x st.stack; + Queue.push x st.queue; st.set <- NSet.add x st.set) let rec iterate g f v w = @@ -91,24 +91,26 @@ struct iterate g f v w) else iterate g f v w - let rec traverse g visited stack x = + let rec traverse g visited lst x = if not (NSet.mem x visited) then ( let visited = NSet.add x visited in let visited = - g.fold_children (fun y visited -> traverse g visited stack y) x visited + g.fold_children (fun y visited -> traverse g visited lst y) x visited in - Stack.push x stack; + lst := x :: !lst; visited) else visited let traverse_all g = - let stack = Stack.create () in + let lst = ref [] in let visited = - NSet.fold (fun x visited -> traverse g visited stack x) g.domain NSet.empty + NSet.fold (fun x visited -> traverse g visited lst x) g.domain NSet.empty in assert (NSet.equal g.domain visited); - stack + let queue = Queue.create () in + List.iter ~f:(fun x -> Queue.push x queue) !lst; + queue let f g f = n := 0; @@ -128,7 +130,7 @@ let t1 = Timer.make () in let t1 = Timer.get t1 in let t2 = Timer.make () in *) - let w = { set = g.domain; stack = traverse_all g } in + let w = { set = g.domain; queue = traverse_all g } in (* let t2 = Timer.get t2 in let t3 = Timer.make () in @@ -206,22 +208,22 @@ struct let m = ref 0 - type stack = - { stack : N.t Stack.t + type queue = + { queue : N.t Queue.t ; set : NSet.t } - let is_empty st = Stack.is_empty st.stack + let is_empty st = Queue.is_empty st.queue let pop st = - let x = Stack.pop st.stack in + let x = Queue.pop st.queue in NSet.add st.set x; x let push x st = if NSet.mem st.set x then ( - Stack.push x st.stack; + Queue.push x st.queue; NSet.remove st.set x) let rec iterate g f v w = @@ -239,19 +241,21 @@ struct iterate g f v w) else iterate g f v w - let rec traverse g to_visit stack x = + let rec traverse g to_visit lst x = if NSet.mem to_visit x then ( NSet.remove to_visit x; incr n; - g.iter_children (fun y -> traverse g to_visit stack y) x; - Stack.push x stack) + g.iter_children (fun y -> traverse g to_visit lst y) x; + lst := x :: !lst) let traverse_all g = - let stack = Stack.create () in + let lst = ref [] in let to_visit = NSet.copy g.domain in - NSet.iter (fun x -> traverse g to_visit stack x) g.domain; - { stack; set = to_visit } + NSet.iter (fun x -> traverse g to_visit lst x) g.domain; + let queue = Queue.create () in + List.iter ~f:(fun x -> Queue.push x queue) !lst; + { queue; set = to_visit } let f size g f = n := 0; From 40df2506edec5e5cffb8744f5d2f8e65db93a449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 6 Jan 2023 22:22:29 +0100 Subject: [PATCH 05/17] Dgraph: allow to suggest possibly updated nodes This is useful when the graph changes dynamically --- compiler/lib/dgraph.ml | 20 ++++++++++++-------- compiler/lib/dgraph.mli | 6 ++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/compiler/lib/dgraph.ml b/compiler/lib/dgraph.ml index 8993e69291..6834620095 100644 --- a/compiler/lib/dgraph.ml +++ b/compiler/lib/dgraph.ml @@ -226,20 +226,19 @@ struct Queue.push x st.queue; NSet.remove st.set x) - let rec iterate g f v w = + let rec iterate g ~update f v w = if is_empty w then v else let x = pop w in let a = NTbl.get v x in incr m; - let b = f v x in - NTbl.set v x b; + let b = f ~update v x in if not (D.equal a b) then ( - g.iter_children (fun y -> push y w) x; - iterate g f v w) - else iterate g f v w + NTbl.set v x b; + g.iter_children (fun y -> push y w) x); + iterate g ~update f v w let rec traverse g to_visit lst x = if NSet.mem to_visit x @@ -257,7 +256,7 @@ struct List.iter ~f:(fun x -> Queue.push x queue) !lst; { queue; set = to_visit } - let f size g f = + let f' size g f = n := 0; m := 0; (* @@ -273,12 +272,17 @@ let t2 = Timer.make () in let t2 = Timer.get t2 in let t3 = Timer.make () in *) - let res = iterate g f v w in + let update ~children x = + if children then g.iter_children (fun y -> push y w) x else push x w + in + let res = iterate g ~update f v w in (* let t3 = Timer.get t3 in Format.eprintf "YYY %.2f %.2f %.2f@." t1 t2 t3; Format.eprintf "YYY %d %d (%f)@." !m !n (float !m /. float !n); *) res + + let f size g f = f' size g (fun ~update:_ v x -> f v x) end end diff --git a/compiler/lib/dgraph.mli b/compiler/lib/dgraph.mli index 424f2e2c81..337569f3ae 100644 --- a/compiler/lib/dgraph.mli +++ b/compiler/lib/dgraph.mli @@ -94,5 +94,11 @@ end) module Solver (D : DOMAIN) : sig val f : NTbl.size -> t -> (D.t NTbl.t -> N.t -> D.t) -> D.t NTbl.t + + val f' : + NTbl.size + -> t + -> (update:(children:bool -> N.t -> unit) -> D.t NTbl.t -> N.t -> D.t) + -> D.t NTbl.t end end From cb7580aa43269bfcfcc185f5e2747174ecda07ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 6 Jan 2023 19:31:46 +0100 Subject: [PATCH 06/17] Effects: explicitly mark CPS calls --- compiler/lib/driver.ml | 30 +++++++++++---- compiler/lib/effects.ml | 69 ++++++++++++++++++++-------------- compiler/lib/effects.mli | 4 +- compiler/lib/generate.ml | 50 +++++++++--------------- compiler/lib/generate.mli | 1 + compiler/lib/lambda_lifting.ml | 6 --- 6 files changed, 85 insertions(+), 75 deletions(-) diff --git a/compiler/lib/driver.ml b/compiler/lib/driver.ml index bc78dff46f..b91fdf780d 100644 --- a/compiler/lib/driver.ml +++ b/compiler/lib/driver.ml @@ -83,19 +83,21 @@ let phi p = if debug () then Format.eprintf "Variable passing simplification...@."; Phisimpl.f p +let ( +> ) f g x = g (f x) + +let map_fst f (x, y) = f x, y + let effects p = if Config.Flag.effects () then ( if debug () then Format.eprintf "Effects...@."; - Deadcode.f p |> Effects.f |> Lambda_lifting.f) - else p + p |> Deadcode.f +> Effects.f +> map_fst Lambda_lifting.f) + else p, (Code.Var.Set.empty : Effects.cps_calls) let print p = if debug () then Code.Print.program (fun _ _ -> "") p; p -let ( +> ) f g x = g (f x) - let rec loop max name round i (p : 'a) : 'a = let p' = round p in if i >= max || Code.eq p' p @@ -154,10 +156,22 @@ let round2 = flow +> specialize' +> eval +> deadcode +> o1 let o3 = loop 10 "tailcall+inline" round1 1 +> loop 10 "flow" round2 1 +> print -let generate d ~exported_runtime ~wrap_with_fun ~warn_on_unhandled_effect (p, live_vars) = +let generate + d + ~exported_runtime + ~wrap_with_fun + ~warn_on_unhandled_effect + ((p, live_vars), cps_calls) = if times () then Format.eprintf "Start Generation...@."; let should_export = should_export wrap_with_fun in - Generate.f p ~exported_runtime ~live_vars ~should_export ~warn_on_unhandled_effect d + Generate.f + p + ~exported_runtime + ~live_vars + ~cps_calls + ~should_export + ~warn_on_unhandled_effect + d let header formatter ~custom_header = match custom_header with @@ -553,7 +567,9 @@ let full d p = let exported_runtime = not standalone in - let opt = specialize_js_once +> profile +> effects +> Generate_closure.f +> deadcode' in + let opt = + specialize_js_once +> profile +> effects +> map_fst (Generate_closure.f +> deadcode') + in let emit = generate d ~exported_runtime ~wrap_with_fun ~warn_on_unhandled_effect:standalone +> link ~standalone ~linkall diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 0de564b4dc..b04813cb18 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -225,6 +225,8 @@ let jump_closures blocks_to_transform idom : jump_closures = idom { closure_of_jump = Addr.Map.empty; closures_of_alloc_site = Addr.Map.empty } +type cps_calls = Var.Set.t + type st = { mutable new_blocks : Code.block Addr.Map.t * Code.Addr.t ; blocks : Code.block Addr.Map.t @@ -234,6 +236,7 @@ type st = ; is_continuation : (Addr.t, [ `Param of Var.t | `Loop ]) Hashtbl.t ; matching_exn_handler : (Addr.t, Addr.t) Hashtbl.t ; live_vars : Deadcode.variable_uses + ; cps_calls : cps_calls ref } let add_block st block = @@ -250,8 +253,9 @@ let allocate_closure ~st ~params ~body:(body, branch) = let name = Var.fresh () in [ Let (name, Closure (params, (pc, []))) ], name -let tail_call ?(instrs = []) ~exact ~f args = +let tail_call ~st ?(instrs = []) ~exact ~f args = let ret = Var.fresh () in + st.cps_calls := Var.Set.add ret !(st.cps_calls); instrs @ [ Let (ret, Apply { f; args; exact }) ], Return ret let cps_branch ~st (pc, args) = @@ -267,7 +271,7 @@ let cps_branch ~st (pc, args) = [ x ], [ Let (x, Constant (Int 0l)) ] else args, [] in - tail_call ~instrs ~exact:true ~f:(closure_of_pc ~st pc) args + tail_call ~st ~instrs ~exact:true ~f:(closure_of_pc ~st pc) args let cps_jump_cont ~st ((pc, _) as cont) = match Addr.Set.mem pc st.blocks_to_transform with @@ -281,7 +285,7 @@ let cps_jump_cont ~st ((pc, _) as cont) = let cps_last ~st pc (last : last) ~k : instr list * last = match last with - | Return x -> tail_call ~exact:true ~f:k [ x ] + | Return x -> tail_call ~st ~exact:true ~f:k [ x ] | Raise (x, _) -> ( match Hashtbl.find_opt st.matching_exn_handler pc with | Some pc when not (Addr.Set.mem pc st.blocks_to_transform) -> @@ -291,6 +295,7 @@ let cps_last ~st pc (last : last) ~k : instr list * last = | _ -> let exn_handler = Var.fresh_n "raise" in tail_call + ~st ~instrs:[ Let (exn_handler, Prim (Extern "caml_pop_trap", [])) ] ~exact:true ~f:exn_handler @@ -386,12 +391,13 @@ let cps_block ~st ~k pc block = [ Let (x, e) ], Return x) in match e with - | Apply { f; args; exact } -> Some (fun ~k -> tail_call ~exact ~f (args @ [ k ])) + | Apply { f; args; exact } -> Some (fun ~k -> tail_call ~st ~exact ~f (args @ [ k ])) | Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ]) -> Some (fun ~k -> let k' = Var.fresh_n "cont" in tail_call + ~st ~instrs:[ Let (k', Prim (Extern "caml_resume_stack", [ Pv stack; Pv k ])) ] ~exact:false ~f @@ -443,7 +449,7 @@ let cps_block ~st ~k pc block = allocate_closure ~st ~params:[ x ] - ~body:(tail_call ~instrs ~exact:true ~f:f' args) + ~body:(tail_call ~st ~instrs ~exact:true ~f:f' args) in let instrs, branch = f ~k:k' in body_prefix, constr_cont @ instrs, branch @@ -485,6 +491,7 @@ let cps_transform ~live_vars p = Hashtbl.add tbl pc k; k in + let cps_calls = ref Var.Set.empty in let wrap_toplevel = ref true in let p = Code.fold_closures @@ -505,6 +512,7 @@ let cps_transform ~live_vars p = ; is_continuation ; matching_exn_handler ; live_vars + ; cps_calls } in let function_needs_cps = @@ -553,28 +561,31 @@ let cps_transform ~live_vars p = { p with blocks; free_pc }) p in - if not !wrap_toplevel - then p - else - (* Call [caml_callback] to set up the execution context. *) - let new_start = p.free_pc in - let blocks = - let main = Var.fresh () in - let args = Var.fresh () in - let res = Var.fresh () in - Addr.Map.add - new_start - { params = [] - ; body = - [ Let (main, Closure ([ closure_continuation p.start ], (p.start, []))) - ; Let (args, Prim (Extern "%js_array", [])) - ; Let (res, Prim (Extern "caml_callback", [ Pv main; Pv args ])) - ] - ; branch = Return res - } - p.blocks - in - { start = new_start; blocks; free_pc = new_start + 1 } + let p = + if not !wrap_toplevel + then p + else + (* Call [caml_callback] to set up the execution context. *) + let new_start = p.free_pc in + let blocks = + let main = Var.fresh () in + let args = Var.fresh () in + let res = Var.fresh () in + Addr.Map.add + new_start + { params = [] + ; body = + [ Let (main, Closure ([ closure_continuation p.start ], (p.start, []))) + ; Let (args, Prim (Extern "%js_array", [])) + ; Let (res, Prim (Extern "caml_callback", [ Pv main; Pv args ])) + ] + ; branch = Return res + } + p.blocks + in + { start = new_start; blocks; free_pc = new_start + 1 } + in + p, !cps_calls (****) @@ -757,6 +768,6 @@ let f (p, live_vars) = let p = remove_empty_blocks ~live_vars p in let p = split_blocks p in let p = rewrite_toplevel p in - let p = cps_transform ~live_vars p in + let p, cps_calls = cps_transform ~live_vars p in if Debug.find "times" () then Format.eprintf " effects: %a@." Timer.print t; - p + p, cps_calls diff --git a/compiler/lib/effects.mli b/compiler/lib/effects.mli index 253bccad4f..c4afc03e72 100644 --- a/compiler/lib/effects.mli +++ b/compiler/lib/effects.mli @@ -16,4 +16,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -val f : Code.program * Deadcode.variable_uses -> Code.program +type cps_calls = Code.Var.Set.t + +val f : Code.program * Deadcode.variable_uses -> Code.program * cps_calls diff --git a/compiler/lib/generate.ml b/compiler/lib/generate.ml index 1dce09880c..dc53ea2d17 100644 --- a/compiler/lib/generate.ml +++ b/compiler/lib/generate.ml @@ -143,7 +143,12 @@ module Share = struct | Pc c -> get_constant c t | _ -> t) - let get ?alias_strings ?(alias_prims = false) ?(alias_apply = true) { blocks; _ } : t = + let get + ~cps_calls + ?alias_strings + ?(alias_prims = false) + ?(alias_apply = true) + { blocks; _ } : t = let alias_strings = match alias_strings with | None -> Config.Flag.use_js_string () && not (Config.Flag.share_constant ()) @@ -152,28 +157,11 @@ module Share = struct let count = Addr.Map.fold (fun _ block share -> - let tailcall_name = - (* Systematic tail-call optimization is only enabled when - supporting effects *) - if Config.Flag.effects () - then - match block.branch with - | Return _ -> ( - match List.last block.body with - | Some (Let (x, _)) -> Some x - | _ -> None) - | _ -> None - else None - in List.fold_left block.body ~init:share ~f:(fun share i -> match i with | Let (_, Constant c) -> get_constant c share | Let (x, Apply { args; exact; _ }) -> - let cps = - match tailcall_name with - | Some y -> Var.equal x y - | None -> false - in + let cps = Var.Set.mem x cps_calls in if (not exact) || cps then add_apply { arity = List.length args; exact; cps } share else share @@ -292,6 +280,7 @@ module Ctx = struct ; exported_runtime : (Code.Var.t * bool ref) option ; should_export : bool ; effect_warning : bool ref + ; cps_calls : Effects.cps_calls } let initial @@ -300,6 +289,7 @@ module Ctx = struct ~should_export blocks live + cps_calls share debug = { blocks @@ -309,6 +299,7 @@ module Ctx = struct ; exported_runtime ; should_export ; effect_warning = ref (not warn_on_unhandled_effect) + ; cps_calls } end @@ -1195,10 +1186,10 @@ let throw_statement ctx cx k loc = , loc ) ] -let rec translate_expr ctx queue loc in_tail_position e level : _ * J.statement_list = - let cps = in_tail_position && Config.Flag.effects () in +let rec translate_expr ctx queue loc x e level : _ * J.statement_list = match e with | Apply { f; args; exact } -> + let cps = Var.Set.mem x ctx.Ctx.cps_calls in let args, prop, queue = List.fold_right ~f:(fun x (args, prop, queue) -> @@ -1471,7 +1462,7 @@ let rec translate_expr ctx queue loc in_tail_position e level : _ * J.statement_ in res, [] -and translate_instr ctx expr_queue loc instr in_tail_position = +and translate_instr ctx expr_queue loc instr = match instr with | Assign (x, y) -> let (_py, cy), expr_queue = access_queue expr_queue y in @@ -1480,9 +1471,7 @@ and translate_instr ctx expr_queue loc instr in_tail_position = mutator_p [ J.Expression_statement (J.EBin (J.Eq, J.EVar (J.V x), cy)), loc ] | Let (x, e) -> ( - let (ce, prop, expr_queue), instrs = - translate_expr ctx expr_queue loc in_tail_position e 0 - in + let (ce, prop, expr_queue), instrs = translate_expr ctx expr_queue loc x e 0 in let keep_name x = match Code.Var.get_name x with | None -> false @@ -1546,12 +1535,7 @@ and translate_instrs ctx expr_queue loc instr last = match instr with | [] -> [], expr_queue | instr :: rem -> - let in_tail_position = - match rem, last with - | [], Return _ -> true - | _ -> false - in - let st, expr_queue = translate_instr ctx expr_queue loc instr in_tail_position in + let st, expr_queue = translate_instr ctx expr_queue loc instr in let instrs, expr_queue = translate_instrs ctx expr_queue loc rem last in st @ instrs, expr_queue @@ -2081,11 +2065,12 @@ let f (p : Code.program) ~exported_runtime ~live_vars + ~cps_calls ~should_export ~warn_on_unhandled_effect debug = let t' = Timer.make () in - let share = Share.get ~alias_prims:exported_runtime p in + let share = Share.get ~cps_calls ~alias_prims:exported_runtime p in let exported_runtime = if exported_runtime then Some (Code.Var.fresh_n "runtime", ref false) else None in @@ -2096,6 +2081,7 @@ let f ~should_export p.blocks live_vars + cps_calls share debug in diff --git a/compiler/lib/generate.mli b/compiler/lib/generate.mli index e512270aa5..66053fdc2c 100644 --- a/compiler/lib/generate.mli +++ b/compiler/lib/generate.mli @@ -22,6 +22,7 @@ val f : Code.program -> exported_runtime:bool -> live_vars:Deadcode.variable_uses + -> cps_calls:Effects.cps_calls -> should_export:bool -> warn_on_unhandled_effect:bool -> Parse_bytecode.Debug.t diff --git a/compiler/lib/lambda_lifting.ml b/compiler/lib/lambda_lifting.ml index 998093ca3e..b14ef61dd3 100644 --- a/compiler/lib/lambda_lifting.ml +++ b/compiler/lib/lambda_lifting.ml @@ -198,12 +198,6 @@ let rec traverse var_depth (program, functions) pc depth limit = Let (f'', Closure (List.map s ~f:snd, (pc'', []))) :: functions in let rem', st = rewrite_body false (program, functions) rem in - assert ( - (not (List.is_empty rem')) - || - match block.branch with - | Return _ -> false - | _ -> true); ( Let (f, Apply { f = f''; args = List.map ~f:fst s; exact = true }) :: rem' , st )) From 67c06e517ec5d126b1f27932b983bb66ecae3636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 6 Jan 2023 19:55:42 +0100 Subject: [PATCH 07/17] Effects: omit some stack depth checks We omit stack checks when jumping from one block to another within a function, except for backward edges. Stack checks are also omitted when calling the function continuations. We have to check the stack depth in `caml_alloc_stack` for the test `evenodd.ml` to succeed. Otherwise, popping all the fibers exhaust the JavaScript stack. We don't have this issue with the OCaml runtime since it allocates one stack per fiber. --- compiler/lib/effects.ml | 56 ++++++++++++------- compiler/tests-compiler/effects.ml | 8 +-- .../tests-compiler/effects_continuations.ml | 29 ++++------ compiler/tests-compiler/effects_exceptions.ml | 17 ++---- compiler/tests-compiler/effects_toplevel.ml | 2 +- compiler/tests-compiler/lambda_lifting.ml | 8 +-- runtime/effect.js | 16 +++--- 7 files changed, 65 insertions(+), 71 deletions(-) diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index b04813cb18..0e05b6b49f 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -52,6 +52,7 @@ let reverse_graph g = type control_flow_graph = { succs : (Addr.t, Addr.Set.t) Hashtbl.t ; reverse_post_order : Addr.t list + ; block_order : (Addr.t, int) Hashtbl.t } let build_graph blocks pc = @@ -68,19 +69,19 @@ let build_graph blocks pc = l := pc :: !l) in traverse pc; - { succs; reverse_post_order = !l } + let block_order = Hashtbl.create 16 in + List.iteri !l ~f:(fun i pc -> Hashtbl.add block_order pc i); + { succs; reverse_post_order = !l; block_order } let dominator_tree g = (* A Simple, Fast Dominance Algorithm Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy *) let dom = Hashtbl.create 16 in - let order = Hashtbl.create 16 in - List.iteri g.reverse_post_order ~f:(fun i pc -> Hashtbl.add order pc i); let rec inter pc pc' = (* Compute closest common ancestor *) if pc = pc' then pc - else if Hashtbl.find order pc < Hashtbl.find order pc' + else if Hashtbl.find g.block_order pc < Hashtbl.find g.block_order pc' then inter pc (Hashtbl.find dom pc') else inter (Hashtbl.find dom pc) pc' in @@ -235,6 +236,7 @@ type st = ; blocks_to_transform : Addr.Set.t ; is_continuation : (Addr.t, [ `Param of Var.t | `Loop ]) Hashtbl.t ; matching_exn_handler : (Addr.t, Addr.t) Hashtbl.t + ; block_order : (Addr.t, int) Hashtbl.t ; live_vars : Deadcode.variable_uses ; cps_calls : cps_calls ref } @@ -253,12 +255,13 @@ let allocate_closure ~st ~params ~body:(body, branch) = let name = Var.fresh () in [ Let (name, Closure (params, (pc, []))) ], name -let tail_call ~st ?(instrs = []) ~exact ~f args = +let tail_call ~st ?(instrs = []) ~exact ~check ~f args = + assert (exact || check); let ret = Var.fresh () in - st.cps_calls := Var.Set.add ret !(st.cps_calls); + if check then st.cps_calls := Var.Set.add ret !(st.cps_calls); instrs @ [ Let (ret, Apply { f; args; exact }) ], Return ret -let cps_branch ~st (pc, args) = +let cps_branch ~st ~src (pc, args) = match Addr.Set.mem pc st.blocks_to_transform with | false -> [], Branch (pc, args) | true -> @@ -271,21 +274,28 @@ let cps_branch ~st (pc, args) = [ x ], [ Let (x, Constant (Int 0l)) ] else args, [] in - tail_call ~st ~instrs ~exact:true ~f:(closure_of_pc ~st pc) args + (* We check the stack depth only for backward edges (so, at + least once per loop iteration) *) + let check = Hashtbl.find st.block_order src >= Hashtbl.find st.block_order pc in + tail_call ~st ~instrs ~exact:true ~check ~f:(closure_of_pc ~st pc) args -let cps_jump_cont ~st ((pc, _) as cont) = +let cps_jump_cont ~st ~src ((pc, _) as cont) = match Addr.Set.mem pc st.blocks_to_transform with | false -> cont | true -> let call_block = - let body, branch = cps_branch ~st cont in + let body, branch = cps_branch ~st ~src cont in add_block st { params = []; body; branch } in call_block, [] let cps_last ~st pc (last : last) ~k : instr list * last = match last with - | Return x -> tail_call ~st ~exact:true ~f:k [ x ] + | Return x -> + (* Is the number of successive 'returns' is unbounded is CPS, it + means that we have an unbounded of calls in direct style + (even with tail call optimization) *) + tail_call ~st ~exact:true ~check:false ~f:k [ x ] | Raise (x, _) -> ( match Hashtbl.find_opt st.matching_exn_handler pc with | Some pc when not (Addr.Set.mem pc st.blocks_to_transform) -> @@ -298,18 +308,19 @@ let cps_last ~st pc (last : last) ~k : instr list * last = ~st ~instrs:[ Let (exn_handler, Prim (Extern "caml_pop_trap", [])) ] ~exact:true + ~check:false ~f:exn_handler [ x ]) | Stop -> [], Stop - | Branch cont -> cps_branch ~st cont + | Branch cont -> cps_branch ~st ~src:pc cont | Cond (x, cont1, cont2) -> - [], Cond (x, cps_jump_cont ~st cont1, cps_jump_cont ~st cont2) + [], Cond (x, cps_jump_cont ~st ~src:pc cont1, cps_jump_cont ~st ~src:pc cont2) | Switch (x, c1, c2) -> (* To avoid code duplication during JavaScript generation, we need to create a single block per continuation *) - let cps_jump_cont = Fun.memoize (cps_jump_cont ~st) in + let cps_jump_cont = Fun.memoize (cps_jump_cont ~st ~src:pc) in [], Switch (x, Array.map c1 ~f:cps_jump_cont, Array.map c2 ~f:cps_jump_cont) - | Pushtrap ((pc, args), _, (handler_pc, _), _) -> ( + | Pushtrap (body_cont, _, (handler_pc, _), _) -> ( assert (Hashtbl.mem st.is_continuation handler_pc); match Addr.Set.mem handler_pc st.blocks_to_transform with | false -> [], last @@ -318,16 +329,16 @@ let cps_last ~st pc (last : last) ~k : instr list * last = let push_trap = Let (Var.fresh (), Prim (Extern "caml_push_trap", [ Pv exn_handler ])) in - let body, branch = cps_branch ~st (pc, args) in + let body, branch = cps_branch ~st ~src:pc body_cont in push_trap :: body, branch) - | Poptrap (pc', args) -> ( + | Poptrap cont -> ( match Addr.Set.mem (Hashtbl.find st.matching_exn_handler pc) st.blocks_to_transform with - | false -> [], Poptrap (cps_jump_cont ~st (pc', args)) + | false -> [], Poptrap (cps_jump_cont ~st ~src:pc cont) | true -> let exn_handler = Var.fresh () in - let body, branch = cps_branch ~st (pc', args) in + let body, branch = cps_branch ~st ~src:pc cont in Let (exn_handler, Prim (Extern "caml_pop_trap", [])) :: body, branch) let cps_instr ~st (instr : instr) : instr = @@ -391,7 +402,8 @@ let cps_block ~st ~k pc block = [ Let (x, e) ], Return x) in match e with - | Apply { f; args; exact } -> Some (fun ~k -> tail_call ~st ~exact ~f (args @ [ k ])) + | Apply { f; args; exact } -> + Some (fun ~k -> tail_call ~st ~exact ~check:true ~f (args @ [ k ])) | Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ]) -> Some (fun ~k -> @@ -400,6 +412,7 @@ let cps_block ~st ~k pc block = ~st ~instrs:[ Let (k', Prim (Extern "caml_resume_stack", [ Pv stack; Pv k ])) ] ~exact:false + ~check:true ~f [ arg; k' ]) | Prim (Extern "%perform", [ Pv effect ]) -> @@ -449,7 +462,7 @@ let cps_block ~st ~k pc block = allocate_closure ~st ~params:[ x ] - ~body:(tail_call ~st ~instrs ~exact:true ~f:f' args) + ~body:(tail_call ~st ~instrs ~exact:true ~check:false ~f:f' args) in let instrs, branch = f ~k:k' in body_prefix, constr_cont @ instrs, branch @@ -511,6 +524,7 @@ let cps_transform ~live_vars p = ; blocks_to_transform ; is_continuation ; matching_exn_handler + ; block_order = cfg.block_order ; live_vars ; cps_calls } diff --git a/compiler/tests-compiler/effects.ml b/compiler/tests-compiler/effects.ml index 5b91a3754d..0994bb3034 100644 --- a/compiler/tests-compiler/effects.ml +++ b/compiler/tests-compiler/effects.ml @@ -47,13 +47,9 @@ let fff () = _b_= [0, function(e,cont) - {return e === E - ?caml_cps_exact_call1 - (cont, - [0,function(k,cont){return caml_cps_exact_call1(cont,11)}]) - :caml_cps_exact_call1(cont,0)}], + {return e === E?cont([0,function(k,cont){return cont(11)}]):cont(0)}], _c_=10; - function _d_(x,cont){return caml_cps_exact_call1(cont,x)} + function _d_(x,cont){return cont(x)} var _e_=Stdlib_Effect[3][5]; return caml_cps_call4 (_e_, diff --git a/compiler/tests-compiler/effects_continuations.ml b/compiler/tests-compiler/effects_continuations.ml index 6b89444da3..1b962e3f54 100644 --- a/compiler/tests-compiler/effects_continuations.ml +++ b/compiler/tests-compiler/effects_continuations.ml @@ -92,50 +92,43 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = {var _z_=runtime.caml_int_of_string(s),n=_z_} catch(_D_) {var _s_=caml_wrap_exception(_D_); - if(_s_[1] !== Stdlib[7]) - {var raise$1=caml_pop_trap();return caml_cps_exact_call1(raise$1,_s_)} + if(_s_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_s_)} var n=0,_t_=0} try {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _y_=7,m=_y_} catch(_C_) {var _u_=caml_wrap_exception(_C_); - if(_u_ !== Stdlib[8]) - {var raise$0=caml_pop_trap();return caml_cps_exact_call1(raise$0,_u_)} + if(_u_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_u_)} var m=0,_v_=0} runtime.caml_push_trap (function(_B_) - {if(_B_ === Stdlib[8])return caml_cps_exact_call1(cont,0); + {if(_B_ === Stdlib[8])return cont(0); var raise=caml_pop_trap(); - return caml_cps_exact_call1(raise,_B_)}); + return raise(_B_)}); if(caml_string_equal(s,cst)) - {var _w_=Stdlib[8],raise=caml_pop_trap(); - return caml_cps_exact_call1(raise,_w_)} + {var _w_=Stdlib[8],raise=caml_pop_trap();return raise(_w_)} var _x_=Stdlib[79]; return caml_cps_call2 (_x_, cst_toto, - function(_A_) - {caml_pop_trap(); - return caml_cps_exact_call1(cont,[0,[0,_A_,n,m]])})} + function(_A_){caml_pop_trap();return cont([0,[0,_A_,n,m]])})} //end function cond1(b,cont) - {function _r_(ic){return caml_cps_exact_call1(cont,[0,ic,7])} + {function _r_(ic){return cont([0,ic,7])} return b ?caml_cps_call2(Stdlib[79],cst_toto$0,_r_) :caml_cps_call2(Stdlib[79],cst_titi,_r_)} //end function cond2(b,cont) - {function _p_(_q_){return caml_cps_exact_call1(cont,7)} + {function _p_(_q_){return cont(7)} return b ?caml_cps_call2(Stdlib_Printf[3],_a_,_p_) :caml_cps_call2(Stdlib_Printf[3],_b_,_p_)} //end function cond3(b,cont) {var x=[0,0]; - function _n_(_o_){return caml_cps_exact_call1(cont,x[1])} - return b - ?(x[1] = 1,caml_cps_exact_call1(_n_,0)) - :caml_cps_call2(Stdlib_Printf[3],_c_,_n_)} + function _n_(_o_){return cont(x[1])} + return b?(x[1] = 1,_n_(0)):caml_cps_call2(Stdlib_Printf[3],_c_,_n_)} //end function loop1(b,cont) {var all=[0,0],_j_=Stdlib[79]; @@ -153,7 +146,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = return b ?caml_cps_call2(Stdlib[53],line,_k_) :caml_cps_exact_call1(_k_,0)})} - return caml_cps_exact_call1(_k_,0)})} + return _k_(0)})} //end function loop2(param,cont) {var all=[0,0],_e_=Stdlib[79]; diff --git a/compiler/tests-compiler/effects_exceptions.ml b/compiler/tests-compiler/effects_exceptions.ml index 8b5d8148b2..ad0486ddf1 100644 --- a/compiler/tests-compiler/effects_exceptions.ml +++ b/compiler/tests-compiler/effects_exceptions.ml @@ -47,29 +47,24 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = {var _h_=runtime.caml_int_of_string(s),n=_h_} catch(_l_) {var _a_=caml_wrap_exception(_l_); - if(_a_[1] !== Stdlib[7]) - {var raise$1=caml_pop_trap();return caml_cps_exact_call1(raise$1,_a_)} + if(_a_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_a_)} var n=0,_b_=0} try {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _g_=7,m=_g_} catch(_k_) {var _c_=caml_wrap_exception(_k_); - if(_c_ !== Stdlib[8]) - {var raise$0=caml_pop_trap();return caml_cps_exact_call1(raise$0,_c_)} + if(_c_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_c_)} var m=0,_d_=0} runtime.caml_push_trap (function(_j_) - {if(_j_ === Stdlib[8])return caml_cps_exact_call1(cont,0); + {if(_j_ === Stdlib[8])return cont(0); var raise=caml_pop_trap(); - return caml_cps_exact_call1(raise,_j_)}); + return raise(_j_)}); if(caml_string_equal(s,cst)) - {var _e_=Stdlib[8],raise=caml_pop_trap(); - return caml_cps_exact_call1(raise,_e_)} + {var _e_=Stdlib[8],raise=caml_pop_trap();return raise(_e_)} var _f_=Stdlib[79]; return caml_cps_call2 (_f_, cst_toto, - function(_i_) - {caml_pop_trap(); - return caml_cps_exact_call1(cont,[0,[0,_i_,n,m]])})} + function(_i_){caml_pop_trap();return cont([0,[0,_i_,n,m]])})} //end |}] diff --git a/compiler/tests-compiler/effects_toplevel.ml b/compiler/tests-compiler/effects_toplevel.ml index c3347c6e78..3b2eb39b67 100644 --- a/compiler/tests-compiler/effects_toplevel.ml +++ b/compiler/tests-compiler/effects_toplevel.ml @@ -80,7 +80,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = var Test=[0]; runtime.caml_register_global(2,Test,"Test"); return})} - return caml_cps_exact_call1(_c_,_b_)}, + return _c_(_b_)}, [])} (globalThis)); //end |}] diff --git a/compiler/tests-compiler/lambda_lifting.ml b/compiler/tests-compiler/lambda_lifting.ml index 9a8b4d08b5..6e056e5af4 100644 --- a/compiler/tests-compiler/lambda_lifting.ml +++ b/compiler/tests-compiler/lambda_lifting.ml @@ -23,10 +23,6 @@ Printf.printf "%d\n" (f 3) (function(globalThis) {"use strict"; var runtime=globalThis.jsoo_runtime,caml_callback=runtime.caml_callback; - function caml_cps_exact_call1(f,a0) - {return runtime.caml_stack_check_depth() - ?f(a0) - :runtime.caml_trampoline_return(f,[a0])} function caml_cps_exact_call2(f,a0,a1) {return runtime.caml_stack_check_depth() ?f(a0,a1) @@ -37,9 +33,7 @@ Printf.printf "%d\n" (f 3) _c_=[0,[4,0,0,0,[12,10,0]],runtime.caml_string_of_jsbytes("%d\n")]; function f(x,cont){var g$0=g(x);return caml_cps_exact_call2(g$0,5,cont)} function h(x,y) - {function h(z,cont) - {return caml_cps_exact_call1(cont,(x + y | 0) + z | 0)} - return h} + {function h(z,cont){return cont((x + y | 0) + z | 0)}return h} function g(x) {function g(y,cont) {var h$0=h(x,y);return caml_cps_exact_call2(h$0,7,cont)} diff --git a/runtime/effect.js b/runtime/effect.js index 6e9deb77dd..886c1ed29c 100644 --- a/runtime/effect.js +++ b/runtime/effect.js @@ -120,20 +120,22 @@ function caml_perform_effect(eff, cont, k0) { } //Provides: caml_alloc_stack -//Requires: caml_pop_fiber, caml_fiber_stack, caml_call_gen +//Requires: caml_pop_fiber, caml_fiber_stack, caml_call_gen, caml_stack_check_depth, caml_trampoline_return //If: effects function caml_alloc_stack(hv, hx, hf) { + function call(i, x) { + var f=caml_fiber_stack.h[i]; + var args = [x, caml_pop_fiber()]; + return caml_stack_check_depth()?caml_call_gen(f,args) + :caml_trampoline_return(f,args); + } function hval(x) { // Call [hv] in the parent fiber - var f=caml_fiber_stack.h[1]; - var k=caml_pop_fiber(); - return caml_call_gen(f, [x, k]); + return call(1, x); } function hexn(e) { // Call [hx] in the parent fiber - var f=caml_fiber_stack.h[2]; - var k=caml_pop_fiber(); - return caml_call_gen(f, [e, k]); + return call(2, e); } return [0, hval, [0, hexn, 0], [0, hv, hx, hf], 0]; } From 6f83958eed1e2efd822ae11e8e8a399e1b15b983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 11 Jan 2023 15:21:17 +0100 Subject: [PATCH 08/17] Effects: fix compilation of loops I think the issue only occurs when optimization of tail recursion is enabled --- compiler/lib/effects.ml | 11 +- .../tests-compiler/effects_continuations.ml | 104 +++++++++++------- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 0e05b6b49f..c85ccaface 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -448,7 +448,16 @@ let cps_block ~st ~k pc block = to bind [x] if it is used in the loop body. In other cases, we can just call the continuation. *) alloc_jump_closures, f' - | [ x' ] when Var.equal x x' -> alloc_jump_closures, f' + | [ x' ] + when Var.equal x x' + && + match Hashtbl.find st.is_continuation pc with + | `Param _ -> true + | `Loop -> st.live_vars.(Var.idx x) = 1 -> + (* When entering a loop, we have to allocate a closure + to bind [x] if it is used in the loop body. In + other cases, we can just call the continuation. *) + alloc_jump_closures, f' | _ -> let args, instrs = if List.is_empty args diff --git a/compiler/tests-compiler/effects_continuations.ml b/compiler/tests-compiler/effects_continuations.ml index 1b962e3f54..0b26cb26f9 100644 --- a/compiler/tests-compiler/effects_continuations.ml +++ b/compiler/tests-compiler/effects_continuations.ml @@ -76,6 +76,15 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = all := line :: !all; prerr_endline line done + + let loop3 () = + let l = List.rev [1;2;3] in + let rec f x = + match x with + | [] -> l + | _ :: r -> f r + in + f l |} in print_fun_decl code (Some "exceptions"); @@ -84,84 +93,97 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = print_fun_decl code (Some "cond3"); print_fun_decl code (Some "loop1"); print_fun_decl code (Some "loop2"); + print_fun_decl code (Some "loop3"); [%expect {| function exceptions(s,cont) {try - {var _z_=runtime.caml_int_of_string(s),n=_z_} - catch(_D_) - {var _s_=caml_wrap_exception(_D_); - if(_s_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_s_)} - var n=0,_t_=0} + {var _B_=runtime.caml_int_of_string(s),n=_B_} + catch(_F_) + {var _u_=caml_wrap_exception(_F_); + if(_u_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_u_)} + var n=0,_v_=0} try - {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _y_=7,m=_y_} - catch(_C_) - {var _u_=caml_wrap_exception(_C_); - if(_u_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_u_)} - var m=0,_v_=0} + {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _A_=7,m=_A_} + catch(_E_) + {var _w_=caml_wrap_exception(_E_); + if(_w_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_w_)} + var m=0,_x_=0} runtime.caml_push_trap - (function(_B_) - {if(_B_ === Stdlib[8])return cont(0); + (function(_D_) + {if(_D_ === Stdlib[8])return cont(0); var raise=caml_pop_trap(); - return raise(_B_)}); + return raise(_D_)}); if(caml_string_equal(s,cst)) - {var _w_=Stdlib[8],raise=caml_pop_trap();return raise(_w_)} - var _x_=Stdlib[79]; + {var _y_=Stdlib[8],raise=caml_pop_trap();return raise(_y_)} + var _z_=Stdlib[79]; return caml_cps_call2 - (_x_, + (_z_, cst_toto, - function(_A_){caml_pop_trap();return cont([0,[0,_A_,n,m]])})} + function(_C_){caml_pop_trap();return cont([0,[0,_C_,n,m]])})} //end function cond1(b,cont) - {function _r_(ic){return cont([0,ic,7])} + {function _t_(ic){return cont([0,ic,7])} return b - ?caml_cps_call2(Stdlib[79],cst_toto$0,_r_) - :caml_cps_call2(Stdlib[79],cst_titi,_r_)} + ?caml_cps_call2(Stdlib[79],cst_toto$0,_t_) + :caml_cps_call2(Stdlib[79],cst_titi,_t_)} //end function cond2(b,cont) - {function _p_(_q_){return cont(7)} + {function _r_(_s_){return cont(7)} return b - ?caml_cps_call2(Stdlib_Printf[3],_a_,_p_) - :caml_cps_call2(Stdlib_Printf[3],_b_,_p_)} + ?caml_cps_call2(Stdlib_Printf[3],_a_,_r_) + :caml_cps_call2(Stdlib_Printf[3],_b_,_r_)} //end function cond3(b,cont) {var x=[0,0]; - function _n_(_o_){return cont(x[1])} - return b?(x[1] = 1,_n_(0)):caml_cps_call2(Stdlib_Printf[3],_c_,_n_)} + function _p_(_q_){return cont(x[1])} + return b?(x[1] = 1,_p_(0)):caml_cps_call2(Stdlib_Printf[3],_c_,_p_)} //end function loop1(b,cont) - {var all=[0,0],_j_=Stdlib[79]; + {var all=[0,0],_l_=Stdlib[79]; return caml_cps_call2 - (_j_, + (_l_, cst_static_examples_ml, function(ic) - {function _k_(_m_) - {var _l_=Stdlib[83]; + {function _m_(_o_) + {var _n_=Stdlib[83]; return caml_cps_call2 - (_l_, + (_n_, ic, function(line) {all[1] = [0,line,all[1]]; return b - ?caml_cps_call2(Stdlib[53],line,_k_) - :caml_cps_exact_call1(_k_,0)})} - return _k_(0)})} + ?caml_cps_call2(Stdlib[53],line,_m_) + :caml_cps_exact_call1(_m_,0)})} + return _m_(0)})} //end function loop2(param,cont) - {var all=[0,0],_e_=Stdlib[79]; + {var all=[0,0],_g_=Stdlib[79]; return caml_cps_call2 - (_e_, + (_g_, cst_static_examples_ml$0, function(ic) - {var _f_=Stdlib_Printf[3]; - function _g_(_i_) - {var _h_=Stdlib[83]; + {var _h_=Stdlib_Printf[3]; + function _i_(_k_) + {var _j_=Stdlib[83]; return caml_cps_call2 - (_h_, + (_j_, ic, function(line) {all[1] = [0,line,all[1]]; - return caml_cps_call2(Stdlib[53],line,_g_)})} - return caml_cps_call2(_f_,_d_,_g_)})} + return caml_cps_call2(Stdlib[53],line,_i_)})} + return caml_cps_call2(_h_,_d_,_i_)})} + //end + function loop3(param,cont) + {var _f_=Stdlib_List[9]; + return caml_cps_call2 + (_f_, + _e_, + function(l) + {function f(x,cont) + {if(! x)return cont(l); + var r=x[2]; + return caml_cps_exact_call2(f,r,cont)} + return caml_cps_exact_call2(f,l,cont)})} //end |}] From cd3bb11fad5d1db2ff54d8b872d486c29cf2ab75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 11 Jan 2023 18:45:44 +0100 Subject: [PATCH 09/17] Effects: reenable optimization of tail recursion --- compiler/lib/driver.ml | 7 +- compiler/lib/effects.ml | 105 ++++++++++-------- .../tests-compiler/effects_continuations.ml | 88 +++++++-------- 3 files changed, 103 insertions(+), 97 deletions(-) diff --git a/compiler/lib/driver.ml b/compiler/lib/driver.ml index b91fdf780d..36ccaa0b87 100644 --- a/compiler/lib/driver.ml +++ b/compiler/lib/driver.ml @@ -28,11 +28,8 @@ let should_export = function | `Named _ | `Anonymous -> true let tailcall p = - if Config.Flag.effects () - then p - else ( - if debug () then Format.eprintf "Tail-call optimization...@."; - Tailcall.f p) + if debug () then Format.eprintf "Tail-call optimization...@."; + Tailcall.f p let deadcode' p = if debug () then Format.eprintf "Dead-code...@."; diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index c85ccaface..057cd922b6 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -232,7 +232,7 @@ type st = { mutable new_blocks : Code.block Addr.Map.t * Code.Addr.t ; blocks : Code.block Addr.Map.t ; jc : jump_closures - ; closure_continuation : Addr.t -> Var.t + ; closure_info : (Addr.t, Var.t * Code.cont) Hashtbl.t ; blocks_to_transform : Addr.Set.t ; is_continuation : (Addr.t, [ `Param of Var.t | `Loop ]) Hashtbl.t ; matching_exn_handler : (Addr.t, Addr.t) Hashtbl.t @@ -343,8 +343,11 @@ let cps_last ~st pc (last : last) ~k : instr list * last = let cps_instr ~st (instr : instr) : instr = match instr with - | Let (x, Closure (params, (pc, args))) -> - Let (x, Closure (params @ [ st.closure_continuation pc ], (pc, args))) + | Let (x, Closure (params, (pc, _))) -> + (* Add the continuation parameter, and change the initial block if + needed *) + let k, cont = Hashtbl.find st.closure_info pc in + Let (x, Closure (params @ [ k ], cont)) | Let (x, Prim (Extern "caml_alloc_dummy_function", [ size; arity ])) -> ( match arity with | Pc (Int a) -> @@ -502,34 +505,41 @@ let cps_block ~st ~k pc block = } let cps_transform ~live_vars p = - let closure_continuation = - (* Provide a name for the continuation of a closure (before CPS - transform), which can be referred from all the blocks it contains *) - let tbl = Hashtbl.create 4 in - fun pc -> - try Hashtbl.find tbl pc - with Not_found -> - let k = Var.fresh_n "cont" in - Hashtbl.add tbl pc k; - k - in + let closure_info = Hashtbl.create 16 in let cps_calls = ref Var.Set.empty in - let wrap_toplevel = ref true in let p = - Code.fold_closures + Code.fold_closures_innermost_first p - (fun name_opt _ (start, _) ({ blocks; free_pc; _ } as p) -> - let cfg = build_graph blocks start in + (fun name_opt _ (start, args) ({ blocks; free_pc; _ } as p) -> + (* We speculatively add a block at the beginning of the + function. In case of tail-recursion optimization, the + function implementing the loop body may have to be placed + there. *) + let initial_start = start in + let start', blocks' = + ( free_pc + , Addr.Map.add + free_pc + { params = []; body = []; branch = Branch (start, args) } + blocks ) + in + let cfg = build_graph blocks' start' in let idom = dominator_tree cfg in let blocks_to_transform, matching_exn_handler, is_continuation = - compute_needed_transformations ~cfg ~idom ~blocks ~start + compute_needed_transformations ~cfg ~idom ~blocks:blocks' ~start:start' in let closure_jc = jump_closures blocks_to_transform idom in + let start, args, blocks, free_pc = + (* Insert an initial block if needed. *) + if Addr.Map.mem start' closure_jc.closures_of_alloc_site + then start', [], blocks', free_pc + 1 + else start, args, blocks, free_pc + in let st = { new_blocks = Addr.Map.empty, free_pc ; blocks ; jc = closure_jc - ; closure_continuation + ; closure_info ; blocks_to_transform ; is_continuation ; matching_exn_handler @@ -545,9 +555,7 @@ let cps_transform ~live_vars p = (* We are handling the toplevel code. If it performs no CPS call, we can leave it in direct style and we don't need to wrap it within a [caml_callback]. *) - let need_cps = not (Addr.Set.is_empty blocks_to_transform) in - wrap_toplevel := need_cps; - need_cps + not (Addr.Set.is_empty blocks_to_transform) in if debug () then ( @@ -564,9 +572,10 @@ let cps_transform ~live_vars p = let blocks = let transform_block = if function_needs_cps - then - let k = closure_continuation start in - fun pc block -> cps_block ~st ~k pc block + then ( + let k = Var.fresh_n "cont" in + Hashtbl.add closure_info initial_start (k, (start, args)); + fun pc block -> cps_block ~st ~k pc block) else fun _ block -> { block with body = List.map block.body ~f:(fun i -> cps_instr ~st i) } @@ -585,28 +594,28 @@ let cps_transform ~live_vars p = p in let p = - if not !wrap_toplevel - then p - else - (* Call [caml_callback] to set up the execution context. *) - let new_start = p.free_pc in - let blocks = - let main = Var.fresh () in - let args = Var.fresh () in - let res = Var.fresh () in - Addr.Map.add - new_start - { params = [] - ; body = - [ Let (main, Closure ([ closure_continuation p.start ], (p.start, []))) - ; Let (args, Prim (Extern "%js_array", [])) - ; Let (res, Prim (Extern "caml_callback", [ Pv main; Pv args ])) - ] - ; branch = Return res - } - p.blocks - in - { start = new_start; blocks; free_pc = new_start + 1 } + match Hashtbl.find_opt closure_info p.start with + | None -> p + | Some (k, _) -> + (* Call [caml_callback] to set up the execution context. *) + let new_start = p.free_pc in + let blocks = + let main = Var.fresh () in + let args = Var.fresh () in + let res = Var.fresh () in + Addr.Map.add + new_start + { params = [] + ; body = + [ Let (main, Closure ([ k ], (p.start, []))) + ; Let (args, Prim (Extern "%js_array", [])) + ; Let (res, Prim (Extern "caml_callback", [ Pv main; Pv args ])) + ] + ; branch = Return res + } + p.blocks + in + { start = new_start; blocks; free_pc = new_start + 1 } in p, !cps_calls diff --git a/compiler/tests-compiler/effects_continuations.ml b/compiler/tests-compiler/effects_continuations.ml index 0b26cb26f9..27911f421b 100644 --- a/compiler/tests-compiler/effects_continuations.ml +++ b/compiler/tests-compiler/effects_continuations.ml @@ -99,81 +99,81 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = function exceptions(s,cont) {try - {var _B_=runtime.caml_int_of_string(s),n=_B_} - catch(_F_) - {var _u_=caml_wrap_exception(_F_); - if(_u_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_u_)} - var n=0,_v_=0} + {var _C_=runtime.caml_int_of_string(s),n=_C_} + catch(_G_) + {var _v_=caml_wrap_exception(_G_); + if(_v_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_v_)} + var n=0,_w_=0} try - {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _A_=7,m=_A_} - catch(_E_) - {var _w_=caml_wrap_exception(_E_); - if(_w_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_w_)} - var m=0,_x_=0} + {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _B_=7,m=_B_} + catch(_F_) + {var _x_=caml_wrap_exception(_F_); + if(_x_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_x_)} + var m=0,_y_=0} runtime.caml_push_trap - (function(_D_) - {if(_D_ === Stdlib[8])return cont(0); + (function(_E_) + {if(_E_ === Stdlib[8])return cont(0); var raise=caml_pop_trap(); - return raise(_D_)}); + return raise(_E_)}); if(caml_string_equal(s,cst)) - {var _y_=Stdlib[8],raise=caml_pop_trap();return raise(_y_)} - var _z_=Stdlib[79]; + {var _z_=Stdlib[8],raise=caml_pop_trap();return raise(_z_)} + var _A_=Stdlib[79]; return caml_cps_call2 - (_z_, + (_A_, cst_toto, - function(_C_){caml_pop_trap();return cont([0,[0,_C_,n,m]])})} + function(_D_){caml_pop_trap();return cont([0,[0,_D_,n,m]])})} //end function cond1(b,cont) - {function _t_(ic){return cont([0,ic,7])} + {function _u_(ic){return cont([0,ic,7])} return b - ?caml_cps_call2(Stdlib[79],cst_toto$0,_t_) - :caml_cps_call2(Stdlib[79],cst_titi,_t_)} + ?caml_cps_call2(Stdlib[79],cst_toto$0,_u_) + :caml_cps_call2(Stdlib[79],cst_titi,_u_)} //end function cond2(b,cont) - {function _r_(_s_){return cont(7)} + {function _s_(_t_){return cont(7)} return b - ?caml_cps_call2(Stdlib_Printf[3],_a_,_r_) - :caml_cps_call2(Stdlib_Printf[3],_b_,_r_)} + ?caml_cps_call2(Stdlib_Printf[3],_a_,_s_) + :caml_cps_call2(Stdlib_Printf[3],_b_,_s_)} //end function cond3(b,cont) {var x=[0,0]; - function _p_(_q_){return cont(x[1])} - return b?(x[1] = 1,_p_(0)):caml_cps_call2(Stdlib_Printf[3],_c_,_p_)} + function _q_(_r_){return cont(x[1])} + return b?(x[1] = 1,_q_(0)):caml_cps_call2(Stdlib_Printf[3],_c_,_q_)} //end function loop1(b,cont) - {var all=[0,0],_l_=Stdlib[79]; + {var all=[0,0],_m_=Stdlib[79]; return caml_cps_call2 - (_l_, + (_m_, cst_static_examples_ml, function(ic) - {function _m_(_o_) - {var _n_=Stdlib[83]; + {function _n_(_p_) + {var _o_=Stdlib[83]; return caml_cps_call2 - (_n_, + (_o_, ic, function(line) {all[1] = [0,line,all[1]]; return b - ?caml_cps_call2(Stdlib[53],line,_m_) - :caml_cps_exact_call1(_m_,0)})} - return _m_(0)})} + ?caml_cps_call2(Stdlib[53],line,_n_) + :caml_cps_exact_call1(_n_,0)})} + return _n_(0)})} //end function loop2(param,cont) - {var all=[0,0],_g_=Stdlib[79]; + {var all=[0,0],_h_=Stdlib[79]; return caml_cps_call2 - (_g_, + (_h_, cst_static_examples_ml$0, function(ic) - {var _h_=Stdlib_Printf[3]; - function _i_(_k_) - {var _j_=Stdlib[83]; + {var _i_=Stdlib_Printf[3]; + function _j_(_l_) + {var _k_=Stdlib[83]; return caml_cps_call2 - (_j_, + (_k_, ic, function(line) {all[1] = [0,line,all[1]]; - return caml_cps_call2(Stdlib[53],line,_i_)})} - return caml_cps_call2(_h_,_d_,_i_)})} + return caml_cps_call2(Stdlib[53],line,_j_)})} + return caml_cps_call2(_i_,_d_,_j_)})} //end function loop3(param,cont) {var _f_=Stdlib_List[9]; @@ -181,9 +181,9 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = (_f_, _e_, function(l) - {function f(x,cont) + {function _g_(x) {if(! x)return cont(l); var r=x[2]; - return caml_cps_exact_call2(f,r,cont)} - return caml_cps_exact_call2(f,l,cont)})} + return caml_cps_exact_call1(_g_,r)} + return _g_(l)})} //end |}] From 287d1c92ed6290f8aa864b50416bbf0117a9cae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 11 Jan 2023 18:56:09 +0100 Subject: [PATCH 10/17] Effects: small clean-up + improve comment --- compiler/lib/effects.ml | 66 ++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 057cd922b6..20bb5fac00 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -249,7 +249,7 @@ let add_block st block = let closure_of_pc ~st pc = try Addr.Map.find pc st.jc.closure_of_jump with Not_found -> assert false -let allocate_closure ~st ~params ~body:(body, branch) = +let allocate_closure ~st ~params ~body ~branch = let block = { params = []; body; branch } in let pc = add_block st block in let name = Var.fresh () in @@ -436,45 +436,31 @@ let cps_block ~st ~k pc block = | Some (body_prefix, Let (x, e)), Branch cont -> let allocate_continuation f = let constr_cont, k' = - (* Construct continuation: it binds the return value [x], - allocates closures for dominated blocks and jumps to the - next block. *) - let pc, args = cont in - let f' = closure_of_pc ~st pc in - assert (Hashtbl.mem st.is_continuation pc); - match args with - | [] - when match Hashtbl.find st.is_continuation pc with - | `Param _ -> true - | `Loop -> st.live_vars.(Var.idx x) = 0 -> - (* When entering a loop, we have to allocate a closure - to bind [x] if it is used in the loop body. In - other cases, we can just call the continuation. *) - alloc_jump_closures, f' - | [ x' ] - when Var.equal x x' - && - match Hashtbl.find st.is_continuation pc with - | `Param _ -> true - | `Loop -> st.live_vars.(Var.idx x) = 1 -> - (* When entering a loop, we have to allocate a closure - to bind [x] if it is used in the loop body. In - other cases, we can just call the continuation. *) - alloc_jump_closures, f' - | _ -> - let args, instrs = - if List.is_empty args - then - (* We use a dummy argument since the continuation - expects at least one argument. *) - let x = Var.fresh () in - [ x ], alloc_jump_closures @ [ Let (x, Constant (Int 0l)) ] - else args, alloc_jump_closures - in - allocate_closure - ~st - ~params:[ x ] - ~body:(tail_call ~st ~instrs ~exact:true ~check:false ~f:f' args) + (* We need to allocate an additional closure if [cont] + does not correspond to a continuation that binds [x]. + This closure binds the return value [x], allocates + closures for dominated blocks and jumps to the next + block. When entering a loop, we also have to allocate a + closure to bind [x] if it is used in the loop body. In + other cases, we can just pass the closure corresponding + to the next block. *) + let pc', args = cont in + if (match args with + | [] -> true + | [ x' ] -> Var.equal x x' + | _ -> false) + && + match Hashtbl.find st.is_continuation pc' with + | `Param _ -> true + | `Loop -> st.live_vars.(Var.idx x) = List.length args + then alloc_jump_closures, closure_of_pc ~st pc' + else + let body, branch = cps_branch ~st ~src:pc cont in + allocate_closure + ~st + ~params:[ x ] + ~body:(alloc_jump_closures @ body) + ~branch in let instrs, branch = f ~k:k' in body_prefix, constr_cont @ instrs, branch From d4e84ce6772dbdb2e0420669b5552c23df916622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 13 Jan 2023 19:13:05 +0100 Subject: [PATCH 11/17] Effects: test improved exact call detection --- compiler/tests-compiler/dune.inc | 16 +++ compiler/tests-compiler/effects_call_opt.ml | 127 ++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 compiler/tests-compiler/effects_call_opt.ml diff --git a/compiler/tests-compiler/dune.inc b/compiler/tests-compiler/dune.inc index 6738b67808..f2046e5a50 100644 --- a/compiler/tests-compiler/dune.inc +++ b/compiler/tests-compiler/dune.inc @@ -79,6 +79,22 @@ (preprocess (pps ppx_expect))) +(library + ;; compiler/tests-compiler/effects_call_opt.ml + (name effects_call_opt_15) + (enabled_if true) + (modules effects_call_opt) + (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) + (inline_tests + (enabled_if true) + (flags -allow-output-patterns) + (deps + (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) + (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) + (flags (:standard -open Jsoo_compiler_expect_tests_helper)) + (preprocess + (pps ppx_expect))) + (library ;; compiler/tests-compiler/effects_continuations.ml (name effects_continuations_15) diff --git a/compiler/tests-compiler/effects_call_opt.ml b/compiler/tests-compiler/effects_call_opt.ml new file mode 100644 index 0000000000..acbe6084de --- /dev/null +++ b/compiler/tests-compiler/effects_call_opt.ml @@ -0,0 +1,127 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, with linking exception; + * either version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *) + +open Util + +let%expect_test "test-compiler/lib-effects/effects_call_opt.ml" = + let code = + compile_and_parse + ~effects:true + {| + (* Arity of the argument of a function / direct call *) + let test1 () = + let f g x = g x in + ignore (f (fun x -> x + 1) 7); + ignore (f (fun x -> x *. 2.) 4.) + + (* Arity of the argument of a function / CPS call *) + let test2 () = + let f g x = g x in + ignore (f (fun x -> x + 1) 7); + ignore (f (fun x -> x ^ "a") "a") + + (* Arity of functions in a functor / direct call *) + let test3 x = + let module F(_ : sig end) = struct let f x = x + 1 end in + let module M1 = F (struct end) in + let module M2 = F (struct end) in + (M1.f 1, M2.f 2) + + (* Arity of functions in a functor / CPS call *) + let test4 x = + let module F(_ : sig end) = + struct let f x = Printf.printf "%d" x end in + let module M1 = F (struct end) in + let module M2 = F (struct end) in + M1.f 1; M2.f 2 +|} + in + print_fun_decl code (Some "test1"); + print_fun_decl code (Some "test2"); + print_fun_decl code (Some "test3"); + print_fun_decl code (Some "test4"); + [%expect + {| + function test1(param,cont) + {function f(g,x,cont){return caml_cps_call2(g,x,cont)} + var _t_=7; + function _u_(x,cont){return cont(x + 1 | 0)} + return caml_cps_exact_call3 + (f, + _u_, + _t_, + function(_v_) + {var _w_=4.; + function _x_(x,cont){return cont(x * 2.)} + return caml_cps_exact_call3 + (f,_x_,_w_,function(_y_){return cont(0)})})} + //end + function test2(param,cont) + {function f(g,x,cont){return caml_cps_call2(g,x,cont)} + var _o_=7; + function _p_(x,cont){return cont(x + 1 | 0)} + return caml_cps_exact_call3 + (f, + _p_, + _o_, + function(_q_) + {function _r_(x,cont) + {return caml_cps_call3(Stdlib[28],x,cst_a$0,cont)} + return caml_cps_exact_call3 + (f,_r_,cst_a,function(_s_){return cont(0)})})} + //end + function test3(x,cont) + {function F(symbol,cont) + {function f(x,cont){return cont(x + 1 | 0)}return cont([0,f])} + var _g_=[0]; + return caml_cps_exact_call2 + (F, + _g_, + function(M1) + {var _h_=[0]; + return caml_cps_exact_call2 + (F, + _h_, + function(M2) + {var _i_=2,_j_=M2[1]; + return caml_cps_call2 + (_j_, + _i_, + function(_k_) + {var _l_=1,_m_=M1[1]; + return caml_cps_call2 + (_m_,_l_,function(_n_){return cont([0,_n_,_k_])})})})})} + //end + function test4(x,cont) + {function F(symbol,cont) + {function f(x,cont){return caml_cps_call3(Stdlib_Printf[2],_a_,x,cont)} + return cont([0,f])} + var _b_=[0]; + return caml_cps_exact_call2 + (F, + _b_, + function(M1) + {var _c_=[0]; + return caml_cps_exact_call2 + (F, + _c_, + function(M2) + {var _d_=1,_e_=M1[1]; + return caml_cps_call2 + (_e_,_d_,function(_f_){return caml_cps_call2(M2[1],2,cont)})})})} + //end |}] From 678c56eda4e8f2b6344ab21e21003d84aebf22fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 12 Jan 2023 15:28:53 +0100 Subject: [PATCH 12/17] Effects: partial CPS transform We analyse the call graph to avoid turning functions into CPS when we know that they don't involve effects. This relies on a global control flow analysis to find which function might be called where. --- compiler/lib/effects.ml | 106 ++-- compiler/lib/global_flow.ml | 623 ++++++++++++++++++++ compiler/lib/global_flow.mli | 42 ++ compiler/lib/partial_cps_analysis.ml | 190 ++++++ compiler/lib/partial_cps_analysis.mli | 21 + compiler/tests-compiler/effects_call_opt.ml | 75 +-- compiler/tests-compiler/lambda_lifting.ml | 20 +- 7 files changed, 972 insertions(+), 105 deletions(-) create mode 100644 compiler/lib/global_flow.ml create mode 100644 compiler/lib/global_flow.mli create mode 100644 compiler/lib/partial_cps_analysis.ml create mode 100644 compiler/lib/partial_cps_analysis.mli diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 20bb5fac00..88c46301f0 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -128,7 +128,7 @@ also mark blocks that correspond to function continuations or exception handlers. And we keep track of the exception handler associated to each Poptrap, and possibly Raise. *) -let compute_needed_transformations ~cfg ~idom ~blocks ~start = +let compute_needed_transformations ~cfg ~idom ~cps_needed ~blocks ~start = let frontiers = dominance_frontier cfg idom in let transformation_needed = ref Addr.Set.empty in let matching_exn_handler = Hashtbl.create 16 in @@ -161,9 +161,10 @@ let compute_needed_transformations ~cfg ~idom ~blocks ~start = | Some (Let (x, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _)))) - -> - (* The block after a function application or an effect - primitive needs to be transformed. *) + when Var.Set.mem x cps_needed -> + (* The block after a function application that needs to + be turned to CPS or an effect primitive needs to be + transformed. *) mark_needed dst; (* We need to transform the englobing exception handlers as well *) @@ -233,6 +234,7 @@ type st = ; blocks : Code.block Addr.Map.t ; jc : jump_closures ; closure_info : (Addr.t, Var.t * Code.cont) Hashtbl.t + ; cps_needed : Var.Set.t ; blocks_to_transform : Addr.Set.t ; is_continuation : (Addr.t, [ `Param of Var.t | `Loop ]) Hashtbl.t ; matching_exn_handler : (Addr.t, Addr.t) Hashtbl.t @@ -343,7 +345,7 @@ let cps_last ~st pc (last : last) ~k : instr list * last = let cps_instr ~st (instr : instr) : instr = match instr with - | Let (x, Closure (params, (pc, _))) -> + | Let (x, Closure (params, (pc, _))) when Var.Set.mem x st.cps_needed -> (* Add the continuation parameter, and change the initial block if needed *) let k, cont = Hashtbl.find st.closure_info pc in @@ -356,6 +358,10 @@ let cps_instr ~st (instr : instr) : instr = , Prim (Extern "caml_alloc_dummy_function", [ size; Pc (Int (Int32.succ a)) ]) ) | _ -> assert false) + | Let (x, Apply { f; args; _ }) when not (Var.Set.mem x st.cps_needed) -> + (* At the moment, we turn into CPS any function not called with + the right number of parameter *) + Let (x, Apply { f; args; exact = true }) | Let (_, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> assert false | _ -> instr @@ -394,7 +400,7 @@ let cps_block ~st ~k pc block = | exception Not_found -> [] in - let rewrite_instr e = + let rewrite_instr x e = let perform_effect ~effect ~continuation = Some (fun ~k -> @@ -405,7 +411,7 @@ let cps_block ~st ~k pc block = [ Let (x, e) ], Return x) in match e with - | Apply { f; args; exact } -> + | Apply { f; args; exact } when Var.Set.mem x st.cps_needed -> Some (fun ~k -> tail_call ~st ~exact ~check:true ~f (args @ [ k ])) | Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ]) -> Some @@ -428,7 +434,7 @@ let cps_block ~st ~k pc block = let rewritten_block = match List.split_last block.body, block.branch with | Some (body_prefix, Let (x, e)), Return ret -> - Option.map (rewrite_instr e) ~f:(fun f -> + Option.map (rewrite_instr x e) ~f:(fun f -> assert (List.is_empty alloc_jump_closures); assert (Var.equal x ret); let instrs, branch = f ~k in @@ -465,7 +471,7 @@ let cps_block ~st ~k pc block = let instrs, branch = f ~k:k' in body_prefix, constr_cont @ instrs, branch in - Option.map (rewrite_instr e) ~f:allocate_continuation + Option.map (rewrite_instr x e) ~f:allocate_continuation | Some (_, (Set_field _ | Offset_ref _ | Array_set _ | Assign _)), _ | Some _, (Raise _ | Stop | Cond _ | Switch _ | Pushtrap _ | Poptrap _) | None, _ -> None @@ -490,7 +496,7 @@ let cps_block ~st ~k pc block = ; branch = last } -let cps_transform ~live_vars p = +let cps_transform ~live_vars ~cps_needed p = let closure_info = Hashtbl.create 16 in let cps_calls = ref Var.Set.empty in let p = @@ -511,8 +517,24 @@ let cps_transform ~live_vars p = in let cfg = build_graph blocks' start' in let idom = dominator_tree cfg in + let should_compute_needed_transformations = + match name_opt with + | Some name -> Var.Set.mem name cps_needed + | None -> + (* We are handling the toplevel code. There may remain + some CPS calls at toplevel. *) + true + in let blocks_to_transform, matching_exn_handler, is_continuation = - compute_needed_transformations ~cfg ~idom ~blocks:blocks' ~start:start' + if should_compute_needed_transformations + then + compute_needed_transformations + ~cfg + ~idom + ~cps_needed + ~blocks:blocks' + ~start:start' + else Addr.Set.empty, Hashtbl.create 1, Hashtbl.create 1 in let closure_jc = jump_closures blocks_to_transform idom in let start, args, blocks, free_pc = @@ -526,6 +548,7 @@ let cps_transform ~live_vars p = ; blocks ; jc = closure_jc ; closure_info + ; cps_needed ; blocks_to_transform ; is_continuation ; matching_exn_handler @@ -536,7 +559,7 @@ let cps_transform ~live_vars p = in let function_needs_cps = match name_opt with - | Some _ -> true + | Some _ -> should_compute_needed_transformations | None -> (* We are handling the toplevel code. If it performs no CPS call, we can leave it in direct style and we @@ -551,7 +574,10 @@ let cps_transform ~live_vars p = (fun pc _ -> if Addr.Set.mem pc blocks_to_transform then Format.eprintf "CPS@."; let block = Addr.Map.find pc blocks in - Code.Print.block (fun _ _ -> "") pc block) + Code.Print.block + (fun _ xi -> Partial_cps_analysis.annot cps_needed xi) + pc + block) start blocks ()); @@ -615,15 +641,16 @@ let current_loop_header frontiers in_loop pc = | Some header when Addr.Set.mem header frontier -> in_loop | _ -> if Addr.Set.mem pc frontier then Some pc else None -let wrap_call p x f args accu = +let wrap_call ~cps_needed p x f args accu = let arg_array = Var.fresh () in ( p + , Var.Set.remove x cps_needed , [ Let (arg_array, Prim (Extern "%js_array", List.map ~f:(fun y -> Pv y) args)) ; Let (x, Prim (Extern "caml_callback", [ Pv f; Pv arg_array ])) ] :: accu ) -let wrap_primitive (p : Code.program) x e accu = +let wrap_primitive ~cps_needed p x e accu = let f = Var.fresh () in let closure_pc = p.free_pc in ( { p with @@ -635,6 +662,7 @@ let wrap_primitive (p : Code.program) x e accu = { params = []; body = [ Let (y, e) ]; branch = Return y }) p.blocks } + , Var.Set.remove x (Var.Set.add f cps_needed) , let args = Var.fresh () in [ Let (f, Closure ([], (closure_pc, []))) ; Let (args, Prim (Extern "%js_array", [])) @@ -642,62 +670,64 @@ let wrap_primitive (p : Code.program) x e accu = ] :: accu ) -let rewrite_toplevel_instr (p, accu) instr = +let rewrite_toplevel_instr (p, cps_needed, accu) instr = match instr with - | Let (x, Apply { f; args; _ }) -> wrap_call p x f args accu + | Let (x, Apply { f; args; _ }) when Var.Set.mem x cps_needed -> + wrap_call ~cps_needed p x f args accu | Let (x, (Prim (Extern ("%resume" | "%perform" | "%reperform"), _) as e)) -> - wrap_primitive p x e accu - | _ -> p, [ instr ] :: accu + wrap_primitive ~cps_needed p x e accu + | _ -> p, cps_needed, [ instr ] :: accu (* Wrap function calls inside [caml_callback] at toplevel to avoid unncessary function nestings. This is not done inside loops since using repeatedly [caml_callback] can be costly. *) -let rewrite_toplevel p = +let rewrite_toplevel ~cps_needed p = let { start; blocks; _ } = p in let cfg = build_graph blocks start in let idom = dominator_tree cfg in let frontiers = dominance_frontier cfg idom in - let rec traverse visited (p : Code.program) in_loop pc = + let rec traverse visited (p : Code.program) cps_needed in_loop pc = if Addr.Set.mem pc visited - then visited, p + then visited, p, cps_needed else let visited = Addr.Set.add pc visited in let in_loop = current_loop_header frontiers in_loop pc in - let p = + let p, cps_needed = if Option.is_none in_loop then let block = Addr.Map.find pc p.blocks in - let p, body_rev = - List.fold_left ~f:rewrite_toplevel_instr ~init:(p, []) block.body + let p, cps_needed, body_rev = + List.fold_left ~f:rewrite_toplevel_instr ~init:(p, cps_needed, []) block.body in let body = List.concat @@ List.rev body_rev in - { p with blocks = Addr.Map.add pc { block with body } p.blocks } - else p + { p with blocks = Addr.Map.add pc { block with body } p.blocks }, cps_needed + else p, cps_needed in Code.fold_children blocks pc - (fun pc (visited, p) -> traverse visited p in_loop pc) - (visited, p) + (fun pc (visited, p, cps_needed) -> traverse visited p cps_needed in_loop pc) + (visited, p, cps_needed) in - let _, p = traverse Addr.Set.empty p None start in - p + let _, p, cps_needed = traverse Addr.Set.empty p cps_needed None start in + p, cps_needed (****) -let split_blocks (p : Code.program) = +let split_blocks ~cps_needed (p : Code.program) = (* Ensure that function applications and effect primitives are in tail position *) let split_block pc block p = let is_split_point i r branch = match i with - | Let (x, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> ( - (not (List.is_empty r)) + | Let (x, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> + ((not (List.is_empty r)) || match branch with | Branch _ -> false | Return x' -> not (Var.equal x x') | _ -> true) + && Var.Set.mem x cps_needed | _ -> false in let rec split (p : Code.program) pc block accu l branch = @@ -784,8 +814,10 @@ let remove_empty_blocks ~live_vars (p : Code.program) : Code.program = let f (p, live_vars) = let t = Timer.make () in let p = remove_empty_blocks ~live_vars p in - let p = split_blocks p in - let p = rewrite_toplevel p in - let p, cps_calls = cps_transform ~live_vars p in + let flow_info = Global_flow.f p in + let cps_needed = Partial_cps_analysis.f p flow_info in + let p, cps_needed = rewrite_toplevel ~cps_needed p in + let p = split_blocks ~cps_needed p in + let p, cps_calls = cps_transform ~live_vars ~cps_needed p in if Debug.find "times" () then Format.eprintf " effects: %a@." Timer.print t; p, cps_calls diff --git a/compiler/lib/global_flow.ml b/compiler/lib/global_flow.ml new file mode 100644 index 0000000000..b744c0ce2f --- /dev/null +++ b/compiler/lib/global_flow.ml @@ -0,0 +1,623 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, with linking exception; + * either version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *) + +(* +The goal of the analysis is to get a good idea of which function might +be called where, and of which functions might be called from some +unknown location (which function 'escapes'). We also keep track of +blocks, to track functions across modules. +*) + +open! Stdlib + +let debug = Debug.find "global-flow" + +let times = Debug.find "times" + +open Code + +(****) + +(* Compute the list of variables containing the return values of each + function *) +let return_values p = + Code.fold_closures + p + (fun name_opt _ (pc, _) rets -> + match name_opt with + | None -> rets + | Some name -> + let s = + Code.traverse + { fold = fold_children } + (fun pc s -> + let block = Addr.Map.find pc p.blocks in + match block.branch with + | Return x -> Var.Set.add x s + | _ -> s) + pc + p.blocks + Var.Set.empty + in + Var.Map.add name s rets) + Var.Map.empty + +(****) + +(* A variable is either let-bound, or a parameter, to which we + associate a set of possible arguments. +*) +type def = + | Expr of Code.expr + | Phi of + { known : Var.Set.t (* Known arguments *) + ; others : bool (* Can there be other arguments *) + } + +let undefined = Phi { known = Var.Set.empty; others = false } + +let is_undefined d = + match d with + | Expr _ -> false + | Phi { known; others } -> Var.Set.is_empty known && not others + +type escape_status = + | Escape + | Escape_constant (* Escapes but we know the value is not modified *) + | No + +type state = + { vars : Var.ISet.t (* Set of all veriables considered *) + ; deps : Var.Set.t array (* Dependency between variables *) + ; defs : def array (* Definition of each variable *) + ; variable_may_escape : escape_status array + (* Any value bound to this variable may escape *) + ; variable_possibly_mutable : bool array + (* Any value bound to this variable may be mutable *) + ; may_escape : escape_status array (* This value may escape *) + ; possibly_mutable : bool array (* This value may be mutable *) + ; return_values : Var.Set.t Var.Map.t + (* Set of variables holding return values of each function *) + ; known_cases : (Var.t, int list) Hashtbl.t + (* Possible tags for a block after a [switch]. This is used to + get a more precise approximation of the effect of a field + access [Field] *) + ; applied_functions : (Var.t * Var.t, unit) Hashtbl.t + (* Functions that have been already considered at a call site. + This is to avoid repeated computations *) + } + +let add_var st x = Var.ISet.add st.vars x + +(* x depends on y *) +let add_dep st x y = + let idx = Var.idx y in + st.deps.(idx) <- Var.Set.add x st.deps.(idx) + +let add_expr_def st x e = + add_var st x; + let idx = Var.idx x in + assert (is_undefined st.defs.(idx)); + st.defs.(idx) <- Expr e + +let add_assign_def st x y = + add_var st x; + add_dep st x y; + let idx = Var.idx x in + match st.defs.(idx) with + | Expr _ -> assert false + | Phi { known; others } -> st.defs.(idx) <- Phi { known = Var.Set.add y known; others } + +let add_param_def st x = + add_var st x; + let idx = Var.idx x in + assert (is_undefined st.defs.(idx)) + +let rec arg_deps st ?ignore params args = + match params, args with + | x :: params, y :: args -> + (* This is to deal with the [else] clause of a conditional, + where we know that the value of the tested variable is 0. *) + (match ignore with + | Some y' when Var.equal y y' -> () + | _ -> add_assign_def st x y); + arg_deps st params args + | _ -> () + +let cont_deps blocks st ?ignore (pc, args) = + let block = Addr.Map.find pc blocks in + arg_deps st ?ignore block.params args + +let do_escape st level x = st.variable_may_escape.(Var.idx x) <- level + +let possibly_mutable st x = st.variable_possibly_mutable.(Var.idx x) <- true + +let expr_deps blocks st x e = + match e with + | Constant _ | Prim ((Vectlength | Not | IsInt | Eq | Neq | Lt | Le | Ult), _) | Block _ + -> () + | Prim ((Extern ("caml_check_bound" | "caml_array_unsafe_get") | Array_get), l) -> + (* The analysis knowns about these primitives, and will compute + an approximation of the value they return based on an + approximation of their arguments *) + List.iter + ~f:(fun a -> + match a with + | Pc _ -> () + | Pv y -> add_dep st x y) + l + | Prim (Extern name, l) -> + (* Set the escape status of the arguments *) + let ka = + match Primitive.kind_args name with + | Some l -> l + | None -> ( + match Primitive.kind name with + | `Mutable | `Mutator -> [] + | `Pure -> List.map l ~f:(fun _ -> `Const)) + in + let rec loop args ka = + match args, ka with + | [], _ -> () + | Pc _ :: ax, [] -> loop ax [] + | Pv a :: ax, [] -> + do_escape st Escape a; + loop ax [] + | a :: ax, k :: kx -> + (match a, k with + | Pc _, _ -> () + | Pv v, `Const -> do_escape st Escape_constant v + | Pv v, `Shallow_const -> ( + match st.defs.(Var.idx v) with + | Expr (Block (_, a, _)) -> + Array.iter a ~f:(fun x -> do_escape st Escape x) + | _ -> do_escape st Escape v) + | Pv v, `Object_literal -> ( + match st.defs.(Var.idx v) with + | Expr (Block (_, a, _)) -> + Array.iter a ~f:(fun x -> + match st.defs.(Var.idx x) with + | Expr (Block (_, [| _k; v |], _)) -> do_escape st Escape v + | _ -> do_escape st Escape x) + | _ -> do_escape st Escape v) + | Pv v, `Mutable -> do_escape st Escape v); + loop ax kx + in + loop l ka + | Apply { f; args; _ } -> ( + add_dep st x f; + (* If [f] is obviously a function, we can add appropriate + dependencies right now. This speeds up the analysis + significantly. *) + match st.defs.(Var.idx f) with + | Expr (Closure (params, _)) when List.length args = List.length params -> + Hashtbl.add st.applied_functions (x, f) (); + List.iter2 ~f:(fun p a -> add_assign_def st p a) params args; + Var.Set.iter (fun y -> add_dep st x y) (Var.Map.find f st.return_values) + | _ -> ()) + | Closure (l, cont) -> + List.iter l ~f:(fun x -> add_param_def st x); + cont_deps blocks st cont + | Field (y, _) -> add_dep st x y + +let program_deps st { blocks; _ } = + Addr.Map.iter + (fun _ block -> + List.iter block.body ~f:(fun i -> + match i with + | Let (x, e) -> + add_expr_def st x e; + expr_deps blocks st x e + | Assign (x, y) -> add_assign_def st x y + | Set_field (x, _, y) | Array_set (x, _, y) -> + possibly_mutable st x; + do_escape st Escape y + | Offset_ref _ -> ()); + match block.branch with + | Return _ | Stop -> () + | Raise (x, _) -> do_escape st Escape x + | Branch cont | Poptrap cont -> cont_deps blocks st cont + | Cond (x, cont1, cont2) -> + cont_deps blocks st cont1; + cont_deps blocks st ~ignore:x cont2 + | Switch (x, a1, a2) -> + Array.iter a1 ~f:(fun cont -> cont_deps blocks st cont); + Array.iter a2 ~f:(fun cont -> cont_deps blocks st cont); + let h = Hashtbl.create 16 in + Array.iteri + ~f:(fun i (pc, _) -> + Hashtbl.replace h pc (i :: (try Hashtbl.find h pc with Not_found -> []))) + a2; + Hashtbl.iter + (fun pc tags -> + let block = Addr.Map.find pc blocks in + List.iter + ~f:(fun i -> + match i with + | Let (y, Field (x', _)) when Var.equal x x' -> + Hashtbl.add st.known_cases y tags + | _ -> ()) + block.body) + h + | Pushtrap (cont, x, cont_h, _) -> + add_var st x; + st.defs.(Var.idx x) <- Phi { known = Var.Set.empty; others = true }; + cont_deps blocks st cont_h; + cont_deps blocks st cont) + blocks + +(* For each variable, we keep track of which values, function or + block, it may contain. Other kinds of values are not relevant and + just ignored. We loose a lot of information when going to [Top] + since we have to assume that all functions might escape. So, having + possibly unknown values does not move us to [Top]; we use a flag + for that instead. *) +type approx = + | Top + | Values of + { known : Var.Set.t (* List of possible values (functions and blocks) *) + ; others : bool (* Whether other functions or blocks are possible *) + } + +module Domain = struct + type t = approx + + let bot = Values { known = Var.Set.empty; others = false } + + let others = Values { known = Var.Set.empty; others = true } + + let singleton x = Values { known = Var.Set.singleton x; others = false } + + let equal x y = + match x, y with + | Top, Top -> true + | Values { known; others }, Values { known = known'; others = others' } -> + Var.Set.equal known known' && Bool.equal others others' + | Top, Values _ | Values _, Top -> false + + let higher_escape_status s s' = + match s, s' with + | Escape, Escape -> false + | Escape, (Escape_constant | No) -> true + | Escape_constant, (Escape | Escape_constant) -> false + | Escape_constant, No -> true + | No, (Escape | Escape_constant | No) -> false + + let rec value_escape ~update ~st ~approx s x = + let idx = Var.idx x in + if higher_escape_status s st.may_escape.(idx) + then ( + st.may_escape.(idx) <- s; + match st.defs.(idx) with + | Expr (Block (_, a, _)) -> + Array.iter ~f:(fun y -> variable_escape ~update ~st ~approx s y) a; + if Poly.equal s Escape + then ( + st.possibly_mutable.(idx) <- true; + update ~children:true x) + | Expr (Closure (params, _)) -> + List.iter + ~f:(fun y -> + (match st.defs.(Var.idx y) with + | Phi { known; _ } -> st.defs.(Var.idx y) <- Phi { known; others = true } + | Expr _ -> assert false); + update ~children:false y) + params; + Var.Set.iter + (fun y -> variable_escape ~update ~st ~approx s y) + (Var.Map.find x st.return_values) + | _ -> ()) + + and variable_escape ~update ~st ~approx s x = + if higher_escape_status s st.variable_may_escape.(Var.idx x) + then ( + st.variable_may_escape.(Var.idx x) <- s; + approx_escape ~update ~st ~approx s (Var.Tbl.get approx x)) + + and approx_escape ~update ~st ~approx s a = + match a with + | Top -> () + | Values { known; _ } -> + Var.Set.iter (fun x -> value_escape ~update ~st ~approx s x) known + + let join ~update ~st ~approx x y = + match x, y with + | Top, _ -> + approx_escape ~update ~st ~approx Escape y; + Top + | _, Top -> + approx_escape ~update ~st ~approx Escape x; + Top + | Values { known; others }, Values { known = known'; others = others' } -> + Values { known = Var.Set.union known known'; others = others || others' } + + let join_set ~update ~st ~approx ?others:(o = false) f s = + Var.Set.fold + (fun x a -> join ~update ~st ~approx (f x) a) + s + (if o then others else bot) + + let mark_mutable ~update ~st a = + match a with + | Top -> () + | Values { known; _ } -> + Var.Set.iter + (fun x -> + if not st.possibly_mutable.(Var.idx x) + then ( + st.possibly_mutable.(Var.idx x) <- true; + update ~children:true x)) + known +end + +let propagate st ~update approx x = + match st.defs.(Var.idx x) with + | Phi { known; others } -> + Domain.join_set ~update ~st ~approx ~others (fun y -> Var.Tbl.get approx y) known + | Expr e -> ( + match e with + | Constant _ -> + (* A constant cannot contain a function *) + Domain.bot + | Closure _ | Block _ -> Domain.singleton x + | Field (y, n) -> ( + match Var.Tbl.get approx y with + | Values { known; others } -> + let tags = + try Some (Hashtbl.find st.known_cases x) with Not_found -> None + in + Domain.join_set + ~others + ~update + ~st + ~approx + (fun z -> + match st.defs.(Var.idx z) with + | Expr (Block (t, a, _)) + when n < Array.length a + && + match tags with + | Some tags -> List.memq t ~set:tags + | None -> true -> + let t = a.(n) in + add_dep st x t; + let a = Var.Tbl.get approx t in + if st.possibly_mutable.(Var.idx z) + then Domain.join ~update ~st ~approx Domain.others a + else a + | Expr (Block _ | Closure _) -> Domain.bot + | Phi _ | Expr _ -> assert false) + known + | Top -> Top) + | Prim (Extern "caml_check_bound", [ Pv y; _ ]) -> Var.Tbl.get approx y + | Prim ((Array_get | Extern "caml_array_unsafe_get"), [ Pv y; _ ]) -> ( + match Var.Tbl.get approx y with + | Values { known; others } -> + Domain.join_set + ~update + ~st + ~approx + ~others + (fun z -> + match st.defs.(Var.idx z) with + | Expr (Block (_, lst, _)) -> + Array.iter ~f:(fun t -> add_dep st x t) lst; + let a = + Array.fold_left + ~f:(fun acc t -> + Domain.join ~update ~st ~approx (Var.Tbl.get approx t) acc) + ~init:Domain.bot + lst + in + if st.possibly_mutable.(Var.idx z) + then Domain.join ~update ~st ~approx Domain.others a + else a + | Expr (Closure _) -> Domain.bot + | Phi _ | Expr _ -> assert false) + known + | Top -> Top) + | Prim (Array_get, _) -> assert false + | Prim ((Vectlength | Not | IsInt | Eq | Neq | Lt | Le | Ult), _) -> + (* The result of these primitive is neither a function nor a + block *) + Domain.bot + | Prim (Extern _, _) -> Domain.others + | Apply { f; args; _ } -> ( + match Var.Tbl.get approx f with + | Values { known; others } -> + if others + then + List.iter + ~f:(fun y -> Domain.variable_escape ~update ~st ~approx Escape y) + args; + Domain.join_set + ~update + ~st + ~approx + ~others + (fun g -> + match st.defs.(Var.idx g) with + | Expr (Closure (params, _)) when List.length args = List.length params + -> + if not (Hashtbl.mem st.applied_functions (x, g)) + then ( + Hashtbl.add st.applied_functions (x, g) (); + List.iter2 + ~f:(fun p a -> + add_assign_def st p a; + update ~children:false p) + params + args; + Var.Set.iter + (fun y -> add_dep st x y) + (Var.Map.find g st.return_values)); + Domain.join_set + ~update + ~st + ~approx + (fun y -> Var.Tbl.get approx y) + (Var.Map.find g st.return_values) + | Expr (Closure (_, _)) -> + (* The funciton is partially applied or over applied *) + List.iter + ~f:(fun y -> Domain.variable_escape ~update ~st ~approx Escape y) + args; + Domain.variable_escape ~update ~st ~approx Escape g; + Domain.others + | Expr (Block _) -> Domain.bot + | Phi _ | Expr _ -> assert false) + known + | Top -> + List.iter + ~f:(fun y -> Domain.variable_escape ~update ~st ~approx Escape y) + args; + Top)) + +let propagate st ~update approx x = + let res = propagate st ~update approx x in + match res with + | Values { known; _ } when Var.Set.cardinal known >= 200 -> + (* When the set of possible values get to large, we give up and + just forget about it. This is crucial to make the analysis + terminates in a reasonable amount of time. This happens when + our analysis is very imprecise (for instance, with + [List.map]), so we may not loose too much by doing that. *) + if debug () then Format.eprintf "TOP %a@." Var.print x; + Domain.approx_escape ~update ~st ~approx Escape res; + Top + | Values _ -> + (match st.variable_may_escape.(Var.idx x) with + | (Escape | Escape_constant) as s -> Domain.approx_escape ~update ~st ~approx s res + | No -> ()); + if st.variable_possibly_mutable.(Var.idx x) then Domain.mark_mutable ~update ~st res; + res + | Top -> Top + +module G = Dgraph.Make_Imperative (Var) (Var.ISet) (Var.Tbl) +module Solver = G.Solver (Domain) + +let solver st = + let g = + { G.domain = st.vars + ; G.iter_children = (fun f x -> Var.Set.iter f st.deps.(Var.idx x)) + } + in + Solver.f' () g (propagate st) + +(****) + +type info = + { info_defs : def array + ; info_approximation : Domain.t Var.Tbl.t + ; info_may_escape : bool array + } + +let f p = + let t = Timer.make () in + let t1 = Timer.make () in + let rets = return_values p in + let nv = Var.count () in + let vars = Var.ISet.empty () in + let deps = Array.make nv Var.Set.empty in + let defs = Array.make nv undefined in + let variable_may_escape = Array.make nv No in + let variable_possibly_mutable = Array.make nv false in + let may_escape = Array.make nv No in + let possibly_mutable = Array.make nv false in + let st = + { vars + ; deps + ; defs + ; return_values = rets + ; variable_may_escape + ; variable_possibly_mutable + ; may_escape + ; possibly_mutable + ; known_cases = Hashtbl.create 16 + ; applied_functions = Hashtbl.create 16 + } + in + program_deps st p; + if times () + then Format.eprintf " global flow analysis (initialize): %a@." Timer.print t1; + let t2 = Timer.make () in + let approximation = solver st in + if times () + then Format.eprintf " global flow analysis (solve): %a@." Timer.print t2; + if times () then Format.eprintf " global flow analysis: %a@." Timer.print t; + if debug () + then + Var.ISet.iter + (fun x -> + let s = Var.Tbl.get approximation x in + if not (Domain.equal s Domain.bot) + then + Format.eprintf + "%a: %a@." + Var.print + x + (fun f a -> + match a with + | Top -> Format.fprintf f "top" + | Values { known; others } -> + Format.fprintf + f + "{%a/%b} mut:%b vmut:%b esc:%s" + (Format.pp_print_list + ~pp_sep:(fun f () -> Format.fprintf f ", ") + (fun f x -> + Format.fprintf + f + "%a(%s)" + Var.print + x + (match st.defs.(Var.idx x) with + | Expr (Closure _) -> "C" + | Expr (Block _) -> + "B" + ^ + if Poly.equal st.may_escape.(Var.idx x) Escape + then "X" + else "" + | _ -> "O"))) + (Var.Set.elements known) + others + st.possibly_mutable.(Var.idx x) + st.variable_possibly_mutable.(Var.idx x) + (match st.may_escape.(Var.idx x) with + | Escape -> "Y" + | Escape_constant -> "y" + | No -> "n")) + s) + vars; + { info_defs = defs + ; info_approximation = approximation + ; info_may_escape = Array.map ~f:(fun s -> Poly.(s <> No)) may_escape + } + +let exact_call info f n = + match Var.Tbl.get info.info_approximation f with + | Top | Values { others = true; _ } -> false + | Values { known; others = false } -> + Var.Set.for_all + (fun g -> + match info.info_defs.(Var.idx g) with + | Expr (Closure (params, _)) -> List.length params = n + | Expr (Block _) -> true + | Expr _ | Phi _ -> assert false) + known diff --git a/compiler/lib/global_flow.mli b/compiler/lib/global_flow.mli new file mode 100644 index 0000000000..3a16223f1c --- /dev/null +++ b/compiler/lib/global_flow.mli @@ -0,0 +1,42 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, with linking exception; + * either version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *) +open Code + +type def = + | Expr of Code.expr + | Phi of + { known : Var.Set.t (* Known arguments *) + ; others : bool (* Can there be other arguments *) + } + +type approx = + | Top + | Values of + { known : Var.Set.t (* List of possible values *) + ; others : bool (* Whether other values are possible *) + } + +type info = + { info_defs : def array + ; info_approximation : approx Var.Tbl.t + ; info_may_escape : bool array + } + +val f : Code.program -> info + +val exact_call : info -> Var.t -> int -> bool diff --git a/compiler/lib/partial_cps_analysis.ml b/compiler/lib/partial_cps_analysis.ml new file mode 100644 index 0000000000..be1f51d636 --- /dev/null +++ b/compiler/lib/partial_cps_analysis.ml @@ -0,0 +1,190 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, with linking exception; + * either version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *) + +(* We compute which functions and which call points needs to be in CPS. *) + +open! Stdlib + +let times = Debug.find "times" + +open Code + +let add_var = Var.ISet.add + +(* x depends on y *) +let add_dep deps x y = + let idx = Var.idx y in + deps.(idx) <- Var.Set.add x deps.(idx) + +let add_tail_dep deps x y = + if not (Var.Map.mem x !deps) then deps := Var.Map.add x Var.Set.empty !deps; + deps := + Var.Map.update + y + (fun s -> Some (Var.Set.add x (Option.value ~default:Var.Set.empty s))) + !deps + +let block_deps ~info ~vars ~tail_deps ~deps ~blocks ~fun_name pc = + let block = Addr.Map.find pc blocks in + List.iter_last block.body ~f:(fun is_last i -> + match i with + | Let (x, Apply { f; _ }) -> ( + add_var vars x; + (match fun_name with + | None -> () + | Some g -> + add_var vars g; + (* If a call point is in CPS, then the englobing + function should be in CPS *) + add_dep deps g x); + match Var.Tbl.get info.Global_flow.info_approximation f with + | Top -> () + | Values { known; others } -> + let known_tail_call = + (not others) + && is_last + && + match block.branch with + | Return x' -> Var.equal x x' + | _ -> false + in + Var.Set.iter + (fun g -> + add_var vars g; + (if known_tail_call + then + match fun_name with + | None -> () + | Some f -> add_tail_dep tail_deps f g); + (* If a called function is in CPS, then the call + point is in CPS *) + add_dep deps x g; + (* Conversally, if a call point is in CPS then all + called functions must be in CPS *) + add_dep deps g x) + known) + | Let (x, Prim (Extern ("%perform" | "%reperform" | "%resume"), _)) -> ( + add_var vars x; + match fun_name with + | None -> () + | Some f -> + add_var vars f; + (* If a function contains effect primitives, it must be + in CPS *) + add_dep deps f x) + | Let (x, Closure _) -> add_var vars x + | Let (_, (Prim _ | Block _ | Constant _ | Field _)) + | Assign _ | Set_field _ | Offset_ref _ | Array_set _ -> ()) + +let program_deps ~info ~vars ~tail_deps ~deps p = + fold_closures + p + (fun fun_name _ (pc, _) _ -> + traverse + { fold = Code.fold_children } + (fun pc () -> + block_deps ~info ~vars ~tail_deps ~deps ~blocks:p.blocks ~fun_name pc) + pc + p.blocks + ()) + () + +module Domain = struct + type t = bool + + let equal = Bool.equal + + let bot = false +end + +module G = Dgraph.Make_Imperative (Var) (Var.ISet) (Var.Tbl) +module Solver = G.Solver (Domain) + +let fold_children g f x acc = + let acc = ref acc in + g.G.iter_children (fun y -> acc := f y !acc) x; + !acc + +let cps_needed ~info ~in_mutual_recursion ~rev_deps st x = + (* Mutually recursive functions are turned into CPS for tail + optimization *) + Var.Set.mem x in_mutual_recursion + || + let idx = Var.idx x in + fold_children rev_deps (fun y acc -> acc || Var.Tbl.get st y) x false + || + match info.Global_flow.info_defs.(idx) with + | Expr (Apply { f; _ }) -> ( + (* If we don't know all possible functions at a call point, it + must be in CPS *) + match Var.Tbl.get info.Global_flow.info_approximation f with + | Top -> true + | Values { others; _ } -> others) + | Expr (Closure _) -> + (* If a function escapes, it must be in CPS *) + info.Global_flow.info_may_escape.(idx) + | Expr (Prim (Extern ("%perform" | "%reperform" | "%resume"), _)) -> + (* Effects primitives are in CPS *) + true + | Expr (Prim _ | Block _ | Constant _ | Field _) | Phi _ -> false + +module SCC = Strongly_connected_components.Make (struct + type t = Var.t + + module Set = Var.Set + module Map = Var.Map +end) + +let find_mutually_recursive_calls tail_deps = + let scc = SCC.component_graph !tail_deps in + Array.fold_left + ~f:(fun s (c, _) -> + match c with + | SCC.No_loop _ -> s + | Has_loop l -> List.fold_left ~f:(fun s x -> Var.Set.add x s) l ~init:s) + ~init:Var.Set.empty + scc + +let annot st xi = + match (xi : Print.xinstr) with + | Instr (Let (x, _)) when Var.Set.mem x st -> "*" + | _ -> " " + +let f p info = + let t = Timer.make () in + let t1 = Timer.make () in + let nv = Var.count () in + let vars = Var.ISet.empty () in + let deps = Array.make nv Var.Set.empty in + let tail_deps = ref Var.Map.empty in + program_deps ~info ~vars ~tail_deps ~deps p; + if times () then Format.eprintf " fun analysis (initialize): %a@." Timer.print t1; + let t2 = Timer.make () in + let in_mutual_recursion = find_mutually_recursive_calls tail_deps in + if times () then Format.eprintf " fun analysis (tail calls): %a@." Timer.print t2; + let t3 = Timer.make () in + let g = + { G.domain = vars; iter_children = (fun f x -> Var.Set.iter f deps.(Var.idx x)) } + in + let rev_deps = G.invert () g in + let res = Solver.f () g (cps_needed ~info ~in_mutual_recursion ~rev_deps) in + if times () then Format.eprintf " fun analysis (solve): %a@." Timer.print t3; + let s = ref Var.Set.empty in + Var.Tbl.iter (fun x v -> if v then s := Var.Set.add x !s) res; + if times () then Format.eprintf " fun analysis: %a@." Timer.print t; + !s diff --git a/compiler/lib/partial_cps_analysis.mli b/compiler/lib/partial_cps_analysis.mli new file mode 100644 index 0000000000..4ec0cb531c --- /dev/null +++ b/compiler/lib/partial_cps_analysis.mli @@ -0,0 +1,21 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, with linking exception; + * either version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *) + +val annot : Code.Var.Set.t -> Code.Print.xinstr -> string + +val f : Code.program -> Global_flow.info -> Code.Var.Set.t diff --git a/compiler/tests-compiler/effects_call_opt.ml b/compiler/tests-compiler/effects_call_opt.ml index acbe6084de..f63ddf2b4a 100644 --- a/compiler/tests-compiler/effects_call_opt.ml +++ b/compiler/tests-compiler/effects_call_opt.ml @@ -58,70 +58,37 @@ let%expect_test "test-compiler/lib-effects/effects_call_opt.ml" = [%expect {| function test1(param,cont) - {function f(g,x,cont){return caml_cps_call2(g,x,cont)} - var _t_=7; - function _u_(x,cont){return cont(x + 1 | 0)} - return caml_cps_exact_call3 - (f, - _u_, - _t_, - function(_v_) - {var _w_=4.; - function _x_(x,cont){return cont(x * 2.)} - return caml_cps_exact_call3 - (f,_x_,_w_,function(_y_){return cont(0)})})} + {function f(g,x){return g(x)} + var _k_=7; + f(function(x){return x + 1 | 0},_k_); + var _l_=4.; + f(function(x){return x * 2.},_l_); + return cont(0)} //end function test2(param,cont) {function f(g,x,cont){return caml_cps_call2(g,x,cont)} - var _o_=7; - function _p_(x,cont){return cont(x + 1 | 0)} + var _f_=7; + function _g_(x,cont){return cont(x + 1 | 0)} return caml_cps_exact_call3 (f, - _p_, - _o_, - function(_q_) - {function _r_(x,cont) + _g_, + _f_, + function(_h_) + {function _i_(x,cont) {return caml_cps_call3(Stdlib[28],x,cst_a$0,cont)} return caml_cps_exact_call3 - (f,_r_,cst_a,function(_s_){return cont(0)})})} + (f,_i_,cst_a,function(_j_){return cont(0)})})} //end function test3(x,cont) - {function F(symbol,cont) - {function f(x,cont){return cont(x + 1 | 0)}return cont([0,f])} - var _g_=[0]; - return caml_cps_exact_call2 - (F, - _g_, - function(M1) - {var _h_=[0]; - return caml_cps_exact_call2 - (F, - _h_, - function(M2) - {var _i_=2,_j_=M2[1]; - return caml_cps_call2 - (_j_, - _i_, - function(_k_) - {var _l_=1,_m_=M1[1]; - return caml_cps_call2 - (_m_,_l_,function(_n_){return cont([0,_n_,_k_])})})})})} + {function F(symbol){function f(x){return x + 1 | 0}return [0,f]} + var M1=F([0]),M2=F([0]),_e_=M2[1](2); + return cont([0,M1[1](1),_e_])} //end function test4(x,cont) - {function F(symbol,cont) + {function F(symbol) {function f(x,cont){return caml_cps_call3(Stdlib_Printf[2],_a_,x,cont)} - return cont([0,f])} - var _b_=[0]; - return caml_cps_exact_call2 - (F, - _b_, - function(M1) - {var _c_=[0]; - return caml_cps_exact_call2 - (F, - _c_, - function(M2) - {var _d_=1,_e_=M1[1]; - return caml_cps_call2 - (_e_,_d_,function(_f_){return caml_cps_call2(M2[1],2,cont)})})})} + return [0,f]} + var M1=F([0]),M2=F([0]),_b_=1,_c_=M1[1]; + return caml_cps_call2 + (_c_,_b_,function(_d_){return caml_cps_call2(M2[1],2,cont)})} //end |}] diff --git a/compiler/tests-compiler/lambda_lifting.ml b/compiler/tests-compiler/lambda_lifting.ml index 6e056e5af4..5026171404 100644 --- a/compiler/tests-compiler/lambda_lifting.ml +++ b/compiler/tests-compiler/lambda_lifting.ml @@ -22,24 +22,16 @@ Printf.printf "%d\n" (f 3) {| (function(globalThis) {"use strict"; - var runtime=globalThis.jsoo_runtime,caml_callback=runtime.caml_callback; - function caml_cps_exact_call2(f,a0,a1) - {return runtime.caml_stack_check_depth() - ?f(a0,a1) - :runtime.caml_trampoline_return(f,[a0,a1])} var + runtime=globalThis.jsoo_runtime, global_data=runtime.caml_get_global_data(), Stdlib_Printf=global_data.Stdlib__Printf, _c_=[0,[4,0,0,0,[12,10,0]],runtime.caml_string_of_jsbytes("%d\n")]; - function f(x,cont){var g$0=g(x);return caml_cps_exact_call2(g$0,5,cont)} - function h(x,y) - {function h(z,cont){return cont((x + y | 0) + z | 0)}return h} - function g(x) - {function g(y,cont) - {var h$0=h(x,y);return caml_cps_exact_call2(h$0,7,cont)} - return g} - var _a_=3,_b_=caml_callback(f,[_a_]),_d_=Stdlib_Printf[2]; - caml_callback(_d_,[_c_,_b_]); + function f(x){var g$0=g(x);return g$0(5)} + function h(x,y){function h(z){return (x + y | 0) + z | 0}return h} + function g(x){function g(y){var h$0=h(x,y);return h$0(7)}return g} + var _a_=3,_b_=f(_a_),_d_=Stdlib_Printf[2]; + runtime.caml_callback(_d_,[_c_,_b_]); var Test=[0]; runtime.caml_register_global(2,Test,"Test"); return} From 17a9049cbeba4d4da02ecdf9f0a4644871611954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 12 Jan 2023 18:14:09 +0100 Subject: [PATCH 13/17] Effects: use global flow analysis to detect more exact calls --- compiler/lib/effects.ml | 16 ++++++++++++---- compiler/tests-compiler/effects_call_opt.ml | 6 +++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 88c46301f0..44362a6606 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -240,6 +240,7 @@ type st = ; matching_exn_handler : (Addr.t, Addr.t) Hashtbl.t ; block_order : (Addr.t, int) Hashtbl.t ; live_vars : Deadcode.variable_uses + ; flow_info : Global_flow.info ; cps_calls : cps_calls ref } @@ -361,6 +362,7 @@ let cps_instr ~st (instr : instr) : instr = | Let (x, Apply { f; args; _ }) when not (Var.Set.mem x st.cps_needed) -> (* At the moment, we turn into CPS any function not called with the right number of parameter *) + assert (Global_flow.exact_call st.flow_info f (List.length args)); Let (x, Apply { f; args; exact = true }) | Let (_, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> assert false @@ -412,7 +414,12 @@ let cps_block ~st ~k pc block = in match e with | Apply { f; args; exact } when Var.Set.mem x st.cps_needed -> - Some (fun ~k -> tail_call ~st ~exact ~check:true ~f (args @ [ k ])) + Some + (fun ~k -> + let exact = + exact || Global_flow.exact_call st.flow_info f (List.length args) + in + tail_call ~st ~exact ~check:true ~f (args @ [ k ])) | Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ]) -> Some (fun ~k -> @@ -420,7 +427,7 @@ let cps_block ~st ~k pc block = tail_call ~st ~instrs:[ Let (k', Prim (Extern "caml_resume_stack", [ Pv stack; Pv k ])) ] - ~exact:false + ~exact:(Global_flow.exact_call st.flow_info f 1) ~check:true ~f [ arg; k' ]) @@ -496,7 +503,7 @@ let cps_block ~st ~k pc block = ; branch = last } -let cps_transform ~live_vars ~cps_needed p = +let cps_transform ~live_vars ~flow_info ~cps_needed p = let closure_info = Hashtbl.create 16 in let cps_calls = ref Var.Set.empty in let p = @@ -553,6 +560,7 @@ let cps_transform ~live_vars ~cps_needed p = ; is_continuation ; matching_exn_handler ; block_order = cfg.block_order + ; flow_info ; live_vars ; cps_calls } @@ -818,6 +826,6 @@ let f (p, live_vars) = let cps_needed = Partial_cps_analysis.f p flow_info in let p, cps_needed = rewrite_toplevel ~cps_needed p in let p = split_blocks ~cps_needed p in - let p, cps_calls = cps_transform ~live_vars ~cps_needed p in + let p, cps_calls = cps_transform ~live_vars ~flow_info ~cps_needed p in if Debug.find "times" () then Format.eprintf " effects: %a@." Timer.print t; p, cps_calls diff --git a/compiler/tests-compiler/effects_call_opt.ml b/compiler/tests-compiler/effects_call_opt.ml index f63ddf2b4a..5ad0e7ccba 100644 --- a/compiler/tests-compiler/effects_call_opt.ml +++ b/compiler/tests-compiler/effects_call_opt.ml @@ -66,7 +66,7 @@ let%expect_test "test-compiler/lib-effects/effects_call_opt.ml" = return cont(0)} //end function test2(param,cont) - {function f(g,x,cont){return caml_cps_call2(g,x,cont)} + {function f(g,x,cont){return caml_cps_exact_call2(g,x,cont)} var _f_=7; function _g_(x,cont){return cont(x + 1 | 0)} return caml_cps_exact_call3 @@ -89,6 +89,6 @@ let%expect_test "test-compiler/lib-effects/effects_call_opt.ml" = {function f(x,cont){return caml_cps_call3(Stdlib_Printf[2],_a_,x,cont)} return [0,f]} var M1=F([0]),M2=F([0]),_b_=1,_c_=M1[1]; - return caml_cps_call2 - (_c_,_b_,function(_d_){return caml_cps_call2(M2[1],2,cont)})} + return caml_cps_exact_call2 + (_c_,_b_,function(_d_){return caml_cps_exact_call2(M2[1],2,cont)})} //end |}] From c4aed1314a72fc44f98e94cca9d41039659c9b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 13 Jan 2023 10:22:42 +0100 Subject: [PATCH 14/17] Benchmark: graph adjustments --- benchmarks/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/Makefile b/benchmarks/Makefile index 368d733a52..b589dc0906 100644 --- a/benchmarks/Makefile +++ b/benchmarks/Makefile @@ -158,7 +158,7 @@ time-effects.svg: __run_effects -omit minesweeper \ -omit planet \ -omit ocamlc \ - -max 5 -svg 7 400 150 -edgecaption -ylabel "Execution time" \ + -min 0.5 -max 1.5 -svg 7 400 150 -edgecaption -ylabel "Execution time" \ > $@ size-effects.svg: __run_effects @@ -175,7 +175,7 @@ size-effects.svg: __run_effects -append planet \ -append js_of_ocaml \ -append ocamlc \ - -max 2 -svg 7 650 150 -edgecaption -ylabel Size \ + -min 0.8 -max 1.25 -svg 7 650 150 -edgecaption -ylabel Size \ > $@ size-gzipped-effects.svg: __run_effects From dcebbfaa690d23f3dc17f0b76fefe921dde29b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 26 Jan 2023 15:09:24 +0100 Subject: [PATCH 15/17] Effects: test miscompilation of exception handlers --- compiler/tests-compiler/effects_exceptions.ml | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/compiler/tests-compiler/effects_exceptions.ml b/compiler/tests-compiler/effects_exceptions.ml index ad0486ddf1..331a25f5c3 100644 --- a/compiler/tests-compiler/effects_exceptions.ml +++ b/compiler/tests-compiler/effects_exceptions.ml @@ -36,6 +36,20 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = Some (open_in "toto", n, m) with Not_found -> None + + let handler_is_loop f g l = + try f () + with exn -> + let rec loop l = + match g l with + | `Fallback l' -> loop l' + | `Raise exn -> raise exn + in + loop l + + let handler_is_merge_node g = + let s = try g () with _ -> "" in + s ^ "aaa" |} in print_fun_decl code (Some "exceptions"); @@ -44,27 +58,53 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = function exceptions(s,cont) {try - {var _h_=runtime.caml_int_of_string(s),n=_h_} - catch(_l_) - {var _a_=caml_wrap_exception(_l_); - if(_a_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_a_)} - var n=0,_b_=0} + {var _n_=runtime.caml_int_of_string(s),n=_n_} + catch(_r_) + {var _g_=caml_wrap_exception(_r_); + if(_g_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_g_)} + var n=0,_h_=0} try - {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _g_=7,m=_g_} - catch(_k_) - {var _c_=caml_wrap_exception(_k_); - if(_c_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_c_)} - var m=0,_d_=0} - runtime.caml_push_trap - (function(_j_) - {if(_j_ === Stdlib[8])return cont(0); + {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _m_=7,m=_m_} + catch(_q_) + {var _i_=caml_wrap_exception(_q_); + if(_i_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_i_)} + var m=0,_j_=0} + caml_push_trap + (function(_p_) + {if(_p_ === Stdlib[8])return cont(0); var raise=caml_pop_trap(); - return raise(_j_)}); + return raise(_p_)}); if(caml_string_equal(s,cst)) - {var _e_=Stdlib[8],raise=caml_pop_trap();return raise(_e_)} - var _f_=Stdlib[79]; + {var _k_=Stdlib[8],raise=caml_pop_trap();return raise(_k_)} + var _l_=Stdlib[79]; return caml_cps_call2 - (_f_, + (_l_, cst_toto, - function(_i_){caml_pop_trap();return cont([0,[0,_i_,n,m]])})} + function(_o_){caml_pop_trap();return cont([0,[0,_o_,n,m]])})} + //end |}]; + print_fun_decl code (Some "handler_is_loop"); + [%expect + {| + function handler_is_loop(f,g,l,cont) + {function _e_(l) + {return caml_cps_call2 + (g, + l, + function(match) + {if(72330306 <= match[1]) + {var l=match[2];return caml_cps_exact_call1(_e_,l)} + var exn=match[2],raise=caml_pop_trap(); + return raise(exn)})} + caml_push_trap(_e_); + var _d_=0; + return caml_cps_call2(f,_d_,function(_f_){caml_pop_trap();return cont(_f_)})} + //end |}]; + print_fun_decl code (Some "handler_is_merge_node"); + [%expect + {| + function handler_is_merge_node(g,cont) + {function _b_(s){return caml_cps_call3(Stdlib[28],s,cst_aaa,cont)} + caml_push_trap(_b_); + var _a_=0; + return caml_cps_call2(g,_a_,function(_c_){caml_pop_trap();return _b_(_c_)})} //end |}] From cc1896da8dd5c4bb812ff51adaf48425798a77fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 26 Jan 2023 12:34:16 +0100 Subject: [PATCH 16/17] Effects: fix compilation of exception handlers --- compiler/lib/effects.ml | 162 ++++++++++++------ compiler/tests-compiler/effects_exceptions.ml | 62 +++---- 2 files changed, 143 insertions(+), 81 deletions(-) diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 44362a6606..55002610c0 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -51,6 +51,7 @@ let reverse_graph g = type control_flow_graph = { succs : (Addr.t, Addr.Set.t) Hashtbl.t + ; preds : (Addr.t, Addr.Set.t) Hashtbl.t ; reverse_post_order : Addr.t list ; block_order : (Addr.t, int) Hashtbl.t } @@ -71,7 +72,8 @@ let build_graph blocks pc = traverse pc; let block_order = Hashtbl.create 16 in List.iteri !l ~f:(fun i pc -> Hashtbl.add block_order pc i); - { succs; reverse_post_order = !l; block_order } + let preds = reverse_graph succs in + { succs; preds; reverse_post_order = !l; block_order } let dominator_tree g = (* A Simple, Fast Dominance Algorithm @@ -102,8 +104,25 @@ let dominator_tree g = l); dom +(* pc dominates pc' *) +let rec dominates g idom pc pc' = + pc = pc' + || Hashtbl.find g.block_order pc < Hashtbl.find g.block_order pc' + && dominates g idom pc (Hashtbl.find idom pc') + +(* pc has at least two forward edges moving into it *) +let is_merge_node g pc = + let s = try Hashtbl.find g.preds pc with Not_found -> assert false in + let o = Hashtbl.find g.block_order pc in + let n = + Addr.Set.fold + (fun pc' n -> if Hashtbl.find g.block_order pc' < o then n + 1 else n) + s + 0 + in + n > 1 + let dominance_frontier g idom = - let preds = reverse_graph g.succs in let frontiers = Hashtbl.create 16 in Hashtbl.iter (fun pc preds -> @@ -117,7 +136,7 @@ let dominance_frontier g idom = loop (Hashtbl.find idom runner)) in Addr.Set.iter loop preds) - preds; + g.preds; frontiers (****) @@ -232,6 +251,8 @@ type cps_calls = Var.Set.t type st = { mutable new_blocks : Code.block Addr.Map.t * Code.Addr.t ; blocks : Code.block Addr.Map.t + ; cfg : control_flow_graph + ; idom : (int, int) Hashtbl.t ; jc : jump_closures ; closure_info : (Addr.t, Var.t * Code.cont) Hashtbl.t ; cps_needed : Var.Set.t @@ -292,14 +313,59 @@ let cps_jump_cont ~st ~src ((pc, _) as cont) = in call_block, [] -let cps_last ~st pc (last : last) ~k : instr list * last = +let allocate_continuation ~st ~alloc_jump_closures ~split_closures pc x cont = + (* We need to allocate an additional closure if [cont] + does not correspond to a continuation that binds [x]. + This closure binds the return value [x], allocates + closures for dominated blocks and jumps to the next + block. When entering a loop, we also have to allocate a + closure to bind [x] if it is used in the loop body. In + other cases, we can just pass the closure corresponding + to the next block. *) + let pc', args = cont in + if (match args with + | [] -> true + | [ x' ] -> Var.equal x x' + | _ -> false) + && + match Hashtbl.find st.is_continuation pc' with + | `Param _ -> true + | `Loop -> st.live_vars.(Var.idx x) = List.length args + then alloc_jump_closures, closure_of_pc ~st pc' + else + let body, branch = cps_branch ~st ~src:pc cont in + let inner_closures, outer_closures = + (* For [Pushtrap], we need to separate the closures + corresponding to the exception handler body (that may make + use of [x]) from the other closures that may be used outside + of the exception handler. *) + if not split_closures + then alloc_jump_closures, [] + else if is_merge_node st.cfg pc' + then [], alloc_jump_closures + else + List.partition + ~f:(fun i -> + match i with + | Let (_, Closure (_, (pc'', []))) -> dominates st.cfg st.idom pc' pc'' + | _ -> assert false) + alloc_jump_closures + in + let body, branch = + allocate_closure ~st ~params:[ x ] ~body:(inner_closures @ body) ~branch + in + outer_closures @ body, branch + +let cps_last ~st ~alloc_jump_closures pc (last : last) ~k : instr list * last = match last with | Return x -> + assert (List.is_empty alloc_jump_closures); (* Is the number of successive 'returns' is unbounded is CPS, it means that we have an unbounded of calls in direct style (even with tail call optimization) *) tail_call ~st ~exact:true ~check:false ~f:k [ x ] | Raise (x, _) -> ( + assert (List.is_empty alloc_jump_closures); match Hashtbl.find_opt st.matching_exn_handler pc with | Some pc when not (Addr.Set.mem pc st.blocks_to_transform) -> (* We are within a try ... with which is not @@ -314,35 +380,51 @@ let cps_last ~st pc (last : last) ~k : instr list * last = ~check:false ~f:exn_handler [ x ]) - | Stop -> [], Stop - | Branch cont -> cps_branch ~st ~src:pc cont + | Stop -> + assert (List.is_empty alloc_jump_closures); + [], Stop + | Branch cont -> + let body, branch = cps_branch ~st ~src:pc cont in + alloc_jump_closures @ body, branch | Cond (x, cont1, cont2) -> - [], Cond (x, cps_jump_cont ~st ~src:pc cont1, cps_jump_cont ~st ~src:pc cont2) + ( alloc_jump_closures + , Cond (x, cps_jump_cont ~st ~src:pc cont1, cps_jump_cont ~st ~src:pc cont2) ) | Switch (x, c1, c2) -> (* To avoid code duplication during JavaScript generation, we need to create a single block per continuation *) let cps_jump_cont = Fun.memoize (cps_jump_cont ~st ~src:pc) in - [], Switch (x, Array.map c1 ~f:cps_jump_cont, Array.map c2 ~f:cps_jump_cont) - | Pushtrap (body_cont, _, (handler_pc, _), _) -> ( + ( alloc_jump_closures + , Switch (x, Array.map c1 ~f:cps_jump_cont, Array.map c2 ~f:cps_jump_cont) ) + | Pushtrap (body_cont, exn, ((handler_pc, _) as handler_cont), _) -> ( assert (Hashtbl.mem st.is_continuation handler_pc); match Addr.Set.mem handler_pc st.blocks_to_transform with - | false -> [], last + | false -> alloc_jump_closures, last | true -> - let exn_handler = closure_of_pc ~st handler_pc in + let constr_cont, exn_handler = + allocate_continuation + ~st + ~alloc_jump_closures + ~split_closures:true + pc + exn + handler_cont + in let push_trap = Let (Var.fresh (), Prim (Extern "caml_push_trap", [ Pv exn_handler ])) in let body, branch = cps_branch ~st ~src:pc body_cont in - push_trap :: body, branch) + constr_cont @ (push_trap :: body), branch) | Poptrap cont -> ( match Addr.Set.mem (Hashtbl.find st.matching_exn_handler pc) st.blocks_to_transform with - | false -> [], Poptrap (cps_jump_cont ~st ~src:pc cont) + | false -> alloc_jump_closures, Poptrap (cps_jump_cont ~st ~src:pc cont) | true -> let exn_handler = Var.fresh () in let body, branch = cps_branch ~st ~src:pc cont in - Let (exn_handler, Prim (Extern "caml_pop_trap", [])) :: body, branch) + ( alloc_jump_closures + @ (Let (exn_handler, Prim (Extern "caml_pop_trap", [])) :: body) + , branch )) let cps_instr ~st (instr : instr) : instr = match instr with @@ -447,38 +529,18 @@ let cps_block ~st ~k pc block = let instrs, branch = f ~k in body_prefix, instrs, branch) | Some (body_prefix, Let (x, e)), Branch cont -> - let allocate_continuation f = - let constr_cont, k' = - (* We need to allocate an additional closure if [cont] - does not correspond to a continuation that binds [x]. - This closure binds the return value [x], allocates - closures for dominated blocks and jumps to the next - block. When entering a loop, we also have to allocate a - closure to bind [x] if it is used in the loop body. In - other cases, we can just pass the closure corresponding - to the next block. *) - let pc', args = cont in - if (match args with - | [] -> true - | [ x' ] -> Var.equal x x' - | _ -> false) - && - match Hashtbl.find st.is_continuation pc' with - | `Param _ -> true - | `Loop -> st.live_vars.(Var.idx x) = List.length args - then alloc_jump_closures, closure_of_pc ~st pc' - else - let body, branch = cps_branch ~st ~src:pc cont in - allocate_closure + Option.map (rewrite_instr x e) ~f:(fun f -> + let constr_cont, k' = + allocate_continuation ~st - ~params:[ x ] - ~body:(alloc_jump_closures @ body) - ~branch - in - let instrs, branch = f ~k:k' in - body_prefix, constr_cont @ instrs, branch - in - Option.map (rewrite_instr x e) ~f:allocate_continuation + ~alloc_jump_closures + ~split_closures:false + pc + x + cont + in + let instrs, branch = f ~k:k' in + body_prefix, constr_cont @ instrs, branch) | Some (_, (Set_field _ | Offset_ref _ | Array_set _ | Assign _)), _ | Some _, (Raise _ | Stop | Cond _ | Switch _ | Pushtrap _ | Poptrap _) | None, _ -> None @@ -489,12 +551,8 @@ let cps_block ~st ~k pc block = | Some (body_prefix, last_instrs, last) -> List.map body_prefix ~f:(fun i -> cps_instr ~st i) @ last_instrs, last | None -> - let last_instrs, last = cps_last ~st pc block.branch ~k in - let body = - List.map block.body ~f:(fun i -> cps_instr ~st i) - @ alloc_jump_closures - @ last_instrs - in + let last_instrs, last = cps_last ~st ~alloc_jump_closures pc block.branch ~k in + let body = List.map block.body ~f:(fun i -> cps_instr ~st i) @ last_instrs in body, last in @@ -553,6 +611,8 @@ let cps_transform ~live_vars ~flow_info ~cps_needed p = let st = { new_blocks = Addr.Map.empty, free_pc ; blocks + ; cfg + ; idom ; jc = closure_jc ; closure_info ; cps_needed diff --git a/compiler/tests-compiler/effects_exceptions.ml b/compiler/tests-compiler/effects_exceptions.ml index 331a25f5c3..cb75576b91 100644 --- a/compiler/tests-compiler/effects_exceptions.ml +++ b/compiler/tests-compiler/effects_exceptions.ml @@ -58,53 +58,55 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = function exceptions(s,cont) {try - {var _n_=runtime.caml_int_of_string(s),n=_n_} - catch(_r_) - {var _g_=caml_wrap_exception(_r_); - if(_g_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_g_)} - var n=0,_h_=0} + {var _p_=runtime.caml_int_of_string(s),n=_p_} + catch(_t_) + {var _i_=caml_wrap_exception(_t_); + if(_i_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_i_)} + var n=0,_j_=0} try - {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _m_=7,m=_m_} - catch(_q_) - {var _i_=caml_wrap_exception(_q_); - if(_i_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_i_)} - var m=0,_j_=0} + {if(caml_string_equal(s,cst$0))throw Stdlib[8];var _o_=7,m=_o_} + catch(_s_) + {var _k_=caml_wrap_exception(_s_); + if(_k_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_k_)} + var m=0,_l_=0} caml_push_trap - (function(_p_) - {if(_p_ === Stdlib[8])return cont(0); + (function(_r_) + {if(_r_ === Stdlib[8])return cont(0); var raise=caml_pop_trap(); - return raise(_p_)}); + return raise(_r_)}); if(caml_string_equal(s,cst)) - {var _k_=Stdlib[8],raise=caml_pop_trap();return raise(_k_)} - var _l_=Stdlib[79]; + {var _m_=Stdlib[8],raise=caml_pop_trap();return raise(_m_)} + var _n_=Stdlib[79]; return caml_cps_call2 - (_l_, + (_n_, cst_toto, - function(_o_){caml_pop_trap();return cont([0,[0,_o_,n,m]])})} + function(_q_){caml_pop_trap();return cont([0,[0,_q_,n,m]])})} //end |}]; print_fun_decl code (Some "handler_is_loop"); [%expect {| function handler_is_loop(f,g,l,cont) - {function _e_(l) - {return caml_cps_call2 - (g, - l, - function(match) - {if(72330306 <= match[1]) - {var l=match[2];return caml_cps_exact_call1(_e_,l)} - var exn=match[2],raise=caml_pop_trap(); - return raise(exn)})} - caml_push_trap(_e_); - var _d_=0; - return caml_cps_call2(f,_d_,function(_f_){caml_pop_trap();return cont(_f_)})} + {caml_push_trap + (function(_g_) + {function _h_(l) + {return caml_cps_call2 + (g, + l, + function(match) + {if(72330306 <= match[1]) + {var l=match[2];return caml_cps_exact_call1(_h_,l)} + var exn=match[2],raise=caml_pop_trap(); + return raise(exn)})} + return _h_(l)}); + var _e_=0; + return caml_cps_call2(f,_e_,function(_f_){caml_pop_trap();return cont(_f_)})} //end |}]; print_fun_decl code (Some "handler_is_merge_node"); [%expect {| function handler_is_merge_node(g,cont) {function _b_(s){return caml_cps_call3(Stdlib[28],s,cst_aaa,cont)} - caml_push_trap(_b_); + caml_push_trap(function(_d_){return _b_(cst$1)}); var _a_=0; return caml_cps_call2(g,_a_,function(_c_){caml_pop_trap();return _b_(_c_)})} //end |}] From 43b3650e0b8b00fc094b37599ea2c97dc526cb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 24 Jan 2023 14:43:26 +0100 Subject: [PATCH 17/17] Documentation updates --- CHANGES.md | 2 +- manual/effects.wiki | 16 ++++++-------- .../files/performances/size-bzip2-effects.png | Bin 17680 -> 17672 bytes manual/files/performances/size-effects.png | Bin 14968 -> 16814 bytes manual/files/performances/time-effects.png | Bin 24109 -> 23711 bytes manual/overview.wiki | 2 +- manual/performances.wiki | 20 +++++++++--------- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1709deba06..2df2d8b848 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## Features/Changes * Misc: bump min ocaml version to 4.08 * Misc: remove some old runtime files to support some external libs -* Effects: improved CPS transform, resulting in lower compilation time and smaller generated code +* Effects: partial CPS transformation, resulting in much better performances, lower compilation time and smaller generated code * Compiler: separate compilation can now drops unused units when linking (similar to ocamlc). (#1378) Feature is disabled by default while dune rules are being fixed. Enable with --enable=auto-link. * Compiler: specialize string to js-string conversion for all valid utf8 strings (previously just ascii) diff --git a/manual/effects.wiki b/manual/effects.wiki index 8825bfcae0..551afee24f 100644 --- a/manual/effects.wiki +++ b/manual/effects.wiki @@ -1,20 +1,16 @@ == Effect handlers == Js_of_ocaml supports effect handlers with the {{{--enable=effects}}} -flag. This is based on transformation of the whole program to +flag. This is based on partially transforming the program to continuation-passing style. As a consequence, [[tailcall|tail calls]] are also fully optimized. -This is not the default for now since the generated code is slower, +This is not the default for now since the generated code can be slower, larger and less readable. -The [[performances|performance impact]] is especially large for code -that involves a lot of function calls without allocation, since the -transformation introduces many intermediate continuation -functions. -We hope to improve on this by transforming the code only partially to -continuation-passing style, and by trying alternative compilation +The transformation is based on an analysis to detect parts of the code that cannot involves effects and keep it in direct style. +The analysis is especially effective on monomorphic code. It is not so effective when higher-order functions are heavily used ({{{Lwt}}}, {{{Async}}}, {{{incremental}}}). +We hope to improve on this by trying alternative compilation strategies. - === Dune integration === We're still working on dune support for compiling js_of_ocaml programs @@ -63,4 +59,4 @@ Trying to use separate compilation would result in a error while attempting to l js_of_ocaml: Error: Incompatible build info detected while linking. - test6.bc.runtime.js: effects=false - .cmphash.eobjs/byte/dune__exe.cmo.js: effects=true -}}} \ No newline at end of file +}}} diff --git a/manual/files/performances/size-bzip2-effects.png b/manual/files/performances/size-bzip2-effects.png index 6d3bec700bbf837976754c1e6f190aee2331f2d9..b718e483a5d399ecf432167ec6968bda0dacc403 100644 GIT binary patch literal 17672 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 literal 17680 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 diff --git a/manual/files/performances/size-effects.png b/manual/files/performances/size-effects.png index 2cc3aa87b7774b7ce263a82ee5e56b82627bd03d..5b55c273c8ca56fcd31139b5c7f3e30c9ca6f5e7 100644 GIT binary patch literal 16814 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
K?l$>v(ahPqvZ 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 diff --git a/manual/overview.wiki b/manual/overview.wiki index 09be05724b..9b6fc1c6fb 100644 --- a/manual/overview.wiki +++ b/manual/overview.wiki @@ -79,7 +79,7 @@ functions are optimized: * trampolines are used otherwise. <>. -Effect handlers are fully supported with the {{{--enable=effects}}} flag. This is not the default for now since the generated code is slower, larger and less readable. +Effect handlers are fully supported with the {{{--enable=effects}}} flag. This is not the default for now since effects are not widely used at the moment and the generated code can be slower, larger and less readable. Data representation differs from the usual one. Most notably, integers are 32 bits (rather than 31 bits or 63 bits), which is their diff --git a/manual/performances.wiki b/manual/performances.wiki index 51d0546753..3715d9f0d8 100644 --- a/manual/performances.wiki +++ b/manual/performances.wiki @@ -26,17 +26,17 @@ See how various js_of_ocaml options affect the generated size and execution time <> We show the performance impact of supporting effect handlers. The code -is about 30% larger. The impact on compressed code is actually much lower -since we are adding a lot of function definitions, which are rather -verbose in JavaScript but compress well: the compressed code size -hardly increases for large files compressed with {{{bzip2}}}. -Code that involves a lot of function calls without allocation, such as -{{{fib}}}, becomes much slower. The overhead is reasonable for -code that performs a lot of allocations ({{{hamming}}}) or that -performs some numeric computations ({{{almabench}}}, {{{fft}}}). -Exception handling is faster ({{{boyer}}}). +is about 20% larger. The impact on compressed code is actually much +lower since we are adding a lot of function definitions, which are +rather verbose in JavaScript but compress well: the compressed code +size hardly increases for large files compressed with {{{bzip2}}}. +Code that heavily uses {{{Lwt}}}, {{{Async}}} or {{{Incremental}}} can +see a larger size increase (up to 45% larger, or 7% larger when +compressed). There is almost no speed impact for small monomorphic +programs. We estimate that the slowdown will usually be below 30%, +though it can be larger for code that heavily use higher-order +functions and polymorphism ({{{Lwt}}} code, for instance). <> <> <> -