diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/EmscriptenPIC.cpp | 221 | ||||
-rw-r--r-- | src/passes/ReplaceStackPointer.cpp | 2 | ||||
-rw-r--r-- | src/passes/pass.cpp | 6 | ||||
-rw-r--r-- | src/passes/passes.h | 2 | ||||
-rw-r--r-- | src/shared-constants.h | 1 | ||||
-rw-r--r-- | src/tools/wasm-emscripten-finalize.cpp | 6 | ||||
-rw-r--r-- | src/wasm/wasm-emscripten.cpp | 176 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 1 |
9 files changed, 239 insertions, 177 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a3eede8ff..e54c5ca18 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -23,6 +23,7 @@ set(passes_SOURCES DuplicateImportElimination.cpp DuplicateFunctionElimination.cpp DWARF.cpp + EmscriptenPIC.cpp ExtractFunction.cpp Flatten.cpp FuncCastEmulation.cpp diff --git a/src/passes/EmscriptenPIC.cpp b/src/passes/EmscriptenPIC.cpp new file mode 100644 index 000000000..495d46358 --- /dev/null +++ b/src/passes/EmscriptenPIC.cpp @@ -0,0 +1,221 @@ +/* + * Copyright 2020 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. + */ + +// +// Convert LLVM PIC ABI to emscripten ABI +// +// When generating -fPIC code llvm will generate imports call GOT.mem and +// GOT.func in order to access the addresses of external global data and +// functions. +// +// However emscripten uses a different ABI where function and data addresses +// are available at runtime via special `g$foo` and `fp$bar` function calls. +// +// Here we internalize all such wasm globals and generte code that sets their +// value based on the result of call `g$foo` and `fp$bar` functions at runtime. +// +// A function called `__assign_got_enties` is generated by this pass that +// performs all the assignments. +// + +#include "abi/js.h" +#include "asm_v_wasm.h" +#include "ir/import-utils.h" +#include "ir/table-utils.h" +#include "pass.h" +#include "shared-constants.h" +#include "support/debug.h" + +#define DEBUG_TYPE "emscripten-pic" + +namespace wasm { + +static Global* ensureGlobalImport(Module* module, Name name, Type type) { + // See if its already imported. + // FIXME: O(N) + ImportInfo info(*module); + if (auto* g = info.getImportedGlobal(ENV, name)) { + return g; + } + // Failing that create a new import. + auto import = new Global; + import->name = name; + import->module = ENV; + import->base = name; + import->type = type; + module->addGlobal(import); + return import; +} + +static Function* +ensureFunctionImport(Module* module, Name name, Signature sig) { + // See if its already imported. + // FIXME: O(N) + ImportInfo info(*module); + if (auto* f = info.getImportedFunction(ENV, name)) { + return f; + } + // Failing that create a new import. + auto import = new Function; + import->name = name; + import->module = ENV; + import->base = name; + import->sig = sig; + module->addFunction(import); + return import; +} + +struct EmscriptenPIC : public WalkerPass<PostWalker<EmscriptenPIC>> { + + EmscriptenPIC(bool sideModule) : sideModule(sideModule) {} + + void visitGlobal(Global* curr) { + if (!curr->imported()) { + return; + } + if (curr->module == "GOT.func") { + gotFuncEntries.push_back(curr); + } else if (curr->module == "GOT.mem") { + gotMemEntries.push_back(curr); + } else { + return; + } + // Make this an internal, non-imported, global. + curr->module.clear(); + curr->init = Builder(*getModule()).makeConst(int32_t(0)); + } + + void visitModule(Module* module) { + BYN_TRACE("generateAssignGOTEntriesFunction\n"); + if (!gotFuncEntries.size() && !gotMemEntries.size()) { + return; + } + + Builder builder(*getModule()); + Function* assignFunc = builder.makeFunction( + ASSIGN_GOT_ENTRIES, std::vector<NameType>{}, Type::none, {}); + Block* block = builder.makeBlock(); + assignFunc->body = block; + + bool hasSingleMemorySegment = + module->memory.exists && module->memory.segments.size() == 1; + + for (Global* g : gotMemEntries) { + // If this global is defined in this module, we export its address + // relative to the relocatable memory. If we are in a main module, we can + // just use that location (since if other modules have this symbol too, we + // will "win" as we are loaded first). Otherwise, import a g$ getter. Note + // that this depends on memory having a single segment, so we know the + // offset, and that the export is a global. + auto base = g->base; + if (hasSingleMemorySegment && !sideModule) { + if (auto* ex = module->getExportOrNull(base)) { + if (ex->kind == ExternalKind::Global) { + // The base relative to which we are computed is the offset of the + // singleton segment. + auto* relativeBase = ExpressionManipulator::copy( + module->memory.segments[0].offset, *module); + + auto* offset = builder.makeGlobalGet( + ex->value, module->getGlobal(ex->value)->type); + auto* add = builder.makeBinary(AddInt32, relativeBase, offset); + GlobalSet* globalSet = builder.makeGlobalSet(g->name, add); + block->list.push_back(globalSet); + continue; + } + } + } + Name getter(std::string("g$") + base.c_str()); + ensureFunctionImport(module, getter, Signature(Type::none, Type::i32)); + Expression* call = builder.makeCall(getter, {}, Type::i32); + GlobalSet* globalSet = builder.makeGlobalSet(g->name, call); + block->list.push_back(globalSet); + } + + ImportInfo importInfo(*module); + + // We may have to add things to the table. + Global* tableBase = nullptr; + + for (Global* g : gotFuncEntries) { + // The function has to exist either as export or an import. + // Note that we don't search for the function by name since its internal + // name may be different. + auto* ex = module->getExportOrNull(g->base); + // If this is exported then it must be one of the functions implemented + // here, and if this is a main module, then we can simply place the + // function in the table: the loader will see it there and resolve all + // other uses to this one. + if (ex && !sideModule) { + assert(ex->kind == ExternalKind::Function); + auto* f = module->getFunction(ex->value); + if (f->imported()) { + Fatal() << "GOT.func entry is both imported and exported: " + << g->base; + } + // The base relative to which we are computed is the offset of the + // singleton segment, which we must ensure exists + if (!tableBase) { + tableBase = ensureGlobalImport(module, TABLE_BASE, Type::i32); + } + if (!module->table.exists) { + module->table.exists = true; + } + if (module->table.segments.empty()) { + module->table.segments.resize(1); + module->table.segments[0].offset = + builder.makeGlobalGet(tableBase->name, Type::i32); + } + auto tableIndex = + TableUtils::getOrAppend(module->table, f->name, *module); + auto* c = LiteralUtils::makeFromInt32(tableIndex, Type::i32, *module); + auto* getBase = builder.makeGlobalGet(tableBase->name, Type::i32); + auto* add = builder.makeBinary(AddInt32, getBase, c); + auto* globalSet = builder.makeGlobalSet(g->name, add); + block->list.push_back(globalSet); + continue; + } + // This is imported or in a side module. Create an fp$ import to get the + // function table index from the dynamic loader. + auto* f = importInfo.getImportedFunction(ENV, g->base); + if (!f) { + if (!ex) { + Fatal() << "GOT.func entry with no import/export: " << g->base; + } + f = module->getFunction(ex->value); + } + Name getter( + (std::string("fp$") + g->base.c_str() + std::string("$") + getSig(f)) + .c_str()); + ensureFunctionImport(module, getter, Signature(Type::none, Type::i32)); + auto* call = builder.makeCall(getter, {}, Type::i32); + auto* globalSet = builder.makeGlobalSet(g->name, call); + block->list.push_back(globalSet); + } + + module->addFunction(assignFunc); + } + + std::vector<Global*> gotFuncEntries; + std::vector<Global*> gotMemEntries; + bool sideModule; +}; + +Pass* createEmscriptenPICPass() { return new EmscriptenPIC(true); } + +Pass* createEmscriptenPICMainModulePass() { return new EmscriptenPIC(false); } + +} // namespace wasm diff --git a/src/passes/ReplaceStackPointer.cpp b/src/passes/ReplaceStackPointer.cpp index d6a85c3e2..da57f8c3d 100644 --- a/src/passes/ReplaceStackPointer.cpp +++ b/src/passes/ReplaceStackPointer.cpp @@ -27,7 +27,7 @@ #include "support/debug.h" #include "wasm-emscripten.h" -#define DEBUG_TYPE "binary" +#define DEBUG_TYPE "replace-stack-pointer" namespace wasm { diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 78717568e..9ff06b153 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -123,6 +123,12 @@ void PassRegistry::registerPasses() { registerPass("emit-target-features", "emit the target features section in the output", createEmitTargetFeaturesPass); + registerPass("emscripten-pic", + "Convert PIC ABI from llvm to emscripten", + createEmscriptenPICPass); + registerPass("emscripten-pic-main-module", + "Convert PIC ABI from llvm to emscripten", + createEmscriptenPICMainModulePass); registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index c844b5acc..ff94ef51c 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -40,6 +40,8 @@ Pass* createDWARFDumpPass(); Pass* createDuplicateImportEliminationPass(); Pass* createDuplicateFunctionEliminationPass(); Pass* createEmitTargetFeaturesPass(); +Pass* createEmscriptenPICPass(); +Pass* createEmscriptenPICMainModulePass(); Pass* createExtractFunctionPass(); Pass* createFlattenPass(); Pass* createFuncCastEmulationPass(); diff --git a/src/shared-constants.h b/src/shared-constants.h index 4f64f1cf6..5367d339f 100644 --- a/src/shared-constants.h +++ b/src/shared-constants.h @@ -65,6 +65,7 @@ extern Name EXIT; extern Name SHARED; extern Name EVENT; extern Name ATTR; +extern Name ASSIGN_GOT_ENTRIES; } // namespace wasm diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index a92b0806b..f2bc25361 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -249,14 +249,18 @@ int main(int argc, const char* argv[]) { BYN_TRACE("finalizing as side module\n"); PassRunner passRunner(&wasm); passRunner.add("replace-stack-pointer"); + passRunner.add("emscripten-pic"); passRunner.run(); generator.generatePostInstantiateFunction(); } else { BYN_TRACE("finalizing as regular module\n"); + PassRunner passRunner(&wasm); + passRunner.add("emscripten-pic-main-module"); + passRunner.run(); generator.internalizeStackPointerGlobal(); generator.generateMemoryGrowthFunction(); // For side modules these gets called via __post_instantiate - if (Function* F = generator.generateAssignGOTEntriesFunction()) { + if (Function* F = wasm.getFunctionOrNull(ASSIGN_GOT_ENTRIES)) { auto* ex = new Export(); ex->value = F->name; ex->name = F->name; diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index 8e0c15aeb..c0b4f6437 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -41,7 +41,6 @@ static Name STACK_INIT("stack$init"); static Name STACK_LIMIT("__stack_limit"); static Name SET_STACK_LIMIT("__set_stack_limit"); static Name POST_INSTANTIATE("__post_instantiate"); -static Name ASSIGN_GOT_ENTIRES("__assign_got_enties"); static Name STACK_OVERFLOW_IMPORT("__handle_stack_overflow"); void addExportedFunction(Module& wasm, Function* function) { @@ -79,179 +78,6 @@ Global* getStackPointerGlobal(Module& wasm) { return nullptr; } -static Function* -ensureFunctionImport(Module* module, Name name, Signature sig) { - // See if its already imported. - // FIXME: O(N) - ImportInfo info(*module); - if (auto* f = info.getImportedFunction(ENV, name)) { - return f; - } - // Failing that create a new import. - auto import = new Function; - import->name = name; - import->module = ENV; - import->base = name; - import->sig = sig; - module->addFunction(import); - return import; -} - -static Global* ensureGlobalImport(Module* module, Name name, Type type) { - // See if its already imported. - // FIXME: O(N) - ImportInfo info(*module); - if (auto* g = info.getImportedGlobal(ENV, name)) { - return g; - } - // Failing that create a new import. - auto import = new Global; - import->name = name; - import->module = ENV; - import->base = name; - import->type = type; - module->addGlobal(import); - return import; -} - -// Convert LLVM PIC ABI to emscripten ABI -// -// When generating -fPIC code llvm will generate imports call GOT.mem and -// GOT.func in order to access the addresses of external global data and -// functions. -// -// However emscripten uses a different ABI where function and data addresses -// are available at runtime via special `g$foo` and `fp$bar` function calls. -// -// Here we internalize all such wasm globals and generte code that sets their -// value based on the result of call `g$foo` and `fp$bar` functions at runtime. -Function* EmscriptenGlueGenerator::generateAssignGOTEntriesFunction() { - BYN_TRACE("generateAssignGOTEntriesFunction\n"); - std::vector<Global*> gotFuncEntries; - std::vector<Global*> gotMemEntries; - for (auto& g : wasm.globals) { - if (!g->imported()) { - continue; - } - if (g->module == "GOT.func") { - gotFuncEntries.push_back(g.get()); - } else if (g->module == "GOT.mem") { - gotMemEntries.push_back(g.get()); - } else { - continue; - } - // Make this an internal, non-imported, global. - g->module.clear(); - g->init = Builder(wasm).makeConst(int32_t(0)); - } - - if (!gotFuncEntries.size() && !gotMemEntries.size()) { - return nullptr; - } - - Function* assignFunc = builder.makeFunction( - ASSIGN_GOT_ENTIRES, std::vector<NameType>{}, Type::none, {}); - Block* block = builder.makeBlock(); - assignFunc->body = block; - - bool hasSingleMemorySegment = - wasm.memory.exists && wasm.memory.segments.size() == 1; - - for (Global* g : gotMemEntries) { - // If this global is defined in this module, we export its address relative - // to the relocatable memory. If we are in a main module, we can just use - // that location (since if other modules have this symbol too, we will "win" - // as we are loaded first). Otherwise, import a g$ getter. - // Note that this depends on memory having a single segment, so we know the - // offset, and that the export is a global. - auto base = g->base; - if (hasSingleMemorySegment && !sideModule) { - if (auto* ex = wasm.getExportOrNull(base)) { - if (ex->kind == ExternalKind::Global) { - // The base relative to which we are computed is the offset of the - // singleton segment. - auto* relativeBase = - ExpressionManipulator::copy(wasm.memory.segments[0].offset, wasm); - - auto* offset = - builder.makeGlobalGet(ex->value, wasm.getGlobal(ex->value)->type); - auto* add = builder.makeBinary(AddInt32, relativeBase, offset); - GlobalSet* globalSet = builder.makeGlobalSet(g->name, add); - block->list.push_back(globalSet); - continue; - } - } - } - Name getter(std::string("g$") + base.c_str()); - ensureFunctionImport(&wasm, getter, Signature(Type::none, Type::i32)); - Expression* call = builder.makeCall(getter, {}, Type::i32); - GlobalSet* globalSet = builder.makeGlobalSet(g->name, call); - block->list.push_back(globalSet); - } - - ImportInfo importInfo(wasm); - - // We may have to add things to the table. - Global* tableBase = nullptr; - - for (Global* g : gotFuncEntries) { - // The function has to exist either as export or an import. - // Note that we don't search for the function by name since its internal - // name may be different. - auto* ex = wasm.getExportOrNull(g->base); - // If this is exported then it must be one of the functions implemented - // here, and if this is a main module, then we can simply place the function - // in the table: the loader will see it there and resolve all other uses - // to this one. - if (ex && !sideModule) { - assert(ex->kind == ExternalKind::Function); - auto* f = wasm.getFunction(ex->value); - if (f->imported()) { - Fatal() << "GOT.func entry is both imported and exported: " << g->base; - } - // The base relative to which we are computed is the offset of the - // singleton segment, which we must ensure exists - if (!tableBase) { - tableBase = ensureGlobalImport(&wasm, TABLE_BASE, Type::i32); - } - if (!wasm.table.exists) { - wasm.table.exists = true; - } - if (wasm.table.segments.empty()) { - wasm.table.segments.resize(1); - wasm.table.segments[0].offset = - builder.makeGlobalGet(tableBase->name, Type::i32); - } - auto tableIndex = TableUtils::getOrAppend(wasm.table, f->name, wasm); - auto* c = LiteralUtils::makeFromInt32(tableIndex, Type::i32, wasm); - auto* getBase = builder.makeGlobalGet(tableBase->name, Type::i32); - auto* add = builder.makeBinary(AddInt32, getBase, c); - auto* globalSet = builder.makeGlobalSet(g->name, add); - block->list.push_back(globalSet); - continue; - } - // This is imported or in a side module. Create an fp$ import to get the - // function table index from the dynamic loader. - auto* f = importInfo.getImportedFunction(ENV, g->base); - if (!f) { - if (!ex) { - Fatal() << "GOT.func entry with no import/export: " << g->base; - } - f = wasm.getFunction(ex->value); - } - Name getter( - (std::string("fp$") + g->base.c_str() + std::string("$") + getSig(f)) - .c_str()); - ensureFunctionImport(&wasm, getter, Signature(Type::none, Type::i32)); - auto* call = builder.makeCall(getter, {}, Type::i32); - auto* globalSet = builder.makeGlobalSet(g->name, call); - block->list.push_back(globalSet); - } - - wasm.addFunction(assignFunc); - return assignFunc; -} - // For emscripten SIDE_MODULE we generate a single exported function called // __post_instantiate which calls two functions: // @@ -271,7 +97,7 @@ void EmscriptenGlueGenerator::generatePostInstantiateFunction() { POST_INSTANTIATE, std::vector<NameType>{}, Type::none, {}); wasm.addFunction(post_instantiate); - if (Function* F = generateAssignGOTEntriesFunction()) { + if (Function* F = wasm.getFunctionOrNull(ASSIGN_GOT_ENTRIES)) { // call __assign_got_enties from post_instantiate Expression* call = builder.makeCall(F->name, {}, Type::none); post_instantiate->body = builder.blockify(post_instantiate->body, call); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index adaa81838..a0c6dbbb6 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -91,6 +91,7 @@ Name EXIT("exit"); Name SHARED("shared"); Name EVENT("event"); Name ATTR("attr"); +Name ASSIGN_GOT_ENTRIES("__assign_got_enties"); // Expressions |