@@ -51,6 +51,7 @@ let reverse_graph g =
5151
5252type control_flow_graph =
5353 { succs : (Addr .t , Addr.Set .t ) Hashtbl .t
54+ ; preds : (Addr .t , Addr.Set .t ) Hashtbl .t
5455 ; reverse_post_order : Addr .t list
5556 ; block_order : (Addr .t , int ) Hashtbl .t
5657 }
@@ -71,7 +72,8 @@ let build_graph blocks pc =
7172 traverse pc;
7273 let block_order = Hashtbl. create 16 in
7374 List. iteri ! l ~f: (fun i pc -> Hashtbl. add block_order pc i);
74- { succs; reverse_post_order = ! l; block_order }
75+ let preds = reverse_graph succs in
76+ { succs; preds; reverse_post_order = ! l; block_order }
7577
7678let dominator_tree g =
7779 (* A Simple, Fast Dominance Algorithm
@@ -102,8 +104,25 @@ let dominator_tree g =
102104 l);
103105 dom
104106
107+ (* pc dominates pc' *)
108+ let rec dominates g idom pc pc' =
109+ pc = pc'
110+ || Hashtbl. find g.block_order pc < Hashtbl. find g.block_order pc'
111+ && dominates g idom pc (Hashtbl. find idom pc')
112+
113+ (* pc has at least two forward edges moving into it *)
114+ let is_merge_node g pc =
115+ let s = try Hashtbl. find g.preds pc with Not_found -> assert false in
116+ let o = Hashtbl. find g.block_order pc in
117+ let n =
118+ Addr.Set. fold
119+ (fun pc' n -> if Hashtbl. find g.block_order pc' < o then n + 1 else n)
120+ s
121+ 0
122+ in
123+ n > 1
124+
105125let dominance_frontier g idom =
106- let preds = reverse_graph g.succs in
107126 let frontiers = Hashtbl. create 16 in
108127 Hashtbl. iter
109128 (fun pc preds ->
@@ -117,7 +136,7 @@ let dominance_frontier g idom =
117136 loop (Hashtbl. find idom runner))
118137 in
119138 Addr.Set. iter loop preds)
120- preds;
139+ g. preds;
121140 frontiers
122141
123142(* ***)
@@ -232,6 +251,8 @@ type cps_calls = Var.Set.t
232251type st =
233252 { mutable new_blocks : Code .block Addr.Map .t * Code.Addr .t
234253 ; blocks : Code .block Addr.Map .t
254+ ; cfg : control_flow_graph
255+ ; idom : (int , int ) Hashtbl .t
235256 ; jc : jump_closures
236257 ; closure_info : (Addr .t , Var .t * Code .cont ) Hashtbl .t
237258 ; cps_needed : Var.Set .t
@@ -292,14 +313,59 @@ let cps_jump_cont ~st ~src ((pc, _) as cont) =
292313 in
293314 call_block, []
294315
295- let cps_last ~st pc (last : last ) ~k : instr list * last =
316+ let allocate_continuation ~st ~alloc_jump_closures ~split_closures pc x cont =
317+ (* We need to allocate an additional closure if [cont]
318+ does not correspond to a continuation that binds [x].
319+ This closure binds the return value [x], allocates
320+ closures for dominated blocks and jumps to the next
321+ block. When entering a loop, we also have to allocate a
322+ closure to bind [x] if it is used in the loop body. In
323+ other cases, we can just pass the closure corresponding
324+ to the next block. *)
325+ let pc', args = cont in
326+ if (match args with
327+ | [] -> true
328+ | [ x' ] -> Var. equal x x'
329+ | _ -> false )
330+ &&
331+ match Hashtbl. find st.is_continuation pc' with
332+ | `Param _ -> true
333+ | `Loop -> st.live_vars.(Var. idx x) = List. length args
334+ then alloc_jump_closures, closure_of_pc ~st pc'
335+ else
336+ let body, branch = cps_branch ~st ~src: pc cont in
337+ let inner_closures, outer_closures =
338+ (* For [Pushtrap], we need to separate the closures
339+ corresponding to the exception handler body (that may make
340+ use of [x]) from the other closures that may be used outside
341+ of the exception handler. *)
342+ if not split_closures
343+ then alloc_jump_closures, []
344+ else if is_merge_node st.cfg pc'
345+ then [] , alloc_jump_closures
346+ else
347+ List. partition
348+ ~f: (fun i ->
349+ match i with
350+ | Let (_ , Closure (_ , (pc'' , [] ))) -> dominates st.cfg st.idom pc' pc''
351+ | _ -> assert false )
352+ alloc_jump_closures
353+ in
354+ let body, branch =
355+ allocate_closure ~st ~params: [ x ] ~body: (inner_closures @ body) ~branch
356+ in
357+ outer_closures @ body, branch
358+
359+ let cps_last ~st ~alloc_jump_closures pc (last : last ) ~k : instr list * last =
296360 match last with
297361 | Return x ->
362+ assert (List. is_empty alloc_jump_closures);
298363 (* Is the number of successive 'returns' is unbounded is CPS, it
299364 means that we have an unbounded of calls in direct style
300365 (even with tail call optimization) *)
301366 tail_call ~st ~exact: true ~check: false ~f: k [ x ]
302367 | Raise (x , _ ) -> (
368+ assert (List. is_empty alloc_jump_closures);
303369 match Hashtbl. find_opt st.matching_exn_handler pc with
304370 | Some pc when not (Addr.Set. mem pc st.blocks_to_transform) ->
305371 (* We are within a try ... with which is not
@@ -314,35 +380,51 @@ let cps_last ~st pc (last : last) ~k : instr list * last =
314380 ~check: false
315381 ~f: exn_handler
316382 [ x ])
317- | Stop -> [] , Stop
318- | Branch cont -> cps_branch ~st ~src: pc cont
383+ | Stop ->
384+ assert (List. is_empty alloc_jump_closures);
385+ [] , Stop
386+ | Branch cont ->
387+ let body, branch = cps_branch ~st ~src: pc cont in
388+ alloc_jump_closures @ body, branch
319389 | Cond (x , cont1 , cont2 ) ->
320- [] , Cond (x, cps_jump_cont ~st ~src: pc cont1, cps_jump_cont ~st ~src: pc cont2)
390+ ( alloc_jump_closures
391+ , Cond (x, cps_jump_cont ~st ~src: pc cont1, cps_jump_cont ~st ~src: pc cont2) )
321392 | Switch (x , c1 , c2 ) ->
322393 (* To avoid code duplication during JavaScript generation, we need
323394 to create a single block per continuation *)
324395 let cps_jump_cont = Fun. memoize (cps_jump_cont ~st ~src: pc) in
325- [] , Switch (x, Array. map c1 ~f: cps_jump_cont, Array. map c2 ~f: cps_jump_cont)
326- | Pushtrap (body_cont , _ , (handler_pc , _ ), _ ) -> (
396+ ( alloc_jump_closures
397+ , Switch (x, Array. map c1 ~f: cps_jump_cont, Array. map c2 ~f: cps_jump_cont) )
398+ | Pushtrap (body_cont , exn , ((handler_pc , _ ) as handler_cont ), _ ) -> (
327399 assert (Hashtbl. mem st.is_continuation handler_pc);
328400 match Addr.Set. mem handler_pc st.blocks_to_transform with
329- | false -> [] , last
401+ | false -> alloc_jump_closures , last
330402 | true ->
331- let exn_handler = closure_of_pc ~st handler_pc in
403+ let constr_cont, exn_handler =
404+ allocate_continuation
405+ ~st
406+ ~alloc_jump_closures
407+ ~split_closures: true
408+ pc
409+ exn
410+ handler_cont
411+ in
332412 let push_trap =
333413 Let (Var. fresh () , Prim (Extern " caml_push_trap" , [ Pv exn_handler ]))
334414 in
335415 let body, branch = cps_branch ~st ~src: pc body_cont in
336- push_trap :: body, branch)
416+ constr_cont @ ( push_trap :: body) , branch)
337417 | Poptrap cont -> (
338418 match
339419 Addr.Set. mem (Hashtbl. find st.matching_exn_handler pc) st.blocks_to_transform
340420 with
341- | false -> [] , Poptrap (cps_jump_cont ~st ~src: pc cont)
421+ | false -> alloc_jump_closures , Poptrap (cps_jump_cont ~st ~src: pc cont)
342422 | true ->
343423 let exn_handler = Var. fresh () in
344424 let body, branch = cps_branch ~st ~src: pc cont in
345- Let (exn_handler, Prim (Extern " caml_pop_trap" , [] )) :: body, branch)
425+ ( alloc_jump_closures
426+ @ (Let (exn_handler, Prim (Extern " caml_pop_trap" , [] )) :: body)
427+ , branch ))
346428
347429let cps_instr ~st (instr : instr ) : instr =
348430 match instr with
@@ -447,38 +529,18 @@ let cps_block ~st ~k pc block =
447529 let instrs, branch = f ~k in
448530 body_prefix, instrs, branch)
449531 | Some (body_prefix , Let (x , e )), Branch cont ->
450- let allocate_continuation f =
451- let constr_cont, k' =
452- (* We need to allocate an additional closure if [cont]
453- does not correspond to a continuation that binds [x].
454- This closure binds the return value [x], allocates
455- closures for dominated blocks and jumps to the next
456- block. When entering a loop, we also have to allocate a
457- closure to bind [x] if it is used in the loop body. In
458- other cases, we can just pass the closure corresponding
459- to the next block. *)
460- let pc', args = cont in
461- if (match args with
462- | [] -> true
463- | [ x' ] -> Var. equal x x'
464- | _ -> false )
465- &&
466- match Hashtbl. find st.is_continuation pc' with
467- | `Param _ -> true
468- | `Loop -> st.live_vars.(Var. idx x) = List. length args
469- then alloc_jump_closures, closure_of_pc ~st pc'
470- else
471- let body, branch = cps_branch ~st ~src: pc cont in
472- allocate_closure
532+ Option. map (rewrite_instr x e) ~f: (fun f ->
533+ let constr_cont, k' =
534+ allocate_continuation
473535 ~st
474- ~params: [ x ]
475- ~body: (alloc_jump_closures @ body)
476- ~branch
477- in
478- let instrs, branch = f ~k: k' in
479- body_prefix, constr_cont @ instrs, branch
480- in
481- Option. map (rewrite_instr x e) ~f: allocate_continuation
536+ ~alloc_jump_closures
537+ ~split_closures: false
538+ pc
539+ x
540+ cont
541+ in
542+ let instrs, branch = f ~k: k' in
543+ body_prefix, constr_cont @ instrs, branch)
482544 | Some (_, (Set_field _ | Offset_ref _ | Array_set _ | Assign _)), _
483545 | Some _, (Raise _ | Stop | Cond _ | Switch _ | Pushtrap _ | Poptrap _)
484546 | None , _ -> None
@@ -489,12 +551,8 @@ let cps_block ~st ~k pc block =
489551 | Some (body_prefix , last_instrs , last ) ->
490552 List. map body_prefix ~f: (fun i -> cps_instr ~st i) @ last_instrs, last
491553 | None ->
492- let last_instrs, last = cps_last ~st pc block.branch ~k in
493- let body =
494- List. map block.body ~f: (fun i -> cps_instr ~st i)
495- @ alloc_jump_closures
496- @ last_instrs
497- in
554+ let last_instrs, last = cps_last ~st ~alloc_jump_closures pc block.branch ~k in
555+ let body = List. map block.body ~f: (fun i -> cps_instr ~st i) @ last_instrs in
498556 body, last
499557 in
500558
@@ -553,6 +611,8 @@ let cps_transform ~live_vars ~flow_info ~cps_needed p =
553611 let st =
554612 { new_blocks = Addr.Map. empty, free_pc
555613 ; blocks
614+ ; cfg
615+ ; idom
556616 ; jc = closure_jc
557617 ; closure_info
558618 ; cps_needed
0 commit comments