summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm2wasm.h131
-rw-r--r--src/ast_utils.h28
-rw-r--r--src/js/wasm.js-post.js8
-rw-r--r--src/passes/CMakeLists.txt2
-rw-r--r--src/passes/DuplicateFunctionElimination.cpp2
-rw-r--r--src/passes/LegalizeJSInterface.cpp60
-rw-r--r--src/passes/RemoveUnusedFunctions.cpp65
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp155
-rw-r--r--src/passes/pass.cpp6
-rw-r--r--src/passes/passes.h2
-rw-r--r--src/tools/asm2wasm.cpp23
-rw-r--r--src/wasm-js.cpp15
-rw-r--r--src/wasm.h22
-rw-r--r--src/wasm/wasm-binary.cpp2
-rw-r--r--src/wasm/wasm-s-parser.cpp2
15 files changed, 369 insertions, 154 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index 2a6bcd0e7..6ed13b79a 100644
--- a/src/asm2wasm.h
+++ b/src/asm2wasm.h
@@ -105,7 +105,11 @@ Name I32_CTTZ("i32_cttz"),
STORE4("store4"),
STORE8("store8"),
STOREF("storef"),
- STORED("stored");
+ STORED("stored"),
+ FTCALL("ftCall_"),
+ MFTCALL("mftCall_"),
+ MAX_("max"),
+ MIN_("min");
// Utilities
@@ -278,6 +282,8 @@ private:
IString Math_floor;
IString Math_ceil;
IString Math_sqrt;
+ IString Math_max;
+ IString Math_min;
IString llvm_cttz_i32;
@@ -604,6 +610,14 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
assert(Math_sqrt.isNull());
Math_sqrt = name;
return;
+ } else if (imported[2] == MAX_) {
+ assert(Math_max.isNull());
+ Math_max = name;
+ return;
+ } else if (imported[2] == MIN_) {
+ assert(Math_min.isNull());
+ Math_min = name;
+ return;
}
}
std::string fullName = module[1][1]->getCString();
@@ -801,7 +815,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
// index 0 in each table is the null func, and each other index should only have one
// non-null func. However, that breaks down when function pointer casts are emulated.
if (wasm.table.segments.size() == 0) {
- wasm.table.segments.emplace_back(wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))));
+ wasm.table.segments.emplace_back(builder.makeGetGlobal(Name("tableBase"), i32));
}
auto& segment = wasm.table.segments[0];
functionTableStarts[name] = segment.data.size(); // this table starts here
@@ -831,25 +845,44 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
for (unsigned k = 0; k < contents->size(); k++) {
Ref pair = contents[k];
IString key = pair[0]->getIString();
- assert(pair[1][0] == NAME);
- IString value = pair[1][1]->getIString();
- if (key == Name("_emscripten_replace_memory")) {
- // asm.js memory growth provides this special non-asm function, which we don't need (we use grow_memory)
- assert(!wasm.checkFunction(value));
- continue;
- } else if (key == UDIVMODDI4) {
- udivmoddi4 = value;
- } else if (key == GET_TEMP_RET0) {
- getTempRet0 = value;
- }
- if (exported.count(key) > 0) {
- // asm.js allows duplicate exports, but not wasm. use the last, like asm.js
- exported[key]->value = value;
+ if (pair[1][0] == NAME) {
+ // exporting a function
+ IString value = pair[1][1]->getIString();
+ if (key == Name("_emscripten_replace_memory")) {
+ // asm.js memory growth provides this special non-asm function, which we don't need (we use grow_memory)
+ assert(!wasm.checkFunction(value));
+ continue;
+ } else if (key == UDIVMODDI4) {
+ udivmoddi4 = value;
+ } else if (key == GET_TEMP_RET0) {
+ getTempRet0 = value;
+ }
+ if (exported.count(key) > 0) {
+ // asm.js allows duplicate exports, but not wasm. use the last, like asm.js
+ exported[key]->value = value;
+ } else {
+ auto* export_ = new Export;
+ export_->name = key;
+ export_->value = value;
+ export_->kind = ExternalKind::Function;
+ wasm.addExport(export_);
+ exported[key] = export_;
+ }
} else {
+ // export a number. create a global and export it
+ assert(pair[1][0] == NUM);
+ assert(exported.count(key) == 0);
+ auto value = pair[1][1]->getInteger();
+ auto global = new Global();
+ global->name = key;
+ global->type = i32;
+ global->init = builder.makeConst(Literal(int32_t(value)));
+ global->mutable_ = false;
+ wasm.addGlobal(global);
auto* export_ = new Export;
export_->name = key;
- export_->value = value;
- export_->kind = ExternalKind::Function;
+ export_->value = global->name;
+ export_->kind = ExternalKind::Global;
wasm.addExport(export_);
exported[key] = export_;
}
@@ -877,7 +910,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
}
import->functionType = ensureFunctionType(getSig(importedFunctionTypes[name].get()), &wasm);
} else if (import->module != ASM2WASM) { // special-case the special module
- // never actually used
+ // never actually used, which means we don't know the function type since the usage tells us, so illegal for it to remain
toErase.push_back(name);
}
}
@@ -956,14 +989,19 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
void visitCallIndirect(CallIndirect* curr) {
// we already call into target = something + offset, where offset is a callImport with the name of the table. replace that with the table offset
- auto add = curr->target->cast<Binary>();
+ // note that for an ftCall or mftCall, we have no asm.js mask, so have nothing to do here
+ auto* add = curr->target->dynCast<Binary>();
+ if (!add) return;
if (add->right->is<CallImport>()) {
- auto offset = add->right->cast<CallImport>();
+ auto* offset = add->right->cast<CallImport>();
auto tableName = offset->target;
+ if (parent->functionTableStarts.find(tableName) == parent->functionTableStarts.end()) return;
add->right = parent->builder.makeConst(Literal((int32_t)parent->functionTableStarts[tableName]));
} else {
- auto offset = add->left->cast<CallImport>();
+ auto* offset = add->left->dynCast<CallImport>();
+ if (!offset) return;
auto tableName = offset->target;
+ if (parent->functionTableStarts.find(tableName) == parent->functionTableStarts.end()) return;
add->left = parent->builder.makeConst(Literal((int32_t)parent->functionTableStarts[tableName]));
}
}
@@ -977,10 +1015,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
passRunner.add<FinalizeCalls>(this);
passRunner.add<ReFinalize>(); // FinalizeCalls changes call types, need to percolate
passRunner.add<AutoDrop>(); // FinalizeCalls may cause us to require additional drops
- if (wasmOnly) {
- // we didn't legalize i64s in fastcomp, and so must legalize the interface to the outside
- passRunner.add("legalize-js-interface");
- }
+ passRunner.add("legalize-js-interface");
if (runOptimizationPasses) {
// autodrop can add some garbage
passRunner.add("vacuum");
@@ -1573,6 +1608,23 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
}
return ret;
}
+ if (name == Math_max || name == Math_min) {
+ // overloaded on type: f32 or f64
+ assert(ast[2]->size() == 2);
+ auto ret = allocator.alloc<Binary>();
+ ret->left = process(ast[2][0]);
+ ret->right = process(ast[2][1]);
+ if (ret->left->type == f32) {
+ ret->op = name == Math_max ? MaxFloat32 : MinFloat32;
+ } else if (ret->left->type == f64) {
+ ret->op = name == Math_max ? MaxFloat64 : MinFloat64;
+ } else {
+ abort();
+ }
+ ret->type = ret->left->type;
+ return ret;
+ }
+ bool tableCall = false;
if (wasmOnly) {
auto num = ast[2]->size();
switch (name.str[0]) {
@@ -1676,10 +1728,25 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
default: {}
}
}
+ // ftCall_* and mftCall_* represent function table calls, either from the outside, or
+ // from the inside of the module. when compiling to wasm, we can just convert those
+ // into table calls
+ if ((name.str[0] == 'f' && strncmp(name.str, FTCALL.str, 7) == 0) ||
+ (name.str[0] == 'm' && strncmp(name.str, MFTCALL.str, 8) == 0)) {
+ tableCall = true;
+ }
Expression* ret;
ExpressionList* operands;
bool import = false;
- if (wasm.checkImport(name)) {
+ Index firstOperand = 0;
+ Ref args = ast[2];
+ if (tableCall) {
+ auto specific = allocator.alloc<CallIndirect>();
+ specific->target = process(args[0]);
+ firstOperand = 1;
+ operands = &specific->operands;
+ ret = specific;
+ } else if (wasm.checkImport(name)) {
import = true;
auto specific = allocator.alloc<CallImport>();
specific->target = name;
@@ -1691,10 +1758,16 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
operands = &specific->operands;
ret = specific;
}
- Ref args = ast[2];
- for (unsigned i = 0; i < args->size(); i++) {
+ for (unsigned i = firstOperand; i < args->size(); i++) {
operands->push_back(process(args[i]));
}
+ if (tableCall) {
+ auto specific = ret->dynCast<CallIndirect>();
+ // note that we could also get the type from the suffix of the name, e.g., mftCall_vi
+ auto* fullType = getFunctionType(astStackHelper.getParent(), specific->operands);
+ specific->fullType = fullType->name;
+ specific->type = fullType->result;
+ }
if (import) {
Ref parent = astStackHelper.getParent();
WasmType type = !!parent ? detectWasmType(parent, &asmData) : none;
diff --git a/src/ast_utils.h b/src/ast_utils.h
index 1a5b2c83f..159762d0b 100644
--- a/src/ast_utils.h
+++ b/src/ast_utils.h
@@ -68,34 +68,6 @@ struct BreakSeeker : public PostWalker<BreakSeeker, Visitor<BreakSeeker>> {
}
};
-// Finds all functions that are reachable via direct calls.
-
-struct DirectCallGraphAnalyzer : public PostWalker<DirectCallGraphAnalyzer, Visitor<DirectCallGraphAnalyzer>> {
- Module *module;
- std::vector<Function*> queue;
- std::unordered_set<Function*> reachable;
-
- DirectCallGraphAnalyzer(Module* module, const std::vector<Function*>& root) : module(module) {
- for (auto* curr : root) {
- queue.push_back(curr);
- }
- while (queue.size()) {
- auto* curr = queue.back();
- queue.pop_back();
- if (reachable.count(curr) == 0) {
- reachable.insert(curr);
- walk(curr->body);
- }
- }
- }
- void visitCall(Call *curr) {
- auto* target = module->getFunction(curr->target);
- if (reachable.count(target) == 0) {
- queue.push_back(target);
- }
- }
-};
-
// Look for side effects, including control flow
// TODO: optimize
diff --git a/src/js/wasm.js-post.js b/src/js/wasm.js-post.js
index 03543cbae..e7a10f49a 100644
--- a/src/js/wasm.js-post.js
+++ b/src/js/wasm.js-post.js
@@ -298,11 +298,17 @@ function integrateWasmJS(Module) {
if (!env['table']) {
var TABLE_SIZE = Module['wasmTableSize'];
if (TABLE_SIZE === undefined) TABLE_SIZE = 1024; // works in binaryen interpreter at least
+ var MAX_TABLE_SIZE = Module['wasmMaxTableSize'];
if (typeof WebAssembly === 'object' && typeof WebAssembly.Table === 'function') {
- env['table'] = new WebAssembly.Table({ initial: TABLE_SIZE, maximum: TABLE_SIZE, element: 'anyfunc' });
+ if (MAX_TABLE_SIZE !== undefined) {
+ env['table'] = new WebAssembly.Table({ initial: TABLE_SIZE, maximum: MAX_TABLE_SIZE, element: 'anyfunc' });
+ } else {
+ env['table'] = new WebAssembly.Table({ initial: TABLE_SIZE, element: 'anyfunc' });
+ }
} else {
env['table'] = new Array(TABLE_SIZE); // works in binaryen interpreter at least
}
+ Module['wasmTable'] = env['table'];
}
if (!env['memoryBase']) {
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index e47a7d892..9db1c66ae 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -22,7 +22,7 @@ SET(passes_SOURCES
RemoveMemory.cpp
RemoveUnusedBrs.cpp
RemoveUnusedNames.cpp
- RemoveUnusedFunctions.cpp
+ RemoveUnusedModuleElements.cpp
ReorderLocals.cpp
ReorderFunctions.cpp
SimplifyLocals.cpp
diff --git a/src/passes/DuplicateFunctionElimination.cpp b/src/passes/DuplicateFunctionElimination.cpp
index cfe2d8565..8e8342729 100644
--- a/src/passes/DuplicateFunctionElimination.cpp
+++ b/src/passes/DuplicateFunctionElimination.cpp
@@ -127,7 +127,7 @@ struct DuplicateFunctionElimination : public Pass {
v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Function>& curr) {
return duplicates.count(curr->name) > 0;
}), v.end());
- module->updateFunctionsMap();
+ module->updateMaps();
// replace direct calls
PassRunner replacerRunner(module);
replacerRunner.add<FunctionReplacer>(&replacements);
diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp
index 3819fcf72..6e070156f 100644
--- a/src/passes/LegalizeJSInterface.cpp
+++ b/src/passes/LegalizeJSInterface.cpp
@@ -22,6 +22,9 @@
// stub methods added in this pass, that thunk i64s into i32, i32 and
// vice versa as necessary.
//
+// This pass also legalizes according to asm.js FFI rules, which
+// disallow f32s. TODO: an option to not do that, if it matters?
+//
#include <wasm.h>
#include <pass.h>
@@ -54,6 +57,15 @@ struct LegalizeJSInterface : public Pass {
auto* legal = makeLegalStub(im.get(), module, funcName);
illegalToLegal[im->name] = funcName;
newImports.push_back(legal);
+ // we need to use the legalized version in the table, as the import from JS
+ // is legal for JS. Our stub makes it look like a native wasm function.
+ for (auto& segment : module->table.segments) {
+ for (auto& name : segment.data) {
+ if (name == im->name) {
+ name = funcName;
+ }
+ }
+ }
}
}
if (illegalToLegal.size() > 0) {
@@ -94,9 +106,9 @@ private:
template<typename T>
bool isIllegal(T* t) {
for (auto param : t->params) {
- if (param == i64) return true;
+ if (param == i64 || param == f32) return true;
}
- if (t->result == i64) return true;
+ if (t->result == i64 || t->result == f32) return true;
return false;
}
@@ -115,6 +127,9 @@ private:
call->operands.push_back(I64Utilities::recreateI64(builder, legal->params.size(), legal->params.size() + 1));
legal->params.push_back(i32);
legal->params.push_back(i32);
+ } else if (param == f32) {
+ call->operands.push_back(builder.makeUnary(DemoteFloat64, builder.makeGetLocal(legal->params.size(), f64)));
+ legal->params.push_back(f64);
} else {
call->operands.push_back(builder.makeGetLocal(legal->params.size(), param));
legal->params.push_back(param);
@@ -126,17 +141,17 @@ private:
auto index = builder.addVar(legal, Name(), i64);
auto* block = builder.makeBlock();
block->list.push_back(builder.makeSetLocal(index, call));
- if (module->checkGlobal(TEMP_RET_0)) {
- block->list.push_back(builder.makeSetGlobal(
- TEMP_RET_0,
- I64Utilities::getI64High(builder, index)
- ));
- } else {
- block->list.push_back(builder.makeUnreachable()); // no way to emit the high bits :(
- }
+ ensureTempRet0(module);
+ block->list.push_back(builder.makeSetGlobal(
+ TEMP_RET_0,
+ I64Utilities::getI64High(builder, index)
+ ));
block->list.push_back(I64Utilities::getI64Low(builder, index));
block->finalize();
legal->body = block;
+ } else if (func->result == f32) {
+ legal->result = f64;
+ legal->body = builder.makeUnary(PromoteFloat32, call);
} else {
legal->result = func->result;
legal->body = call;
@@ -173,6 +188,9 @@ private:
call->operands.push_back(I64Utilities::getI64High(builder, func->params.size()));
type->params.push_back(i32);
type->params.push_back(i32);
+ } else if (param == f32) {
+ call->operands.push_back(builder.makeUnary(PromoteFloat32, builder.makeGetLocal(func->params.size(), f32)));
+ type->params.push_back(f64);
} else {
call->operands.push_back(builder.makeGetLocal(func->params.size(), param));
type->params.push_back(param);
@@ -183,13 +201,14 @@ private:
if (im->functionType->result == i64) {
call->type = i32;
Expression* get;
- if (module->checkGlobal(TEMP_RET_0)) {
- get = builder.makeGetGlobal(TEMP_RET_0, i32);
- } else {
- get = builder.makeUnreachable(); // no way to emit the high bits :(
- }
+ ensureTempRet0(module);
+ get = builder.makeGetGlobal(TEMP_RET_0, i32);
func->body = I64Utilities::recreateI64(builder, call, get);
type->result = i32;
+ } else if (im->functionType->result == f32) {
+ call->type = f64;
+ func->body = builder.makeUnary(DemoteFloat64, call);
+ type->result = f64;
} else {
call->type = im->functionType->result;
func->body = call;
@@ -201,6 +220,17 @@ private:
module->addFunctionType(type);
return legal;
}
+
+ void ensureTempRet0(Module* module) {
+ if (!module->checkGlobal(TEMP_RET_0)) {
+ Global* global = new Global;
+ global->name = TEMP_RET_0;
+ global->type = i32;
+ global->init = module->allocator.alloc<Const>()->set(Literal(int32_t(0)));
+ global->mutable_ = true;
+ module->addGlobal(global);
+ }
+ }
};
Pass *createLegalizeJSInterfacePass() {
diff --git a/src/passes/RemoveUnusedFunctions.cpp b/src/passes/RemoveUnusedFunctions.cpp
deleted file mode 100644
index ec9e271b7..000000000
--- a/src/passes/RemoveUnusedFunctions.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2016 WebAssembly Community Group participants
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Removes functions that are never used.
-//
-
-
-#include <memory>
-
-#include "wasm.h"
-#include "pass.h"
-#include "ast_utils.h"
-
-namespace wasm {
-
-struct RemoveUnusedFunctions : public Pass {
- void run(PassRunner* runner, Module* module) override {
- std::vector<Function*> root;
- // Module start is a root.
- if (module->start.is()) {
- root.push_back(module->getFunction(module->start));
- }
- // Exports are roots.
- for (auto& curr : module->exports) {
- if (curr->kind == ExternalKind::Function) {
- root.push_back(module->getFunction(curr->value));
- }
- }
- // For now, all functions that can be called indirectly are marked as roots.
- for (auto& segment : module->table.segments) {
- for (auto& curr : segment.data) {
- root.push_back(module->getFunction(curr));
- }
- }
- // Compute function reachability starting from the root set.
- DirectCallGraphAnalyzer analyzer(module, root);
- // Remove unreachable functions.
- auto& v = module->functions;
- v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Function>& curr) {
- return analyzer.reachable.count(curr.get()) == 0;
- }), v.end());
- assert(module->functions.size() == analyzer.reachable.size());
- module->updateFunctionsMap();
- }
-};
-
-Pass *createRemoveUnusedFunctionsPass() {
- return new RemoveUnusedFunctions();
-}
-
-} // namespace wasm
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp
new file mode 100644
index 000000000..cf9741961
--- /dev/null
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2016 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Removes module elements that are are never used: functions and globals,
+// which may be imported or not.
+//
+
+
+#include <memory>
+
+#include "wasm.h"
+#include "pass.h"
+#include "ast_utils.h"
+
+namespace wasm {
+
+enum class ModuleElementKind {
+ Function,
+ Global
+};
+
+typedef std::pair<ModuleElementKind, Name> ModuleElement;
+
+// Finds reachabilities
+
+struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer, Visitor<ReachabilityAnalyzer>> {
+ Module* module;
+ std::vector<ModuleElement> queue;
+ std::set<ModuleElement> reachable;
+
+ ReachabilityAnalyzer(Module* module, const std::vector<ModuleElement>& roots) : module(module) {
+ queue = roots;
+ // Globals used in memory/table init expressions are also roots
+ for (auto& segment : module->memory.segments) {
+ walk(segment.offset);
+ }
+ for (auto& segment : module->table.segments) {
+ walk(segment.offset);
+ }
+ // main loop
+ while (queue.size()) {
+ auto& curr = queue.back();
+ queue.pop_back();
+ if (reachable.count(curr) == 0) {
+ reachable.insert(curr);
+ if (curr.first == ModuleElementKind::Function) {
+ // if not an import, walk it
+ auto* func = module->checkFunction(curr.second);
+ if (func) {
+ walk(func->body);
+ }
+ } else {
+ // if not imported, it has an init expression we need to walk
+ auto* glob = module->checkGlobal(curr.second);
+ if (glob) {
+ walk(glob->init);
+ }
+ }
+ }
+ }
+ }
+
+ void visitCall(Call* curr) {
+ if (reachable.count(ModuleElement(ModuleElementKind::Function, curr->target)) == 0) {
+ queue.emplace_back(ModuleElementKind::Function, curr->target);
+ }
+ }
+ void visitCallImport(CallImport* curr) {
+ if (reachable.count(ModuleElement(ModuleElementKind::Function, curr->target)) == 0) {
+ queue.emplace_back(ModuleElementKind::Function, curr->target);
+ }
+ }
+
+ void visitGetGlobal(GetGlobal* curr) {
+ if (reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) == 0) {
+ queue.emplace_back(ModuleElementKind::Global, curr->name);
+ }
+ }
+ void visitSetGlobal(SetGlobal* curr) {
+ if (reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) == 0) {
+ queue.emplace_back(ModuleElementKind::Global, curr->name);
+ }
+ }
+};
+
+struct RemoveUnusedModuleElements : public Pass {
+ void run(PassRunner* runner, Module* module) override {
+ std::vector<ModuleElement> roots;
+ // Module start is a root.
+ if (module->start.is()) {
+ roots.emplace_back(ModuleElementKind::Function, module->start);
+ }
+ // Exports are roots.
+ for (auto& curr : module->exports) {
+ if (curr->kind == ExternalKind::Function) {
+ roots.emplace_back(ModuleElementKind::Function, curr->value);
+ } else if (curr->kind == ExternalKind::Global) {
+ roots.emplace_back(ModuleElementKind::Global, curr->value);
+ }
+ }
+ // For now, all functions that can be called indirectly are marked as roots.
+ for (auto& segment : module->table.segments) {
+ for (auto& curr : segment.data) {
+ roots.emplace_back(ModuleElementKind::Function, curr);
+ }
+ }
+ // Compute reachability starting from the root set.
+ ReachabilityAnalyzer analyzer(module, roots);
+ // Remove unreachable elements.
+ {
+ auto& v = module->functions;
+ v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Function>& curr) {
+ return analyzer.reachable.count(ModuleElement(ModuleElementKind::Function, curr->name)) == 0;
+ }), v.end());
+ }
+ {
+ auto& v = module->globals;
+ v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Global>& curr) {
+ return analyzer.reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) == 0;
+ }), v.end());
+ }
+ {
+ auto& v = module->imports;
+ v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Import>& curr) {
+ if (curr->kind == ExternalKind::Function) {
+ return analyzer.reachable.count(ModuleElement(ModuleElementKind::Function, curr->name)) == 0;
+ } else if (curr->kind == ExternalKind::Global) {
+ return analyzer.reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) == 0;
+ }
+ return false;
+ }), v.end());
+ }
+ module->updateMaps();
+ }
+};
+
+Pass* createRemoveUnusedModuleElementsPass() {
+ return new RemoveUnusedModuleElements();
+}
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 20e002f4b..32b596eef 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -86,7 +86,7 @@ void PassRegistry::registerPasses() {
registerPass("remove-imports", "removes imports and replaces them with nops", createRemoveImportsPass);
registerPass("remove-memory", "removes memory segments", createRemoveMemoryPass);
registerPass("remove-unused-brs", "removes breaks from locations that are not needed", createRemoveUnusedBrsPass);
- registerPass("remove-unused-functions", "removes unused functions", createRemoveUnusedFunctionsPass);
+ registerPass("remove-unused-module-elements", "removes unused module elements", createRemoveUnusedModuleElementsPass);
registerPass("remove-unused-names", "removes names from locations that are never branched to", createRemoveUnusedNamesPass);
registerPass("reorder-functions", "sorts functions by access frequency", createReorderFunctionsPass);
registerPass("reorder-locals", "sorts locals by access frequency", createReorderLocalsPass);
@@ -103,7 +103,7 @@ void PassRunner::addDefaultOptimizationPasses() {
add("duplicate-function-elimination");
addDefaultFunctionOptimizationPasses();
add("duplicate-function-elimination"); // optimizations show more functions as duplicate
- add("remove-unused-functions");
+ add("remove-unused-module-elements");
add("memory-packing");
}
@@ -133,7 +133,7 @@ void PassRunner::addDefaultFunctionOptimizationPasses() {
void PassRunner::addDefaultGlobalOptimizationPasses() {
add("duplicate-function-elimination");
- add("remove-unused-functions");
+ add("remove-unused-module-elements");
add("memory-packing");
}
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 98f99654e..cbfc48327 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -46,7 +46,7 @@ Pass *createRelooperJumpThreadingPass();
Pass *createRemoveImportsPass();
Pass *createRemoveMemoryPass();
Pass *createRemoveUnusedBrsPass();
-Pass *createRemoveUnusedFunctionsPass();
+Pass *createRemoveUnusedModuleElementsPass();
Pass *createRemoveUnusedNamesPass();
Pass *createReorderFunctionsPass();
Pass *createReorderLocalsPass();
diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp
index db6d723db..9ba6062ea 100644
--- a/src/tools/asm2wasm.cpp
+++ b/src/tools/asm2wasm.cpp
@@ -55,7 +55,7 @@ int main(int argc, const char *argv[]) {
[](Options *o, const std::string &argument) {
o->extra["mem base"] = argument;
})
- .add("--mem-max", "-mm", "Set the maximum size of memory in the wasm module (in bytes). Without this, TOTAL_MEMORY is used (as it is used for the initial value), or if memory growth is enabled, no limit is set. This overrides both of those.", Options::Arguments::One,
+ .add("--mem-max", "-mm", "Set the maximum size of memory in the wasm module (in bytes). -1 means no limit. Without this, TOTAL_MEMORY is used (as it is used for the initial value), or if memory growth is enabled, no limit is set. This overrides both of those.", Options::Arguments::One,
[](Options *o, const std::string &argument) {
o->extra["mem max"] = argument;
})
@@ -63,6 +63,10 @@ int main(int argc, const char *argv[]) {
[](Options *o, const std::string &argument) {
o->extra["total memory"] = argument;
})
+ .add("--table-max", "-tM", "Set the maximum size of the table. Without this, it is set depending on how many functions are in the module. -1 means no limit", Options::Arguments::One,
+ [](Options *o, const std::string &argument) {
+ o->extra["table max"] = argument;
+ })
#include "optimization-options.h"
.add("--no-opts", "-n", "Disable optimization passes (deprecated)", Options::Arguments::Zero,
[](Options *o, const std::string &) {
@@ -130,7 +134,22 @@ int main(int argc, const char *argv[]) {
// Set the max memory size, if requested
const auto &memMax = options.extra.find("mem max");
if (memMax != options.extra.end()) {
- wasm.memory.max = atoi(memMax->second.c_str()) / Memory::kPageSize;
+ int max = atoi(memMax->second.c_str());
+ if (max >= 0) {
+ wasm.memory.max = max / Memory::kPageSize;
+ } else {
+ wasm.memory.max = Memory::kMaxSize;
+ }
+ }
+ // Set the table sizes, if requested
+ const auto &tableMax = options.extra.find("table max");
+ if (tableMax != options.extra.end()) {
+ int max = atoi(tableMax->second.c_str());
+ if (max >= 0) {
+ wasm.table.max = max;
+ } else {
+ wasm.table.max = Table::kMaxSize;
+ }
}
if (options.debug) std::cerr << "printing..." << std::endl;
diff --git a/src/wasm-js.cpp b/src/wasm-js.cpp
index e5032264a..2735f84f8 100644
--- a/src/wasm-js.cpp
+++ b/src/wasm-js.cpp
@@ -227,9 +227,18 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() {
Address offset = ConstantExpressionRunner(instance.globals).visit(segment.offset).value.geti32();
assert(offset + segment.data.size() <= wasm.table.initial);
for (size_t i = 0; i != segment.data.size(); ++i) {
- EM_ASM_({
- Module['outside']['wasmTable'][$0] = $1;
- }, offset + i, wasm.getFunction(segment.data[i]));
+ Name name = segment.data[i];
+ auto* func = wasm.checkFunction(name);
+ if (func) {
+ EM_ASM_({
+ Module['outside']['wasmTable'][$0] = $1;
+ }, offset + i, func);
+ } else {
+ auto* import = wasm.getImport(name);
+ EM_ASM_({
+ Module['outside']['wasmTable'][$0] = Module['lookupImport'](Pointer_stringify($1), Pointer_stringify($2));
+ }, offset + i, import->module.str, import->base.str);
+ }
}
}
}
diff --git a/src/wasm.h b/src/wasm.h
index c1ef75ea9..75d6a174c 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -1609,10 +1609,26 @@ public:
}
// TODO: remove* for other elements
- void updateFunctionsMap() {
+ void updateMaps() {
functionsMap.clear();
- for (auto& func : functions) {
- functionsMap[func->name] = func.get();
+ for (auto& curr : functions) {
+ functionsMap[curr->name] = curr.get();
+ }
+ functionTypesMap.clear();
+ for (auto& curr : functionTypes) {
+ functionTypesMap[curr->name] = curr.get();
+ }
+ importsMap.clear();
+ for (auto& curr : imports) {
+ importsMap[curr->name] = curr.get();
+ }
+ exportsMap.clear();
+ for (auto& curr : exports) {
+ exportsMap[curr->name] = curr.get();
+ }
+ globalsMap.clear();
+ for (auto& curr : globals) {
+ globalsMap[curr->name] = curr.get();
}
}
};
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 66f744e17..ebbc5a8ee 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -38,8 +38,8 @@ void WasmBinaryWriter::write() {
writeMemory();
writeGlobals();
writeExports();
- writeTableElements();
writeStart();
+ writeTableElements();
writeFunctions();
writeDataSegments();
if (debugInfo) writeNames();
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 5898cc252..3fcb85c6b 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -1573,7 +1573,7 @@ void SExpressionWasmBuilder::parseImport(Element& s) {
if (j < inner.size() - 1) {
wasm.table.max = atoi(inner[j++]->c_str());
} else {
- wasm.table.max = wasm.table.initial;
+ wasm.table.max = Table::kMaxSize;
}
// ends with the table element type
} else if (im->kind == ExternalKind::Memory) {