diff options
author | Sam Clegg <sbc@chromium.org> | 2019-04-02 14:53:48 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-02 14:53:48 -0700 |
commit | b192c53c6ef549bb5264bc58a71424ce311a4890 (patch) | |
tree | 895f6535ba16bed2c5ec75d365dfe37eda776315 /src | |
parent | 8e8cb825cd28bc1c188f9cf796bd97626bc1bb40 (diff) | |
download | binaryen-b192c53c6ef549bb5264bc58a71424ce311a4890.tar.gz binaryen-b192c53c6ef549bb5264bc58a71424ce311a4890.tar.bz2 binaryen-b192c53c6ef549bb5264bc58a71424ce311a4890.zip |
wasm-emscripten-finalize: Improve shared library support (#1961)
Convert PIC code generated by llvm to work with the current emscripten
ABI for dynamic linking:
- Convert mutable global imports from GOT.mem and GOT.func into
internal globals.
- Initialize these globals on started up in g$foo and f$foo imported functions
to calculate addresses at runtime.
Also:
- Add a test case for linking and finalizing a shared library
- Allow __stack_pointer global to be non-existent as can be case for
a shared library.
- Allow __stack_pointer global to be an import, as can be the case for
a shared library.
Diffstat (limited to 'src')
-rw-r--r-- | src/shared-constants.h | 1 | ||||
-rw-r--r-- | src/tools/wasm-emscripten-finalize.cpp | 25 | ||||
-rw-r--r-- | src/wasm-emscripten.h | 2 | ||||
-rw-r--r-- | src/wasm/wasm-emscripten.cpp | 149 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 1 |
5 files changed, 144 insertions, 34 deletions
diff --git a/src/shared-constants.h b/src/shared-constants.h index ae7d915ef..52124d891 100644 --- a/src/shared-constants.h +++ b/src/shared-constants.h @@ -22,6 +22,7 @@ namespace wasm { extern Name GROW_WASM_MEMORY, + WASM_CALL_CTORS, MEMORY_BASE, TABLE_BASE, GET_TEMP_RET0, diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index 2aa5de46f..180383a90 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -46,6 +46,7 @@ int main(int argc, const char *argv[]) { std::string dataSegmentFile; bool emitBinary = true; bool debugInfo = false; + bool isSideModule = false; bool legalizeJavaScriptFFI = true; uint64_t globalBase = INVALID_BASE; uint64_t initialStackPointer = INVALID_BASE; @@ -79,7 +80,11 @@ int main(int argc, const char *argv[]) { [&initialStackPointer](Options*, const std::string&argument ) { initialStackPointer = std::stoull(argument); }) - + .add("--side-module", "", "Input is an emscripten side module", + Options::Arguments::Zero, + [&isSideModule](Options *o, const std::string& argument) { + isSideModule = true; + }) .add("--input-source-map", "-ism", "Consume source map from the specified file", Options::Arguments::One, [&inputSourceMapFilename](Options *o, const std::string& argument) { inputSourceMapFilename = argument; }) @@ -130,7 +135,6 @@ int main(int argc, const char *argv[]) { WasmPrinter::printModule(&wasm, std::cerr); } - bool isSideModule = false; for (const UserSection& section : wasm.userSections) { if (section.name == BinaryConsts::UserSections::Dylink) { isSideModule = true; @@ -166,12 +170,6 @@ int main(int argc, const char *argv[]) { std::vector<Name> initializerFunctions; - // The names of standard imports/exports used by lld doesn't quite match that - // expected by emscripten. - // TODO(sbc): Unify these - if (Export* ex = wasm.getExportOrNull("__wasm_call_ctors")) { - ex->name = "__post_instantiate"; - } if (wasm.table.imported()) { if (wasm.table.base != "table") wasm.table.base = Name("table"); } @@ -182,14 +180,17 @@ int main(int argc, const char *argv[]) { if (isSideModule) { generator.replaceStackPointerGlobal(); + generator.generatePostInstantiateFunction(); } else { generator.generateRuntimeFunctions(); generator.generateMemoryGrowthFunction(); generator.generateStackInitialization(initialStackPointer); - // emscripten calls this by default for side libraries so we only need - // to include in as a static ctor for main module case. - if (wasm.getExportOrNull("__post_instantiate")) { - initializerFunctions.push_back("__post_instantiate"); + // For side modules these gets called via __post_instantiate + if (Function* F = generator.generateAssignGOTEntriesFunction()) { + initializerFunctions.push_back(F->name); + } + if (auto* e = wasm.getExportOrNull(WASM_CALL_CTORS)) { + initializerFunctions.push_back(e->value); } } diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h index acb2994ad..67ba408f5 100644 --- a/src/wasm-emscripten.h +++ b/src/wasm-emscripten.h @@ -36,7 +36,9 @@ public: void generateRuntimeFunctions(); Function* generateMemoryGrowthFunction(); + Function* generateAssignGOTEntriesFunction(); void generateStackInitialization(Address addr); + void generatePostInstantiateFunction(); // Create thunks for use with emscripten Runtime.dynCall. Creates one for each // signature in the indirect function table. diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index ea24b945d..404d93ca3 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -36,7 +36,9 @@ cashew::IString EM_JS_PREFIX("__em_js__"); static Name STACK_SAVE("stackSave"), STACK_RESTORE("stackRestore"), STACK_ALLOC("stackAlloc"), - STACK_INIT("stack$init"); + STACK_INIT("stack$init"), + POST_INSTANTIATE("__post_instantiate"), + ASSIGN_GOT_ENTIRES("__assign_got_enties"); void addExportedFunction(Module& wasm, Function* function) { wasm.addFunction(function); @@ -47,15 +49,20 @@ void addExportedFunction(Module& wasm, Function* function) { } Global* EmscriptenGlueGenerator::getStackPointerGlobal() { - // Assumption: The first non-imported global is global is __stack_pointer + // Assumption: The stack pointer is either imported as __stack_pointer or + // its the first non-imported global. // TODO(sbc): Find a better way to discover the stack pointer. Perhaps the // linker could export it by name? for (auto& g : wasm.globals) { - if (!g->imported()) { + if (g->imported()) { + if (g->base == "__stack_pointer") { + return g.get(); + } + } else { return g.get(); } } - Fatal() << "stack pointer global not found"; + return nullptr; } Expression* EmscriptenGlueGenerator::generateLoadStackPointer() { @@ -70,6 +77,8 @@ Expression* EmscriptenGlueGenerator::generateLoadStackPointer() { ); } Global* stackPointer = getStackPointerGlobal(); + if (!stackPointer) + Fatal() << "stack pointer global not found"; return builder.makeGetGlobal(stackPointer->name, i32); } @@ -85,6 +94,8 @@ Expression* EmscriptenGlueGenerator::generateStoreStackPointer(Expression* value ); } Global* stackPointer = getStackPointerGlobal(); + if (!stackPointer) + Fatal() << "stack pointer global not found"; return builder.makeSetGlobal(stackPointer->name, value); } @@ -143,6 +154,116 @@ void EmscriptenGlueGenerator::generateRuntimeFunctions() { generateStackRestoreFunction(); } +static Function* ensureFunctionImport(Module* module, Name name, std::string sig) { + // Then see if its already imported + ImportInfo info(*module); + if (Function* f = info.getImportedFunction(ENV, name)) { + return f; + } + // Failing that create a new function import. + auto import = new Function; + import->name = name; + import->module = ENV; + import->base = name; + auto* functionType = ensureFunctionType(sig, module); + import->type = functionType->name; + FunctionTypeUtils::fillFunction(import, functionType); + module->addFunction(import); + return import; +} + +Function* EmscriptenGlueGenerator::generateAssignGOTEntriesFunction() { + std::vector<Global*> got_entries_func; + std::vector<Global*> got_entries_mem; + for (auto& g : wasm.globals) { + if (!g->imported()) { + continue; + } + if (g->module == "GOT.func") { + got_entries_func.push_back(g.get()); + } else if (g->module == "GOT.mem") { + got_entries_mem.push_back(g.get()); + } else { + continue; + } + // Make this an internal, non-imported, global. + g->module.clear(); + g->init = Builder(wasm).makeConst(Literal(0)); + } + + if (!got_entries_func.size() && !got_entries_mem.size()) { + return nullptr; + } + + Function* assign_func = + builder.makeFunction(ASSIGN_GOT_ENTIRES, std::vector<NameType>{}, none, {}); + Block* block = builder.makeBlock(); + assign_func->body = block; + + for (Global* g : got_entries_mem) { + Name getter(std::string("g$") + g->base.c_str()); + ensureFunctionImport(&wasm, getter, "i"); + Expression* call = builder.makeCall(getter, {}, i32); + SetGlobal* set_global = builder.makeSetGlobal(g->name, call); + block->list.push_back(set_global); + } + + for (Global* g : got_entries_func) { + Name getter(std::string("f$") + g->base.c_str()); + if (auto* f = wasm.getFunctionOrNull(g->base)) { + getter.set((getter.c_str() + std::string("$") + getSig(f)).c_str(), false); + } + ensureFunctionImport(&wasm, getter, "i"); + Expression* call = builder.makeCall(getter, {}, i32); + SetGlobal* set_global = builder.makeSetGlobal(g->name, call); + block->list.push_back(set_global); + } + + wasm.addFunction(assign_func); + return assign_func; +} + +// For emscripten SIDE_MODULE we generate a single exported function called +// __post_instantiate which calls two functions: +// +// - __assign_got_enties +// - __wasm_call_ctors +// +// The former is function we generate here which calls imported g$XXX functions +// order to assign values to any globals imported from GOT.func or GOT.mem. +// These globals hold address of functions and globals respectively. +// +// The later is the constructor function generaed by lld which performs any +// fixups on the memory section and calls static constructors. +void EmscriptenGlueGenerator::generatePostInstantiateFunction() { + Builder builder(wasm); + Function* post_instantiate = + builder.makeFunction(POST_INSTANTIATE, std::vector<NameType>{}, none, {}); + wasm.addFunction(post_instantiate); + + if (Function* F = generateAssignGOTEntriesFunction()) { + // call __assign_got_enties from post_instantiate + Expression* call = builder.makeCall(F->name, {}, none); + post_instantiate->body = builder.blockify(call); + } + + // The names of standard imports/exports used by lld doesn't quite match that + // expected by emscripten. + // TODO(sbc): Unify these + if (auto* e = wasm.getExportOrNull(WASM_CALL_CTORS)) { + Expression* call = builder.makeCall(e->value, {}, none); + post_instantiate->body = builder.blockify(post_instantiate->body, call); + wasm.removeExport(WASM_CALL_CTORS); + } + + auto* ex = new Export(); + ex->value = post_instantiate->name; + ex->name = POST_INSTANTIATE; + ex->kind = ExternalKind::Function; + wasm.addExport(ex); + wasm.updateMaps(); +} + Function* EmscriptenGlueGenerator::generateMemoryGrowthFunction() { Name name(GROW_WASM_MEMORY); std::vector<NameType> params { { NEW_SIZE, i32 } }; @@ -215,24 +336,6 @@ void EmscriptenGlueGenerator::generateDynCallThunks() { } } -static Function* ensureFunctionImport(Module* module, Name name, std::string sig) { - // Then see if its already imported - ImportInfo info(*module); - if (Function* f = info.getImportedFunction(ENV, name)) { - return f; - } - // Failing that create a new function import. - auto import = new Function; - import->name = name; - import->module = ENV; - import->base = name; - auto* functionType = ensureFunctionType(sig, module); - import->type = functionType->name; - FunctionTypeUtils::fillFunction(import, functionType); - module->addFunction(import); - return import; -} - struct RemoveStackPointer : public PostWalker<RemoveStackPointer> { RemoveStackPointer(Global* stackPointer) : stackPointer(stackPointer) {} @@ -262,6 +365,8 @@ private: void EmscriptenGlueGenerator::replaceStackPointerGlobal() { Global* stackPointer = getStackPointerGlobal(); + if (!stackPointer) + return; // Replace all uses of stack pointer global RemoveStackPointer walker(stackPointer); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index e135ba750..f7081e49c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -37,6 +37,7 @@ const char* TargetFeatures = "target_features"; } Name GROW_WASM_MEMORY("__growWasmMemory"), + WASM_CALL_CTORS("__wasm_call_ctors"), MEMORY_BASE("__memory_base"), TABLE_BASE("__table_base"), GET_TEMP_RET0("getTempRet0"), |