Skip to content

Commit f81c78b

Browse files
authored
wasm2js: start to optionally optimize the JS (#2046)
Removes redundant | 0s and similar things. (Apparently closure compiler doesn't do that, so makes sense to do here.)
1 parent 5d3fcff commit f81c78b

53 files changed

Lines changed: 5121 additions & 5188 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/test/wasm2js.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_wasm2js_output():
5959
with open('split.wast', 'w') as o:
6060
o.write(module + '\n'.join(asserts))
6161

62-
cmd = WASM2JS + ['split.wast']
62+
cmd = WASM2JS + ['split.wast', '-O']
6363
if 'emscripten' in wasm:
6464
cmd += ['--emscripten']
6565
out = run_command(cmd)
@@ -150,7 +150,7 @@ def update_wasm2js_tests():
150150
with open('split.wast', 'w') as o:
151151
o.write(module + '\n'.join(asserts))
152152

153-
cmd = WASM2JS + ['split.wast']
153+
cmd = WASM2JS + ['split.wast', '-O']
154154
if 'emscripten' in wasm:
155155
cmd += ['--emscripten']
156156
out = run_command(cmd)

src/emscripten-optimizer/optimizer.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,6 @@ extern bool preciseF32,
2727

2828
extern cashew::Ref extraInfo;
2929

30-
void eliminateDeadFuncs(cashew::Ref ast);
31-
void eliminate(cashew::Ref ast, bool memSafe=false);
32-
void eliminateMemSafe(cashew::Ref ast);
33-
void simplifyExpressions(cashew::Ref ast);
34-
void optimizeFrounds(cashew::Ref ast);
35-
void simplifyIfs(cashew::Ref ast);
36-
void registerize(cashew::Ref ast);
37-
void registerizeHarder(cashew::Ref ast);
38-
void minifyLocals(cashew::Ref ast);
39-
void asmLastOpts(cashew::Ref ast);
40-
4130
//
4231

4332
enum AsmType {

src/emscripten-optimizer/simple_ast.cpp

Lines changed: 43 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -183,189 +183,64 @@ void dump(const char *str, Ref node, bool pretty) {
183183
std::cerr << std::endl;
184184
}
185185

186-
// AST traversals
187-
188186
// Traversals
189187

190188
struct TraverseInfo {
191189
TraverseInfo() = default;
192-
TraverseInfo(Ref node, ArrayStorage* arr) : node(node), arr(arr), index(0) {}
193-
Ref node;
194-
ArrayStorage* arr;
195-
int index;
196-
};
197-
198-
template<class T, int init>
199-
struct StackedStack { // a stack, on the stack
200-
T stackStorage[init];
201-
T* storage;
202-
int used = 0;
203-
int available = init; // used amount, available amount
204-
bool alloced = false;
205-
206-
StackedStack() {
207-
storage = stackStorage;
208-
}
209-
~StackedStack() {
210-
if (alloced) free(storage);
211-
}
212-
213-
int size() { return used; }
214-
215-
void push_back(const T& t) {
216-
assert(used <= available);
217-
if (used == available) {
218-
available *= 2;
219-
if (!alloced) {
220-
T* old = storage;
221-
storage = (T*)malloc(sizeof(T)*available);
222-
memcpy(storage, old, sizeof(T)*used);
223-
alloced = true;
224-
} else {
225-
T *newStorage = (T*)realloc(storage, sizeof(T)*available);
226-
assert(newStorage);
227-
storage = newStorage;
228-
}
229-
}
230-
assert(used < available);
231-
assert(storage);
232-
storage[used++] = t;
233-
}
234-
235-
T& back() {
236-
assert(used > 0);
237-
return storage[used-1];
238-
}
239-
240-
void pop_back() {
241-
assert(used > 0);
242-
used--;
243-
}
244-
};
245-
246-
#define visitable(node) (node->isArray() && node->size() > 0)
247-
248-
#define TRAV_STACK 40
249-
250-
// Traverse, calling visit before the children
251-
void traversePre(Ref node, std::function<void (Ref)> visit) {
252-
if (!visitable(node)) return;
253-
visit(node);
254-
StackedStack<TraverseInfo, TRAV_STACK> stack;
255-
int index = 0;
256-
ArrayStorage* arr = &node->getArray();
257-
int arrsize = (int)arr->size();
258-
Ref* arrdata = &(*arr)[0];
259-
stack.push_back(TraverseInfo(node, arr));
260-
while (1) {
261-
if (index < arrsize) {
262-
Ref sub = *(arrdata+index);
263-
index++;
264-
if (visitable(sub)) {
265-
stack.back().index = index;
266-
index = 0;
267-
visit(sub);
268-
arr = &sub->getArray();
269-
arrsize = (int)arr->size();
270-
arrdata = &(*arr)[0];
271-
stack.push_back(TraverseInfo(sub, arr));
190+
TraverseInfo(Ref node) : node(node) {
191+
assert(node.get());
192+
if (node->isArray()) {
193+
for (size_t i = 0; i < node->size(); i++) {
194+
maybeAdd(node[i]);
272195
}
196+
} else if (node->isAssign()) {
197+
auto assign = node->asAssign();
198+
maybeAdd(assign->target());
199+
maybeAdd(assign->value());
200+
} else if (node->isAssignName()) {
201+
auto assign = node->asAssignName();
202+
maybeAdd(assign->value());
273203
} else {
274-
stack.pop_back();
275-
if (stack.size() == 0) break;
276-
TraverseInfo& back = stack.back();
277-
index = back.index;
278-
arr = back.arr;
279-
arrsize = (int)arr->size();
280-
arrdata = &(*arr)[0];
204+
// no children
281205
}
282206
}
283-
}
207+
Ref node;
208+
size_t index = -1;
209+
std::vector<Ref> children;
284210

285-
// Traverse, calling visitPre before the children and visitPost after
286-
void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function<void (Ref)> visitPost) {
287-
if (!visitable(node)) return;
288-
visitPre(node);
289-
StackedStack<TraverseInfo, TRAV_STACK> stack;
290-
int index = 0;
291-
ArrayStorage* arr = &node->getArray();
292-
int arrsize = (int)arr->size();
293-
Ref* arrdata = &(*arr)[0];
294-
stack.push_back(TraverseInfo(node, arr));
295-
while (1) {
296-
if (index < arrsize) {
297-
Ref sub = *(arrdata+index);
298-
index++;
299-
if (visitable(sub)) {
300-
stack.back().index = index;
301-
index = 0;
302-
visitPre(sub);
303-
arr = &sub->getArray();
304-
arrsize = (int)arr->size();
305-
arrdata = &(*arr)[0];
306-
stack.push_back(TraverseInfo(sub, arr));
307-
}
308-
} else {
309-
visitPost(stack.back().node);
310-
stack.pop_back();
311-
if (stack.size() == 0) break;
312-
TraverseInfo& back = stack.back();
313-
index = back.index;
314-
arr = back.arr;
315-
arrsize = (int)arr->size();
316-
arrdata = &(*arr)[0];
211+
private:
212+
void maybeAdd(Ref child) {
213+
if (child.get()) {
214+
children.push_back(child);
317215
}
318216
}
319-
}
217+
};
320218

321-
// Traverse, calling visitPre before the children and visitPost after. If pre returns false, do not traverse children
322-
void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, std::function<void (Ref)> visitPost) {
323-
if (!visitable(node)) return;
324-
if (!visitPre(node)) return;
325-
StackedStack<TraverseInfo, TRAV_STACK> stack;
326-
int index = 0;
327-
ArrayStorage* arr = &node->getArray();
328-
int arrsize = (int)arr->size();
329-
Ref* arrdata = &(*arr)[0];
330-
stack.push_back(TraverseInfo(node, arr));
331-
while (1) {
332-
if (index < arrsize) {
333-
Ref sub = *(arrdata+index);
334-
index++;
335-
if (visitable(sub)) {
336-
if (visitPre(sub)) {
337-
stack.back().index = index;
338-
index = 0;
339-
arr = &sub->getArray();
340-
arrsize = (int)arr->size();
341-
arrdata = &(*arr)[0];
342-
stack.push_back(TraverseInfo(sub, arr));
343-
}
219+
// Traverse, calling visit after the children
220+
void traversePost(Ref node, std::function<void (Ref)> visit) {
221+
std::vector<TraverseInfo> stack;
222+
stack.push_back(TraverseInfo(node));
223+
while (!stack.empty()) {
224+
TraverseInfo& back = stack.back();
225+
if (back.index == size_t(-1)) {
226+
// This is the first time we see this. Push its children.
227+
back.index = 0;
228+
for (auto child : back.children) {
229+
stack.emplace_back(child);
344230
}
345-
} else {
346-
visitPost(stack.back().node);
347-
stack.pop_back();
348-
if (stack.size() == 0) break;
349-
TraverseInfo& back = stack.back();
350-
index = back.index;
351-
arr = back.arr;
352-
arrsize = (int)arr->size();
353-
arrdata = &(*arr)[0];
231+
continue;
354232
}
355-
}
356-
}
357-
358-
// Traverses all the top-level functions in the document
359-
void traverseFunctions(Ref ast, std::function<void (Ref)> visit) {
360-
if (!ast || ast->size() == 0) return;
361-
if (ast[0] == TOPLEVEL) {
362-
Ref stats = ast[1];
363-
for (size_t i = 0; i < stats->size(); i++) {
364-
Ref curr = stats[i];
365-
if (curr[0] == DEFUN) visit(curr);
233+
if (back.index < back.children.size()) {
234+
// Visit this child.
235+
back.index++;
236+
visit(back.children[back.index - 1]);
237+
continue;
366238
}
367-
} else if (ast[0] == DEFUN) {
368-
visit(ast);
239+
assert(back.index == back.children.size());
240+
// Time to visit the node itself
241+
auto node = back.node;
242+
stack.pop_back();
243+
visit(node);
369244
}
370245
}
371246

src/emscripten-optimizer/simple_ast.h

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -527,17 +527,8 @@ struct AssignName : public Value {
527527

528528
// AST traversals
529529

530-
// Traverse, calling visit before the children
531-
void traversePre(Ref node, std::function<void (Ref)> visit);
532-
533-
// Traverse, calling visitPre before the children and visitPost after
534-
void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function<void (Ref)> visitPost);
535-
536-
// Traverse, calling visitPre before the children and visitPost after. If pre returns false, do not traverse children
537-
void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, std::function<void (Ref)> visitPost);
538-
539-
// Traverses all the top-level functions in the document
540-
void traverseFunctions(Ref ast, std::function<void (Ref)> visit);
530+
// Traverse, calling visit after the children
531+
void traversePost(Ref node, std::function<void (Ref)> visit);
541532

542533
// JS printing support
543534

src/tools/wasm2js.cpp

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#include "support/file.h"
2424
#include "wasm-s-parser.h"
2525
#include "wasm2js.h"
26-
#include "tool-options.h"
26+
#include "optimization-options.h"
2727

2828
using namespace cashew;
2929
using namespace wasm;
@@ -32,14 +32,70 @@ using namespace wasm;
3232

3333
namespace {
3434

35-
static void emitWasm(Module& wasm, Output& output, Wasm2JSBuilder::Flags flags, Name name) {
35+
template<typename T>
36+
static void printJS(Ref ast, T& output) {
37+
JSPrinter jser(true, true, ast);
38+
jser.printAst();
39+
output << jser.buffer << std::endl;
40+
}
41+
42+
static void optimizeJS(Ref ast) {
43+
// helpers
44+
auto isOrZero = [](Ref node) {
45+
return node->isArray() && node->size() > 0 && node[0] == BINARY && node[1] == OR && node[3]->isNumber() && node[3]->getNumber() == 0;
46+
};
47+
48+
auto isBitwise = [](Ref node) {
49+
if (node->isArray() && node->size() > 0 && node[0] == BINARY) {
50+
auto op = node[1];
51+
return op == OR || op == AND || op == XOR || op == RSHIFT || op == TRSHIFT || op == LSHIFT;
52+
}
53+
return false;
54+
};
55+
56+
// x >> 0 => x | 0
57+
traversePost(ast, [](Ref node) {
58+
if (node->isArray() && node->size() > 0 && node[0] == BINARY && node[1] == RSHIFT && node[3]->isNumber()) {
59+
if (node[3]->getNumber() == 0) {
60+
node[1]->setString(OR);
61+
}
62+
}
63+
});
64+
65+
traversePost(ast, [&](Ref node) {
66+
// x | 0 | 0 => x | 0
67+
if (isOrZero(node)) {
68+
while (isOrZero(node[2])) {
69+
node[2] = node[2][2];
70+
}
71+
if (isBitwise(node[2])) {
72+
auto child = node[2];
73+
node[1] = child[1];
74+
node[2] = child[2];
75+
node[3] = child[3];
76+
}
77+
}
78+
// x | 0 going into a bitwise op => skip the | 0
79+
else if (isBitwise(node)) {
80+
while (isOrZero(node[2])) {
81+
node[2] = node[2][2];
82+
}
83+
while (isOrZero(node[3])) {
84+
node[3] = node[3][2];
85+
}
86+
}
87+
});
88+
}
89+
90+
static void emitWasm(Module& wasm, Output& output, Wasm2JSBuilder::Flags flags, Name name, bool optimize=false) {
3691
Wasm2JSBuilder wasm2js(flags);
3792
auto js = wasm2js.processWasm(&wasm, name);
93+
if (optimize) {
94+
optimizeJS(js);
95+
}
3896
Wasm2JSGlue glue(wasm, output, flags, name);
3997
glue.emitPre();
40-
JSPrinter jser(true, true, js);
41-
jser.printAst();
42-
output << jser.buffer << std::endl;
98+
printJS(js, output);
4399
glue.emitPost();
44100
}
45101

@@ -352,7 +408,7 @@ void AssertionEmitter::emit() {
352408

353409
int main(int argc, const char *argv[]) {
354410
Wasm2JSBuilder::Flags flags;
355-
ToolOptions options("wasm2js", "Transform .wasm/.wast files to asm.js");
411+
OptimizationOptions options("wasm2js", "Transform .wasm/.wast files to asm.js");
356412
options
357413
.add("--output", "-o", "Output file (stdout if not specified)",
358414
Options::Arguments::One,
@@ -438,7 +494,7 @@ int main(int argc, const char *argv[]) {
438494
if (!binaryInput && options.extra["asserts"] == "1") {
439495
AssertionEmitter(*root, *sexprBuilder, output, flags).emit();
440496
} else {
441-
emitWasm(wasm, output, flags, "asmFunc");
497+
emitWasm(wasm, output, flags, "asmFunc", options.passOptions.optimizeLevel > 0);
442498
}
443499

444500
if (options.debug) std::cerr << "done." << std::endl;

0 commit comments

Comments
 (0)