diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 131 | ||||
-rw-r--r-- | src/ast_utils.h | 28 | ||||
-rw-r--r-- | src/js/wasm.js-post.js | 8 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/passes/DuplicateFunctionElimination.cpp | 2 | ||||
-rw-r--r-- | src/passes/LegalizeJSInterface.cpp | 60 | ||||
-rw-r--r-- | src/passes/RemoveUnusedFunctions.cpp | 65 | ||||
-rw-r--r-- | src/passes/RemoveUnusedModuleElements.cpp | 155 | ||||
-rw-r--r-- | src/passes/pass.cpp | 6 | ||||
-rw-r--r-- | src/passes/passes.h | 2 | ||||
-rw-r--r-- | src/tools/asm2wasm.cpp | 23 | ||||
-rw-r--r-- | src/wasm-js.cpp | 15 | ||||
-rw-r--r-- | src/wasm.h | 22 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 2 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 2 |
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) { |