Skip to content

Commit 16d8210

Browse files
authored
Compiler: exit loop early, even leading loops (#2077)
1 parent e9cabc0 commit 16d8210

File tree

8 files changed

+1752
-1546
lines changed

8 files changed

+1752
-1546
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# dev
2+
3+
## Features/Changes
4+
* Compiler: exit-loop-early in more cases (#2077)
5+
16
# 6.1.1 (2025-07-07) - Lille
27

38
## Bug fixes

compiler/lib-wasm/generate.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,7 @@ let start () = make_context ~value_type:Gc_target.Type.value
15201520

15211521
let f ~context ~unit_name p ~live_vars ~in_cps ~deadcode_sentinal ~global_flow_data =
15221522
let state, info = global_flow_data in
1523+
let p = Structure.norm p in
15231524
let types = Typing.f ~state ~info ~deadcode_sentinal p in
15241525
let t = Timer.make () in
15251526
let p = fix_switch_branches p in

compiler/lib/generate.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,6 +2399,7 @@ let f
23992399
~should_export
24002400
~warn_on_unhandled_effect
24012401
~deadcode_sentinal =
2402+
let p = Structure.norm p in
24022403
let mutated_vars = Freevars.f_mutable p in
24032404
let freevars = Freevars.f p in
24042405
let t' = Timer.make () in

compiler/lib/structure.ml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,67 @@ let build_graph blocks pc =
277277
let g = build_graph blocks pc in
278278
shrink_loops blocks g;
279279
g
280+
281+
(* Ensure that all loops have a predecessor block. Function
282+
shrink_loops assumes this. *)
283+
let norm p =
284+
let free_pc = ref p.free_pc in
285+
let visited = BitSet.create' p.free_pc in
286+
let rec mark_used ~function_start pc =
287+
if not (BitSet.mem visited pc)
288+
then (
289+
if not function_start then BitSet.set visited pc;
290+
let block = Addr.Map.find pc p.blocks in
291+
List.iter
292+
~f:(fun i ->
293+
match i with
294+
| Let (_, Closure (_, (pc', _), _)) -> mark_used ~function_start:true pc'
295+
| _ -> ())
296+
block.body;
297+
fold_children p.blocks pc (fun pc' () -> mark_used ~function_start:false pc') ())
298+
in
299+
mark_used ~function_start:true p.start;
300+
let closure_need_update = function
301+
| Let (_, Closure (_, (pc, _), _)) -> BitSet.mem visited pc
302+
| _ -> false
303+
in
304+
let rewrite_cont cont blocks =
305+
let npc = !free_pc in
306+
incr free_pc;
307+
let body =
308+
let b = Addr.Map.find (fst cont) blocks in
309+
match b.body with
310+
| (Event _ as e) :: _ -> [ e ]
311+
| _ -> []
312+
in
313+
let blocks = Addr.Map.add npc { body; params = []; branch = Branch cont } blocks in
314+
(npc, []), blocks
315+
in
316+
let blocks =
317+
Addr.Map.fold
318+
(fun pc block blocks ->
319+
if List.exists block.body ~f:closure_need_update
320+
then
321+
let blocks = ref blocks in
322+
let body =
323+
List.map block.body ~f:(function
324+
| Let (x, Closure (params, cont, loc)) as i when closure_need_update i ->
325+
let cont', blocks' = rewrite_cont cont !blocks in
326+
blocks := blocks';
327+
Let (x, Closure (params, cont', loc))
328+
| i -> i)
329+
in
330+
Addr.Map.add pc { block with body } !blocks
331+
else blocks)
332+
p.blocks
333+
p.blocks
334+
in
335+
if BitSet.mem visited p.start
336+
then (
337+
let npc = !free_pc in
338+
incr free_pc;
339+
let blocks =
340+
Addr.Map.add npc { body = []; params = []; branch = Branch (p.start, []) } blocks
341+
in
342+
{ blocks; free_pc = !free_pc; start = npc })
343+
else { blocks; free_pc = !free_pc; start = p.start }

compiler/lib/structure.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ val sort_in_post_order : t -> Addr.t list -> Addr.t list
2424
val blocks_in_reverse_post_order : t -> Code.Addr.t list
2525

2626
val get_nodes : t -> Addr.Set.t
27+
28+
val norm : program -> program
29+
(** [norm p] normalizes a program [p] to accommodate [Structure.build_graph] logic.
30+
In practice, it ensures that all loops have a predecessor block and allows to exit loops early. *)

compiler/tests-compiler/gh1559.ml

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -195,37 +195,36 @@ let () = my_ref := 2
195195
my_ref = [0, 1],
196196
nesting = 1;
197197
function handle_state(t$1){
198-
var t$0 = t$1;
199-
for(;;){
200-
let t$1 = t$0;
201-
var
202-
this_will_be_undefined =
203-
function(param){
204-
a:
205-
{if(t$1 && 1 === t$1[1]){var _c_ = 1; break a;} var _c_ = 0;}
206-
return _c_ ? 1 : 2;
207-
};
208-
if(t$0) var i = t$0[1], match = i; else var match = - 1;
209-
if(0 === match){
210-
var
211-
g = function(param){return 2 + this_will_be_undefined(0) | 0;},
212-
_b_ = g(0);
213-
return g(0) + _b_ | 0;
214-
}
215-
if(1 === match){
216-
if(caml_call2(Stdlib_Int[8], nesting, 0)) return nesting;
198+
a:
199+
{
200+
var t$0 = t$1;
201+
for(;;){
202+
let t$1 = t$0;
217203
var
218-
g$0 =
204+
this_will_be_undefined =
219205
function(param){
220-
return 1 < caml_call1(Stdlib_Random[5], 3)
221-
? 2 + this_will_be_undefined(0) | 0
222-
: 1;
223-
},
224-
_c_ = g$0(0);
225-
return g$0(0) + _c_ | 0;
206+
a:
207+
{if(t$1 && 1 === t$1[1]){var _c_ = 1; break a;} var _c_ = 0;}
208+
return _c_ ? 1 : 2;
209+
};
210+
if(t$0) var i = t$0[1], match = i; else var match = - 1;
211+
if(0 === match) break;
212+
if(1 === match) break a;
213+
t$0 = t;
226214
}
227-
t$0 = t;
215+
var
216+
g = function(param){return 2 + this_will_be_undefined(0) | 0;},
217+
_b_ = g(0);
218+
return g(0) + _b_ | 0;
219+
}
220+
if(caml_call2(Stdlib_Int[8], nesting, 0)) return nesting;
221+
function g$0(param){
222+
return 1 < caml_call1(Stdlib_Random[5], 3)
223+
? 2 + this_will_be_undefined(0) | 0
224+
: 1;
228225
}
226+
var _c_ = g$0(0);
227+
return g$0(0) + _c_ | 0;
229228
}
230229
var _a_ = handle_state(init), _b_ = caml_call1(Stdlib_Int[12], _a_);
231230
caml_call1(Stdlib[46], _b_);

compiler/tests-compiler/loops.ml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,7 @@ let rec fun_with_loop acc = function
8585
return caml_call1
8686
(list_rev, caml_call1(list_rev, caml_call1(list_rev, acc)));
8787
var x = param[1];
88-
if(1 === x && ! param[2]){
89-
var a$0 = [0, acc], i$0 = 0;
90-
for(;;){
91-
a$0[1] = [0, 1, a$0[1]];
92-
var _b_ = i$0 + 1 | 0;
93-
if(10 === i$0) return a$0[1];
94-
i$0 = _b_;
95-
}
96-
}
88+
if(1 === x && ! param[2]) break;
9789
var xs = param[2], a = [0, acc], i = 0;
9890
for(;;){
9991
a[1] = [0, 1, a[1]];
@@ -105,6 +97,13 @@ let rec fun_with_loop acc = function
10597
acc = acc$0;
10698
param = xs;
10799
}
100+
var a$0 = [0, acc], i$0 = 0;
101+
for(;;){
102+
a$0[1] = [0, 1, a$0[1]];
103+
var _b_ = i$0 + 1 | 0;
104+
if(10 === i$0) return a$0[1];
105+
i$0 = _b_;
106+
}
108107
}
109108
//end
110109
|}]

0 commit comments

Comments
 (0)