Skip to content

Commit 33fc33d

Browse files
committed
Effects: fix compilation of exception handlers
1 parent fc88c1f commit 33fc33d

File tree

2 files changed

+143
-81
lines changed

2 files changed

+143
-81
lines changed

compiler/lib/effects.ml

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ let reverse_graph g =
5151

5252
type 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

7678
let 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+
105125
let 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
232251
type 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

347429
let 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

compiler/tests-compiler/effects_exceptions.ml

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -58,51 +58,53 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
5858

5959
function exceptions(s,cont)
6060
{try
61-
{var _n_=runtime.caml_int_of_string(s),n=_n_}
62-
catch(_r_)
63-
{var _g_=caml_wrap_exception(_r_);
64-
if(_g_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_g_)}
65-
var n=0,_h_=0}
61+
{var _p_=runtime.caml_int_of_string(s),n=_p_}
62+
catch(_t_)
63+
{var _i_=caml_wrap_exception(_t_);
64+
if(_i_[1] !== Stdlib[7]){var raise$1=caml_pop_trap();return raise$1(_i_)}
65+
var n=0,_j_=0}
6666
try
67-
{if(caml_string_equal(s,cst$0))throw Stdlib[8];var _m_=7,m=_m_}
68-
catch(_q_)
69-
{var _i_=caml_wrap_exception(_q_);
70-
if(_i_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_i_)}
71-
var m=0,_j_=0}
67+
{if(caml_string_equal(s,cst$0))throw Stdlib[8];var _o_=7,m=_o_}
68+
catch(_s_)
69+
{var _k_=caml_wrap_exception(_s_);
70+
if(_k_ !== Stdlib[8]){var raise$0=caml_pop_trap();return raise$0(_k_)}
71+
var m=0,_l_=0}
7272
caml_push_trap
73-
(function(_p_)
74-
{if(_p_ === Stdlib[8])return cont(0);
73+
(function(_r_)
74+
{if(_r_ === Stdlib[8])return cont(0);
7575
var raise=caml_pop_trap();
76-
return raise(_p_)});
76+
return raise(_r_)});
7777
if(caml_string_equal(s,cst))
78-
{var _k_=Stdlib[8],raise=caml_pop_trap();return raise(_k_)}
79-
var _l_=Stdlib[79];
78+
{var _m_=Stdlib[8],raise=caml_pop_trap();return raise(_m_)}
79+
var _n_=Stdlib[79];
8080
return caml_cps_call2
81-
(_l_,
81+
(_n_,
8282
cst_toto,
83-
function(_o_){caml_pop_trap();return cont([0,[0,_o_,n,m]])})}
83+
function(_q_){caml_pop_trap();return cont([0,[0,_q_,n,m]])})}
8484
//end |}];
8585
print_fun_decl code (Some "handler_is_loop");
8686
[%expect {|
8787
function handler_is_loop(f,g,l,cont)
88-
{function _e_(l)
89-
{return caml_cps_call2
90-
(g,
91-
l,
92-
function(match)
93-
{if(72330306 <= match[1])
94-
{var l=match[2];return caml_cps_exact_call1(_e_,l)}
95-
var exn=match[2],raise=caml_pop_trap();
96-
return raise(exn)})}
97-
caml_push_trap(_e_);
98-
var _d_=0;
99-
return caml_cps_call2(f,_d_,function(_f_){caml_pop_trap();return cont(_f_)})}
88+
{caml_push_trap
89+
(function(_g_)
90+
{function _h_(l)
91+
{return caml_cps_call2
92+
(g,
93+
l,
94+
function(match)
95+
{if(72330306 <= match[1])
96+
{var l=match[2];return caml_cps_exact_call1(_h_,l)}
97+
var exn=match[2],raise=caml_pop_trap();
98+
return raise(exn)})}
99+
return _h_(l)});
100+
var _e_=0;
101+
return caml_cps_call2(f,_e_,function(_f_){caml_pop_trap();return cont(_f_)})}
100102
//end |}];
101103
print_fun_decl code (Some "handler_is_merge_node");
102104
[%expect {|
103105
function handler_is_merge_node(g,cont)
104106
{function _b_(s){return caml_cps_call3(Stdlib[28],s,cst_aaa,cont)}
105-
caml_push_trap(_b_);
107+
caml_push_trap(function(_d_){return _b_(cst$1)});
106108
var _a_=0;
107109
return caml_cps_call2(g,_a_,function(_c_){caml_pop_trap();return _b_(_c_)})}
108110
//end |}]

0 commit comments

Comments
 (0)