blob: 495d46358cb662e7c72cd94f8b756d213684e234 [file] [log] [blame] [edit]
/*
* 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