/* * 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> { 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{}, 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 gotFuncEntries; std::vector gotMemEntries; bool sideModule; }; Pass* createEmscriptenPICPass() { return new EmscriptenPIC(true); } Pass* createEmscriptenPICMainModulePass() { return new EmscriptenPIC(false); } } // namespace wasm