From b004b8069826fec9ff1bb5ad08018605cd79cae6 Mon Sep 17 00:00:00 2001 From: Olivier Nicole Date: Thu, 9 Mar 2023 18:45:48 +0100 Subject: [PATCH] Effects: double translation of functions and ... dynamic switching between direct-style and CPS code. (#1461) --- CHANGES.md | 5 + compiler/lib/build_info.ml | 9 +- compiler/lib/code.ml | 4 + compiler/lib/code.mli | 2 + compiler/lib/config.ml | 2 + compiler/lib/config.mli | 2 + compiler/lib/driver.ml | 26 +- compiler/lib/effects.ml | 829 +++++++++++++++--- compiler/lib/effects.mli | 15 +- compiler/lib/flow.ml | 2 +- compiler/lib/freevars.ml | 15 +- compiler/lib/freevars.mli | 11 +- compiler/lib/generate.ml | 168 ++-- compiler/lib/generate.mli | 1 + compiler/lib/lambda_lifting.ml | 4 +- compiler/lib/lambda_lifting_simple.ml | 354 ++++++++ compiler/lib/lambda_lifting_simple.mli | 53 ++ compiler/lib/linker.ml | 18 + compiler/lib/phisimpl.ml | 2 +- compiler/lib/stdlib.ml | 15 + compiler/lib/subst.ml | 199 +++-- compiler/lib/subst.mli | 43 +- compiler/tests-compiler/direct_calls.ml | 20 +- .../double-translation/direct_calls.ml | 224 +++++ .../tests-compiler/double-translation/dune | 14 + .../double-translation/dune.inc | 60 ++ .../effects_continuations.ml | 301 +++++++ .../double-translation/effects_exceptions.ml | 195 ++++ .../double-translation/effects_toplevel.ml | 89 ++ compiler/tests-compiler/effects.ml | 9 +- .../tests-compiler/effects_continuations.ml | 74 +- compiler/tests-compiler/effects_exceptions.ml | 58 +- compiler/tests-compiler/effects_toplevel.ml | 18 +- compiler/tests-compiler/lambda_lifting.ml | 9 +- compiler/tests-compiler/log | 715 +++++++++++++++ compiler/tests-compiler/test.bc | Bin 0 -> 181418 bytes compiler/tests-compiler/test.js | 125 +++ compiler/tests-compiler/util/util.ml | 67 +- compiler/tests-compiler/util/util.mli | 5 + .../lib-effects/double-translation/cmphash.ml | 24 + .../double-translation/cmphash.reference | 2 + .../lib-effects/double-translation/dune | 463 ++++++++++ .../lib-effects/double-translation/effects.ml | 226 +++++ .../double-translation/effects.reference | 18 + .../lib-effects/double-translation/evenodd.ml | 22 + .../double-translation/evenodd.reference | 1 + .../double-translation/manylive.ml | 27 + .../double-translation/manylive.reference | 1 + .../lib-effects/double-translation/marshal.ml | 21 + .../double-translation/marshal.reference | 1 + .../double-translation/overflow.ml | 40 + .../double-translation/overflow.reference | 1 + .../lib-effects/double-translation/partial.ml | 28 + .../double-translation/partial.reference | 1 + .../double-translation/reperform.ml | 37 + .../double-translation/reperform.reference | 22 + .../lib-effects/double-translation/sched.ml | 65 ++ .../double-translation/sched.reference | 1 + .../double-translation/shallow_state.ml | 48 + .../shallow_state.reference | 3 + .../double-translation/shallow_state_io.ml | 51 ++ .../shallow_state_io.reference | 3 + .../lib-effects/double-translation/test1.ml | 15 + .../double-translation/test1.reference | 1 + .../lib-effects/double-translation/test10.ml | 34 + .../double-translation/test10.reference | 1 + .../lib-effects/double-translation/test11.ml | 22 + .../double-translation/test11.reference | 2 + .../lib-effects/double-translation/test2.ml | 30 + .../double-translation/test2.reference | 6 + .../lib-effects/double-translation/test3.ml | 22 + .../double-translation/test3.reference | 2 + .../lib-effects/double-translation/test4.ml | 21 + .../double-translation/test4.reference | 1 + .../lib-effects/double-translation/test5.ml | 24 + .../double-translation/test5.reference | 1 + .../lib-effects/double-translation/test6.ml | 30 + .../double-translation/test6.reference | 3 + .../double-translation/test_lazy.ml | 49 ++ .../double-translation/test_lazy.reference | 3 + .../double-translation/unhandled_unlinked.ml | 7 + .../unhandled_unlinked.reference | 1 + .../double-translation/used_cont.ml | 21 + .../double-translation/used_cont.reference | 1 + runtime/effect.js | 113 ++- runtime/jslib.js | 36 +- runtime/stdlib.js | 111 ++- runtime/stdlib_modern.js | 101 +++ 88 files changed, 5134 insertions(+), 392 deletions(-) create mode 100644 compiler/lib/lambda_lifting_simple.ml create mode 100644 compiler/lib/lambda_lifting_simple.mli create mode 100644 compiler/tests-compiler/double-translation/direct_calls.ml create mode 100644 compiler/tests-compiler/double-translation/dune create mode 100644 compiler/tests-compiler/double-translation/dune.inc create mode 100644 compiler/tests-compiler/double-translation/effects_continuations.ml create mode 100644 compiler/tests-compiler/double-translation/effects_exceptions.ml create mode 100644 compiler/tests-compiler/double-translation/effects_toplevel.ml create mode 100644 compiler/tests-compiler/log create mode 100755 compiler/tests-compiler/test.bc create mode 100644 compiler/tests-compiler/test.js create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/cmphash.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/cmphash.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/dune create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/effects.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/effects.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/evenodd.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/evenodd.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/manylive.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/manylive.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/marshal.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/marshal.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/overflow.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/overflow.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/partial.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/partial.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/reperform.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/reperform.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/sched.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/sched.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/shallow_state.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/shallow_state.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/shallow_state_io.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/shallow_state_io.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test1.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test1.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test10.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test10.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test11.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test11.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test2.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test2.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test3.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test3.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test4.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test4.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test5.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test5.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test6.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test6.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test_lazy.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/test_lazy.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/unhandled_unlinked.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/unhandled_unlinked.reference create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/used_cont.ml create mode 100644 compiler/tests-ocaml/lib-effects/double-translation/used_cont.reference diff --git a/CHANGES.md b/CHANGES.md index 56919dd197..7138f327be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +# dev (2024-??) - ?? + +## Features/Changes +* Effects: add an optional feature of "dynamic switching" between CPS and direct style, resulting in better performance when no effect handler is installed + # 5.8.2 (2024-05-26) - Luc ## Bug fixes diff --git a/compiler/lib/build_info.ml b/compiler/lib/build_info.ml index 66b0e4b442..bdc0643021 100644 --- a/compiler/lib/build_info.ml +++ b/compiler/lib/build_info.ml @@ -56,6 +56,7 @@ let create kind = in [ "use-js-string", string_of_bool (Config.Flag.use_js_string ()) ; "effects", string_of_bool (Config.Flag.effects ()) + ; "doubletranslate", string_of_bool (Config.Flag.double_translation ()) ; "version", version ; "kind", string_of_kind kind ] @@ -122,9 +123,10 @@ let merge fname1 info1 fname2 info2 = match k, v1, v2 with | "kind", v1, v2 -> if Option.equal String.equal v1 v2 then v1 else Some (string_of_kind `Unknown) - | ("effects" | "use-js-string" | "version"), Some v1, Some v2 + | ("effects" | "doubletranslate" | "use-js-string" | "version"), Some v1, Some v2 when String.equal v1 v2 -> Some v1 - | (("effects" | "use-js-string" | "version") as key), v1, v2 -> + | (("effects" | "doubletranslate" | "use-js-string" | "version") as key), v1, v2 + -> raise (Incompatible_build_info { key; first = fname1, v1; second = fname2, v2 }) | _, Some v1, Some v2 when String.equal v1 v2 -> Some v1 @@ -139,6 +141,7 @@ let configure t = StringMap.iter (fun k v -> match k with - | "use-js-string" | "effects" -> Config.Flag.set k (bool_of_string v) + | "use-js-string" | "effects" | "doubletranslate" -> + Config.Flag.set k (bool_of_string v) | _ -> ()) t diff --git a/compiler/lib/code.ml b/compiler/lib/code.ml index 0558eab6d7..2985093da6 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -106,6 +106,8 @@ module Var : sig val set : 'a t -> key -> 'a -> unit + val length : 'a t -> int + val make : size -> 'a -> 'a t val iter : (key -> 'a -> unit) -> 'a t -> unit @@ -212,6 +214,8 @@ end = struct let set t x v = t.(x) <- v + let length t = Array.length t + let make () v = Array.make (count ()) v let iter f t = diff --git a/compiler/lib/code.mli b/compiler/lib/code.mli index e11a3c8b0b..144830e52b 100644 --- a/compiler/lib/code.mli +++ b/compiler/lib/code.mli @@ -99,6 +99,8 @@ module Var : sig val set : 'a t -> key -> 'a -> unit + val length : 'a t -> int + val make : size -> 'a -> 'a t val iter : (key -> 'a -> unit) -> 'a t -> unit diff --git a/compiler/lib/config.ml b/compiler/lib/config.ml index 9385a063ba..154c8628a2 100644 --- a/compiler/lib/config.ml +++ b/compiler/lib/config.ml @@ -70,6 +70,8 @@ module Flag = struct let effects = o ~name:"effects" ~default:false + let double_translation = o ~name:"doubletranslate" ~default:false + let staticeval = o ~name:"staticeval" ~default:true let share_constant = o ~name:"share" ~default:true diff --git a/compiler/lib/config.mli b/compiler/lib/config.mli index e4c86d37b0..c810a22107 100644 --- a/compiler/lib/config.mli +++ b/compiler/lib/config.mli @@ -41,6 +41,8 @@ module Flag : sig val effects : unit -> bool + val double_translation : unit -> bool + val genprim : unit -> bool val strictmode : unit -> bool diff --git a/compiler/lib/driver.ml b/compiler/lib/driver.ml index 86a329177a..e379cb8888 100644 --- a/compiler/lib/driver.ml +++ b/compiler/lib/driver.ml @@ -87,7 +87,7 @@ let phi p = let ( +> ) f g x = g (f x) -let map_fst f (x, y) = f x, y +let map_triple_fst f (x, y, z) = f x, y, z let effects ~deadcode_sentinal p = if Config.Flag.effects () @@ -104,9 +104,14 @@ let effects ~deadcode_sentinal p = Deadcode.f p else p, live_vars in - let p, cps = p |> Effects.f ~flow_info:info ~live_vars +> map_fst Lambda_lifting.f in - p, cps) - else p, (Code.Var.Set.empty : Effects.cps_calls) + p + |> Effects.f ~flow_info:info ~live_vars + +> map_triple_fst + (if Config.Flag.double_translation () then Fun.id else Lambda_lifting.f)) + else + ( p + , (Code.Var.Set.empty : Effects.cps_calls) + , (Code.Var.Set.empty : Effects.single_version_closures) ) let exact_calls profile ~deadcode_sentinal p = if not (Config.Flag.effects ()) @@ -193,7 +198,7 @@ let generate ~wrap_with_fun ~warn_on_unhandled_effect ~deadcode_sentinal - ((p, live_vars), cps_calls) = + ((p, live_vars), cps_calls, single_version_closures) = if times () then Format.eprintf "Start Generation...@."; let should_export = should_export wrap_with_fun in Generate.f @@ -201,6 +206,7 @@ let generate ~exported_runtime ~live_vars ~cps_calls + ~single_version_closures ~should_export ~warn_on_unhandled_effect ~deadcode_sentinal @@ -671,8 +677,14 @@ let full ~standalone ~wrap_with_fun ~profile ~link ~source_map formatter d p = | O3 -> o3) +> exact_calls ~deadcode_sentinal profile +> effects ~deadcode_sentinal - +> map_fst (if Config.Flag.effects () then fun x -> x else Generate_closure.f) - +> map_fst deadcode' + +> fun (p, cps_calls, single_version_closures) -> + let p, single_version_closures = + if Config.Flag.effects () + then p, single_version_closures + else Generate_closure.f p, single_version_closures + in + let p = deadcode' p in + p, cps_calls, single_version_closures in let emit = generate diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 97abfcf635..5343f86c32 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -38,6 +38,11 @@ open Code let debug = Debug.find "effects" +let double_translate = Config.Flag.double_translation + +let debug_print fmt = + if debug () then Format.(eprintf (fmt ^^ "%!")) else Format.(ifprintf err_formatter fmt) + let get_edges g src = try Hashtbl.find g src with Not_found -> Addr.Set.empty let add_edge g src dst = Hashtbl.replace g src (Addr.Set.add dst (get_edges g src)) @@ -220,7 +225,9 @@ let compute_needed_transformations ~cfg ~idom ~cps_needed ~blocks ~start = dominator of the block. [closure_of_jump] provides the name of the function correspoding to each block. [closures_of_alloc_site] provides the list of functions which should be defined in a given - block. Exception handlers are dealt with separately. + block. In case of double translation, the keys are the addresses of the + original (direct-style) blocks. Exception handlers are dealt with + separately. *) type jump_closures = { closure_of_jump : Var.t Addr.Map.t @@ -249,13 +256,16 @@ let jump_closures blocks_to_transform idom : jump_closures = type cps_calls = Var.Set.t +type single_version_closures = 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 + ; closure_info : (Addr.t, Var.t list * (Addr.t * Var.t list)) Hashtbl.t + (* Associates a function's address with its CPS parameters and CPS continuation *) ; cps_needed : Var.Set.t ; blocks_to_transform : Addr.Set.t ; is_continuation : (Addr.t, [ `Param of Var.t | `Loop ]) Hashtbl.t @@ -264,6 +274,12 @@ type st = ; live_vars : Deadcode.variable_uses ; flow_info : Global_flow.info ; cps_calls : cps_calls ref + ; cps_pc_of_direct : (int, int) Hashtbl.t + (* Mapping from direct-style to CPS addresses of functions (used when + double translation is enabled) *) + ; single_version_closures : single_version_closures ref + (* Functions that exist in only one version, even when double translation + is enabled *) } let add_block st block = @@ -271,15 +287,37 @@ let add_block st block = st.new_blocks <- Addr.Map.add free_pc block blocks, free_pc + 1; free_pc +let mk_cps_pc_of_direct cps_pc_of_direct free_pc pc = + if double_translate () + then ( + try Hashtbl.find cps_pc_of_direct pc, free_pc + with Not_found -> + Hashtbl.add cps_pc_of_direct pc free_pc; + free_pc, free_pc + 1) + else pc, free_pc + +(* Provide the address of the CPS translation of a block *) +let mk_cps_pc_of_direct ~st pc = + let new_blocks, free_pc = st.new_blocks in + let cps_pc, free_pc = mk_cps_pc_of_direct st.cps_pc_of_direct free_pc pc in + st.new_blocks <- new_blocks, free_pc; + cps_pc + +let cps_cont_of_direct ~st (pc, args) = mk_cps_pc_of_direct ~st pc, args + 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 ~branch loc = + debug_print "@[allocate_closure ~branch:(%a)@,@]" Code.Print.last branch; let block = { params = []; body; branch } in let pc = add_block st block in let name = Var.fresh () in [ Let (name, Closure (params, (pc, []))), loc ], name +let mark_single_version ~st cname = + st.single_version_closures := Var.Set.add cname !(st.single_version_closures) + let tail_call ~st ?(instrs = []) ~exact ~check ~f args loc = assert (exact || check); let ret = Var.fresh () in @@ -288,7 +326,7 @@ let tail_call ~st ?(instrs = []) ~exact ~check ~f args loc = let cps_branch ~st ~src (pc, args) loc = match Addr.Set.mem pc st.blocks_to_transform with - | false -> [], (Branch (pc, args), loc) + | false -> [], (Branch (mk_cps_pc_of_direct ~st pc, args), loc) | true -> let args, instrs = if List.is_empty args && Hashtbl.mem st.is_continuation pc @@ -302,11 +340,13 @@ let cps_branch ~st ~src (pc, args) loc = (* 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 loc + let f = closure_of_pc ~st pc in + mark_single_version ~st f; + tail_call ~st ~instrs ~exact:true ~check ~f args loc let cps_jump_cont ~st ~src ((pc, _) as cont) loc = match Addr.Set.mem pc st.blocks_to_transform with - | false -> cont + | false -> cps_cont_of_direct ~st cont | true -> let call_block = let body, branch = cps_branch ~st ~src cont loc in @@ -314,7 +354,52 @@ let cps_jump_cont ~st ~src ((pc, _) as cont) loc = in call_block, [] -let allocate_continuation ~st ~alloc_jump_closures ~split_closures pc x cont loc = +let do_alloc_jump_closures ~st (to_allocate : (Var.t * Code.Addr.t) list) : + (instr * loc) list = + List.map to_allocate ~f:(fun (cname, jump_pc) -> + let params = + let jump_block = Addr.Map.find jump_pc st.blocks in + (* For a function to be used as a continuation, it needs + exactly one parameter. So, we add a parameter if + needed. *) + if List.is_empty jump_block.params && Hashtbl.mem st.is_continuation jump_pc + then + (* We reuse the name of the value of the tail call of + one a the previous blocks. When there is a single + previous block, this is exactly what we want. For a + merge node, the variable is not used so we can just + as well use it. For a loop, we don't want the + return value of a call right before entering the + loop to be overriden by the value returned by the + last call in the loop. So, we may need to use an + additional closure to bind it, and we have to use a + fresh variable here *) + let x = + match Hashtbl.find st.is_continuation jump_pc with + | `Param x -> x + | `Loop -> Var.fresh () + in + [ x ] + else jump_block.params + in + mark_single_version ~st cname; + let cps_jump_pc = mk_cps_pc_of_direct ~st jump_pc in + Let (cname, Closure (params, (cps_jump_pc, []))), noloc) + +let allocate_continuation + ~st + ~alloc_jump_closures + ~split_closures + ~direct_pc + src_pc + x + cont + loc = + debug_print + "@[allocate_continuation ~direct_pc:%d ~src_pc:%d ~cont_pc:%d@,@]" + direct_pc + src_pc + (fst 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 @@ -323,18 +408,18 @@ let allocate_continuation ~st ~alloc_jump_closures ~split_closures pc x cont loc 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 + let _, args = cont in if (match args with | [] -> true | [ x' ] -> Var.equal x x' | _ -> false) && - match Hashtbl.find st.is_continuation pc' with + match Hashtbl.find st.is_continuation direct_pc with | `Param _ -> true | `Loop -> st.live_vars.(Var.idx x) = List.length args - then alloc_jump_closures, closure_of_pc ~st pc' + then alloc_jump_closures, closure_of_pc ~st direct_pc else - let body, branch = cps_branch ~st ~src:pc cont loc in + let body, branch = cps_branch ~st ~src:src_pc cont loc in let inner_closures, outer_closures = (* For [Pushtrap], we need to separate the closures corresponding to the exception handler body (that may make @@ -342,15 +427,18 @@ let allocate_continuation ~st ~alloc_jump_closures ~split_closures pc x cont loc of the exception handler. *) if not split_closures then alloc_jump_closures, [] - else if is_merge_node st.cfg pc' + else if is_merge_node st.cfg direct_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 + let to_allocate = + try Addr.Map.find src_pc st.jc.closures_of_alloc_site with Not_found -> [] + in + let inner, outer = + List.partition + ~f:(fun (_, pc'') -> dominates st.cfg st.idom direct_pc pc'') + to_allocate + in + do_alloc_jump_closures ~st inner, do_alloc_jump_closures ~st outer in let body, branch = allocate_closure ~st ~params:[ x ] ~body:(inner_closures @ body) ~branch loc @@ -362,7 +450,7 @@ let cps_last ~st ~alloc_jump_closures pc ((last, last_loc) : last * loc) ~k : match last with | Return x -> assert (List.is_empty alloc_jump_closures); - (* Is the number of successive 'returns' is unbounded is CPS, it + (* If the number of successive 'returns' is unbounded in 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 ] last_loc @@ -426,18 +514,26 @@ let cps_last ~st ~alloc_jump_closures pc ((last, last_loc) : last * loc) ~k : | 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 -> alloc_jump_closures, (last, last_loc) + | false -> + let body_cont = cps_cont_of_direct ~st body_cont in + let handler_cont = cps_cont_of_direct ~st handler_cont in + let last = Pushtrap (body_cont, exn, handler_cont) in + alloc_jump_closures, (last, last_loc) | true -> let constr_cont, exn_handler = allocate_continuation ~st ~alloc_jump_closures ~split_closures:true + ~direct_pc:handler_pc pc exn handler_cont + (* We pass the direct pc, the mapping to CPS is made + by the called functions. *) last_loc in + mark_single_version ~st exn_handler; let push_trap = Let (Var.fresh (), Prim (Extern "caml_push_trap", [ Pv exn_handler ])), noloc in @@ -457,61 +553,185 @@ let cps_last ~st ~alloc_jump_closures pc ((last, last_loc) : last * loc) ~k : @ ((Let (exn_handler, Prim (Extern "caml_pop_trap", [])), noloc) :: body) , branch )) -let cps_instr ~st (instr : instr) : instr = +module DuplicateSt : sig + type st = Addr.t Addr.Map.t * Addr.t * block Addr.Map.t + + type 'a m = st -> st * 'a + + val return : 'a -> 'a m + + val ( let* ) : 'a m -> ('a -> 'b m) -> 'b m + + val run : 'a m -> st -> st * 'a + + val find_or_add_pc : Addr.t -> Addr.t m + + val add_block : Addr.t -> block -> unit m + + val list_fold_left : f:('acc -> 'a -> 'acc m) -> init:'acc -> 'a list -> 'acc m + + val array_map : f:('a -> 'b m) -> 'a array -> 'b array m +end = struct + type st = Addr.t Addr.Map.t * Addr.t * block Addr.Map.t + + type 'a m = st -> st * 'a + + let return x st = st, x + + let bind f g st = + let st, a = f st in + g a st + + let ( let* ) f g st = bind f g st + + let run f st = f st + + let find_or_add_pc pc (new_pc_of_old, free_pc, new_blocks) = + try (new_pc_of_old, free_pc, new_blocks), Addr.Map.find pc new_pc_of_old + with Not_found -> + (Addr.Map.add pc free_pc new_pc_of_old, free_pc + 1, new_blocks), free_pc + + let add_block pc block (new_pc_of_old, free_pc, new_blocks) = + (new_pc_of_old, free_pc, Addr.Map.add pc block new_blocks), () + + let list_fold_left ~(f : 'acc -> 'a -> 'b m) ~(init : 'acc) (l : 'a list) (st : st) = + List.fold_left + l + ~f:(fun (st, acc) x -> + let st, acc = f acc x st in + st, acc) + ~init:(st, init) + + let array_map ~f arr st = Array.fold_left_map arr ~f:(fun st x -> f x st) ~init:st +end + +let duplicate_code ~st pc = + let rec duplicate ~blocks pc state = + Code.traverse + { fold = Code.fold_children } + (fun pc (state, ()) -> + state + |> DuplicateSt.run + (let open DuplicateSt in + let block = Addr.Map.find pc st.blocks in + (* Also duplicate nested functions *) + let* rev_new_body = + list_fold_left + block.body + ~f:(fun body_acc (instr, loc) -> + match instr with + | Let (f, Closure (params, (pc', args))) -> + let* () = duplicate ~blocks pc' in + let* new_pc' = find_or_add_pc pc' in + return + ((Let (f, Closure (params, (new_pc', args))), loc) :: body_acc) + | i -> return ((i, loc) :: body_acc)) + ~init:[] + in + let new_body = List.rev rev_new_body in + (* Update branch targets *) + let update (pc, args) = + let* pc = find_or_add_pc pc in + return (pc, args) + in + let* branch = + match block.branch with + | ((Return _ | Raise _ | Stop) as b), loc -> return (b, loc) + | Branch cont, loc -> + let* cont = update cont in + return (Branch cont, loc) + | Cond (x, c1, c2), loc -> + let* c1 = update c1 in + let* c2 = update c2 in + return (Cond (x, c1, c2), loc) + | Switch (x, conts), loc -> + let* conts = array_map conts ~f:update in + return (Switch (x, conts), loc) + | Pushtrap (c1, x, c2), loc -> + let* c1 = update c1 in + let* c2 = update c2 in + return (Pushtrap (c1, x, c2), loc) + | Poptrap cont, loc -> + let* cont = update cont in + return (Poptrap cont, loc) + in + let new_block = { block with body = new_body; branch } in + let* new_pc = find_or_add_pc pc in + let* () = add_block new_pc new_block in + return ())) + pc + blocks + (state, ()) + in + let new_blocks, free_pc = st.new_blocks in + let (new_pc_of_old, free_pc, new_blocks), () = + duplicate ~blocks:st.blocks pc (Addr.Map.empty, free_pc, new_blocks) + in + st.new_blocks <- new_blocks, free_pc; + Addr.Map.find pc new_pc_of_old + +let cps_instr ~st (instr : instr) : instr list = match instr with - | Let (x, Closure (params, (pc, _))) when Var.Set.mem x st.cps_needed -> + | Let (x, Closure (_, (pc, _))) + when Var.Set.mem x st.cps_needed && Var.Set.mem x !(st.single_version_closures) -> (* 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 cps_params, cps_cont = Hashtbl.find st.closure_info pc in + [ Let (x, Closure (cps_params, cps_cont)) ] + | Let (x, Closure (params, ((pc, _) as cont))) + when Var.Set.mem x st.cps_needed && not (Var.Set.mem x !(st.single_version_closures)) + -> + let direct_c = Var.fork x in + let cps_c = Var.fork x in + let cps_params, cps_cont = Hashtbl.find st.closure_info pc in + [ Let (direct_c, Closure (params, cont)) + ; Let (cps_c, Closure (cps_params, cps_cont)) + ; Let (x, Prim (Extern "caml_cps_closure", [ Pv direct_c; Pv cps_c ])) + ] + | Let (x, Closure (params, (pc, args))) + when (not (Var.Set.mem x st.cps_needed)) + && not (Var.Set.mem x !(st.single_version_closures)) -> + (* This function definition does not need to be in CPS. However, we must + duplicate its body lest the same function body will appear twice in + the program with exactly the same variables that are bound, resulting + in double definition, which is not allowed. *) + let new_pc = duplicate_code ~st pc in + (* We leave [params] and [args] unchanged here because they will be + replaced with fresh variables in a later, global substitution pass. *) + [ Let (x, Closure (params, (new_pc, args))) ] | Let (x, Prim (Extern "caml_alloc_dummy_function", [ size; arity ])) -> ( match arity with | Pc (Int a) -> - Let - ( x - , Prim (Extern "caml_alloc_dummy_function", [ size; Pc (Int (Int32.succ a)) ]) - ) + [ Let + ( x + , 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 *) - assert (Global_flow.exact_call st.flow_info f (List.length args)); - Let (x, Apply { f; args; exact = true }) + assert ( + (* If this function is unknown to the global flow analysis, then it was + introduced by the lambda lifting and does not require CPS *) + Var.idx f >= Var.Tbl.length st.flow_info.info_approximation + || Global_flow.exact_call st.flow_info f (List.length args)); + [ Let (x, Apply { f; args; exact = true }) ] + | Let (_, Apply { f; args = _; exact = _ }) + when Var.Set.mem f !(st.single_version_closures) -> + (* Nothing to do for single-version functions. *) + [ instr ] | Let (_, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> assert false - | _ -> instr + | _ -> [ instr ] -let cps_block ~st ~k pc block = +let cps_block ~st ~k ~lifter_functions ~orig_pc block = + debug_print "cps_block %d\n" orig_pc; + debug_print "cps pc evaluates to %d\n" (mk_cps_pc_of_direct ~st orig_pc); let alloc_jump_closures = - match Addr.Map.find pc st.jc.closures_of_alloc_site with - | to_allocate -> - List.map to_allocate ~f:(fun (cname, jump_pc) -> - let params = - let jump_block = Addr.Map.find jump_pc st.blocks in - (* For a function to be used as a continuation, it needs - exactly one parameter. So, we add a parameter if - needed. *) - if List.is_empty jump_block.params && Hashtbl.mem st.is_continuation jump_pc - then - (* We reuse the name of the value of the tail call of - one a the previous blocks. When there is a single - previous block, this is exactly what we want. For a - merge node, the variable is not used so we can just - as well use it. For a loop, we don't want the - return value of a call right before entering the - loop to be overriden by the value returned by the - last call in the loop. So, we may need to use an - additional closure to bind it, and we have to use a - fresh variable here *) - let x = - match Hashtbl.find st.is_continuation jump_pc with - | `Param x -> x - | `Loop -> Var.fresh () - in - [ x ] - else jump_block.params - in - Let (cname, Closure (params, (jump_pc, []))), noloc) + match Addr.Map.find orig_pc st.jc.closures_of_alloc_site with + | to_allocate -> do_alloc_jump_closures ~st to_allocate | exception Not_found -> [] in @@ -530,7 +750,11 @@ let cps_block ~st ~k pc block = Some (fun ~k -> let exact = - exact || Global_flow.exact_call st.flow_info f (List.length args) + exact + (* If this function is unknown to the global flow analysis, then it was + introduced by the lambda lifting and is exact *) + || Var.idx f >= Var.Tbl.length st.flow_info.info_approximation + || Global_flow.exact_call st.flow_info f (List.length args) in tail_call ~st ~exact ~check:true ~f (args @ [ k ]) loc) | Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ]) -> @@ -555,22 +779,30 @@ let cps_block ~st ~k pc block = let rewritten_block = match List.split_last block.body, block.branch with + | Some (_, (Let (_, Apply { f; args = _; exact = _ }), _)), ((Return _ | Branch _), _) + when Var.Set.mem f lifter_functions -> + (* No need to construct a continuation as no effect can be performed from a + lifter function *) + None | Some (body_prefix, (Let (x, e), loc)), (Return ret, _loc_ret) -> Option.map (rewrite_instr x e loc) ~f:(fun f -> assert (List.is_empty alloc_jump_closures); assert (Var.equal x ret); let instrs, branch = f ~k in body_prefix, instrs, branch) - | Some (body_prefix, (Let (x, e), loc)), (Branch cont, loc_ret) -> + | Some (body_prefix, (Let (x, e), loc)), (Branch ((direct_pc, _) as cont), loc_ret) -> Option.map (rewrite_instr x e loc) ~f:(fun f -> let constr_cont, k' = allocate_continuation ~st ~alloc_jump_closures ~split_closures:false - pc + ~direct_pc + orig_pc x cont + (* We pass the direct pc, the mapping to CPS is made by + the called functions. *) loc_ret in let instrs, branch = f ~k:k' in @@ -583,27 +815,192 @@ let cps_block ~st ~k pc block = let body, last = match rewritten_block with | Some (body_prefix, last_instrs, last) -> - List.map body_prefix ~f:(fun (i, loc) -> cps_instr ~st i, loc) @ last_instrs, last + let body_prefix = + (* For each instruction... *) + List.map body_prefix ~f:(fun (i, loc) -> + (* ... apply [cps_instr] ... *) + cps_instr ~st i + (* ... and decorate all resulting instructions with [loc] *) + |> List.map ~f:(fun i -> i, loc)) + |> List.concat + in + body_prefix @ last_instrs, last | None -> - let last_instrs, last = cps_last ~st ~alloc_jump_closures pc block.branch ~k in + let last_instrs, last = + cps_last ~st ~alloc_jump_closures orig_pc block.branch ~k + in let body = - List.map block.body ~f:(fun (i, loc) -> cps_instr ~st i, loc) @ last_instrs + (* For each instruction... *) + List.map block.body ~f:(fun (i, loc) -> + (* ... apply [cps_instr] ... *) + cps_instr ~st i + (* ... and decorate all resulting instructions with [loc] *) + |> List.map ~f:(fun i -> i, loc)) + |> List.concat in - body, last + body @ last_instrs, last in - { params = (if Addr.Set.mem pc st.blocks_to_transform then [] else block.params) + { params = (if Addr.Set.mem orig_pc st.blocks_to_transform then [] else block.params) ; body ; branch = last } -let cps_transform ~live_vars ~flow_info ~cps_needed p = +let rewrite_direct_instr ~st (instr, loc) = + match instr with + | Let (x, Closure (_, (pc, _))) when Var.Set.mem x st.cps_needed -> + (* Add the continuation parameter, and change the initial block if + needed *) + let cps_params, cps_cont = Hashtbl.find st.closure_info pc in + Let (x, Closure (cps_params, cps_cont)), loc + | Let (x, Prim (Extern "caml_alloc_dummy_function", [ size; arity ])) -> ( + match arity with + | Pc (Int a) -> + ( Let + ( x + , Prim + (Extern "caml_alloc_dummy_function", [ size; Pc (Int (Int32.succ a)) ]) + ) + , loc ) + | _ -> 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 *) + assert (Global_flow.exact_call st.flow_info f (List.length args)); + Let (x, Apply { f; args; exact = true }), loc + | Let (_, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> + assert false + | _ -> instr, loc + +(* If double-translating, modify all function applications and closure + creations to take into account the fact that some closures must now have a + CPS version. Also rewrite the effect primitives to switch to the CPS version + of functions (for resume) or fail (for perform). + If not double-translating, then just add continuation arguments to function + definitions, and mark as exact all non-CPS calls. *) +let rewrite_direct_block + ~st + ~cps_needed + ~closure_info + ~ident_fn + ~pc + ~lifter_functions + block = + debug_print "@[rewrite_direct_block %d@,@]" pc; + if double_translate () + then + let rewrite_instr = function + | Let (x, Closure (params, ((pc, _) as cont))) + when Var.Set.mem x cps_needed && not (Var.Set.mem x lifter_functions) -> + let direct_c = Var.fork x in + let cps_c = Var.fork x in + let cps_params, cps_cont = Hashtbl.find closure_info pc in + [ Let (direct_c, Closure (params, cont)) + ; Let (cps_c, Closure (cps_params, cps_cont)) + ; Let (x, Prim (Extern "caml_cps_closure", [ Pv direct_c; Pv cps_c ])) + ] + | Let (x, Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ])) -> + (* Pass the identity as a continuation and pass to + [caml_trampoline_cps], which will 1. install a trampoline, 2. call + the CPS version of [f] and 3. handle exceptions. *) + let k = Var.fresh_n "cont" in + let args = Var.fresh_n "args" in + [ Let (k, Prim (Extern "caml_resume_stack", [ Pv stack; Pv ident_fn ])) + ; Let (args, Prim (Extern "%js_array", [ Pv arg; Pv k ])) + ; Let (x, Prim (Extern "caml_trampoline_cps", [ Pv f; Pv args ])) + ] + | Let (x, Prim (Extern "%perform", [ Pv effect ])) -> + (* Perform the effect, which should call the "Unhandled effect" handler. *) + let k = Int 0l in + (* Dummy continuation *) + [ Let (x, Prim (Extern "caml_perform_effect", [ Pv effect; Pc (Int 0l); Pc k ])) + ] + | Let (x, Prim (Extern "%reperform", [ Pv effect; Pv continuation ])) -> + (* Similar to previous case *) + let k = Int 0l in + [ Let + ( x + , Prim (Extern "caml_perform_effect", [ Pv effect; Pv continuation; Pc k ]) + ) + ] + | (Let _ | Assign _ | Set_field _ | Offset_ref _ | Array_set _) as instr -> + [ instr ] + in + let body = + (* For each instruction... *) + List.concat_map block.body ~f:(fun (i, loc) -> + (* ... apply [rewrite_instr] ... *) + rewrite_instr i + (* ... and decorate all resulting instructions with [loc] *) + |> List.map ~f:(fun i -> i, loc)) + in + { block with body } + else { block with body = List.map ~f:(rewrite_direct_instr ~st) block.body } + +(* Apply a substitution in a set of blocks *) +let subst_in_blocks blocks s = + Addr.Map.mapi + (fun pc block -> + if debug () + then ( + debug_print "@[block before first subst: @,"; + Code.Print.block (fun _ _ -> "") pc block; + debug_print "@]"); + let res = Subst.Excluding_Binders.block s block in + if debug () + then ( + debug_print "@[block after first subst: @,"; + Code.Print.block (fun _ _ -> "") pc res; + debug_print "@]"); + res) + blocks + +(* Apply a substitution in a set of blocks, including to bound variables *) +let subst_bound_in_blocks blocks s = + Addr.Map.mapi + (fun pc block -> + if debug () + then ( + debug_print "@[block before first subst: @,"; + Code.Print.block (fun _ _ -> "") pc block; + debug_print "@]"); + let res = Subst.Including_Binders.block s block in + if debug () + then ( + debug_print "@[block after first subst: @,"; + Code.Print.block (fun _ _ -> "") pc res; + debug_print "@]"); + res) + blocks + +let cps_transform ~lifter_functions ~live_vars ~flow_info ~cps_needed p = + (* Define an identity function, needed for the boilerplate around "resume" *) + let ident_fn = Var.fresh_n "identity" in let closure_info = Hashtbl.create 16 in let cps_calls = ref Var.Set.empty in - let p = + let single_version_closures = + ref + (if double_translate () + then lifter_functions + else + Code.fold_closures + p + (fun name _ _ acc -> + match name with + | None -> acc + | Some name -> Var.Set.add name acc) + Var.Set.empty) + in + let cps_pc_of_direct = Hashtbl.create 512 in + let p, bound_subst, param_subst, new_blocks = Code.fold_closures_innermost_first p - (fun name_opt _ (start, args) ({ blocks; free_pc; _ } as p) -> + (fun name_opt + params + (start, args) + (({ blocks; free_pc; _ } as p), bound_subst, param_subst, new_blocks) -> + Option.iter name_opt ~f:(fun v -> + debug_print "@[cname = %s@,@]" @@ Var.to_string v); (* 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 @@ -622,9 +1019,10 @@ let cps_transform ~live_vars ~flow_info ~cps_needed p = 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 + (* We need to handle the CPS calls that are at toplevel, except + if we double-translate (in which case they are like all other + CPS calls from direct code). *) + not (double_translate ()) in let blocks_to_transform, matching_exn_handler, is_continuation = if should_compute_needed_transformations @@ -640,7 +1038,8 @@ let cps_transform ~live_vars ~flow_info ~cps_needed p = 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 + if should_compute_needed_transformations + && Addr.Map.mem start' closure_jc.closures_of_alloc_site then start', [], blocks', free_pc + 1 else start, args, blocks, free_pc in @@ -659,16 +1058,21 @@ let cps_transform ~live_vars ~flow_info ~cps_needed p = ; flow_info ; live_vars ; cps_calls + ; cps_pc_of_direct + ; single_version_closures } in let function_needs_cps = match name_opt with - | Some _ -> should_compute_needed_transformations + | Some name -> + should_compute_needed_transformations + && not (Var.Set.mem name lifter_functions) | None -> - (* 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]. *) - not (Addr.Set.is_empty blocks_to_transform) + (* Toplevel code: if we double-translate, no need to handle it + specially: CPS calls in it are like all other CPS calls from + direct code. Otherwise, it needs to wrapped within a + [caml_callback], but only if it performs CPS calls. *) + (not (double_translate ())) && not (Addr.Set.is_empty blocks_to_transform) in if debug () then ( @@ -685,57 +1089,188 @@ let cps_transform ~live_vars ~flow_info ~cps_needed p = start blocks ()); - let blocks = - let transform_block = - if function_needs_cps + let blocks, free_pc, bound_subst, param_subst, new_blocks = + (* For every block in the closure, + 1. CPS-translate it if needed. If we double-translate, add its CPS + translation to the block map at a fresh address. Otherwise, + just replace the original block. + 2. If we double-translate, keep the direct-style block but modify function + definitions to add the CPS version where needed, and turn uses of %resume + and %perform into switchings to CPS. *) + let param_subst, transform_block = + if function_needs_cps && double_translate () + then ( + let k = Var.fresh_n "cont" in + let cps_start = mk_cps_pc_of_direct ~st start in + let params' = List.map ~f:Var.fork params in + let param_subst = + List.fold_left2 + ~f:(fun m p p' -> Var.Map.add p p' m) + ~init:param_subst + params + params' + in + let cps_args = List.map ~f:(Subst.from_map param_subst) args in + Hashtbl.add + st.closure_info + initial_start + (params' @ [ k ], (cps_start, cps_args)); + ( param_subst + , fun pc block -> + let cps_block = cps_block ~st ~lifter_functions ~k ~orig_pc:pc block in + ( rewrite_direct_block + ~st + ~cps_needed + ~closure_info:st.closure_info + ~ident_fn + ~pc + ~lifter_functions + block + , Some cps_block ) )) + else if function_needs_cps && not (double_translate ()) 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) + Hashtbl.add st.closure_info initial_start (params @ [ k ], (start, args)); + ( param_subst + , fun pc block -> cps_block ~st ~lifter_functions ~k ~orig_pc:pc block, None + )) else - fun _ block -> - { block with - body = List.map block.body ~f:(fun (i, loc) -> cps_instr ~st i, loc) - } + ( param_subst + , fun pc block -> + ( rewrite_direct_block + ~st + ~cps_needed + ~closure_info:st.closure_info + ~ident_fn + ~pc + ~lifter_functions + block + , None ) ) in - Code.traverse - { fold = Code.fold_children } - (fun pc blocks -> - Addr.Map.add pc (transform_block pc (Addr.Map.find pc blocks)) blocks) - start - st.blocks - st.blocks + let blocks = + Code.traverse + { fold = Code.fold_children } + (fun pc blocks -> + let block, cps_block_opt = transform_block pc (Addr.Map.find pc blocks) in + let blocks = Addr.Map.add pc block blocks in + match cps_block_opt with + | None -> blocks + | Some b -> + let cps_pc = mk_cps_pc_of_direct ~st pc in + let new_blocks, free_pc = st.new_blocks in + st.new_blocks <- Addr.Map.add cps_pc b new_blocks, free_pc; + Addr.Map.add cps_pc b blocks) + start + st.blocks + st.blocks + in + let new_blocks_this_clos, free_pc = st.new_blocks in + (* If double-translating, all variables bound in the CPS version will have to be + subst with fresh ones to avoid clashing with the definitions in the original + blocks (the actual substitution is done later). *) + let bound_subst = + if double_translate () + then + let bound = + Addr.Map.fold + (fun _ block bound -> + Var.Set.union + bound + (Freevars.block_bound_vars ~closure_params:true block)) + new_blocks_this_clos + Var.Set.empty + in + Var.Set.fold (fun v m -> Var.Map.add v (Var.fork v) m) bound bound_subst + else bound_subst + in + let blocks = Addr.Map.fold Addr.Map.add new_blocks_this_clos blocks in + ( blocks + , free_pc + , bound_subst + , param_subst + , Addr.Map.union (fun _ _ -> assert false) new_blocks new_blocks_this_clos ) in - let new_blocks, free_pc = st.new_blocks in - let blocks = Addr.Map.fold Addr.Map.add new_blocks blocks in - { p with blocks; free_pc }) - p + { p with blocks; free_pc }, bound_subst, param_subst, new_blocks) + (p, Var.Map.empty, Var.Map.empty, Addr.Map.empty) in + let bound_subst = Subst.from_map bound_subst in + let new_blocks = subst_bound_in_blocks new_blocks bound_subst in + (* Also apply that substitution to the sets of CPS calls and lifter functions *) + cps_calls := Var.Set.map bound_subst !cps_calls; + single_version_closures := Var.Set.map bound_subst !single_version_closures; + (* All variables that were a closure parameter in a direct-style block must be + substituted by a fresh name. *) + let param_subst = Subst.from_map param_subst in + let new_blocks = subst_in_blocks new_blocks param_subst in + (* Also apply that 2nd substitution to the sets of CPS calls and lifter functions *) + cps_calls := Var.Set.map param_subst !cps_calls; + single_version_closures := Var.Set.map param_subst !single_version_closures; let p = - 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, []))), noloc - ; Let (args, Prim (Extern "%js_array", [])), noloc - ; Let (res, Prim (Extern "caml_callback", [ Pv main; Pv args ])), noloc - ] - ; branch = Return res, noloc - } - p.blocks - in - { start = new_start; blocks; free_pc = new_start + 1 } + { p with + blocks = + Addr.Map.merge + (fun _ a b -> + match a, b with + | _, Some b -> Some b + | a, None -> a) + p.blocks + new_blocks + } + in + let p = + if double_translate () + then + (* Initialize the global fiber stack and define a global identity function, + needed to translate [%resume] *) + let id_pc = p.free_pc in + let blocks = + let id_param = Var.fresh_n "x" in + Addr.Map.add + id_pc + { params = [ id_param ]; body = []; branch = Return id_param, noloc } + p.blocks + in + let id_arg = Var.fresh_n "x" in + let dummy = Var.fresh_n "dummy" in + let new_start = id_pc + 1 in + let blocks = + Addr.Map.add + new_start + { params = [] + ; body = + [ Let (ident_fn, Closure ([ id_arg ], (id_pc, [ id_arg ]))), noloc + ; Let (dummy, Prim (Extern "caml_initialize_fiber_stack", [])), noloc + ] + ; branch = Branch (p.start, []), noloc + } + blocks + in + { start = new_start; blocks; free_pc = new_start + 1 } + else + match Hashtbl.find_opt closure_info p.start with + | None -> p + | Some (cps_params, cps_cont) -> + (* 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 (cps_params, cps_cont)), noloc + ; Let (args, Prim (Extern "%js_array", [])), noloc + ; Let (res, Prim (Extern "caml_callback", [ Pv main; Pv args ])), noloc + ] + ; branch = Return res, noloc + } + p.blocks + in + { start = new_start; blocks; free_pc = new_start + 1 } in - p, !cps_calls + p, !cps_calls, !single_version_closures (****) @@ -820,13 +1355,13 @@ let rewrite_toplevel ~cps_needed p = (****) -let split_blocks ~cps_needed (p : Code.program) = +let split_blocks ~cps_needed ~lifter_functions (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"), _))) -> + | Let (x, (Apply _ | Prim (Extern ("%resume" | "%perform" | "%reperform"), _))) -> ( ((not (List.is_empty r)) || match fst branch with @@ -834,6 +1369,11 @@ let split_blocks ~cps_needed (p : Code.program) = | Return x' -> not (Var.equal x x') | _ -> true) && Var.Set.mem x cps_needed + && + match i with + | Let (_, Apply { f; args = _; exact = _ }) -> + not (Var.Set.mem f lifter_functions) + | _ -> true) | _ -> false in let rec split (p : Code.program) pc block accu l branch = @@ -925,9 +1465,40 @@ let remove_empty_blocks ~live_vars (p : Code.program) : Code.program = let f ~flow_info ~live_vars p = let t = Timer.make () 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 ~flow_info ~cps_needed p in + let p, lifter_functions, cps_needed = + if double_translate () + then ( + let p, lifter_functions, liftings = Lambda_lifting_simple.f ~to_lift:cps_needed p in + let cps_needed = + Var.Set.map + (fun f -> try Subst.from_map liftings f with Not_found -> f) + cps_needed + in + if debug () + then ( + debug_print "@[Lifting closures:@,"; + lifter_functions |> Var.Set.iter (fun v -> debug_print "%s,@ " (Var.to_string v)); + debug_print "@]"; + debug_print "@[cps_needed (after lifting) = @["; + Var.Set.iter (fun v -> debug_print "%s,@ " (Var.to_string v)) cps_needed; + debug_print "@]@,@]"; + debug_print "@[After lambda lifting...@,"; + Code.Print.program (fun _ _ -> "") p; + debug_print "@]"); + p, lifter_functions, cps_needed) + else + let p, cps_needed = rewrite_toplevel ~cps_needed p in + p, Var.Set.empty, cps_needed + in + let p = split_blocks ~cps_needed ~lifter_functions p in + let p, cps_calls, single_version_closures = + cps_transform ~lifter_functions ~live_vars ~flow_info ~cps_needed p + in if Debug.find "times" () then Format.eprintf " effects: %a@." Timer.print t; Code.invariant p; - p, cps_calls + if debug () + then ( + debug_print "@[After CPS transform:@,"; + Code.Print.program (fun _ _ -> "") p; + debug_print "@]"); + p, cps_calls, single_version_closures diff --git a/compiler/lib/effects.mli b/compiler/lib/effects.mli index 29095a19db..1eabe9e638 100644 --- a/compiler/lib/effects.mli +++ b/compiler/lib/effects.mli @@ -20,8 +20,21 @@ type cps_calls = Code.Var.Set.t val remove_empty_blocks : live_vars:Deadcode.variable_uses -> Code.program -> Code.program +type single_version_closures = Code.Var.Set.t + val f : flow_info:Global_flow.info -> live_vars:Deadcode.variable_uses -> Code.program - -> Code.program * cps_calls + -> Code.program * cps_calls * single_version_closures +(** Perform a partial CPS transform to translate the result effect handlers. + + In addition, if the [doubletranslate] feature is enabled, functions are + created in two versions (direct-style and CPS) and the generated program + switches to CPS when entering the first effect handler, and back to direct + style when exiting it. In addition to this dynamic behavior, the transform + performs a static analysis to detect which functions do not need to be + CPS-transformed. As a consequence, some functions become pairs of functions + while others remain in a single version. This functions returns the + locations of CPS calls, and the set of closures that exist in a single + version (which can be in direct style or CPS). *) diff --git a/compiler/lib/flow.ml b/compiler/lib/flow.ml index 412af3089a..a7fac3c257 100644 --- a/compiler/lib/flow.ml +++ b/compiler/lib/flow.ml @@ -454,7 +454,7 @@ let f ?skip_param p = } in let s = build_subst info vars in - let p = Subst.program (Subst.from_array s) p in + let p = Subst.Excluding_Binders.program (Subst.from_array s) p in if times () then Format.eprintf " flow analysis 5: %a@." Timer.print t5; if times () then Format.eprintf " flow analysis: %a@." Timer.print t; Code.invariant p; diff --git a/compiler/lib/freevars.ml b/compiler/lib/freevars.ml index d65a54c64c..3d04173b34 100644 --- a/compiler/lib/freevars.ml +++ b/compiler/lib/freevars.ml @@ -76,8 +76,11 @@ let iter_block_free_vars f block = List.iter block.body ~f:(fun (i, _) -> iter_instr_free_vars f i); iter_last_free_var f (fst block.branch) -let iter_instr_bound_vars f i = +let iter_instr_bound_vars ?(closure_params = false) f i = match i with + | Let (x, Closure (params, _)) when closure_params -> + f x; + List.iter ~f params | Let (x, _) -> f x | Set_field _ | Offset_ref _ | Array_set _ | Assign _ -> () @@ -86,11 +89,17 @@ let iter_last_bound_vars f l = | Return _ | Raise _ | Stop | Branch _ | Cond _ | Switch _ | Poptrap _ -> () | Pushtrap (_, x, _) -> f x -let iter_block_bound_vars f block = +let iter_block_bound_vars ?(closure_params = false) f block = List.iter ~f block.params; - List.iter block.body ~f:(fun (i, _) -> iter_instr_bound_vars f i); + List.iter block.body ~f:(fun (i, _) -> iter_instr_bound_vars ~closure_params f i); iter_last_bound_vars f (fst block.branch) +let block_bound_vars ?(closure_params = false) block = + let open Code.Var.Set in + let bound = ref empty in + iter_block_bound_vars ~closure_params (fun var -> bound := add var !bound) block; + !bound + (****) type st = diff --git a/compiler/lib/freevars.mli b/compiler/lib/freevars.mli index f30723923f..18b84ee6a4 100644 --- a/compiler/lib/freevars.mli +++ b/compiler/lib/freevars.mli @@ -21,7 +21,16 @@ open! Stdlib val iter_block_free_vars : (Code.Var.t -> unit) -> Code.block -> unit -val iter_block_bound_vars : (Code.Var.t -> unit) -> Code.block -> unit +val iter_block_bound_vars : + ?closure_params:bool -> (Code.Var.t -> unit) -> Code.block -> unit +(** Iterate on the variables bound in a block (let-bound identifiers and block + parameters). If [closure_params] is [true] (by default, it is [false]), + these variables include the parameters of closures created in the block. *) + +val block_bound_vars : ?closure_params:bool -> Code.block -> Code.Var.Set.t +(** Computes the set of variables that are bound in a block. If + [closure_params] is [true] (by default, it is [false]), these variables + include the parameters of closures created in the block. *) val f_mutable : Code.program -> Code.Var.Set.t Code.Addr.Map.t diff --git a/compiler/lib/generate.ml b/compiler/lib/generate.ml index 1e5ccc87dd..923717c3f2 100644 --- a/compiler/lib/generate.ml +++ b/compiler/lib/generate.ml @@ -56,6 +56,7 @@ type application_description = { arity : int ; exact : bool ; cps : bool + ; single_version : bool } module Share = struct @@ -134,6 +135,7 @@ module Share = struct let get ~cps_calls + ~single_version_closures ?alias_strings ?(alias_prims = false) ?(alias_apply = true) @@ -151,8 +153,15 @@ module Share = struct | Let (_, Constant c) -> get_constant c share | Let (x, Apply { args; exact; _ }) -> let cps = Var.Set.mem x cps_calls in + let single_version = + (not (Config.Flag.double_translation ())) + || Var.Set.mem x single_version_closures + in if (not exact) || cps - then add_apply { arity = List.length args; exact; cps } share + then + add_apply + { arity = List.length args; exact; cps; single_version } + share else share | Let (_, Special (Alias_prim name)) -> let name = Primitive.resolve name in @@ -244,15 +253,22 @@ module Share = struct try J.EVar (AppMap.find desc t.vars.applies) with Not_found -> let x = - let { arity; exact; cps } = desc in + let { arity; exact; cps; single_version } = desc in Var.fresh_n (Printf.sprintf "caml_%scall%d" - (match exact, cps with - | true, false -> assert false - | true, true -> "cps_exact_" - | false, false -> "" - | false, true -> "cps_") + (match exact, cps, single_version with + | true, true, false -> "cps_exact_double_" + | true, true, true -> "cps_exact_" + | false, false, false -> "double" + | false, false, true -> "" + | false, true, false -> "cps_" + | false, true, true -> + assert (not (Config.Flag.double_translation ())); + "cps_" + | true, false, _ -> + (* Should not happen: no intermediary function needed *) + assert false) arity) in let v = J.V x in @@ -273,6 +289,7 @@ module Ctx = struct ; deadcode_sentinal : Var.t ; mutated_vars : Code.Var.Set.t Code.Addr.Map.t ; freevars : Code.Var.Set.t Code.Addr.Map.t + ; single_version_closures : Effects.single_version_closures } let initial @@ -285,6 +302,7 @@ module Ctx = struct blocks live cps_calls + single_version_closures share debug = { blocks @@ -298,6 +316,7 @@ module Ctx = struct ; deadcode_sentinal ; mutated_vars ; freevars + ; single_version_closures } end @@ -773,49 +792,74 @@ let parallel_renaming back_edge params args continuation queue = (****) -let apply_fun_raw ctx f params exact cps = - let n = List.length params in - let apply_directly = - (* Make sure we are performing a regular call, not a (slower) - method call *) - match f with - | J.EAccess _ | J.EDot _ -> - J.call (J.dot f (Utf8_string.of_string_exn "call")) (s_var "null" :: params) J.N - | _ -> J.call f params J.N - in - let apply = - (* We skip the arity check when we know that we have the right - number of parameters, since this test is expensive. *) - if exact - then apply_directly - else - let l = Utf8_string.of_string_exn "l" in +let apply_fun_raw = + let cps_field = Utf8_string.of_string_exn "cps" in + fun ctx f params exact cps single_version -> + let n = List.length params in + let apply_directly f params = + (* Make sure we are performing a regular call, not a (slower) + method call *) + match f with + | J.EAccess _ | J.EDot _ -> + J.call (J.dot f (Utf8_string.of_string_exn "call")) (s_var "null" :: params) J.N + | _ -> J.call f params J.N + in + let apply cps single = + (* Adapt if [f] is a (direct-style, CPS) closure pair *) + let real_closure = + if (not (Config.Flag.effects ())) || (not cps) || single + then f + else + (* Effects enabled, CPS version, not single-version *) + J.EDot (f, J.ANormal, cps_field) + in + (* We skip the arity check when we know that we have the right + number of parameters, since this test is expensive. *) + if exact + then apply_directly real_closure params + else + let l = Utf8_string.of_string_exn "l" in + J.ECond + ( J.EBin + ( J.EqEq + , J.ECond + ( J.EBin (J.Ge, J.dot real_closure l, int 0) + , J.dot real_closure l + , J.EBin + ( J.Eq + , J.dot real_closure l + , J.dot real_closure (Utf8_string.of_string_exn "length") ) ) + , int n ) + , apply_directly real_closure params + , J.call + (* Note: when double translation is enabled, [caml_call_gen*] functions takes a two-version function *) + (runtime_fun + ctx + (if cps && Config.Flag.double_translation () + then "caml_call_gen_cps" + else "caml_call_gen")) + [ f; J.array params ] + J.N ) + in + if cps + then ( + assert (Config.Flag.effects ()); + (* When supporting effect, we systematically perform tailcall + optimization. To implement it, we check the stack depth and + bounce to a trampoline if needed, to avoid a stack overflow. + The trampoline then performs the call in an shorter stack. *) + let f = + if single_version && Config.Flag.double_translation () + then J.(EObj [ Property (PNS (Utf8_string.of_string_exn "cps"), f) ]) + else f + in J.ECond - ( J.EBin - ( J.EqEq - , J.ECond - ( J.EBin (J.Ge, J.dot f l, int 0) - , J.dot f l - , J.EBin (J.Eq, J.dot f l, J.dot f (Utf8_string.of_string_exn "length")) - ) - , int n ) - , apply_directly - , J.call (runtime_fun ctx "caml_call_gen") [ f; J.array params ] J.N ) - in - if cps - then ( - assert (Config.Flag.effects ()); - (* When supporting effect, we systematically perform tailcall - optimization. To implement it, we check the stack depth and - bounce to a trampoline if needed, to avoid a stack overflow. - The trampoline then performs the call in an shorter stack. *) - J.ECond - ( J.call (runtime_fun ctx "caml_stack_check_depth") [] J.N - , apply - , J.call (runtime_fun ctx "caml_trampoline_return") [ f; J.array params ] J.N )) - else apply - -let generate_apply_fun ctx { arity; exact; cps } = + ( J.call (runtime_fun ctx "caml_stack_check_depth") [] J.N + , apply cps single_version + , J.call (runtime_fun ctx "caml_trampoline_return") [ f; J.array params ] J.N )) + else apply cps single_version + +let generate_apply_fun ctx { arity; exact; cps; single_version } = let f' = Var.fresh_n "f" in let f = J.V f' in let params = @@ -830,10 +874,13 @@ let generate_apply_fun ctx { arity; exact; cps } = ( None , J.fun_ (f :: params) - [ J.Return_statement (Some (apply_fun_raw ctx f' params' exact cps)), J.N ] + [ ( J.Return_statement + (Some (apply_fun_raw ctx f' params' exact cps single_version)) + , J.N ) + ] J.N ) -let apply_fun ctx f params exact cps loc = +let apply_fun ctx f params exact cps single_version loc = (* We always go through an intermediate function when doing CPS calls. This function first checks the stack depth to prevent a stack overflow. This makes the code smaller than inlining @@ -841,12 +888,12 @@ let apply_fun ctx f params exact cps loc = since the function should get inlined by the JavaScript engines. *) if Config.Flag.inline_callgen () || (exact && not cps) - then apply_fun_raw ctx f params exact cps + then apply_fun_raw ctx f params exact cps single_version else let y = Share.get_apply (generate_apply_fun ctx) - { arity = List.length params; exact; cps } + { arity = List.length params; exact; cps; single_version } ctx.Ctx.share in J.call y (f :: params) loc @@ -1030,6 +1077,10 @@ 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 single_version = + (not (Config.Flag.double_translation ())) + || Var.Set.mem f ctx.Ctx.single_version_closures + in let args, prop, queue = List.fold_right ~f:(fun x (args, prop, queue) -> @@ -1040,7 +1091,7 @@ let rec translate_expr ctx queue loc x e level : _ * J.statement_list = in let (prop', f), queue = access_queue queue f in let prop = or_p prop prop' in - let e = apply_fun ctx f args exact cps loc in + let e = apply_fun ctx f args exact cps single_version loc in (e, prop, queue), [] | Block (tag, a, array_or_not, _mut) -> let contents, prop, queue = @@ -1446,7 +1497,8 @@ and translate_instrs_rev (ctx : Ctx.t) expr_queue instrs acc_rev muts_map : _ * List.fold_left pcs ~init:(ctx.blocks, Addr.Set.empty) - ~f:(fun (blocks, visited) pc -> Subst.cont' subst pc blocks visited) + ~f:(fun (blocks, visited) pc -> + Subst.Excluding_Binders.cont' subst pc blocks visited) in { ctx with blocks = p } in @@ -1950,12 +2002,15 @@ let f ~exported_runtime ~live_vars ~cps_calls + ~single_version_closures ~should_export ~warn_on_unhandled_effect ~deadcode_sentinal debug = let t' = Timer.make () in - let share = Share.get ~cps_calls ~alias_prims:exported_runtime p in + let share = + Share.get ~cps_calls ~single_version_closures ~alias_prims:exported_runtime p + in let exported_runtime = if exported_runtime then Some (Code.Var.fresh_n "runtime", ref false) else None in @@ -1972,6 +2027,7 @@ let f p.blocks live_vars cps_calls + single_version_closures share debug in diff --git a/compiler/lib/generate.mli b/compiler/lib/generate.mli index eaf1ab8f5e..b39b9f465c 100644 --- a/compiler/lib/generate.mli +++ b/compiler/lib/generate.mli @@ -23,6 +23,7 @@ val f : -> exported_runtime:bool -> live_vars:Deadcode.variable_uses -> cps_calls:Effects.cps_calls + -> single_version_closures:Effects.single_version_closures -> should_export:bool -> warn_on_unhandled_effect:bool -> deadcode_sentinal:Code.Var.t diff --git a/compiler/lib/lambda_lifting.ml b/compiler/lib/lambda_lifting.ml index bb3f7dc540..47dda19995 100644 --- a/compiler/lib/lambda_lifting.ml +++ b/compiler/lib/lambda_lifting.ml @@ -174,7 +174,9 @@ let rec traverse var_depth (program, functions) pc depth limit = free_vars Var.Map.empty in - let program = Subst.cont (Subst.from_map s) pc' program in + let program = + Subst.Excluding_Binders.cont (Subst.from_map s) pc' program + in let f' = try Var.Map.find f s with Not_found -> Var.fork f in let s = Var.Map.bindings (Var.Map.remove f s) in let f'' = Var.fork f in diff --git a/compiler/lib/lambda_lifting_simple.ml b/compiler/lib/lambda_lifting_simple.ml new file mode 100644 index 0000000000..5200ffb7e3 --- /dev/null +++ b/compiler/lib/lambda_lifting_simple.ml @@ -0,0 +1,354 @@ +(* 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! Stdlib +open Code + +let debug = Debug.find "lifting_simple" + +let baseline = 0 (* Depth to which the functions are lifted *) + +let rec compute_depth program pc = + Code.preorder_traverse + { fold = Code.fold_children } + (fun pc d -> + let block = Code.Addr.Map.find pc program.blocks in + List.fold_left block.body ~init:d ~f:(fun d (i, _) -> + match i with + | Let (_, Closure (_, (pc', _))) -> + let d' = compute_depth program pc' in + max d (d' + 1) + | _ -> d)) + pc + program.blocks + 0 + +let collect_free_vars program var_depth depth pc = + let vars = ref Var.Set.empty in + let rec traverse pc = + Code.preorder_traverse + { fold = Code.fold_children } + (fun pc () -> + let block = Code.Addr.Map.find pc program.blocks in + Freevars.iter_block_free_vars + (fun x -> + let idx = Var.idx x in + if idx < Array.length var_depth + then ( + let d = var_depth.(idx) in + assert (d >= 0); + if d > baseline && d < depth then vars := Var.Set.add x !vars)) + block; + List.iter block.body ~f:(fun (i, _) -> + match i with + | Let (_, Closure (_, (pc', _))) -> traverse pc' + | _ -> ())) + pc + program.blocks + () + in + traverse pc; + !vars + +let mark_bound_variables var_depth block depth = + Freevars.iter_block_bound_vars + (fun x -> + let idx = Var.idx x in + if idx < Array.length var_depth then var_depth.(idx) <- depth) + block; + List.iter block.body ~f:(fun (i, _) -> + match i with + | Let (_, Closure (params, _)) -> + List.iter params ~f:(fun x -> var_depth.(Var.idx x) <- depth + 1) + | _ -> ()) + +let rec traverse + ~to_lift + var_depth + (program, (functions : (instr * loc) list), lifters) + pc + depth : _ * _ * (Var.Set.t * Var.t Var.Map.t) = + Code.preorder_traverse + { fold = Code.fold_children } + (fun pc (program, functions, lifters) -> + let block = Code.Addr.Map.find pc program.blocks in + mark_bound_variables var_depth block depth; + if depth = 0 + then ( + assert (List.is_empty functions); + let program, body, lifters' = + List.fold_right + block.body + ~init:(program, [], (Var.Set.empty, Var.Map.empty)) + ~f:(fun (i, loc) (program, rem, lifters) -> + match i with + | Let (_, Closure (_, (pc', _))) as i -> + let program, functions, lifters = + traverse ~to_lift var_depth (program, [], lifters) pc' (depth + 1) + in + program, List.rev_append functions ((i, loc) :: rem), lifters + | i -> program, (i, loc) :: rem, lifters) + in + ( { program with blocks = Addr.Map.add pc { block with body } program.blocks } + , [] + , ( Var.Set.union (fst lifters) (fst lifters') + , Var.Map.union (fun _ _ -> assert false) (snd lifters) (snd lifters') ) )) + else + (* We lift possibly mutually recursive closures (that are created by + contiguous statements) together. Isolated closures are lambda-lifted + normally. *) + let does_not_start_with_closure l = + match l with + | (Let (_, Closure _), _) :: _ -> false + | _ -> true + in + let rec rewrite_body + current_contiguous + (st : Code.program * (instr * loc) list * (Var.Set.t * Var.t Var.Map.t)) + l = + match l with + | (Let (f, (Closure (_, (pc', _)) as cl)), loc) :: rem + when List.is_empty current_contiguous + && Var.Set.mem f to_lift + && does_not_start_with_closure rem -> + (* We lift an isolated closure *) + if debug () + then Format.eprintf "@[lifting isolated closure %s@,@]" (Var.to_string f); + let program, functions, lifters = + traverse ~to_lift var_depth st pc' (depth + 1) + in + let free_vars = collect_free_vars program var_depth (depth + 1) pc' in + if debug () + then ( + Format.eprintf "@[free variables:@,"; + free_vars + |> Var.Set.iter (fun v -> Format.eprintf "%s,@ " (Var.to_string v)); + Format.eprintf "@]"); + let s = + Var.Set.fold + (fun x m -> Var.Map.add x (Var.fork x) m) + free_vars + Var.Map.empty + in + let program = Subst.Excluding_Binders.cont (Subst.from_map s) pc' program in + let f' = try Var.Map.find f s with Not_found -> Var.fork f in + let s = Var.Map.bindings (Var.Map.remove f s) in + let f'' = Var.fork f in + if debug () + then + Format.eprintf + "LIFT %s (depth:%d free_vars:%d inner_depth:%d)@." + (Code.Var.to_string f'') + depth + (Var.Set.cardinal free_vars) + (compute_depth program pc'); + let pc'' = program.free_pc in + let bl = + { params = []; body = [ Let (f', cl), noloc ]; branch = Return f', noloc } + in + let program = + { program with + free_pc = pc'' + 1 + ; blocks = Addr.Map.add pc'' bl program.blocks + } + in + let functions = + (Let (f'', Closure (List.map s ~f:snd, (pc'', []))), noloc) :: functions + in + let lifters = + Var.Set.add f'' (fst lifters), Var.Map.add f f' (snd lifters) + in + let rem', st = rewrite_body [] (program, functions, lifters) rem in + ( (Let (f, Apply { f = f''; args = List.map ~f:fst s; exact = true }), loc) + :: rem' + , st ) + | (Let (cname, Closure (params, (pc', args))), loc) :: rem -> + let st = traverse ~to_lift var_depth st pc' (depth + 1) in + rewrite_body ((cname, params, pc', args, loc) :: current_contiguous) st rem + | l -> ( + assert ( + match current_contiguous with + | [ (f, _, _, _, _) ] -> not (Var.Set.mem f to_lift) + | _ -> true); + match current_contiguous with + | [] -> ( + match l with + | i :: rem -> + let rem', st = rewrite_body [] st rem in + i :: rem', st + | [] -> [], st) + | _ + when List.exists + ~f:(fun (f, _, _, _, _) -> Var.Set.mem f to_lift) + current_contiguous -> + let program, functions, lifters = + (if debug () + then + Format.( + eprintf + "@[Need to lift:@,%a@,@]" + (pp_print_list ~pp_sep:pp_print_space pp_print_string) + (List.map + ~f:(fun (f, _, _, _, _) -> Code.Var.to_string f) + current_contiguous))); + List.fold_left + current_contiguous + ~f:(fun st (_, _, pc, _, _) -> + traverse ~to_lift var_depth st pc (depth + 1)) + ~init:st + in + let free_vars = + List.fold_left + current_contiguous + ~f:(fun acc (_, _, pc, _, _) -> + Var.Set.union acc + @@ collect_free_vars program var_depth (depth + 1) pc) + ~init:Var.Set.empty + in + let s = + Var.Set.fold + (fun x m -> Var.Map.add x (Var.fork x) m) + free_vars + Var.Map.empty + in + let program = + List.fold_left + current_contiguous + ~f:(fun program (_, _, pc, _, _) -> + Subst.Excluding_Binders.cont (Subst.from_map s) pc program) + ~init:program + in + let f's = + List.map current_contiguous ~f:(fun (f, _, _, _, _) -> + Var.(try Map.find f s with Not_found -> fork f)) + in + let s = + List.fold_left + current_contiguous + ~f:(fun s (f, _, _, _, _) -> Var.Map.remove f s) + ~init:s + |> Var.Map.bindings + in + let f_tuple = Var.fresh_n "recfuncs" in + (if debug () + then + Format.( + eprintf + "LIFT %a in tuple %s (depth:%d free_vars:%d)@," + (pp_print_list ~pp_sep:pp_print_space pp_print_string) + (List.map ~f:Code.Var.to_string f's) + (Code.Var.to_string f_tuple) + depth + (Var.Set.cardinal free_vars))); + let pc_tuple = program.free_pc in + let lifted_block = + let tuple = Var.fresh_n "tuple" in + { params = [] + ; body = + List.map2 + f's + current_contiguous + ~f:(fun f' (_, params, pc, args, loc) -> + Let (f', Closure (params, (pc, args))), loc) + @ [ ( Let + (tuple, Block (0, Array.of_list f's, NotArray, Immutable)) + , noloc ) + ] + ; branch = Return tuple, noloc + } + in + let program = + { program with + free_pc = pc_tuple + 1 + ; blocks = Addr.Map.add pc_tuple lifted_block program.blocks + } + in + let functions = + (Let (f_tuple, Closure (List.map s ~f:snd, (pc_tuple, []))), noloc) + :: functions + in + let lifters = + ( Var.Set.add f_tuple (fst lifters) + , Var.Map.add_seq + (List.to_seq + @@ List.combine + (List.map current_contiguous ~f:(fun (f, _, _, _, _) -> f)) + f's) + (snd lifters) ) + in + let rem', st = + match l with + | i :: rem -> + let rem', st = + rewrite_body [] (program, functions, lifters) rem + in + i :: rem', st + | [] -> [], (program, functions, lifters) + in + let tuple = Var.fresh_n "tuple" in + ( ( Let + ( tuple + , Apply { f = f_tuple; args = List.map ~f:fst s; exact = true } ) + , noloc ) + :: List.mapi current_contiguous ~f:(fun i (f, _, _, _, loc) -> + Let (f, Field (tuple, i)), loc) + @ rem' + , st ) + | _ :: _ -> + let rem, st = + match l with + | i :: rem -> + let rem, st = rewrite_body [] st rem in + i :: rem, st + | [] -> [], st + in + ( List.map current_contiguous ~f:(fun (f, params, pc, args, loc) -> + Let (f, Closure (params, (pc, args))), loc) + @ rem + , st )) + in + let body, (program, functions, lifters) = + rewrite_body [] (program, functions, lifters) block.body + in + ( { program with blocks = Addr.Map.add pc { block with body } program.blocks } + , functions + , lifters )) + pc + program.blocks + (program, functions, lifters) + +let f ~to_lift program = + if debug () + then ( + Format.eprintf "@[Program before lambda lifting:@,"; + Code.Print.program (fun _ _ -> "") program; + Format.eprintf "@]"); + let t = Timer.make () in + let nv = Var.count () in + let var_depth = Array.make nv (-1) in + let program, functions, (lifters, liftings) = + traverse + ~to_lift + var_depth + (program, [], (Var.Set.empty, Var.Map.empty)) + program.start + 0 + in + assert (List.is_empty functions); + if Debug.find "times" () then Format.eprintf " lambda lifting: %a@." Timer.print t; + program, lifters, liftings diff --git a/compiler/lib/lambda_lifting_simple.mli b/compiler/lib/lambda_lifting_simple.mli new file mode 100644 index 0000000000..c0f2eea66e --- /dev/null +++ b/compiler/lib/lambda_lifting_simple.mli @@ -0,0 +1,53 @@ +(* 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 + +val f : to_lift:Var.Set.t -> program -> program * Var.Set.t * Var.t Var.Map.t +(** Lambda-lift all functions of the program that are in [to_lift]. All + functions are lifted to toplevel. Functions that may be + mutually recursive are lifted together. Also yields the names of the + lifting closures generated, as well as the names of the lambda-lifted + functions. E.g. consider: + + let y = -3 in + (* ... *) + let rec fib n = + match n with + | 0 | 1 -> 1 + | _ -> fib (n-1) + fib (n-2) + y + in + fib 42 + + After lambda-lifting of [fib], it will look like: + + let y = -3 in + (* ... *) + let fib' y = + let rec fib_l n = + match n with + | 0 | 1 -> 1 + | _ -> fib_l (n-1) + fib_l (n-2) + y + in + fib_l + in + let fib = fib' y in + fib 42 + + [fib_l] is the lifted version of [fib], [fib'] is the lifting closure. + *) diff --git a/compiler/lib/linker.ml b/compiler/lib/linker.ml index 1629afc5a8..e3e2d57573 100644 --- a/compiler/lib/linker.ml +++ b/compiler/lib/linker.ml @@ -179,6 +179,7 @@ module Fragment = struct ; code : Javascript.program pack ; js_string : bool option ; effects : bool option + ; double_translate : bool option ; fragment_target : Target_env.t option ; aliases : StringSet.t } @@ -249,6 +250,7 @@ module Fragment = struct ; code = Ok code ; js_string = None ; effects = None + ; double_translate = None ; fragment_target = None ; aliases = StringSet.empty } @@ -299,6 +301,15 @@ module Fragment = struct if Option.is_some fragment.effects then Format.eprintf "Duplicated effects in %s\n" (loc pi); { fragment with effects = Some b } + | (`Ifnot "doubletranslate" | `If "doubletranslate") as i -> + let b = + match i with + | `If _ -> true + | `Ifnot _ -> false + in + if Option.is_some fragment.double_translate + then Format.eprintf "Duplicated doubletranslate in %s\n" (loc pi); + { fragment with double_translate = Some b } | `If name when Option.is_some (Target_env.of_string name) -> if Option.is_some fragment.fragment_target then Format.eprintf "Duplicated target_env in %s\n" (loc pi); @@ -453,6 +464,7 @@ let load_fragment ~target_env ~filename (f : Fragment.t) = ; code ; js_string ; effects + ; double_translate ; fragment_target ; aliases ; has_macro @@ -467,9 +479,15 @@ let load_fragment ~target_env ~filename (f : Fragment.t) = | Some true, false | Some false, true -> true | None, _ | Some true, true | Some false, false -> false in + let ignore_because_of_double_translate = + match double_translate, Config.Flag.double_translation () with + | Some true, false | Some false, true -> true + | None, _ | Some true, true | Some false, false -> false + in if (not version_constraint_ok) || ignore_because_of_js_string || ignore_because_of_effects + || ignore_because_of_double_translate then `Ignored else match provides with diff --git a/compiler/lib/phisimpl.ml b/compiler/lib/phisimpl.ml index bbb3537af9..7a27019278 100644 --- a/compiler/lib/phisimpl.ml +++ b/compiler/lib/phisimpl.ml @@ -158,6 +158,6 @@ let f p = if times () then Format.eprintf " phi-simpl. 2: %a@." Timer.print t'; Array.iteri subst ~f:(fun idx y -> if Var.idx y = idx then () else Code.Var.propagate_name (Var.of_idx idx) y); - let p = Subst.program (Subst.from_array subst) p in + let p = Subst.Excluding_Binders.program (Subst.from_array subst) p in if times () then Format.eprintf " phi-simpl.: %a@." Timer.print t; p diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index e1a108e6a6..6db007b937 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -1141,6 +1141,21 @@ module Array = struct incr i done; !i = len_a + + let fold_left_map ~f ~init input_array = + let len = length input_array in + if len = 0 + then init, [||] + else + let acc, elt = f init (unsafe_get input_array 0) in + let output_array = make len elt in + let acc = ref acc in + for i = 1 to len - 1 do + let acc', elt = f !acc (unsafe_get input_array i) in + acc := acc'; + unsafe_set output_array i elt + done; + !acc, output_array end module Filename = struct diff --git a/compiler/lib/subst.ml b/compiler/lib/subst.ml index 4e735576c3..2572d29aa0 100644 --- a/compiler/lib/subst.ml +++ b/compiler/lib/subst.ml @@ -23,80 +23,82 @@ open Code let subst_cont s (pc, arg) = pc, List.map arg ~f:(fun x -> s x) -let expr s e = - match e with - | Constant _ -> e - | Apply { f; args; exact } -> - Apply { f = s f; args = List.map args ~f:(fun x -> s x); exact } - | Block (n, a, k, mut) -> Block (n, Array.map a ~f:(fun x -> s x), k, mut) - | Field (x, n) -> Field (s x, n) - | Closure (l, pc) -> Closure (l, subst_cont s pc) - | Special _ -> e - | Prim (p, l) -> - Prim - ( p - , List.map l ~f:(fun x -> - match x with - | Pv x -> Pv (s x) - | Pc _ -> x) ) - -let instr s i = - match i with - | Let (x, e) -> Let (x, expr s e) - | Assign (x, y) -> Assign (x, s y) (* x is handled like a parameter *) - | Set_field (x, n, y) -> Set_field (s x, n, s y) - | Offset_ref (x, n) -> Offset_ref (s x, n) - | Array_set (x, y, z) -> Array_set (s x, s y, s z) - -let instrs s l = List.map l ~f:(fun (i, loc) -> instr s i, loc) - -let last s (l, loc) = - let l = - match l with - | Stop -> l - | Branch cont -> Branch (subst_cont s cont) - | Pushtrap (cont1, x, cont2) -> Pushtrap (subst_cont s cont1, x, subst_cont s cont2) - | Return x -> Return (s x) - | Raise (x, k) -> Raise (s x, k) - | Cond (x, cont1, cont2) -> Cond (s x, subst_cont s cont1, subst_cont s cont2) - | Switch (x, a1) -> Switch (s x, Array.map a1 ~f:(fun cont -> subst_cont s cont)) - | Poptrap cont -> Poptrap (subst_cont s cont) - in - l, loc - -let block s block = - { params = block.params; body = instrs s block.body; branch = last s block.branch } - -let program s p = - let blocks = Addr.Map.map (fun b -> block s b) p.blocks in - { p with blocks } - -let rec cont' s pc blocks visited = - if Addr.Set.mem pc visited - then blocks, visited - else - let visited = Addr.Set.add pc visited in - let b = Addr.Map.find pc blocks in - let b = block s b in - let blocks = Addr.Map.add pc b blocks in - let blocks, visited = - List.fold_left - b.body - ~init:(blocks, visited) - ~f:(fun (blocks, visited) (instr, _) -> - match instr with - | Let (_, Closure (_, (pc, _))) -> cont' s pc blocks visited - | _ -> blocks, visited) +module Excluding_Binders = struct + let expr s e = + match e with + | Constant _ -> e + | Apply { f; args; exact } -> + Apply { f = s f; args = List.map args ~f:(fun x -> s x); exact } + | Block (n, a, k, mut) -> Block (n, Array.map a ~f:(fun x -> s x), k, mut) + | Field (x, n) -> Field (s x, n) + | Closure (l, pc) -> Closure (l, subst_cont s pc) + | Special _ -> e + | Prim (p, l) -> + Prim + ( p + , List.map l ~f:(fun x -> + match x with + | Pv x -> Pv (s x) + | Pc _ -> x) ) + + let instr s i = + match i with + | Let (x, e) -> Let (x, expr s e) + | Assign (x, y) -> Assign (x, s y) (* x is handled like a parameter *) + | Set_field (x, n, y) -> Set_field (s x, n, s y) + | Offset_ref (x, n) -> Offset_ref (s x, n) + | Array_set (x, y, z) -> Array_set (s x, s y, s z) + + let instrs s l = List.map l ~f:(fun (i, loc) -> instr s i, loc) + + let last s (l, loc) = + let l = + match l with + | Stop -> l + | Branch cont -> Branch (subst_cont s cont) + | Pushtrap (cont1, x, cont2) -> Pushtrap (subst_cont s cont1, x, subst_cont s cont2) + | Return x -> Return (s x) + | Raise (x, k) -> Raise (s x, k) + | Cond (x, cont1, cont2) -> Cond (s x, subst_cont s cont1, subst_cont s cont2) + | Switch (x, a1) -> Switch (s x, Array.map a1 ~f:(fun cont -> subst_cont s cont)) + | Poptrap cont -> Poptrap (subst_cont s cont) in - Code.fold_children - blocks - pc - (fun pc (blocks, visited) -> cont' s pc blocks visited) - (blocks, visited) + l, loc + + let block s block = + { params = block.params; body = instrs s block.body; branch = last s block.branch } -let cont s addr p = - let blocks, _ = cont' s addr p.blocks Addr.Set.empty in - { p with blocks } + let program s p = + let blocks = Addr.Map.map (fun b -> block s b) p.blocks in + { p with blocks } + + let rec cont' s pc blocks visited = + if Addr.Set.mem pc visited + then blocks, visited + else + let visited = Addr.Set.add pc visited in + let b = Addr.Map.find pc blocks in + let b = block s b in + let blocks = Addr.Map.add pc b blocks in + let blocks, visited = + List.fold_left + b.body + ~init:(blocks, visited) + ~f:(fun (blocks, visited) (instr, _) -> + match instr with + | Let (_, Closure (_, (pc, _))) -> cont' s pc blocks visited + | _ -> blocks, visited) + in + Code.fold_children + blocks + pc + (fun pc (blocks, visited) -> cont' s pc blocks visited) + (blocks, visited) + + let cont s addr p = + let blocks, _ = cont' s addr p.blocks Addr.Set.empty in + { p with blocks } +end (****) @@ -111,3 +113,56 @@ let rec build_mapping params args = | _ -> assert false let from_map m x = try Var.Map.find x m with Not_found -> x + +(****) + +module Including_Binders = struct + let expr s e = + match e with + | Constant _ -> e + | Apply { f; args; exact } -> + Apply { f = s f; args = List.map args ~f:(fun x -> s x); exact } + | Block (n, a, k, mut) -> Block (n, Array.map a ~f:(fun x -> s x), k, mut) + | Field (x, n) -> Field (s x, n) + | Closure (l, pc) -> Closure (List.map l ~f:s, subst_cont s pc) + | Special _ -> e + | Prim (p, l) -> + Prim + ( p + , List.map l ~f:(fun x -> + match x with + | Pv x -> Pv (s x) + | Pc _ -> x) ) + + let instr s i = + match i with + | Let (x, e) -> Let (s x, expr s e) + | Assign (x, y) -> Assign (s x, s y) + | Set_field (x, n, y) -> Set_field (s x, n, s y) + | Offset_ref (x, n) -> Offset_ref (s x, n) + | Array_set (x, y, z) -> Array_set (s x, s y, s z) + + let instrs s l = List.map l ~f:(fun (i, loc) -> instr s i, loc) + + let last s (l, loc) = + let l = + match l with + | Stop -> l + | Branch cont -> Branch (subst_cont s cont) + | Pushtrap (cont1, x, cont2) -> + Pushtrap (subst_cont s cont1, s x, subst_cont s cont2) + | Return x -> Return (s x) + | Raise (x, k) -> Raise (s x, k) + | Cond (x, cont1, cont2) -> Cond (s x, subst_cont s cont1, subst_cont s cont2) + | Switch (x, conts) -> + Switch (s x, Array.map conts ~f:(fun cont -> subst_cont s cont)) + | Poptrap cont -> Poptrap (subst_cont s cont) + in + l, loc + + let block s block = + { params = List.map block.params ~f:s + ; body = instrs s block.body + ; branch = last s block.branch + } +end diff --git a/compiler/lib/subst.mli b/compiler/lib/subst.mli index faa7ac6b27..80c6de91ed 100644 --- a/compiler/lib/subst.mli +++ b/compiler/lib/subst.mli @@ -20,29 +20,46 @@ open Code -val program : (Var.t -> Var.t) -> program -> program +(** The operations of this module substitute variable names that appear in + expressions, except for binders, i.e., names on the right-hand side of a + {!constructor:Code.Let}. *) +module Excluding_Binders : sig + val program : (Var.t -> Var.t) -> program -> program -val expr : (Var.t -> Var.t) -> expr -> expr + val expr : (Var.t -> Var.t) -> expr -> expr -val instr : (Var.t -> Var.t) -> instr -> instr + val instr : (Var.t -> Var.t) -> instr -> instr -val instrs : (Var.t -> Var.t) -> (instr * loc) list -> (instr * loc) list + val instrs : (Var.t -> Var.t) -> (instr * loc) list -> (instr * loc) list -val block : (Var.t -> Var.t) -> block -> block + val block : (Var.t -> Var.t) -> block -> block -val last : (Var.t -> Var.t) -> last * loc -> last * loc + val last : (Var.t -> Var.t) -> last * loc -> last * loc -val cont : (Var.t -> Var.t) -> int -> program -> program + val cont : (Var.t -> Var.t) -> int -> program -> program -val cont' : - (Var.t -> Var.t) - -> int - -> block Addr.Map.t - -> Addr.Set.t - -> block Addr.Map.t * Addr.Set.t + val cont' : + (Var.t -> Var.t) + -> int + -> block Addr.Map.t + -> Addr.Set.t + -> block Addr.Map.t * Addr.Set.t +end val from_array : Var.t array -> Var.t -> Var.t val build_mapping : Var.t list -> Var.t list -> Var.t Var.Map.t val from_map : Var.t Var.Map.t -> Var.t -> Var.t + +(** The operations of this module also substitute the variables names that + appear on the left-hand-side of a {!constructor:Code.Let}, or as block + parameters, or as closure parameters, or are bound by an exception handler. + *) +module Including_Binders : sig + val instr : (Var.t -> Var.t) -> instr -> instr + + val instrs : (Var.t -> Var.t) -> (instr * loc) list -> (instr * loc) list + + val block : (Var.t -> Var.t) -> block -> block +end diff --git a/compiler/tests-compiler/direct_calls.ml b/compiler/tests-compiler/direct_calls.ml index a0d8f7b58a..439792ce84 100644 --- a/compiler/tests-compiler/direct_calls.ml +++ b/compiler/tests-compiler/direct_calls.ml @@ -97,7 +97,8 @@ let%expect_test "direct calls without --enable effects" = M1[1].call(null, 1); return M2[1].call(null, 2); } - //end |}] + //end + |}] let%expect_test "direct calls with --enable effects" = let code = @@ -159,38 +160,39 @@ let%expect_test "direct calls with --enable effects" = return raise(e$0); }); return caml_cps_exact_call2 - (g, x, function(_f_){caml_pop_trap(); return cont(undef);}); + (g, x, function(_t_){caml_pop_trap(); return cont(undef);}); } return caml_cps_exact_call3 (f, function(x, cont){return cont(undef);}, 7, - function(_d_){ + function(_r_){ return caml_cps_exact_call3 (f, function(x, cont){ return caml_cps_call3(Stdlib[28], x, cst_a$0, cont); }, cst_a, - function(_e_){return cont(0);}); + function(_s_){return cont(0);}); }); } //end function test3(x, cont){ function F(symbol){function f(x){return x + 1 | 0;} return [0, f];} - var M1 = F(undef), M2 = F(undef), _c_ = M2[1].call(null, 2); - return cont([0, M1[1].call(null, 1), _c_]); + var M1 = F(undef), M2 = F(undef), _q_ = M2[1].call(null, 2); + return cont([0, M1[1].call(null, 1), _q_]); } //end function test4(x, cont){ function F(symbol){ - function f(x, cont){return caml_cps_call3(Stdlib_Printf[2], _a_, x, cont);} + function f(x, cont){return caml_cps_call3(Stdlib_Printf[2], _o_, x, cont);} return [0, f]; } var M1 = F(undef), M2 = F(undef); return caml_cps_exact_call2 (M1[1], 1, - function(_b_){return caml_cps_exact_call2(M2[1], 2, cont);}); + function(_p_){return caml_cps_exact_call2(M2[1], 2, cont);}); } - //end |}] + //end + |}] diff --git a/compiler/tests-compiler/double-translation/direct_calls.ml b/compiler/tests-compiler/double-translation/direct_calls.ml new file mode 100644 index 0000000000..4684602099 --- /dev/null +++ b/compiler/tests-compiler/double-translation/direct_calls.ml @@ -0,0 +1,224 @@ +(* 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 "direct calls with --enable effects,doubletranslate" = + let code = + compile_and_parse + ~effects:true + ~doubletranslate:true + {| + (* Arity of the argument of a function / direct call *) + let test1 () = + let f g x = try g x with e -> raise e 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 = try g x with e -> raise e 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_program code; + [%expect + {| + (function(globalThis){ + "use strict"; + var + runtime = globalThis.jsoo_runtime, + caml_cps_closure = runtime.caml_cps_closure, + caml_maybe_attach_backtrace = runtime.caml_maybe_attach_backtrace, + caml_pop_trap = runtime.caml_pop_trap, + caml_string_of_jsbytes = runtime.caml_string_of_jsbytes, + caml_wrap_exception = runtime.caml_wrap_exception; + function caml_doublecall1(f, a0){ + return (f.l >= 0 ? f.l : f.l = f.length) == 1 + ? f(a0) + : runtime.caml_call_gen(f, [a0]); + } + function caml_doublecall2(f, a0, a1){ + return (f.l >= 0 ? f.l : f.l = f.length) == 2 + ? f(a0, a1) + : runtime.caml_call_gen(f, [a0, a1]); + } + function caml_cps_exact_double_call2(f, a0, a1){ + return runtime.caml_stack_check_depth() + ? f.cps.call(null, a0, a1) + : runtime.caml_trampoline_return(f, [a0, a1]); + } + function caml_cps_call3(f, a0, a1, a2){ + return runtime.caml_stack_check_depth() + ? (f.cps.l + >= 0 + ? f.cps.l + : f.cps.l = f.cps.length) + == 3 + ? f.cps.call(null, a0, a1, a2) + : runtime.caml_call_gen_cps(f, [a0, a1, a2]) + : runtime.caml_trampoline_return(f, [a0, a1, a2]); + } + function caml_cps_exact_double_call3(f, a0, a1, a2){ + return runtime.caml_stack_check_depth() + ? f.cps.call(null, a0, a1, a2) + : runtime.caml_trampoline_return(f, [a0, a1, a2]); + } + runtime.caml_initialize_fiber_stack(); + var + undef = undefined, + global_data = runtime.caml_get_global_data(), + _s_ = [0, [4, 0, 0, 0, 0], caml_string_of_jsbytes("%d")], + cst_a$0 = caml_string_of_jsbytes("a"), + cst_a = caml_string_of_jsbytes("a"), + Stdlib_Printf = global_data.Stdlib__Printf, + Stdlib = global_data.Stdlib; + function test1$0(param){ + function f(g, x){ + try{caml_doublecall1(g, undef); return;} + catch(e$0){ + var e = caml_wrap_exception(e$0); + throw caml_maybe_attach_backtrace(e, 0); + } + } + f(function(x){return;}, undef); + f(function(x){return;}, undef); + return 0; + } + function test1$1(param, cont){ + function f(g, x){ + try{caml_doublecall1(g, undef); return;} + catch(e$0){ + var e = caml_wrap_exception(e$0); + throw caml_maybe_attach_backtrace(e, 0); + } + } + f(function(x){return;}, undef); + f(function(x){return;}, undef); + return cont(0); + } + var test1 = caml_cps_closure(test1$0, test1$1); + function f$0(){ + function f$0(g, x){ + try{caml_doublecall1(g, x); return;} + catch(e$0){ + var e = caml_wrap_exception(e$0); + throw caml_maybe_attach_backtrace(e, 0); + } + } + function f$1(g, x, cont){ + runtime.caml_push_trap + (function(e){ + var raise = caml_pop_trap(), e$0 = caml_maybe_attach_backtrace(e, 0); + return raise(e$0); + }); + return caml_cps_exact_double_call2 + (g, x, function(_y_){caml_pop_trap(); return cont(undef);}); + } + var f = caml_cps_closure(f$0, f$1); + return f; + } + function _h_(){ + return caml_cps_closure + (function(x){return;}, function(x, cont){return cont(undef);}); + } + function _j_(){ + return caml_cps_closure + (function(x){return caml_doublecall2(Stdlib[28], x, cst_a$0);}, + function(x, cont){ + return caml_cps_call3(Stdlib[28], x, cst_a$0, cont); + }); + } + function test2$0(param){ + var f = f$0(); + f(_h_(), 7); + f(_j_(), cst_a); + return 0; + } + function test2$1(param, cont){ + var f = f$0(); + return caml_cps_exact_double_call3 + (f, + _h_(), + 7, + function(_w_){ + return caml_cps_exact_double_call3 + (f, _j_(), cst_a, function(_x_){return cont(0);}); + }); + } + var test2 = caml_cps_closure(test2$0, test2$1); + function test3$0(x){ + function F(symbol){function f(x){return x + 1 | 0;} return [0, f];} + var M1 = F(undef), M2 = F(undef), _v_ = caml_doublecall1(M2[1], 2); + return [0, caml_doublecall1(M1[1], 1), _v_]; + } + function test3$1(x, cont){ + function F(symbol){function f(x){return x + 1 | 0;} return [0, f];} + var M1 = F(undef), M2 = F(undef), _u_ = M2[1].call(null, 2); + return cont([0, M1[1].call(null, 1), _u_]); + } + var test3 = caml_cps_closure(test3$0, test3$1); + function f(){ + function f$0(x){return caml_doublecall2(Stdlib_Printf[2], _s_, x);} + function f$1(x, cont){ + return caml_cps_call3(Stdlib_Printf[2], _s_, x, cont); + } + var f = caml_cps_closure(f$0, f$1); + return f; + } + function test4$0(x){ + function F(symbol){var f$0 = f(); return [0, f$0];} + var M1 = F(undef), M2 = F(undef); + caml_doublecall1(M1[1], 1); + return caml_doublecall1(M2[1], 2); + } + function test4$1(x, cont){ + function F(symbol){var f$0 = f(); return [0, f$0];} + var M1 = F(undef), M2 = F(undef); + return caml_cps_exact_double_call2 + (M1[1], + 1, + function(_t_){ + return caml_cps_exact_double_call2(M2[1], 2, cont); + }); + } + var + test4 = caml_cps_closure(test4$0, test4$1), + Test = [0, test1, test2, test3, test4]; + runtime.caml_register_global(7, Test, "Test"); + return; + } + (globalThis)); + //end + |}] diff --git a/compiler/tests-compiler/double-translation/dune b/compiler/tests-compiler/double-translation/dune new file mode 100644 index 0000000000..063207b8a9 --- /dev/null +++ b/compiler/tests-compiler/double-translation/dune @@ -0,0 +1,14 @@ +(include dune.inc) + +(rule + (deps + (glob_files *.ml)) + (action + (with-stdout-to + dune.inc.gen + (run ../gen-rules/gen.exe jsoo_compiler_test)))) + +(rule + (alias runtest) + (action + (diff dune.inc dune.inc.gen))) diff --git a/compiler/tests-compiler/double-translation/dune.inc b/compiler/tests-compiler/double-translation/dune.inc new file mode 100644 index 0000000000..1cecd7aa8b --- /dev/null +++ b/compiler/tests-compiler/double-translation/dune.inc @@ -0,0 +1,60 @@ + +(library + ;; compiler/tests-compiler/double-translation/direct_calls.ml + (name direct_calls_47) + (enabled_if true) + (modules direct_calls) + (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) + (inline_tests + (enabled_if true) + (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/double-translation/effects_continuations.ml + (name effects_continuations_47) + (enabled_if true) + (modules effects_continuations) + (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) + (inline_tests + (enabled_if true) + (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/double-translation/effects_exceptions.ml + (name effects_exceptions_47) + (enabled_if true) + (modules effects_exceptions) + (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) + (inline_tests + (enabled_if true) + (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/double-translation/effects_toplevel.ml + (name effects_toplevel_47) + (enabled_if true) + (modules effects_toplevel) + (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) + (inline_tests + (enabled_if true) + (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))) diff --git a/compiler/tests-compiler/double-translation/effects_continuations.ml b/compiler/tests-compiler/double-translation/effects_continuations.ml new file mode 100644 index 0000000000..22454723c5 --- /dev/null +++ b/compiler/tests-compiler/double-translation/effects_continuations.ml @@ -0,0 +1,301 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * Copyright (C) 2019 Hugo Heuzard + * + * 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/test1.ml" = + let code = + compile_and_parse + ~effects:true + ~doubletranslate:true + {| + let list_rev = List.rev + (* Avoid to expose the offset of stdlib modules *) + let () = ignore (list_rev []) + + let exceptions s = + (* Compiled using 'try ... catch', + and 'throw' within the try block *) + let n = try int_of_string s with Failure _ -> 0 in + let m = + try if s = "" then raise Not_found else 7 with Not_found -> 0 in + (* Uses caml_{push,pop}_trap. *) + try + if s = "" then raise Not_found; + Some (open_in "toto", n, m) + with Not_found -> + None + + (* Conditional whose result is used *) + let cond1 b = + let ic = if b then open_in "toto" else open_in "titi" in + (ic , 7) + + (* Conditional whose result is not used *) + let cond2 b = + if b then Printf.eprintf "toto" else Printf.eprintf "toto"; + 7 + + (* A dummy argument is used to call the continuation in the + [then] clause *) + let cond3 b = + let x= ref 0 in if b then x := 1 else Printf.eprintf "toto"; + !x + + (* Two continuation functions are created. One to bind [ic] before + entering the loop, and one for the loop. We use a dummy argument + to go back to the begining of the loop if [b] is false *) + let loop1 b = + let all = ref [] in + let ic = open_in "/static/examples.ml" in + while true do + let line = input_line ic in + all := line :: !all; + if b then prerr_endline line + done + + (* There is a single continuation for the loop since the result of + [Printf.eprintf] is ignored. *) + let loop2 () = + let all = ref [] in + let ic = open_in "/static/examples.ml" in + Printf.eprintf "titi"; + while true do + let line = input_line ic in + 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_double_fun_decl code "exceptions"; + print_double_fun_decl code "cond1"; + print_double_fun_decl code "cond2"; + print_double_fun_decl code "cond3"; + print_double_fun_decl code "loop1"; + print_double_fun_decl code "loop2"; + print_double_fun_decl code "loop3"; + [%expect + {| + function exceptions$0(s){ + try{var _K_ = caml_int_of_string(s), n = _K_;} + catch(_N_){ + var _F_ = caml_wrap_exception(_N_); + if(_F_[1] !== Stdlib[7]) throw caml_maybe_attach_backtrace(_F_, 0); + var n = 0; + } + try{ + if(caml_string_equal(s, cst$0)) + throw caml_maybe_attach_backtrace(Stdlib[8], 1); + var _J_ = 7, m = _J_; + } + catch(_M_){ + var _G_ = caml_wrap_exception(_M_); + if(_G_ !== Stdlib[8]) throw caml_maybe_attach_backtrace(_G_, 0); + var m = 0; + } + try{ + if(caml_string_equal(s, cst)) + throw caml_maybe_attach_backtrace(Stdlib[8], 1); + var _I_ = [0, [0, caml_doublecall1(Stdlib[79], cst_toto), n, m]]; + return _I_; + } + catch(_L_){ + var _H_ = caml_wrap_exception(_L_); + if(_H_ === Stdlib[8]) return 0; + throw caml_maybe_attach_backtrace(_H_, 0); + } + } + //end + function exceptions$1(s, cont){ + try{var _A_ = caml_int_of_string(s), n = _A_;} + catch(_E_){ + var _w_ = caml_wrap_exception(_E_); + if(_w_[1] !== Stdlib[7]){ + var raise$1 = caml_pop_trap(); + return raise$1(caml_maybe_attach_backtrace(_w_, 0)); + } + var n = 0; + } + try{ + if(caml_string_equal(s, cst$0)) + throw caml_maybe_attach_backtrace(Stdlib[8], 1); + var _z_ = 7, m = _z_; + } + catch(_D_){ + var _x_ = caml_wrap_exception(_D_); + if(_x_ !== Stdlib[8]){ + var raise$0 = caml_pop_trap(); + return raise$0(caml_maybe_attach_backtrace(_x_, 0)); + } + var m = 0; + } + runtime.caml_push_trap + (function(_C_){ + if(_C_ === Stdlib[8]) return cont(0); + var raise = caml_pop_trap(); + return raise(caml_maybe_attach_backtrace(_C_, 0)); + }); + if(! caml_string_equal(s, cst)) + return caml_cps_call2 + (Stdlib[79], + cst_toto, + function(_B_){caml_pop_trap(); return cont([0, [0, _B_, n, m]]);}); + var _y_ = Stdlib[8], raise = caml_pop_trap(); + return raise(caml_maybe_attach_backtrace(_y_, 1)); + } + //end + var exceptions = caml_cps_closure(exceptions$0, exceptions$1); + //end + function cond1$0(b){ + var + ic = + b + ? caml_doublecall1(Stdlib[79], cst_toto$0) + : caml_doublecall1(Stdlib[79], cst_titi); + return [0, ic, 7]; + } + //end + function cond1$1(b, cont){ + function _v_(ic){return cont([0, ic, 7]);} + return b + ? caml_cps_call2(Stdlib[79], cst_toto$0, _v_) + : caml_cps_call2(Stdlib[79], cst_titi, _v_); + } + //end + var cond1 = caml_cps_closure(cond1$0, cond1$1); + //end + function cond2$0(b){ + if(b) + caml_doublecall1(Stdlib_Printf[3], _h_); + else + caml_doublecall1(Stdlib_Printf[3], _i_); + return 7; + } + //end + function cond2$1(b, cont){ + function _t_(_u_){return cont(7);} + return b + ? caml_cps_call2(Stdlib_Printf[3], _h_, _t_) + : caml_cps_call2(Stdlib_Printf[3], _i_, _t_); + } + //end + var cond2 = caml_cps_closure(cond2$0, cond2$1); + //end + function cond3$0(b){ + var x = [0, 0]; + if(b) x[1] = 1; else caml_doublecall1(Stdlib_Printf[3], _j_); + return x[1]; + } + //end + function cond3$1(b, cont){ + var x = [0, 0]; + function _r_(_s_){return cont(x[1]);} + return b ? (x[1] = 1, _r_(0)) : caml_cps_call2(Stdlib_Printf[3], _j_, _r_); + } + //end + var cond3 = caml_cps_closure(cond3$0, cond3$1); + //end + function loop1$0(b){ + var ic = caml_doublecall1(Stdlib[79], cst_static_examples_ml); + for(;;){ + var line = caml_doublecall1(Stdlib[83], ic); + if(b) caml_doublecall1(Stdlib[53], line); + } + } + //end + function loop1$1(b, cont){ + return caml_cps_call2 + (Stdlib[79], + cst_static_examples_ml, + function(ic){ + function _p_(_q_){ + return caml_cps_call2 + (Stdlib[83], + ic, + function(line){ + return b + ? caml_cps_call2(Stdlib[53], line, _p_) + : caml_cps_exact_call1(_p_, 0); + }); + } + return _p_(0); + }); + } + //end + var loop1 = caml_cps_closure(loop1$0, loop1$1); + //end + function loop2$0(param){ + var ic = caml_doublecall1(Stdlib[79], cst_static_examples_ml$0); + caml_doublecall1(Stdlib_Printf[3], _k_); + for(;;){ + var line = caml_doublecall1(Stdlib[83], ic); + caml_doublecall1(Stdlib[53], line); + } + } + //end + function loop2$1(param, cont){ + return caml_cps_call2 + (Stdlib[79], + cst_static_examples_ml$0, + function(ic){ + function _n_(_o_){ + return caml_cps_call2 + (Stdlib[83], + ic, + function(line){ + return caml_cps_call2(Stdlib[53], line, _n_); + }); + } + return caml_cps_call2(Stdlib_Printf[3], _k_, _n_); + }); + } + //end + var loop2 = caml_cps_closure(loop2$0, loop2$1); + //end + function loop3$0(param){ + var l = caml_doublecall1(list_rev, _l_), x = l; + for(;;){if(! x) return l; var r = x[2]; x = r;} + } + //end + function loop3$1(param, cont){ + return caml_cps_call2 + (list_rev, + _l_, + function(l){ + function _m_(x){ + if(! x) return cont(l); + var r = x[2]; + return caml_cps_exact_call1(_m_, r); + } + return _m_(l); + }); + } + //end + var loop3 = caml_cps_closure(loop3$0, loop3$1); + //end + |}] diff --git a/compiler/tests-compiler/double-translation/effects_exceptions.ml b/compiler/tests-compiler/double-translation/effects_exceptions.ml new file mode 100644 index 0000000000..92f318abe5 --- /dev/null +++ b/compiler/tests-compiler/double-translation/effects_exceptions.ml @@ -0,0 +1,195 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * Copyright (C) 2019 Hugo Heuzard + * + * 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/test1.ml" = + let code = + compile_and_parse + ~effects:true + ~doubletranslate:true + {| + let exceptions s = + (* Compiled using 'try ... catch', + and 'throw' within the try block *) + let n = try int_of_string s with Failure _ -> 0 in + let m = + try if s = "" then raise Not_found else 7 with Not_found -> 0 in + (* Uses caml_{push,pop}_trap. *) + try + if s = "" then raise Not_found; + 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_double_fun_decl code "exceptions"; + [%expect + {| + function exceptions$0(s){ + try{var _B_ = caml_int_of_string(s), n = _B_;} + catch(_E_){ + var _w_ = caml_wrap_exception(_E_); + if(_w_[1] !== Stdlib[7]) throw caml_maybe_attach_backtrace(_w_, 0); + var n = 0; + } + try{ + if(caml_string_equal(s, cst$0)) + throw caml_maybe_attach_backtrace(Stdlib[8], 1); + var _A_ = 7, m = _A_; + } + catch(_D_){ + var _x_ = caml_wrap_exception(_D_); + if(_x_ !== Stdlib[8]) throw caml_maybe_attach_backtrace(_x_, 0); + var m = 0; + } + try{ + if(caml_string_equal(s, cst)) + throw caml_maybe_attach_backtrace(Stdlib[8], 1); + var _z_ = [0, [0, caml_doublecall1(Stdlib[79], cst_toto), n, m]]; + return _z_; + } + catch(_C_){ + var _y_ = caml_wrap_exception(_C_); + if(_y_ === Stdlib[8]) return 0; + throw caml_maybe_attach_backtrace(_y_, 0); + } + } + //end + function exceptions$1(s, cont){ + try{var _r_ = caml_int_of_string(s), n = _r_;} + catch(_v_){ + var _n_ = caml_wrap_exception(_v_); + if(_n_[1] !== Stdlib[7]){ + var raise$1 = caml_pop_trap(); + return raise$1(caml_maybe_attach_backtrace(_n_, 0)); + } + var n = 0; + } + try{ + if(caml_string_equal(s, cst$0)) + throw caml_maybe_attach_backtrace(Stdlib[8], 1); + var _q_ = 7, m = _q_; + } + catch(_u_){ + var _o_ = caml_wrap_exception(_u_); + if(_o_ !== Stdlib[8]){ + var raise$0 = caml_pop_trap(); + return raise$0(caml_maybe_attach_backtrace(_o_, 0)); + } + var m = 0; + } + caml_push_trap + (function(_t_){ + if(_t_ === Stdlib[8]) return cont(0); + var raise = caml_pop_trap(); + return raise(caml_maybe_attach_backtrace(_t_, 0)); + }); + if(! caml_string_equal(s, cst)) + return caml_cps_call2 + (Stdlib[79], + cst_toto, + function(_s_){caml_pop_trap(); return cont([0, [0, _s_, n, m]]);}); + var _p_ = Stdlib[8], raise = caml_pop_trap(); + return raise(caml_maybe_attach_backtrace(_p_, 1)); + } + //end + var exceptions = caml_cps_closure(exceptions$0, exceptions$1); + //end + |}]; + print_double_fun_decl code "handler_is_loop"; + [%expect + {| + function handler_is_loop$0(f, g, l){ + try{var _l_ = caml_doublecall1(f, 0); return _l_;} + catch(_m_){ + var l$0 = l; + for(;;){ + var match = caml_doublecall1(g, l$0); + if(72330306 > match[1]){ + var exn = match[2]; + throw caml_maybe_attach_backtrace(exn, 1); + } + var l$1 = match[2]; + l$0 = l$1; + } + } + } + //end + function handler_is_loop$1(f, g, l, cont){ + caml_push_trap + (function(_j_){ + function _k_(l){ + return caml_cps_call2 + (g, + l, + function(match){ + if(72330306 <= match[1]){ + var l = match[2]; + return caml_cps_exact_call1(_k_, l); + } + var + exn = match[2], + raise = caml_pop_trap(), + exn$0 = caml_maybe_attach_backtrace(exn, 1); + return raise(exn$0); + }); + } + return _k_(l); + }); + return caml_cps_call2 + (f, 0, function(_i_){caml_pop_trap(); return cont(_i_);}); + } + //end + var handler_is_loop = caml_cps_closure(handler_is_loop$0, handler_is_loop$1); + //end + |}]; + print_double_fun_decl code "handler_is_merge_node"; + [%expect + {| + function handler_is_merge_node$0(g){ + try{var _g_ = caml_doublecall1(g, 0), s = _g_;}catch(_h_){var s = cst$1;} + return caml_doublecall2(Stdlib[28], s, cst_aaa); + } + //end + function handler_is_merge_node$1(g, cont){ + function _d_(s){return caml_cps_call3(Stdlib[28], s, cst_aaa, cont);} + caml_push_trap(function(_f_){return _d_(cst$1);}); + return caml_cps_call2(g, 0, function(_e_){caml_pop_trap(); return _d_(_e_);}); + } + //end + var + handler_is_merge_node = + caml_cps_closure(handler_is_merge_node$0, handler_is_merge_node$1); + //end + |}] diff --git a/compiler/tests-compiler/double-translation/effects_toplevel.ml b/compiler/tests-compiler/double-translation/effects_toplevel.ml new file mode 100644 index 0000000000..2dda2642d9 --- /dev/null +++ b/compiler/tests-compiler/double-translation/effects_toplevel.ml @@ -0,0 +1,89 @@ +(* Js_of_ocaml compiler + * http://www.ocsigen.org/js_of_ocaml/ + * Copyright (C) 2019 Hugo Heuzard + * + * 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/test1.ml" = + let code = + compile_and_parse + ~effects:true + ~doubletranslate:true + {| + (* Function calls at toplevel outside of loops do not use + [caml_callback] when double translation is enabled. *) + let g () = Printf.printf "abc" in + let f () = for i = 1 to 5 do g () done in + g (); f (); g () + |} + in + print_program code; + [%expect + {| + (function(globalThis){ + "use strict"; + var + runtime = globalThis.jsoo_runtime, + caml_string_of_jsbytes = runtime.caml_string_of_jsbytes; + function caml_doublecall1(f, a0){ + return (f.l >= 0 ? f.l : f.l = f.length) == 1 + ? f(a0) + : runtime.caml_call_gen(f, [a0]); + } + function caml_cps_call2(f, a0, a1){ + return runtime.caml_stack_check_depth() + ? (f.cps.l + >= 0 + ? f.cps.l + : f.cps.l = f.cps.length) + == 2 + ? f.cps.call(null, a0, a1) + : runtime.caml_call_gen_cps(f, [a0, a1]) + : runtime.caml_trampoline_return(f, [a0, a1]); + } + runtime.caml_initialize_fiber_stack(); + var + undef = undefined, + global_data = runtime.caml_get_global_data(), + _b_ = + [0, + [11, caml_string_of_jsbytes("abc"), 0], + caml_string_of_jsbytes("abc")], + Stdlib_Printf = global_data.Stdlib__Printf; + function g$0(param){return caml_doublecall1(Stdlib_Printf[2], _b_);} + function g$1(param, cont){ + return caml_cps_call2(Stdlib_Printf[2], _b_, cont); + } + var g = runtime.caml_cps_closure(g$0, g$1); + g(undef); + var i = 1; + for(;;){ + g(undef); + var _c_ = i + 1 | 0; + if(5 === i){ + g(undef); + var Test = [0]; + runtime.caml_register_global(2, Test, "Test"); + return; + } + i = _c_; + } + } + (globalThis)); + //end + |}] diff --git a/compiler/tests-compiler/effects.ml b/compiler/tests-compiler/effects.ml index 95892eb396..8fed80c3a4 100644 --- a/compiler/tests-compiler/effects.ml +++ b/compiler/tests-compiler/effects.ml @@ -53,11 +53,12 @@ let fff () = ? cont([0, function(k, cont){return cont(11);}]) : cont(0); }], - function(_b_){ + function(_f_){ return caml_cps_call2 (Stdlib_Printf[2], - _a_, - function(_c_){return caml_cps_call2(_c_, _b_, cont);}); + _e_, + function(_g_){return caml_cps_call2(_g_, _f_, cont);}); }); } - //end |}] + //end + |}] diff --git a/compiler/tests-compiler/effects_continuations.ml b/compiler/tests-compiler/effects_continuations.ml index 0da72bd5ee..1b65bb0836 100644 --- a/compiler/tests-compiler/effects_continuations.ml +++ b/compiler/tests-compiler/effects_continuations.ml @@ -101,63 +101,62 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = print_fun_decl code (Some "loop3"); [%expect {| - function exceptions(s, cont){ - try{var _t_ = runtime.caml_int_of_string(s), n = _t_;} - catch(_x_){ - var _p_ = caml_wrap_exception(_x_); - if(_p_[1] !== Stdlib[7]){ + try{var _A_ = runtime.caml_int_of_string(s), n = _A_;} + catch(_E_){ + var _w_ = caml_wrap_exception(_E_); + if(_w_[1] !== Stdlib[7]){ var raise$1 = caml_pop_trap(); - return raise$1(caml_maybe_attach_backtrace(_p_, 0)); + return raise$1(caml_maybe_attach_backtrace(_w_, 0)); } var n = 0; } try{ if(caml_string_equal(s, cst$0)) throw caml_maybe_attach_backtrace(Stdlib[8], 1); - var _s_ = 7, m = _s_; + var _z_ = 7, m = _z_; } - catch(_w_){ - var _q_ = caml_wrap_exception(_w_); - if(_q_ !== Stdlib[8]){ + catch(_D_){ + var _x_ = caml_wrap_exception(_D_); + if(_x_ !== Stdlib[8]){ var raise$0 = caml_pop_trap(); - return raise$0(caml_maybe_attach_backtrace(_q_, 0)); + return raise$0(caml_maybe_attach_backtrace(_x_, 0)); } var m = 0; } runtime.caml_push_trap - (function(_v_){ - if(_v_ === Stdlib[8]) return cont(0); + (function(_C_){ + if(_C_ === Stdlib[8]) return cont(0); var raise = caml_pop_trap(); - return raise(caml_maybe_attach_backtrace(_v_, 0)); + return raise(caml_maybe_attach_backtrace(_C_, 0)); }); if(! caml_string_equal(s, cst)) return caml_cps_call2 (Stdlib[79], cst_toto, - function(_u_){caml_pop_trap(); return cont([0, [0, _u_, n, m]]);}); - var _r_ = Stdlib[8], raise = caml_pop_trap(); - return raise(caml_maybe_attach_backtrace(_r_, 1)); + function(_B_){caml_pop_trap(); return cont([0, [0, _B_, n, m]]);}); + var _y_ = Stdlib[8], raise = caml_pop_trap(); + return raise(caml_maybe_attach_backtrace(_y_, 1)); } //end function cond1(b, cont){ - function _o_(ic){return cont([0, ic, 7]);} + function _v_(ic){return cont([0, ic, 7]);} return b - ? caml_cps_call2(Stdlib[79], cst_toto$0, _o_) - : caml_cps_call2(Stdlib[79], cst_titi, _o_); + ? caml_cps_call2(Stdlib[79], cst_toto$0, _v_) + : caml_cps_call2(Stdlib[79], cst_titi, _v_); } //end function cond2(b, cont){ - function _m_(_n_){return cont(7);} + function _t_(_u_){return cont(7);} return b - ? caml_cps_call2(Stdlib_Printf[3], _a_, _m_) - : caml_cps_call2(Stdlib_Printf[3], _b_, _m_); + ? caml_cps_call2(Stdlib_Printf[3], _h_, _t_) + : caml_cps_call2(Stdlib_Printf[3], _i_, _t_); } //end function cond3(b, cont){ var x = [0, 0]; - function _k_(_l_){return cont(x[1]);} - return b ? (x[1] = 1, _k_(0)) : caml_cps_call2(Stdlib_Printf[3], _c_, _k_); + function _r_(_s_){return cont(x[1]);} + return b ? (x[1] = 1, _r_(0)) : caml_cps_call2(Stdlib_Printf[3], _j_, _r_); } //end function loop1(b, cont){ @@ -165,17 +164,17 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = (Stdlib[79], cst_static_examples_ml, function(ic){ - function _i_(_j_){ + function _p_(_q_){ return caml_cps_call2 (Stdlib[83], ic, function(line){ return b - ? caml_cps_call2(Stdlib[53], line, _i_) - : caml_cps_exact_call1(_i_, 0); + ? caml_cps_call2(Stdlib[53], line, _p_) + : caml_cps_exact_call1(_p_, 0); }); } - return _i_(0); + return _p_(0); }); } //end @@ -184,29 +183,30 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = (Stdlib[79], cst_static_examples_ml$0, function(ic){ - function _g_(_h_){ + function _n_(_o_){ return caml_cps_call2 (Stdlib[83], ic, function(line){ - return caml_cps_call2(Stdlib[53], line, _g_); + return caml_cps_call2(Stdlib[53], line, _n_); }); } - return caml_cps_call2(Stdlib_Printf[3], _d_, _g_); + return caml_cps_call2(Stdlib_Printf[3], _k_, _n_); }); } //end function loop3(param, cont){ return caml_cps_call2 (list_rev, - _e_, + _l_, function(l){ - function _f_(x){ + function _m_(x){ if(! x) return cont(l); var r = x[2]; - return caml_cps_exact_call1(_f_, r); + return caml_cps_exact_call1(_m_, r); } - return _f_(l); + return _m_(l); }); } - //end |}] + //end + |}] diff --git a/compiler/tests-compiler/effects_exceptions.ml b/compiler/tests-compiler/effects_exceptions.ml index f227b7b881..f0dee813d8 100644 --- a/compiler/tests-compiler/effects_exceptions.ml +++ b/compiler/tests-compiler/effects_exceptions.ml @@ -55,59 +55,59 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = print_fun_decl code (Some "exceptions"); [%expect {| - function exceptions(s, cont){ - try{var _k_ = runtime.caml_int_of_string(s), n = _k_;} - catch(_o_){ - var _g_ = caml_wrap_exception(_o_); - if(_g_[1] !== Stdlib[7]){ + try{var _n_ = runtime.caml_int_of_string(s), n = _n_;} + catch(_r_){ + var _j_ = caml_wrap_exception(_r_); + if(_j_[1] !== Stdlib[7]){ var raise$1 = caml_pop_trap(); - return raise$1(caml_maybe_attach_backtrace(_g_, 0)); + return raise$1(caml_maybe_attach_backtrace(_j_, 0)); } var n = 0; } try{ if(caml_string_equal(s, cst$0)) throw caml_maybe_attach_backtrace(Stdlib[8], 1); - var _j_ = 7, m = _j_; + var _m_ = 7, m = _m_; } - catch(_n_){ - var _h_ = caml_wrap_exception(_n_); - if(_h_ !== Stdlib[8]){ + catch(_q_){ + var _k_ = caml_wrap_exception(_q_); + if(_k_ !== Stdlib[8]){ var raise$0 = caml_pop_trap(); - return raise$0(caml_maybe_attach_backtrace(_h_, 0)); + return raise$0(caml_maybe_attach_backtrace(_k_, 0)); } var m = 0; } caml_push_trap - (function(_m_){ - if(_m_ === Stdlib[8]) return cont(0); + (function(_p_){ + if(_p_ === Stdlib[8]) return cont(0); var raise = caml_pop_trap(); - return raise(caml_maybe_attach_backtrace(_m_, 0)); + return raise(caml_maybe_attach_backtrace(_p_, 0)); }); if(! caml_string_equal(s, cst)) return caml_cps_call2 (Stdlib[79], cst_toto, - function(_l_){caml_pop_trap(); return cont([0, [0, _l_, n, m]]);}); - var _i_ = Stdlib[8], raise = caml_pop_trap(); - return raise(caml_maybe_attach_backtrace(_i_, 1)); + function(_o_){caml_pop_trap(); return cont([0, [0, _o_, n, m]]);}); + var _l_ = Stdlib[8], raise = caml_pop_trap(); + return raise(caml_maybe_attach_backtrace(_l_, 1)); } - //end |}]; + //end + |}]; print_fun_decl code (Some "handler_is_loop"); [%expect {| function handler_is_loop(f, g, l, cont){ caml_push_trap - (function(_e_){ - function _f_(l){ + (function(_h_){ + function _i_(l){ return caml_cps_call2 (g, l, function(match){ if(72330306 <= match[1]){ var l = match[2]; - return caml_cps_exact_call1(_f_, l); + return caml_cps_exact_call1(_i_, l); } var exn = match[2], @@ -116,18 +116,20 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = return raise(exn$0); }); } - return _f_(l); + return _i_(l); }); return caml_cps_call2 - (f, 0, function(_d_){caml_pop_trap(); return cont(_d_);}); + (f, 0, function(_g_){caml_pop_trap(); return cont(_g_);}); } - //end |}]; + //end + |}]; print_fun_decl code (Some "handler_is_merge_node"); [%expect {| function handler_is_merge_node(g, cont){ - function _a_(s){return caml_cps_call3(Stdlib[28], s, cst_aaa, cont);} - caml_push_trap(function(_c_){return _a_(cst$1);}); - return caml_cps_call2(g, 0, function(_b_){caml_pop_trap(); return _a_(_b_);}); + function _d_(s){return caml_cps_call3(Stdlib[28], s, cst_aaa, cont);} + caml_push_trap(function(_f_){return _d_(cst$1);}); + return caml_cps_call2(g, 0, function(_e_){caml_pop_trap(); return _d_(_e_);}); } - //end |}] + //end + |}] diff --git a/compiler/tests-compiler/effects_toplevel.ml b/compiler/tests-compiler/effects_toplevel.ml index 596b47f1b7..5176a97516 100644 --- a/compiler/tests-compiler/effects_toplevel.ml +++ b/compiler/tests-compiler/effects_toplevel.ml @@ -34,7 +34,6 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = print_program code; [%expect {| - (function(globalThis){ "use strict"; var @@ -68,30 +67,31 @@ let%expect_test "test-compiler/lib-effects/test1.ml" = undef = undefined, global_data = runtime.caml_get_global_data(), Stdlib_Printf = global_data.Stdlib__Printf, - _a_ = + _b_ = [0, [11, caml_string_of_jsbytes("abc"), 0], caml_string_of_jsbytes("abc")]; function g(param, cont){ - return caml_cps_call2(Stdlib_Printf[2], _a_, cont); + return caml_cps_call2(Stdlib_Printf[2], _b_, cont); } caml_callback(g, [undef]); - function _b_(i){ + function _c_(i){ return caml_cps_exact_call2 (g, undef, - function(_c_){ - var _d_ = i + 1 | 0; - if(5 !== i) return caml_cps_exact_call1(_b_, _d_); + function(_d_){ + var _e_ = i + 1 | 0; + if(5 !== i) return caml_cps_exact_call1(_c_, _e_); caml_callback(g, [undef]); var Test = [0]; runtime.caml_register_global(2, Test, "Test"); return; }); } - return _b_(1); + return _c_(1); }, []); } (globalThis)); - //end |}] + //end + |}] diff --git a/compiler/tests-compiler/lambda_lifting.ml b/compiler/tests-compiler/lambda_lifting.ml index 44ba220119..801af1d553 100644 --- a/compiler/tests-compiler/lambda_lifting.ml +++ b/compiler/tests-compiler/lambda_lifting.ml @@ -26,16 +26,17 @@ Printf.printf "%d\n" (f 3) runtime = globalThis.jsoo_runtime, global_data = runtime.caml_get_global_data(), Stdlib_Printf = global_data.Stdlib__Printf, - _b_ = + _e_ = [0, [4, 0, 0, 0, [12, 10, 0]], runtime.caml_string_of_jsbytes("%d\n")]; 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;} function f(x){var g$0 = g(x); return g$0(5);} - var _a_ = f(3); - runtime.caml_callback(Stdlib_Printf[2], [_b_, _a_]); + var _d_ = f(3); + runtime.caml_callback(Stdlib_Printf[2], [_e_, _d_]); var Test = [0]; runtime.caml_register_global(2, Test, "Test"); return; } (globalThis)); - //end |}] + //end + |}] diff --git a/compiler/tests-compiler/log b/compiler/tests-compiler/log new file mode 100644 index 0000000000..ffa5e8f9c9 --- /dev/null +++ b/compiler/tests-compiler/log @@ -0,0 +1,715 @@ +Entering directory '/home/olivier/jsoo/js_of_ocaml' +Leaving directory '/home/olivier/jsoo/js_of_ocaml' +Before CPS transform: +Entry point: 0 + +==== 0 () ==== + global_data_a_ = "caml_get_global_data"() + cst_b_ = CONST{""} + cst_aaa_c_ = CONST{"aaa"} + cst_toto_d_ = CONST{"toto"} + Stdlib_e_ = "caml_js_get"(global_data_a_, "Stdlib"j) + branch 159 () + +==== 2 () ==== + branch 3 () + +==== 3 () ==== + pushtrap 4 () handler _f_ => 14 (cst_b_) continuation 8 + +==== 4 () ==== + _g_ = CONST{0} + _h_ = g_i_(_g_) + poptrap 14 (_h_) + +==== 8 () ==== + branch 14 (_h_) + +==== 10 () ==== + branch 14 (cst_b_) + +==== 14 (s_j_) ==== + _k_ = Stdlib_e_[27] + _l_ = _k_(s_j_, cst_aaa_c_) + return _l_ + +==== 22 (l_m_) ==== + match_n_ = g_o_(l_m_) + _p_ = match_n_[0] + _q_ = 72330306 <= _p_ + if _q_ then 31 () else 37 () + +==== 31 () ==== + l_r_ = match_n_[1] + branch 22 (l_r_) + +==== 37 () ==== + exn_s_ = match_n_[1] + raise exn_s_ + +==== 44 () ==== + branch 45 () + +==== 45 () ==== + pushtrap 46 () handler _t_ => 22 (l_u_) continuation 50 + +==== 46 () ==== + _v_ = CONST{0} + _w_ = f_x_(_v_) + poptrap 50 () + +==== 50 () ==== + return _w_ + +==== 52 () ==== + branch 22 (l_u_) + +==== 61 () ==== + branch 62 () + +==== 62 () ==== + pushtrap 63 () handler _y_ => 69 () continuation 67 + +==== 63 () ==== + _z_ = "caml_int_of_string"(s_A_) + poptrap 85 (_z_) + +==== 67 () ==== + branch 85 (_z_) + +==== 69 () ==== + _B_ = _y_[0] + _C_ = Stdlib_e_[6] + _D_ = _B_ === _C_ + if _D_ then 78 () else 81 () + +==== 78 () ==== + _E_ = CONST{0} + branch 85 (_E_) + +==== 81 () ==== + reraise _y_ + +==== 83 () ==== + branch 85 (_E_) + +==== 85 (n_F_) ==== + branch 87 () + +==== 87 () ==== + pushtrap 88 () handler _G_ => 104 () continuation 102 + +==== 88 () ==== + _H_ = "caml_string_equal"(s_A_, "") + if _H_ then 95 () else 99 () + +==== 95 () ==== + _I_ = Stdlib_e_[7] + raise _I_ + +==== 99 () ==== + _J_ = CONST{7} + poptrap 118 (_J_) + +==== 102 () ==== + branch 118 (_J_) + +==== 104 () ==== + _K_ = Stdlib_e_[7] + _L_ = _G_ === _K_ + if _L_ then 111 () else 114 () + +==== 111 () ==== + _M_ = CONST{0} + branch 118 (_M_) + +==== 114 () ==== + reraise _G_ + +==== 116 () ==== + branch 118 (_M_) + +==== 118 (m_N_) ==== + branch 120 () + +==== 120 () ==== + pushtrap 121 () handler _O_ => 147 () continuation 145 + +==== 121 () ==== + _P_ = "caml_string_equal"(s_A_, "") + if _P_ then 128 () else 132 () + +==== 128 () ==== + _Q_ = Stdlib_e_[7] + raise _Q_ + +==== 132 () ==== + _R_ = Stdlib_e_[78] + _S_ = _R_(cst_toto_d_) + _T_ = {tag=0; 0 = _S_; 1 = n_F_; 2 = m_N_} + _U_ = {tag=0; 0 = _T_} + poptrap 145 () + +==== 145 () ==== + return _U_ + +==== 147 () ==== + _V_ = Stdlib_e_[7] + _W_ = _O_ === _V_ + if _W_ then 154 () else 157 () + +==== 154 () ==== + _X_ = CONST{0} + return _X_ + +==== 157 () ==== + reraise _O_ + +==== 159 () ==== + exceptions_Y_ = fun(s_A_){61 ()} + handler_is_loop_Z_ = fun(f_x_, g_o_, l_u_){44 ()} + handler_is_merge_node___ = fun(g_i_){2 ()} + Test_$_ = {tag=0; 0 = exceptions_Y_; 1 = handler_is_loop_Z_; 2 = handler_is_merge_node___} + _aa_ = "caml_register_global"(6, Test_$_, "Test"j) + stop + +==== 181 () ==== + branch 22 (l_u_) + +cname = exceptions_Y_ +======== true +==== 61 () ==== + branch 62 () + +==== 62 () ==== + pushtrap 63 () handler _y_ => 69 () continuation 67 + +==== 63 () ==== + _z_ = "caml_int_of_string"(s_A_) + poptrap 85 (_z_) + +==== 85 (n_F_) ==== + branch 87 () + +==== 87 () ==== + pushtrap 88 () handler _G_ => 104 () continuation 102 + +==== 88 () ==== + _H_ = "caml_string_equal"(s_A_, "") + if _H_ then 95 () else 99 () + +==== 95 () ==== + _I_ = Stdlib_e_[7] + raise _I_ + +==== 99 () ==== + _J_ = CONST{7} + poptrap 118 (_J_) + +==== 118 (m_N_) ==== + branch 120 () + +==== 120 () ==== + pushtrap 121 () handler _O_ => 147 () continuation 145 + +==== 121 () ==== + _P_ = "caml_string_equal"(s_A_, "") + if _P_ then 128 () else 132 () + +==== 128 () ==== + _Q_ = Stdlib_e_[7] + raise _Q_ + +==== 132 () ==== + _R_ = Stdlib_e_[78] + * _S_ = _R_(cst_toto_d_) + branch 185 () + +CPS +==== 185 () ==== + _T_ = {tag=0; 0 = _S_; 1 = n_F_; 2 = m_N_} + _U_ = {tag=0; 0 = _T_} + poptrap 145 () + +==== 145 () ==== + return _U_ + +CPS +==== 147 () ==== + _V_ = Stdlib_e_[7] + _W_ = _O_ === _V_ + if _W_ then 154 () else 157 () + +==== 154 () ==== + _X_ = CONST{0} + return _X_ + +==== 157 () ==== + reraise _O_ + +==== 104 () ==== + _K_ = Stdlib_e_[7] + _L_ = _G_ === _K_ + if _L_ then 111 () else 114 () + +==== 111 () ==== + _M_ = CONST{0} + branch 118 (_M_) + +==== 114 () ==== + reraise _G_ + +==== 69 () ==== + _B_ = _y_[0] + _C_ = Stdlib_e_[6] + _D_ = _B_ === _C_ + if _D_ then 78 () else 81 () + +==== 78 () ==== + _E_ = CONST{0} + branch 85 (_E_) + +==== 81 () ==== + reraise _y_ + +cps_block 95 +cps pc evaluates to 95 +cps_block 128 +cps pc evaluates to 128 +cps_block 145 +cps pc evaluates to 145 +cps_block 185 +cps pc evaluates to 185 +cps_block 132 +cps pc evaluates to 132 +allocate_continuation ~direct_pc:185 ~src_pc:132 ~cont_pc:185 +cps_block 121 +cps pc evaluates to 121 +cps_block 154 +cps pc evaluates to 154 +cps_block 157 +cps pc evaluates to 157 +cps_block 147 +cps pc evaluates to 147 +cps_block 120 +cps pc evaluates to 120 +allocate_continuation ~direct_pc:147 ~src_pc:120 ~cont_pc:147 +cps_block 118 +cps pc evaluates to 118 +cps_block 99 +cps pc evaluates to 99 +cps_block 88 +cps pc evaluates to 88 +cps_block 111 +cps pc evaluates to 111 +cps_block 114 +cps pc evaluates to 114 +cps_block 104 +cps pc evaluates to 104 +cps_block 87 +cps pc evaluates to 87 +cps_block 85 +cps pc evaluates to 85 +cps_block 63 +cps pc evaluates to 63 +cps_block 78 +cps pc evaluates to 78 +cps_block 81 +cps pc evaluates to 81 +cps_block 69 +cps pc evaluates to 69 +cps_block 62 +cps pc evaluates to 62 +cps_block 61 +cps pc evaluates to 61 +cname = handler_is_loop_Z_ +======== true +==== 44 () ==== + branch 45 () + +==== 45 () ==== + pushtrap 46 () handler _t_ => 22 (l_u_) continuation 50 + +==== 46 () ==== + _v_ = CONST{0} + * _w_ = f_x_(_v_) + branch 184 () + +CPS +==== 184 () ==== + poptrap 50 () + +==== 50 () ==== + return _w_ + +CPS +==== 22 (l_m_) ==== + * match_n_ = g_o_(l_m_) + branch 183 () + +CPS +==== 183 () ==== + _p_ = match_n_[0] + _q_ = 72330306 <= _p_ + if _q_ then 31 () else 37 () + +==== 31 () ==== + l_r_ = match_n_[1] + branch 22 (l_r_) + +==== 37 () ==== + exn_s_ = match_n_[1] + raise exn_s_ + +cps_block 50 +cps pc evaluates to 50 +cps_block 184 +cps pc evaluates to 184 +cps_block 46 +cps pc evaluates to 46 +allocate_continuation ~direct_pc:184 ~src_pc:46 ~cont_pc:184 +cps_block 31 +cps pc evaluates to 31 +cps_block 37 +cps pc evaluates to 37 +cps_block 183 +cps pc evaluates to 183 +cps_block 22 +cps pc evaluates to 22 +allocate_continuation ~direct_pc:183 ~src_pc:22 ~cont_pc:183 +cps_block 45 +cps pc evaluates to 45 +allocate_continuation ~direct_pc:22 ~src_pc:45 ~cont_pc:22 +allocate_closure ~branch:(return _ab_) +cps_block 44 +cps pc evaluates to 44 +cname = handler_is_merge_node___ +======== true +==== 2 () ==== + branch 3 () + +==== 3 () ==== + pushtrap 4 () handler _f_ => 14 (cst_b_) continuation 8 + +==== 4 () ==== + _g_ = CONST{0} + * _h_ = g_i_(_g_) + branch 182 () + +CPS +==== 182 () ==== + poptrap 14 (_h_) + +CPS +==== 14 (s_j_) ==== + _k_ = Stdlib_e_[27] + * _l_ = _k_(s_j_, cst_aaa_c_) + return _l_ + +cps_block 14 +cps pc evaluates to 14 +cps_block 182 +cps pc evaluates to 182 +cps_block 4 +cps pc evaluates to 4 +allocate_continuation ~direct_pc:182 ~src_pc:4 ~cont_pc:182 +cps_block 3 +cps pc evaluates to 3 +allocate_continuation ~direct_pc:14 ~src_pc:3 ~cont_pc:14 +allocate_closure ~branch:(return _ac_) +cps_block 2 +cps pc evaluates to 2 +======== false +==== 0 () ==== + global_data_a_ = "caml_get_global_data"() + cst_b_ = CONST{""} + cst_aaa_c_ = CONST{"aaa"} + cst_toto_d_ = CONST{"toto"} + Stdlib_e_ = "caml_js_get"(global_data_a_, "Stdlib"j) + branch 159 () + +==== 159 () ==== + * exceptions_Y_ = fun(s_A_){61 ()} + * handler_is_loop_Z_ = fun(f_x_, g_o_, l_u_){44 ()} + * handler_is_merge_node___ = fun(g_i_){2 ()} + Test_$_ = {tag=0; 0 = exceptions_Y_; 1 = handler_is_loop_Z_; 2 = handler_is_merge_node___} + _aa_ = "caml_register_global"(6, Test_$_, "Test"j) + stop + +rewrite_direct_block 159 +rewrite_direct_block 0 +block before first subst: +==== 186 () ==== + _ad_ = fun(l_m_){22 ()} + _ab_ = _ad_!(l_u_) + return _ab_ + +block after first subst: +==== 186 () ==== + _ad_ = fun(l_m_){22 ()} + _ab_ = _ad_!(l_u_) + return _ab_ + +block before first subst: +==== 187 () ==== + _ac_ = _ae_!(cst_b_) + return _ac_ + +block after first subst: +==== 187 () ==== + _ac_ = _ae_!(cst_b_) + return _ac_ + +block before first subst: +==== 186 () ==== + _ad_ = fun(l_m_){22 ()} + _ab_ = _ad_!(l_u_) + return _ab_ + +block after first subst: +==== 186 () ==== + _ad_ = fun(l_m_){22 ()} + _ab_ = _ad_!(l_u_) + return _ab_ + +block before first subst: +==== 187 () ==== + _ac_ = _ae_!(cst_b_) + return _ac_ + +block after first subst: +==== 187 () ==== + _ac_ = _ae_!(cst_b_) + return _ac_ + +After CPS transform: +Entry point: 0 + +==== 0 () ==== + global_data_a_ = "caml_get_global_data"() + cst_b_ = CONST{""} + cst_aaa_c_ = CONST{"aaa"} + cst_toto_d_ = CONST{"toto"} + Stdlib_e_ = "caml_js_get"(global_data_a_, "Stdlib"j) + branch 159 () + +==== 2 () ==== + branch 3 () + +==== 3 () ==== + _ae_ = fun(s_j_){14 ()} + _af_ = fun(_f_){187 ()} + _ag_ = "caml_push_trap"(_af_) + branch 4 () + +==== 4 () ==== + _g_ = CONST{0} + _ah_ = fun(_h_){182 ()} + _ai_ = g_i_(_g_, _ah_) + return _ai_ + +==== 8 () ==== + branch 14 (_h_) + +==== 10 () ==== + branch 14 (cst_b_) + +==== 14 () ==== + _k_ = Stdlib_e_[27] + _aj_ = _k_(s_j_, cst_aaa_c_, cont_ak_) + return _aj_ + +==== 22 () ==== + _al_ = fun(match_n_){183 ()} + _am_ = g_o_(l_m_, _al_) + return _am_ + +==== 31 () ==== + l_r_ = match_n_[1] + _an_ = _ad_!(l_r_) + return _an_ + +==== 37 () ==== + exn_s_ = match_n_[1] + raise_ao_ = "caml_pop_trap"() + exn_ap_ = "caml_maybe_attach_backtrace"(exn_s_, 1) + _aq_ = raise_ao_!(exn_ap_) + return _aq_ + +==== 44 () ==== + branch 45 () + +==== 45 () ==== + _ar_ = fun(_t_){186 ()} + _as_ = "caml_push_trap"(_ar_) + branch 46 () + +==== 46 () ==== + _v_ = CONST{0} + _at_ = fun(_w_){184 ()} + _au_ = f_x_(_v_, _at_) + return _au_ + +==== 50 () ==== + _av_ = cont_aw_!(_w_) + return _av_ + +==== 52 () ==== + branch 22 (l_u_) + +==== 61 () ==== + branch 62 () + +==== 62 () ==== + pushtrap 63 () handler _y_ => 69 () continuation 67 + +==== 63 () ==== + _z_ = "caml_int_of_string"(s_A_) + poptrap 85 (_z_) + +==== 67 () ==== + branch 85 (_z_) + +==== 69 () ==== + _B_ = _y_[0] + _C_ = Stdlib_e_[6] + _D_ = _B_ === _C_ + if _D_ then 78 () else 81 () + +==== 78 () ==== + _E_ = CONST{0} + branch 85 (_E_) + +==== 81 () ==== + raise_ax_ = "caml_pop_trap"() + _ay_ = "caml_maybe_attach_backtrace"(_y_, 0) + _az_ = raise_ax_!(_ay_) + return _az_ + +==== 83 () ==== + branch 85 (_E_) + +==== 85 (n_F_) ==== + branch 87 () + +==== 87 () ==== + pushtrap 88 () handler _G_ => 104 () continuation 102 + +==== 88 () ==== + _H_ = "caml_string_equal"(s_A_, "") + if _H_ then 95 () else 99 () + +==== 95 () ==== + _I_ = Stdlib_e_[7] + raise _I_ + +==== 99 () ==== + _J_ = CONST{7} + poptrap 118 (_J_) + +==== 102 () ==== + branch 118 (_J_) + +==== 104 () ==== + _K_ = Stdlib_e_[7] + _L_ = _G_ === _K_ + if _L_ then 111 () else 114 () + +==== 111 () ==== + _M_ = CONST{0} + branch 118 (_M_) + +==== 114 () ==== + raise_aA_ = "caml_pop_trap"() + _aB_ = "caml_maybe_attach_backtrace"(_G_, 0) + _aC_ = raise_aA_!(_aB_) + return _aC_ + +==== 116 () ==== + branch 118 (_M_) + +==== 118 (m_N_) ==== + branch 120 () + +==== 120 () ==== + _aD_ = fun(_O_){147 ()} + _aE_ = "caml_push_trap"(_aD_) + branch 121 () + +==== 121 () ==== + _P_ = "caml_string_equal"(s_A_, "") + if _P_ then 128 () else 132 () + +==== 128 () ==== + _Q_ = Stdlib_e_[7] + raise_aF_ = "caml_pop_trap"() + _aG_ = "caml_maybe_attach_backtrace"(_Q_, 1) + _aH_ = raise_aF_!(_aG_) + return _aH_ + +==== 132 () ==== + _R_ = Stdlib_e_[78] + _aI_ = fun(_S_){185 ()} + _aJ_ = _R_(cst_toto_d_, _aI_) + return _aJ_ + +==== 145 () ==== + _aK_ = cont_aL_!(_U_) + return _aK_ + +==== 147 () ==== + _V_ = Stdlib_e_[7] + _W_ = _O_ === _V_ + if _W_ then 154 () else 157 () + +==== 154 () ==== + _X_ = CONST{0} + _aM_ = cont_aL_!(_X_) + return _aM_ + +==== 157 () ==== + raise_aN_ = "caml_pop_trap"() + _aO_ = "caml_maybe_attach_backtrace"(_O_, 0) + _aP_ = raise_aN_!(_aO_) + return _aP_ + +==== 159 () ==== + exceptions_Y_ = fun(s_A_, cont_aL_){61 ()} + handler_is_loop_Z_ = fun(f_x_, g_o_, l_u_, cont_aw_){44 ()} + handler_is_merge_node___ = fun(g_i_, cont_ak_){2 ()} + Test_$_ = {tag=0; 0 = exceptions_Y_; 1 = handler_is_loop_Z_; 2 = handler_is_merge_node___} + _aa_ = "caml_register_global"(6, Test_$_, "Test"j) + stop + +==== 181 () ==== + branch 22 (l_u_) + +==== 182 () ==== + _aQ_ = "caml_pop_trap"() + _aR_ = _ae_!(_h_) + return _aR_ + +==== 183 () ==== + _p_ = match_n_[0] + _q_ = 72330306 <= _p_ + if _q_ then 31 () else 37 () + +==== 184 () ==== + _aS_ = "caml_pop_trap"() + branch 50 () + +==== 185 () ==== + _T_ = {tag=0; 0 = _S_; 1 = n_F_; 2 = m_N_} + _U_ = {tag=0; 0 = _T_} + _aT_ = "caml_pop_trap"() + branch 145 () + +==== 186 () ==== + _ad_ = fun(l_m_){22 ()} + _ab_ = _ad_!(l_u_) + return _ab_ + +==== 187 () ==== + _ac_ = _ae_!(cst_b_) + return _ac_ + diff --git a/compiler/tests-compiler/test.bc b/compiler/tests-compiler/test.bc new file mode 100755 index 0000000000000000000000000000000000000000..4d39172e1725c9d85464f86c979e4f1cbb436b39 GIT binary patch literal 181418 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