Skip to content

Commit 574472f

Browse files
authored
chore(compiler): Translate default optimization passes from binaryen (#1215)
1 parent 511030c commit 574472f

File tree

4 files changed

+257
-2
lines changed

4 files changed

+257
-2
lines changed

compiler/src/codegen/compcore.re

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3718,8 +3718,9 @@ let compile_wasm_module = (~env=?, ~name=?, prog) => {
37183718
Bytes.to_string(serialized_cmi),
37193719
);
37203720
validate_module(~name?, wasm_mod);
3721+
37213722
switch (Config.optimization_level^) {
3722-
| Level_three => Module.optimize(wasm_mod)
3723+
| Level_three => Optimize_mod.optimize(wasm_mod)
37233724
| Level_zero
37243725
| Level_one
37253726
| Level_two => ()
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
open Binaryen;
2+
open Grain_utils;
3+
4+
// Defaults from https://github.com/WebAssembly/binaryen/blob/version_107/src/pass.h#L170-L171
5+
let default_optimize_level = 2;
6+
let default_shrink_level = 1;
7+
8+
let has_gc = wasm_mod =>
9+
List.mem(Module.Feature.gc, Module.get_features(wasm_mod));
10+
11+
// Translation of https://github.com/WebAssembly/binaryen/blob/version_107/src/passes/pass.cpp#L546-L566
12+
let default_global_optimization_pre_passes =
13+
(~optimize_level, ~shrink_level, wasm_mod) => {
14+
List.concat([
15+
[Passes.duplicate_function_elimination, Passes.memory_packing],
16+
if (optimize_level >= 2) {
17+
[Passes.once_reduction];
18+
} else {
19+
[];
20+
},
21+
if (has_gc(wasm_mod)
22+
&& /* TODO: getTypeSystem() == TypeSystem::Nominal && */ optimize_level
23+
>= 2) {
24+
[
25+
Passes.type_refining,
26+
Passes.signature_refining,
27+
Passes.global_refining,
28+
// Global type optimization can remove fields that are not needed, which can
29+
// remove ref.funcs that were once assigned to vtables but are no longer
30+
// needed, which can allow more code to be removed globally. After those,
31+
// constant field propagation can be more effective.
32+
Passes.gto,
33+
Passes.remove_unused_module_elements,
34+
Passes.cfp,
35+
];
36+
} else {
37+
[];
38+
},
39+
]);
40+
};
41+
42+
// Translation of https://github.com/WebAssembly/binaryen/blob/version_107/src/passes/pass.cpp#L447-L544
43+
let default_function_optimization_passes =
44+
(~optimize_level, ~shrink_level, wasm_mod) => {
45+
List.concat([
46+
// All the additions here are optional if DWARF must be preserved. That is,
47+
// when DWARF is relevant we run fewer optimizations.
48+
// FIXME: support DWARF in all of them.
49+
// Untangling to semi-ssa form is helpful (but best to ignore merges
50+
// so as to not introduce new copies).
51+
if (optimize_level >= 3 || shrink_level >= 1) {
52+
[Passes.ssa_nomerge];
53+
} else {
54+
[];
55+
},
56+
// if we are willing to work very very hard, flatten the IR and do opts
57+
// that depend on flat IR
58+
if (optimize_level >= 4) {
59+
[
60+
Passes.flatten,
61+
// LocalCSE is particularly useful after flatten (see comment in the pass
62+
// itself), but we must simplify locals a little first (as flatten adds many
63+
// new and redundant ones, which make things seem different if we do not
64+
// run some amount of simplify-locals first).
65+
Passes.simplify_locals_notee_nostructure,
66+
Passes.local_cse,
67+
// TODO: add rereloop etc. here
68+
];
69+
} else {
70+
[];
71+
},
72+
[
73+
Passes.dce,
74+
Passes.remove_unused_names,
75+
Passes.remove_unused_brs,
76+
Passes.remove_unused_names,
77+
Passes.optimize_instructions,
78+
],
79+
if (optimize_level >= 2 || shrink_level >= 2) {
80+
[Passes.pick_load_signs];
81+
} else {
82+
[];
83+
},
84+
// early propagation
85+
if (optimize_level >= 3 || shrink_level >= 2) {
86+
[Passes.precompute_propagate];
87+
} else {
88+
[Passes.precompute];
89+
},
90+
if (Settings.get_low_memory_unused()) {
91+
if (optimize_level >= 3 || shrink_level >= 1) {
92+
[Passes.optimize_added_constants_propagate];
93+
} else {
94+
[Passes.optimize_added_constants];
95+
};
96+
} else {
97+
[];
98+
},
99+
if (optimize_level >= 2 || shrink_level >= 2) {
100+
[Passes.code_pushing];
101+
} else {
102+
[];
103+
},
104+
[
105+
// don't create if/block return values yet, as coalesce can remove copies that
106+
// that could inhibit
107+
Passes.simplify_locals_nostructure,
108+
Passes.vacuum, // previous pass creates garbage
109+
Passes.reorder_locals,
110+
// simplify-locals opens opportunities for optimizations
111+
Passes.remove_unused_brs,
112+
],
113+
if (optimize_level > 1 && has_gc(wasm_mod)) {
114+
[Passes.heap2local];
115+
} else {
116+
[];
117+
},
118+
// if we are willing to work hard, also optimize copies before coalescing
119+
if (optimize_level >= 3 || shrink_level >= 2) {
120+
[
121+
Passes.merge_locals // very slow on e.g. sqlite
122+
];
123+
} else {
124+
[];
125+
},
126+
if (optimize_level > 1 && has_gc(wasm_mod)) {
127+
[
128+
// Coalescing may prevent subtyping (as a coalesced local must have the
129+
// supertype of all those combined into it), so subtype first.
130+
// TODO: when optimizing for size, maybe the order should reverse?
131+
Passes.local_subtyping,
132+
];
133+
} else {
134+
[];
135+
},
136+
[Passes.coalesce_locals],
137+
if (optimize_level >= 3 || shrink_level >= 1) {
138+
[Passes.local_cse];
139+
} else {
140+
[];
141+
},
142+
[
143+
Passes.simplify_locals,
144+
Passes.vacuum,
145+
Passes.reorder_locals,
146+
Passes.coalesce_locals,
147+
Passes.reorder_locals,
148+
Passes.vacuum,
149+
],
150+
if (optimize_level >= 3 || shrink_level >= 1) {
151+
[Passes.code_folding];
152+
} else {
153+
[];
154+
},
155+
[
156+
Passes.merge_blocks, // makes remove-unused-brs more effective
157+
Passes.remove_unused_brs, // coalesce-locals opens opportunities
158+
Passes.remove_unused_names, // remove-unused-brs opens opportunities
159+
Passes.merge_blocks // clean up remove-unused-brs new blocks
160+
],
161+
// late propagation
162+
if (optimize_level >= 3 || shrink_level >= 2) {
163+
[Passes.precompute_propagate];
164+
} else {
165+
[Passes.precompute];
166+
},
167+
[Passes.optimize_instructions],
168+
if (optimize_level >= 2 || shrink_level >= 1) {
169+
[
170+
Passes.rse // after all coalesce-locals, and before a final vacuum
171+
];
172+
} else {
173+
[];
174+
},
175+
[Passes.vacuum] // just to be safe
176+
]);
177+
};
178+
179+
// Translation of https://github.com/WebAssembly/binaryen/blob/version_107/src/passes/pass.cpp#L568-L599
180+
let default_global_optimization_post_passes =
181+
(~optimize_level, ~shrink_level, wasm_mod) => {
182+
List.concat([
183+
if (optimize_level >= 2 || shrink_level >= 1) {
184+
[Passes.dae_optimizing];
185+
} else {
186+
[];
187+
},
188+
if (optimize_level >= 2 || shrink_level >= 2) {
189+
[Passes.inlining_optimizing];
190+
} else {
191+
[];
192+
},
193+
// Optimizations show more functions as duplicate, so run this here in Post.
194+
[
195+
Passes.duplicate_function_elimination,
196+
Passes.duplicate_import_elimination,
197+
],
198+
// perform after the number of functions is reduced by inlining-optimizing
199+
if (shrink_level >= 2) {
200+
[Passes.merge_similar_functions];
201+
} else {
202+
[];
203+
},
204+
if (optimize_level >= 2 || shrink_level >= 2) {
205+
[Passes.simplify_globals_optimizing];
206+
} else {
207+
[Passes.simplify_globals];
208+
},
209+
[
210+
Passes.remove_unused_module_elements,
211+
// may allow more inlining/dae/etc., need --converge for that
212+
Passes.directize,
213+
],
214+
// perform Stack IR optimizations here, at the very end of the
215+
// optimization pipeline
216+
if (optimize_level >= 2 || shrink_level >= 1) {
217+
[Passes.generate_stack_ir, Passes.optimize_stack_ir];
218+
} else {
219+
[];
220+
},
221+
]);
222+
};
223+
224+
let optimize =
225+
(
226+
~optimize_level=default_optimize_level,
227+
~shrink_level=default_shrink_level,
228+
wasm_mod,
229+
) => {
230+
// Translation of https://github.com/WebAssembly/binaryen/blob/version_107/src/passes/pass.cpp#L441-L445
231+
let default_optimizations_passes =
232+
List.concat([
233+
default_global_optimization_pre_passes(
234+
~optimize_level,
235+
~shrink_level,
236+
wasm_mod,
237+
),
238+
default_function_optimization_passes(
239+
~optimize_level,
240+
~shrink_level,
241+
wasm_mod,
242+
),
243+
default_global_optimization_post_passes(
244+
~optimize_level,
245+
~shrink_level,
246+
wasm_mod,
247+
),
248+
]);
249+
250+
Module.run_passes(wasm_mod, default_optimizations_passes);
251+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
open Binaryen;
2+
3+
let optimize: (~optimize_level: int=?, ~shrink_level: int=?, Module.t) => unit;

compiler/src/linking/link.re

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ let link_modules = ({asm: wasm_mod, signature}) => {
612612
failwith("Generated invalid linked module");
613613
};
614614
switch (Config.optimization_level^) {
615-
| Level_three => Module.optimize(linked_mod)
615+
| Level_three => Optimize_mod.optimize(linked_mod)
616616
| Level_zero
617617
| Level_one
618618
| Level_two => ()

0 commit comments

Comments
 (0)