Skip to content

Commit ac1985e

Browse files
committed
Compiler: exit loop early
1 parent 4db5147 commit ac1985e

File tree

3 files changed

+197
-82
lines changed

3 files changed

+197
-82
lines changed

compiler/lib/generate.ml

Lines changed: 134 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020

2121
(*XXX
2222
Patterns:
23-
=> loops should avoid absorbing the whole continuation...
24-
(detect when the continuation does not loop anymore and close
25-
the loop at this point)
2623
=> should have special code for switches that include the preceding
2724
if statement when possible
2825
=> if e1 then {if e2 then P else Q} else {if e3 then P else Q}
@@ -741,6 +738,7 @@ let dominance_frontier st pc =
741738
| Old -> dominance_frontier_ref_implem st pc
742739
| New -> dominance_frontier_opt st pc
743740
in
741+
if debug () then Format.eprintf "Dominance(%d): %s@." pc (string_of_set frontier);
744742
(if debug_dominance_frontier ()
745743
then
746744
let o, n =
@@ -1357,6 +1355,49 @@ and translate_instrs ctx expr_queue loc instr =
13571355
let instrs, expr_queue = translate_instrs ctx expr_queue loc rem in
13581356
st @ instrs, expr_queue
13591357

1358+
and compute_exit_loop st pc_loop =
1359+
let cache = Hashtbl.create 17 in
1360+
let rec loop pc =
1361+
match Hashtbl.find cache pc with
1362+
| x -> x
1363+
| exception Not_found ->
1364+
let succs = Hashtbl.find st.succs pc in
1365+
let backs = Hashtbl.find st.backs pc in
1366+
let all_backs =
1367+
List.fold_left succs ~init:backs ~f:(fun acc pc -> Addr.Set.union acc (loop pc))
1368+
in
1369+
Hashtbl.replace cache pc all_backs;
1370+
all_backs
1371+
in
1372+
ignore (loop pc_loop);
1373+
let visited = Hashtbl.create 18 in
1374+
let rec find pc_loops exn_handler pc acc =
1375+
if Hashtbl.mem visited pc
1376+
then acc
1377+
else
1378+
let () = Hashtbl.add visited pc () in
1379+
let succs = Hashtbl.find st.succs pc in
1380+
let backs = Hashtbl.find cache pc in
1381+
if Addr.Set.cardinal (Addr.Set.inter pc_loops backs) > 0
1382+
|| not (List.is_empty exn_handler)
1383+
then
1384+
let pc_loops =
1385+
if Addr.Set.mem pc st.loops then Addr.Set.add pc pc_loops else pc_loops
1386+
in
1387+
let block = Addr.Map.find pc st.blocks in
1388+
List.fold_left succs ~init:acc ~f:(fun acc pc' ->
1389+
let exn_handler =
1390+
match block.branch with
1391+
| Pushtrap (_, _, (pc_exn, _), _) when pc' <> pc_exn ->
1392+
block.branch :: exn_handler
1393+
| Poptrap _ -> List.tl exn_handler
1394+
| _ -> exn_handler
1395+
in
1396+
find pc_loops exn_handler pc' acc)
1397+
else Addr.Set.add pc acc
1398+
in
1399+
find (Addr.Set.singleton pc_loop) [] pc_loop Addr.Set.empty
1400+
13601401
and compile_block st queue (pc : Addr.t) frontier interm =
13611402
if (not (List.is_empty queue))
13621403
&& (Addr.Set.mem pc st.loops || not (Config.Flag.inline ()))
@@ -1374,7 +1415,65 @@ and compile_block st queue (pc : Addr.t) frontier interm =
13741415
| [] -> J.Label.zero
13751416
in
13761417
st.loop_stack <- (pc, (lab, ref false)) :: st.loop_stack;
1377-
let never_body, body = compile_block_no_loop st queue pc frontier interm in
1418+
let exit_grey = compute_exit_loop st pc in
1419+
let size_of the_pc =
1420+
let fold _blocs pc f acc =
1421+
let succs = Hashtbl.find st.succs pc in
1422+
List.fold_left succs ~init:acc ~f:(fun acc pc -> f pc acc)
1423+
in
1424+
Code.traverse
1425+
{ fold }
1426+
(fun pc i ->
1427+
let b = Addr.Map.find pc st.blocks in
1428+
(* prefer to not nest for loops *)
1429+
let i = if Addr.Set.mem pc st.loops then i + 10000 else i in
1430+
let i =
1431+
if Addr.Set.is_empty (Hashtbl.find st.backs pc) then i else i + 10000
1432+
in
1433+
let i =
1434+
match b.branch with
1435+
| Cond _ | Switch _ -> i + 10
1436+
| Branch _ -> i
1437+
| Stop | Raise _ | Return _ | Poptrap _ -> i
1438+
| Pushtrap _ -> i + 10
1439+
in
1440+
i + List.length b.body)
1441+
the_pc
1442+
st.blocks
1443+
0
1444+
in
1445+
let list_max f = function
1446+
| [] -> None
1447+
| x :: xs ->
1448+
Some
1449+
(List.fold_left
1450+
~init:(f x, x)
1451+
xs
1452+
~f:(fun (v, data) x ->
1453+
let y = f x in
1454+
if y > x then y, x else v, data))
1455+
in
1456+
let exit_grey =
1457+
Addr.Set.elements exit_grey
1458+
|> List.partition ~f:(fun pc -> Addr.Set.is_empty (dominance_frontier st pc))
1459+
|> function
1460+
| _, [ other ] -> Addr.Set.singleton other
1461+
| l, _ -> (
1462+
match list_max (fun x -> size_of x) l with
1463+
| None -> Addr.Set.empty
1464+
| Some (size, pc) ->
1465+
if size < 50 then Addr.Set.empty else Addr.Set.singleton pc)
1466+
in
1467+
let exit_frontier = resolve_nodes interm exit_grey in
1468+
let exit_prefix, exit_cont, exit_interm =
1469+
colapse_frontier "for-loop" st exit_frontier interm
1470+
in
1471+
assert (Addr.Set.cardinal exit_cont <= 1);
1472+
let frontier_inner = Addr.Set.union frontier exit_cont in
1473+
Addr.Set.iter (protect_preds st) exit_frontier;
1474+
let never_body, body =
1475+
compile_block_no_loop st queue pc frontier_inner exit_interm
1476+
in
13781477
let body =
13791478
let rec remove_tailing_continue acc = function
13801479
| [] -> body
@@ -1383,6 +1482,7 @@ and compile_block st queue (pc : Addr.t) frontier interm =
13831482
in
13841483
remove_tailing_continue [] body
13851484
in
1485+
Addr.Set.iter (unprotect_preds st) exit_frontier;
13861486
let for_loop =
13871487
( J.For_statement
13881488
( J.Left None
@@ -1405,9 +1505,20 @@ and compile_block st queue (pc : Addr.t) frontier interm =
14051505
if !used then Some l else None
14061506
| [] -> assert false
14071507
in
1408-
match label with
1409-
| None -> never_body, [ for_loop ]
1410-
| Some label -> never_body, [ J.Labelled_statement (label, for_loop), J.N ])
1508+
let for_loop =
1509+
match label with
1510+
| None -> for_loop
1511+
| Some label -> J.Labelled_statement (label, for_loop), J.N
1512+
in
1513+
let for_loop = exit_prefix @ [ for_loop ] in
1514+
match Addr.Set.choose exit_cont with
1515+
| exception Not_found -> never_body, for_loop
1516+
| pc ->
1517+
if Addr.Set.mem pc frontier
1518+
then never_body, for_loop
1519+
else
1520+
let never_after, after = compile_block st [] pc frontier interm in
1521+
never_body || never_after, for_loop @ after)
14111522

14121523
and compile_block_no_loop st queue (pc : Addr.t) frontier interm =
14131524
if pc >= 0
@@ -1448,23 +1559,23 @@ and compile_block_no_loop st queue (pc : Addr.t) frontier interm =
14481559
let pc3s = Addr.Set.filter (fun pc -> Hashtbl.mem st.succs pc) pc3s in
14491560
(* no need to limit body for simple flow with no
14501561
instruction. eg return and branch *)
1451-
let rec limit pc =
1452-
if Addr.Set.mem pc exn_frontier
1562+
let rec keep_frontier st frontier pc =
1563+
if Addr.Set.mem pc frontier
14531564
then false
14541565
else
14551566
match Addr.Map.find pc st.blocks with
14561567
| { body = []; branch = Return _; _ } -> false
1457-
| { body = []; branch = Branch (pc', _); _ } -> limit pc'
1568+
| { body = []; branch = Branch (pc', _); _ } -> keep_frontier st frontier pc'
14581569
| _ -> true
14591570
in
1460-
let handler_frontier = Addr.Set.filter limit pc3s in
1571+
let handler_frontier = Addr.Set.filter (keep_frontier st exn_frontier) pc3s in
14611572
(* TODO: Check that we are inside the [frontier/new_frontier] *)
14621573
let handler_frontier =
14631574
resolve_nodes interm (Addr.Set.union exn_frontier handler_frontier)
14641575
in
14651576
Addr.Set.iter (incr_preds st) handler_frontier;
14661577
let prefix, handler_frontier_cont, handler_interm =
1467-
colapse_frontier st handler_frontier interm
1578+
colapse_frontier "try-catch" st handler_frontier interm
14681579
in
14691580
assert (Addr.Set.cardinal handler_frontier_cont <= 1);
14701581
let try_catch_frontier = Addr.Set.union new_frontier handler_frontier_cont in
@@ -1550,7 +1661,9 @@ and compile_block_no_loop st queue (pc : Addr.t) frontier interm =
15501661
, source_location st.ctx pc )
15511662
:: after) )
15521663
| _ ->
1553-
let prefix, frontier_cont, new_interm = colapse_frontier st new_frontier interm in
1664+
let prefix, frontier_cont, new_interm =
1665+
colapse_frontier "default" st new_frontier interm
1666+
in
15541667
assert (Addr.Set.cardinal frontier_cont <= 1);
15551668
List.iter succs ~f:(fun (pc, _) ->
15561669
if Addr.Map.mem pc new_interm then decr_preds st pc);
@@ -1568,13 +1681,14 @@ and compile_block_no_loop st queue (pc : Addr.t) frontier interm =
15681681
in
15691682
never_cond || never_after, seq @ prefix @ cond @ after
15701683

1571-
and colapse_frontier st new_frontier interm =
1684+
and colapse_frontier name st new_frontier interm =
15721685
if Addr.Set.cardinal new_frontier > 1
15731686
then (
15741687
if debug ()
15751688
then
15761689
Format.eprintf
1577-
"colapse frontier into %d: %s@."
1690+
"colapse frontier (%s) into %d: %s@."
1691+
name
15781692
st.interm_idx
15791693
(string_of_set new_frontier);
15801694
let x = Code.Var.fresh_n "switch" in
@@ -1605,11 +1719,14 @@ and colapse_frontier st new_frontier interm =
16051719
Addr.Set.iter (fun pc -> protect_preds st pc) new_frontier;
16061720
Hashtbl.add st.succs idx (Addr.Set.elements new_frontier);
16071721
Hashtbl.add st.backs idx Addr.Set.empty;
1722+
let interm =
1723+
List.fold_right pc_i ~init:interm ~f:(fun (pc, i) interm ->
1724+
Addr.Map.add pc (idx, (x, i, default = i)) interm)
1725+
in
16081726
(* The [dominance_frontier_cache] is invalidated by [incr_preds] and [protect_preds] *)
16091727
( [ J.Variable_statement [ J.V x, Some (int default, J.N) ], J.N ]
16101728
, Addr.Set.singleton idx
1611-
, List.fold_right pc_i ~init:interm ~f:(fun (pc, i) interm ->
1612-
Addr.Map.add pc (idx, (x, i, default = i)) interm) ))
1729+
, interm ))
16131730
else [], new_frontier, interm
16141731

16151732
and compile_decision_tree st backs frontier interm loc cx dtree =

compiler/tests-compiler/gh1007.ml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ let () = M.myfun M.x
156156
{|
157157
function myfun(x)
158158
{var x$0=x;
159-
a:
160159
for(;;)
161160
{if(! x$0)return 0;
162161
var
@@ -287,9 +286,9 @@ let () = M.myfun M.x
287286
{if(param)
288287
{var param$0=param[2],len$0=len + 1 | 0,len=len$0,param=param$0;
289288
continue}
290-
if(2 <= len)sort(len,l);
291-
var x$0=next;
292-
continue a}}}
289+
break}
290+
if(2 <= len)sort(len,l);
291+
var x$0=next}}
293292
//end |}]
294293

295294
let%expect_test _ =
@@ -505,7 +504,6 @@ let () = M.run ()
505504
{|
506505
function run(param$0)
507506
{var i=0;
508-
a:
509507
for(;;)
510508
{var
511509
closures=
@@ -549,10 +547,11 @@ let () = M.run ()
549547
for(;;)
550548
{if(759635106 > param$1[1])
551549
{var f=param$1[2],param$2=caml_call1(f,0),param$1=param$2;continue}
552-
var _g_=i + 1 | 0;
553-
if(4 !== i){var i=_g_;continue a}
554-
var
555-
_e_=caml_call1(Stdlib_List[9],delayed[1]),
556-
_f_=function(f){return caml_call1(f,0)};
557-
return caml_call2(Stdlib_List[17],_f_,_e_)}}}
550+
break}
551+
var _g_=i + 1 | 0;
552+
if(4 !== i){var i=_g_;continue}
553+
var
554+
_e_=caml_call1(Stdlib_List[9],delayed[1]),
555+
_f_=function(f){return caml_call1(f,0)};
556+
return caml_call2(Stdlib_List[17],_f_,_e_)}}
558557
//end |}]

0 commit comments

Comments
 (0)