blob: 8357983b2baf808787a6d5e97ac3e5b21b351e39 [file] [log] [blame] [edit]
/*
* 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.
*/
#include "wasm-emscripten.h"
#include <sstream>
#include "asm_v_wasm.h"
#include "asmjs/shared-constants.h"
#include "ir/import-utils.h"
#include "ir/literal-utils.h"
#include "ir/module-utils.h"
#include "shared-constants.h"
#include "support/debug.h"
#include "wasm-builder.h"
#include "wasm-traversal.h"
#include "wasm.h"
#define DEBUG_TYPE "emscripten"
namespace wasm {
cashew::IString EM_ASM_PREFIX("emscripten_asm_const");
cashew::IString EM_JS_PREFIX("__em_js__");
static Name STACK_SAVE("stackSave");
static Name STACK_RESTORE("stackRestore");
static Name STACK_ALLOC("stackAlloc");
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) {
wasm.addFunction(function);
auto export_ = new Export;
export_->name = export_->value = function->name;
export_->kind = ExternalKind::Function;
wasm.addExport(export_);
}
// TODO(sbc): There should probably be a better way to do this.
bool isExported(Module& wasm, Name name) {
for (auto& ex : wasm.exports) {
if (ex->value == name) {
return true;
}
}
return false;
}
Global* EmscriptenGlueGenerator::getStackPointerGlobal() {
// Assumption: The stack pointer is either imported as __stack_pointer or
// its the first non-imported and non-exported 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->base == STACK_POINTER) {
return g.get();
}
} else if (!isExported(wasm, g->name)) {
return g.get();
}
}
return nullptr;
}
Expression* EmscriptenGlueGenerator::generateLoadStackPointer() {
if (!useStackPointerGlobal) {
return builder.makeLoad(
/* bytes =*/4,
/* signed =*/false,
/* offset =*/stackPointerOffset,
/* align =*/4,
/* ptr =*/builder.makeConst(Literal(0)),
/* type =*/Type::i32);
}
Global* stackPointer = getStackPointerGlobal();
if (!stackPointer) {
Fatal() << "stack pointer global not found";
}
return builder.makeGlobalGet(stackPointer->name, Type::i32);
}
inline Expression* stackBoundsCheck(Builder& builder,
Function* func,
Expression* value,
Global* stackPointer,
Global* stackLimit,
Name handlerName) {
// Add a local to store the value of the expression. We need the value twice:
// once to check if it has overflowed, and again to assign to store it.
auto newSP = Builder::addVar(func, stackPointer->type);
// If we imported a handler, call it. That can show a nice error in JS.
// Otherwise, just trap.
Expression* handler;
if (handlerName.is()) {
handler = builder.makeCall(handlerName, {}, Type::none);
} else {
handler = builder.makeUnreachable();
}
// (if (i32.lt_u (local.tee $newSP (...value...)) (global.get $__stack_limit))
auto check =
builder.makeIf(builder.makeBinary(
BinaryOp::LtUInt32,
builder.makeLocalTee(newSP, value, stackPointer->type),
builder.makeGlobalGet(stackLimit->name, stackLimit->type)),
handler);
// (global.set $__stack_pointer (local.get $newSP))
auto newSet = builder.makeGlobalSet(
stackPointer->name, builder.makeLocalGet(newSP, stackPointer->type));
return builder.blockify(check, newSet);
}
Expression*
EmscriptenGlueGenerator::generateStoreStackPointer(Function* func,
Expression* value) {
BYN_TRACE("generateStoreStackPointer\n");
if (!useStackPointerGlobal) {
return builder.makeStore(
/* bytes =*/4,
/* offset =*/stackPointerOffset,
/* align =*/4,
/* ptr =*/builder.makeConst(Literal(0)),
/* value =*/value,
/* type =*/Type::i32);
}
Global* stackPointer = getStackPointerGlobal();
if (!stackPointer) {
Fatal() << "stack pointer global not found";
}
if (auto* stackLimit = wasm.getGlobalOrNull(STACK_LIMIT)) {
return stackBoundsCheck(builder,
func,
value,
stackPointer,
stackLimit,
importStackOverflowHandler());
}
return builder.makeGlobalSet(stackPointer->name, value);
}
void EmscriptenGlueGenerator::generateStackSaveFunction() {
BYN_TRACE("generateStackSaveFunction\n");
std::vector<NameType> params{};
Function* function =
builder.makeFunction(STACK_SAVE, std::move(params), Type::i32, {});
function->body = generateLoadStackPointer();
addExportedFunction(wasm, function);
}
void EmscriptenGlueGenerator::generateStackAllocFunction() {
BYN_TRACE("generateStackAllocFunction\n");
std::vector<NameType> params{{"0", Type::i32}};
Function* function = builder.makeFunction(
STACK_ALLOC, std::move(params), Type::i32, {{"1", Type::i32}});
Expression* loadStack = generateLoadStackPointer();
LocalGet* getSizeArg = builder.makeLocalGet(0, Type::i32);
Binary* sub = builder.makeBinary(SubInt32, loadStack, getSizeArg);
const static uint32_t bitAlignment = 16;
const static uint32_t bitMask = bitAlignment - 1;
Const* subConst = builder.makeConst(Literal(~bitMask));
Binary* maskedSub = builder.makeBinary(AndInt32, sub, subConst);
LocalSet* teeStackLocal = builder.makeLocalTee(1, maskedSub, Type::i32);
Expression* storeStack = generateStoreStackPointer(function, teeStackLocal);
Block* block = builder.makeBlock();
block->list.push_back(storeStack);
LocalGet* getStackLocal2 = builder.makeLocalGet(1, Type::i32);
block->list.push_back(getStackLocal2);
block->type = Type::i32;
function->body = block;
addExportedFunction(wasm, function);
}
void EmscriptenGlueGenerator::generateStackRestoreFunction() {
BYN_TRACE("generateStackRestoreFunction\n");
std::vector<NameType> params{{"0", Type::i32}};
Function* function =
builder.makeFunction(STACK_RESTORE, std::move(params), Type::none, {});
LocalGet* getArg = builder.makeLocalGet(0, Type::i32);
Expression* store = generateStoreStackPointer(function, getArg);
function->body = store;
addExportedFunction(wasm, function);
}
void EmscriptenGlueGenerator::generateRuntimeFunctions() {
BYN_TRACE("generateRuntimeFunctions\n");
generateStackSaveFunction();
generateStackAllocFunction();
generateStackRestoreFunction();
}
static Function*
ensureFunctionImport(Module* module, Name name, Signature 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;
import->sig = sig;
module->addFunction(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(Literal(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;
for (Global* g : gotMemEntries) {
Name getter(std::string("g$") + 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);
}
for (Global* g : gotFuncEntries) {
Function* f = nullptr;
// 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 (ex) {
assert(ex->kind == ExternalKind::Function);
f = wasm.getFunction(ex->value);
} else {
ImportInfo info(wasm);
f = info.getImportedFunction(ENV, g->base);
if (!f) {
Fatal() << "GOT.func entry with no import/export: " << g->base;
}
}
Name getter(
(std::string("fp$") + g->base.c_str() + std::string("$") + getSig(f))
.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);
}
wasm.addFunction(assignFunc);
return assignFunc;
}
// 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() {
BYN_TRACE("generatePostInstantiateFunction\n");
Builder builder(wasm);
Function* post_instantiate = builder.makeFunction(
POST_INSTANTIATE, std::vector<NameType>{}, Type::none, {});
wasm.addFunction(post_instantiate);
if (Function* F = generateAssignGOTEntriesFunction()) {
// call __assign_got_enties from post_instantiate
Expression* call = builder.makeCall(F->name, {}, Type::none);
post_instantiate->body = builder.blockify(post_instantiate->body, 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, {}, Type::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);
}
Function* EmscriptenGlueGenerator::generateMemoryGrowthFunction() {
Name name(GROW_WASM_MEMORY);
std::vector<NameType> params{{NEW_SIZE, Type::i32}};
Function* growFunction =
builder.makeFunction(name, std::move(params), Type::i32, {});
growFunction->body =
builder.makeHost(MemoryGrow, Name(), {builder.makeLocalGet(0, Type::i32)});
addExportedFunction(wasm, growFunction);
return growFunction;
}
inline void exportFunction(Module& wasm, Name name, bool must_export) {
if (!wasm.getFunctionOrNull(name)) {
assert(!must_export);
return;
}
if (wasm.getExportOrNull(name)) {
return; // Already exported
}
auto exp = new Export;
exp->name = exp->value = name;
exp->kind = ExternalKind::Function;
wasm.addExport(exp);
}
void EmscriptenGlueGenerator::generateDynCallThunk(Signature sig) {
if (!sigs.insert(sig).second) {
return; // sig is already in the set
}
Name name = std::string("dynCall_") + getSig(sig.results, sig.params);
if (wasm.getFunctionOrNull(name) || wasm.getExportOrNull(name)) {
return; // module already contains this dyncall
}
std::vector<NameType> params;
params.emplace_back("fptr", Type::i32); // function pointer param
int p = 0;
const std::vector<Type>& paramTypes = sig.params.expand();
for (const auto& ty : paramTypes) {
params.emplace_back(std::to_string(p++), ty);
}
Function* f = builder.makeFunction(name, std::move(params), sig.results, {});
Expression* fptr = builder.makeLocalGet(0, Type::i32);
std::vector<Expression*> args;
for (unsigned i = 0; i < paramTypes.size(); ++i) {
args.push_back(builder.makeLocalGet(i + 1, paramTypes[i]));
}
Expression* call = builder.makeCallIndirect(fptr, args, sig);
f->body = call;
wasm.addFunction(f);
exportFunction(wasm, f->name, true);
}
void EmscriptenGlueGenerator::generateDynCallThunks() {
Builder builder(wasm);
std::vector<Name> tableSegmentData;
if (wasm.table.segments.size() > 0) {
tableSegmentData = wasm.table.segments[0].data;
}
for (const auto& indirectFunc : tableSegmentData) {
generateDynCallThunk(wasm.getFunction(indirectFunc)->sig);
}
}
struct RemoveStackPointer : public PostWalker<RemoveStackPointer> {
RemoveStackPointer(Global* stackPointer) : stackPointer(stackPointer) {}
void visitGlobalGet(GlobalGet* curr) {
if (getModule()->getGlobalOrNull(curr->name) == stackPointer) {
needStackSave = true;
if (!builder) {
builder = make_unique<Builder>(*getModule());
}
replaceCurrent(builder->makeCall(STACK_SAVE, {}, Type::i32));
}
}
void visitGlobalSet(GlobalSet* curr) {
if (getModule()->getGlobalOrNull(curr->name) == stackPointer) {
needStackRestore = true;
if (!builder) {
builder = make_unique<Builder>(*getModule());
}
replaceCurrent(
builder->makeCall(STACK_RESTORE, {curr->value}, Type::none));
}
}
bool needStackSave = false;
bool needStackRestore = false;
private:
std::unique_ptr<Builder> builder;
Global* stackPointer;
};
// lld can sometimes produce a build with an imported mutable __stack_pointer
// (i.e. when linking with -fpie). This method internalizes the
// __stack_pointer and initializes it from an immutable global instead.
// For -shared builds we instead call replaceStackPointerGlobal.
void EmscriptenGlueGenerator::internalizeStackPointerGlobal() {
Global* stackPointer = getStackPointerGlobal();
if (!stackPointer || !stackPointer->imported() || !stackPointer->mutable_) {
return;
}
Name internalName = stackPointer->name;
Name externalName = internalName.c_str() + std::string("_import");
// Rename the imported global, and make it immutable
stackPointer->name = externalName;
stackPointer->mutable_ = false;
wasm.updateMaps();
// Create a new global with the old name that is not imported.
Builder builder(wasm);
auto* init = builder.makeGlobalGet(externalName, stackPointer->type);
auto* sp = builder.makeGlobal(
internalName, stackPointer->type, init, Builder::Mutable);
wasm.addGlobal(sp);
}
void EmscriptenGlueGenerator::replaceStackPointerGlobal() {
Global* stackPointer = getStackPointerGlobal();
if (!stackPointer) {
return;
}
// Replace all uses of stack pointer global
RemoveStackPointer walker(stackPointer);
walker.walkModule(&wasm);
if (walker.needStackSave) {
ensureFunctionImport(&wasm, STACK_SAVE, Signature(Type::none, Type::i32));
}
if (walker.needStackRestore) {
ensureFunctionImport(
&wasm, STACK_RESTORE, Signature(Type::i32, Type::none));
}
// Finally remove the stack pointer global itself. This avoids importing
// a mutable global.
wasm.removeGlobal(stackPointer->name);
}
struct StackLimitEnforcer : public WalkerPass<PostWalker<StackLimitEnforcer>> {
StackLimitEnforcer(Global* stackPointer,
Global* stackLimit,
Builder& builder,
Name handler)
: stackPointer(stackPointer), stackLimit(stackLimit), builder(builder),
handler(handler) {}
bool isFunctionParallel() override { return true; }
Pass* create() override {
return new StackLimitEnforcer(stackPointer, stackLimit, builder, handler);
}
void visitGlobalSet(GlobalSet* curr) {
if (getModule()->getGlobalOrNull(curr->name) == stackPointer) {
replaceCurrent(stackBoundsCheck(builder,
getFunction(),
curr->value,
stackPointer,
stackLimit,
handler));
}
}
private:
Global* stackPointer;
Global* stackLimit;
Builder& builder;
Name handler;
};
void EmscriptenGlueGenerator::enforceStackLimit() {
Global* stackPointer = getStackPointerGlobal();
if (!stackPointer) {
return;
}
auto* stackLimit = builder.makeGlobal(STACK_LIMIT,
stackPointer->type,
builder.makeConst(Literal(0)),
Builder::Mutable);
wasm.addGlobal(stackLimit);
Name handler = importStackOverflowHandler();
StackLimitEnforcer walker(stackPointer, stackLimit, builder, handler);
PassRunner runner(&wasm);
walker.run(&runner, &wasm);
generateSetStackLimitFunction();
}
void EmscriptenGlueGenerator::generateSetStackLimitFunction() {
Function* function =
builder.makeFunction(SET_STACK_LIMIT, Signature(Type::i32, Type::none), {});
LocalGet* getArg = builder.makeLocalGet(0, Type::i32);
Expression* store = builder.makeGlobalSet(STACK_LIMIT, getArg);
function->body = store;
addExportedFunction(wasm, function);
}
Name EmscriptenGlueGenerator::importStackOverflowHandler() {
// We can call an import to handle stack overflows normally, but not in
// standalone mode, where we can't import from JS.
if (standalone) {
return Name();
}
ImportInfo info(wasm);
if (auto* existing = info.getImportedFunction(ENV, STACK_OVERFLOW_IMPORT)) {
return existing->name;
} else {
auto* import = new Function;
import->name = STACK_OVERFLOW_IMPORT;
import->module = ENV;
import->base = STACK_OVERFLOW_IMPORT;
import->sig = Signature(Type::none, Type::none);
wasm.addFunction(import);
return STACK_OVERFLOW_IMPORT;
}
}
const Address UNKNOWN_OFFSET(uint32_t(-1));
std::vector<Address> getSegmentOffsets(Module& wasm) {
std::unordered_map<Index, Address> passiveOffsets;
if (wasm.features.hasBulkMemory()) {
// Fetch passive segment offsets out of memory.init instructions
struct OffsetSearcher : PostWalker<OffsetSearcher> {
std::unordered_map<Index, Address>& offsets;
OffsetSearcher(std::unordered_map<unsigned, Address>& offsets)
: offsets(offsets) {}
void visitMemoryInit(MemoryInit* curr) {
auto* dest = curr->dest->dynCast<Const>();
if (!dest) {
return;
}
auto it = offsets.find(curr->segment);
if (it != offsets.end()) {
Fatal() << "Cannot get offset of passive segment initialized "
"multiple times";
}
offsets[curr->segment] = dest->value.geti32();
}
} searcher(passiveOffsets);
searcher.walkModule(&wasm);
}
std::vector<Address> segmentOffsets;
for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) {
auto& segment = wasm.memory.segments[i];
if (segment.isPassive) {
auto it = passiveOffsets.find(i);
if (it != passiveOffsets.end()) {
segmentOffsets.push_back(it->second);
} else {
// This was a non-constant offset (perhaps TLS)
segmentOffsets.push_back(UNKNOWN_OFFSET);
}
} else if (auto* addrConst = segment.offset->dynCast<Const>()) {
auto address = addrConst->value.geti32();
segmentOffsets.push_back(address);
} else {
// TODO(sbc): Wasm shared libraries have data segments with non-const
// offset.
segmentOffsets.push_back(0);
}
}
return segmentOffsets;
}
std::string escape(const char* input) {
std::string code = input;
// replace newlines quotes with escaped newlines
size_t curr = 0;
while ((curr = code.find("\\n", curr)) != std::string::npos) {
code = code.replace(curr, 2, "\\\\n");
curr += 3; // skip this one
}
// replace double quotes with escaped single quotes
curr = 0;
while ((curr = code.find('"', curr)) != std::string::npos) {
if (curr == 0 || code[curr - 1] != '\\') {
code = code.replace(curr,
1,
"\\"
"\"");
curr += 2; // skip this one
} else { // already escaped, escape the slash as well
code = code.replace(curr,
1,
"\\"
"\\"
"\"");
curr += 3; // skip this one
}
}
return code;
}
const char* stringAtAddr(Module& wasm,
std::vector<Address> const& segmentOffsets,
Address address) {
for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) {
Memory::Segment& segment = wasm.memory.segments[i];
Address offset = segmentOffsets[i];
if (offset != UNKNOWN_OFFSET && address >= offset &&
address < offset + segment.data.size()) {
return &segment.data[address - offset];
}
}
return nullptr;
}
std::string codeForConstAddr(Module& wasm,
std::vector<Address> const& segmentOffsets,
int32_t address) {
const char* str = stringAtAddr(wasm, segmentOffsets, address);
if (!str) {
// If we can't find the segment corresponding with the address, then we
// omitted the segment and the address points to an empty string.
return escape("");
}
return escape(str);
}
enum class Proxying {
None,
Sync,
Async,
};
std::string proxyingSuffix(Proxying proxy) {
switch (proxy) {
case Proxying::None:
return "";
case Proxying::Sync:
return "sync_on_main_thread_";
case Proxying::Async:
return "async_on_main_thread_";
}
WASM_UNREACHABLE("invalid prozy type");
}
struct AsmConstWalker : public LinearExecutionWalker<AsmConstWalker> {
Module& wasm;
std::vector<Address> segmentOffsets; // segment index => address offset
struct AsmConst {
std::set<Signature> sigs;
Address id;
std::string code;
Proxying proxy;
};
std::vector<AsmConst> asmConsts;
std::set<std::pair<Signature, Proxying>> allSigs;
// last sets in the current basic block, per index
std::map<Index, LocalSet*> sets;
AsmConstWalker(Module& _wasm)
: wasm(_wasm), segmentOffsets(getSegmentOffsets(wasm)) {}
void noteNonLinear(Expression* curr);
void visitLocalSet(LocalSet* curr);
void visitCall(Call* curr);
void visitTable(Table* curr);
void process();
private:
Signature fixupName(Name& name, Signature baseSig, Proxying proxy);
AsmConst&
createAsmConst(uint32_t id, std::string code, Signature sig, Name name);
Signature asmConstSig(Signature baseSig);
Name nameForImportWithSig(Signature sig, Proxying proxy);
void queueImport(Name importName, Signature baseSig);
void addImports();
Proxying proxyType(Name name);
std::vector<std::unique_ptr<Function>> queuedImports;
};
void AsmConstWalker::noteNonLinear(Expression* curr) {
// End of this basic block; clear sets.
sets.clear();
}
void AsmConstWalker::visitLocalSet(LocalSet* curr) { sets[curr->index] = curr; }
void AsmConstWalker::visitCall(Call* curr) {
auto* import = wasm.getFunction(curr->target);
// Find calls to emscripten_asm_const* functions whose first argument is
// is always a string constant.
if (!import->imported()) {
return;
}
auto importName = import->base;
if (!importName.hasSubstring(EM_ASM_PREFIX)) {
return;
}
auto baseSig = wasm.getFunction(curr->target)->sig;
auto sig = asmConstSig(baseSig);
auto* arg = curr->operands[0];
while (!arg->dynCast<Const>()) {
if (auto* get = arg->dynCast<LocalGet>()) {
// The argument may be a local.get, in which case, the last set in this
// basic block has the value.
auto* set = sets[get->index];
if (set) {
assert(set->index == get->index);
arg = set->value;
} else {
Fatal() << "local.get of unknown in arg0 of call to " << importName
<< " (used by EM_ASM* macros) in function "
<< getFunction()->name
<< ".\nThis might be caused by aggressive compiler "
"transformations. Consider using EM_JS instead.";
}
continue;
}
if (auto* setlocal = arg->dynCast<LocalSet>()) {
// The argument may be a local.tee, in which case we take first child
// which is the value being copied into the local.
if (setlocal->isTee()) {
arg = setlocal->value;
continue;
}
}
if (auto* bin = arg->dynCast<Binary>()) {
if (bin->op == AddInt32) {
// In the dynamic linking case the address of the string constant
// is the result of adding its offset to __memory_base.
// In this case are only looking for the offset from __memory_base
// the RHS of the addition is just what we want.
arg = bin->right;
continue;
}
}
Fatal() << "Unexpected arg0 type (" << getExpressionName(arg)
<< ") in call to: " << importName;
}
auto* value = arg->cast<Const>();
int32_t address = value->value.geti32();
auto code = codeForConstAddr(wasm, segmentOffsets, address);
auto& asmConst = createAsmConst(address, code, sig, importName);
fixupName(curr->target, baseSig, asmConst.proxy);
}
Proxying AsmConstWalker::proxyType(Name name) {
if (name.hasSubstring("_sync_on_main_thread")) {
return Proxying::Sync;
} else if (name.hasSubstring("_async_on_main_thread")) {
return Proxying::Async;
}
return Proxying::None;
}
void AsmConstWalker::visitTable(Table* curr) {
for (auto& segment : curr->segments) {
for (auto& name : segment.data) {
auto* func = wasm.getFunction(name);
if (func->imported() && func->base.hasSubstring(EM_ASM_PREFIX)) {
auto proxy = proxyType(func->base);
fixupName(name, func->sig, proxy);
}
}
}
}
void AsmConstWalker::process() {
// Find and queue necessary imports
walkModule(&wasm);
// Add them after the walk, to avoid iterator invalidation on
// the list of functions.
addImports();
}
Signature
AsmConstWalker::fixupName(Name& name, Signature baseSig, Proxying proxy) {
auto sig = asmConstSig(baseSig);
auto importName = nameForImportWithSig(sig, proxy);
name = importName;
auto pair = std::make_pair(sig, proxy);
if (allSigs.count(pair) == 0) {
allSigs.insert(pair);
queueImport(importName, baseSig);
}
return sig;
}
AsmConstWalker::AsmConst& AsmConstWalker::createAsmConst(uint32_t id,
std::string code,
Signature sig,
Name name) {
AsmConst asmConst;
asmConst.id = id;
asmConst.code = code;
asmConst.sigs.insert(sig);
asmConst.proxy = proxyType(name);
asmConsts.push_back(asmConst);
return asmConsts.back();
}
Signature AsmConstWalker::asmConstSig(Signature baseSig) {
std::vector<Type> params = baseSig.params.expand();
assert(params.size() >= 1);
// Omit the signature of the "code" parameter, taken as a string, as the
// first argument
params.erase(params.begin());
return Signature(Type(params), baseSig.results);
}
Name AsmConstWalker::nameForImportWithSig(Signature sig, Proxying proxy) {
std::string fixedTarget = EM_ASM_PREFIX.str + std::string("_") +
proxyingSuffix(proxy) +
getSig(sig.results, sig.params);
return Name(fixedTarget.c_str());
}
void AsmConstWalker::queueImport(Name importName, Signature baseSig) {
auto import = new Function;
import->name = import->base = importName;
import->module = ENV;
import->sig = baseSig;
queuedImports.push_back(std::unique_ptr<Function>(import));
}
void AsmConstWalker::addImports() {
for (auto& import : queuedImports) {
wasm.addFunction(import.release());
}
}
AsmConstWalker fixEmAsmConstsAndReturnWalker(Module& wasm) {
// Collect imports to remove
// This would find our generated functions if we ran it later
std::vector<Name> toRemove;
for (auto& import : wasm.functions) {
if (import->imported() && import->base.hasSubstring(EM_ASM_PREFIX)) {
toRemove.push_back(import->name);
}
}
// Walk the module, generate _sig versions of EM_ASM functions
AsmConstWalker walker(wasm);
walker.process();
// Remove the base functions that we didn't generate
for (auto importName : toRemove) {
wasm.removeFunction(importName);
}
return walker;
}
struct EmJsWalker : public PostWalker<EmJsWalker> {
Module& wasm;
std::vector<Address> segmentOffsets; // segment index => address offset
std::map<std::string, std::string> codeByName;
EmJsWalker(Module& _wasm)
: wasm(_wasm), segmentOffsets(getSegmentOffsets(wasm)) {}
void visitExport(Export* curr) {
if (curr->kind != ExternalKind::Function) {
return;
}
if (!curr->name.startsWith(EM_JS_PREFIX.str)) {
return;
}
auto* func = wasm.getFunction(curr->value);
auto funcName = std::string(curr->name.stripPrefix(EM_JS_PREFIX.str));
// An EM_JS has a single const in the body. Typically it is just returned,
// but in unoptimized code it might be stored to a local and loaded from
// there, and in relocatable code it might get added to __memory_base etc.
FindAll<Const> consts(func->body);
if (consts.list.size() != 1) {
Fatal() << "Unexpected generated __em_js__ function body: " << curr->name;
}
auto* addrConst = consts.list[0];
int32_t address = addrConst->value.geti32();
auto code = codeForConstAddr(wasm, segmentOffsets, address);
codeByName[funcName] = code;
}
};
EmJsWalker fixEmJsFuncsAndReturnWalker(Module& wasm) {
EmJsWalker walker(wasm);
walker.walkModule(&wasm);
std::vector<Name> toRemove;
for (auto& func : wasm.functions) {
if (func->name.startsWith(EM_JS_PREFIX.str)) {
toRemove.push_back(func->name);
}
}
for (auto funcName : toRemove) {
wasm.removeFunction(funcName);
wasm.removeExport(funcName);
}
return walker;
}
// Fixes function name hacks caused by LLVM exception & setjmp/longjmp
// handling pass for wasm.
// This does two things:
// 1. Change emscripten_longjmp_jmpbuf to emscripten_longjmp.
// In setjmp/longjmp handling pass in wasm backend, what we want to do is
// to change all function calls to longjmp to calls to emscripten_longjmp.
// Because we replace all calls to longjmp to emscripten_longjmp, the
// signature of that function should be the same as longjmp:
// emscripten_longjmp(jmp_buf, int)
// But after calling a function that might longjmp, while we test whether
// a longjmp occurred, we have to load an int address value and call
// emscripten_longjmp again with that address as the first argument. (Refer
// to lib/Target/WebAssembly/WebAssemblyEmscriptenEHSjLj.cpp in LLVM for
// details.)
// In this case we need the signature of emscripten_longjmp to be (int,
// int). So we need two different kinds of emscripten_longjmp signatures in
// LLVM IR. Both signatures will be lowered to (int, int) eventually, but
// in LLVM IR, types are not lowered yet.
// So we declare two functions in LLVM:
// emscripten_longjmp_jmpbuf(jmp_buf, int)
// emscripten_longjmp(int, int)
// And we change the name of emscripten_longjmp_jmpbuf to
// emscripten_longjmp here.
// 2. Converts invoke wrapper names.
// Refer to the comments in fixEmExceptionInvoke below.
struct FixInvokeFunctionNamesWalker
: public PostWalker<FixInvokeFunctionNamesWalker> {
Module& wasm;
std::map<Name, Name> importRenames;
std::map<Name, Name> functionReplace;
std::set<Signature> invokeSigs;
ImportInfo imports;
FixInvokeFunctionNamesWalker(Module& _wasm) : wasm(_wasm), imports(wasm) {}
// Converts invoke wrapper names generated by LLVM backend to real invoke
// wrapper names that are expected by JavaScript glue code.
// This is required to support wasm exception handling (asm.js style).
//
// LLVM backend lowers
// invoke @func(arg1, arg2) to label %invoke.cont unwind label %lpad
// into
// ... (some code)
// call @invoke_SIG(func, arg1, arg2)
// ... (some code)
// SIG is a mangled string generated based on the LLVM IR-level function
// signature. In LLVM IR, types are not lowered yet, so this mangling scheme
// simply takes LLVM's string representtion of parameter types and concatenate
// them with '_'. For example, the name of an invoke wrapper for function
// void foo(struct mystruct*, int) will be
// "__invoke_void_%struct.mystruct*_int".
// This function converts the names of invoke wrappers based on their lowered
// argument types and a return type. In the example above, the resulting new
// wrapper name becomes "invoke_vii".
Name fixEmExceptionInvoke(const Name& name, Signature sig) {
std::string nameStr = name.c_str();
if (nameStr.front() == '"' && nameStr.back() == '"') {
nameStr = nameStr.substr(1, nameStr.size() - 2);
}
if (nameStr.find("__invoke_") != 0) {
return name;
}
const std::vector<Type>& params = sig.params.expand();
std::vector<Type> newParams(params.begin() + 1, params.end());
Signature sigWoOrigFunc = Signature(Type(newParams), sig.results);
invokeSigs.insert(sigWoOrigFunc);
return Name("invoke_" +
getSig(sigWoOrigFunc.results, sigWoOrigFunc.params));
}
Name fixEmEHSjLjNames(const Name& name, Signature sig) {
if (name == "emscripten_longjmp_jmpbuf") {
return "emscripten_longjmp";
}
return fixEmExceptionInvoke(name, sig);
}
void visitFunction(Function* curr) {
if (!curr->imported()) {
return;
}
Name newname = fixEmEHSjLjNames(curr->base, curr->sig);
if (newname == curr->base) {
return;
}
BYN_TRACE("renaming import: " << curr->module << "." << curr->base << " ("
<< curr->name << ") -> " << newname << "\n");
assert(importRenames.count(curr->base) == 0);
importRenames[curr->base] = newname;
// Either rename the import, or replace it with an existing one
Function* existingFunc = imports.getImportedFunction(curr->module, newname);
if (existingFunc) {
BYN_TRACE("replacing with an existing import: " << existingFunc->name
<< "\n");
functionReplace[curr->name] = existingFunc->name;
} else {
BYN_TRACE("renaming the import in place\n");
curr->base = newname;
}
}
void visitModule(Module* curr) {
// For each replaced function first remove the function itself then
// rename all uses to the point to the new function.
for (auto& pair : functionReplace) {
BYN_TRACE("removeFunction " << pair.first << "\n");
wasm.removeFunction(pair.first);
}
// Rename all uses of the removed functions
ModuleUtils::renameFunctions(wasm, functionReplace);
// For imports that for renamed, update any associated GOT.func imports.
for (auto& pair : importRenames) {
BYN_TRACE("looking for: GOT.func." << pair.first << "\n");
if (auto g = imports.getImportedGlobal("GOT.func", pair.first)) {
BYN_TRACE("renaming corresponding GOT entry: " << g->base << " -> "
<< pair.second << "\n");
g->base = pair.second;
}
}
}
};
void EmscriptenGlueGenerator::fixInvokeFunctionNames() {
BYN_TRACE("fixInvokeFunctionNames\n");
FixInvokeFunctionNamesWalker walker(wasm);
walker.walkModule(&wasm);
BYN_TRACE("generating dyncall thunks\n");
for (auto sig : walker.invokeSigs) {
generateDynCallThunk(sig);
}
}
void printSignatures(std::ostream& o, const std::set<Signature>& c) {
o << "[";
bool first = true;
for (auto& sig : c) {
if (first) {
first = false;
} else {
o << ",";
}
o << '"' << getSig(sig.results, sig.params) << '"';
}
o << "]";
}
std::string EmscriptenGlueGenerator::generateEmscriptenMetadata(
Address staticBump, std::vector<Name> const& initializerFunctions) {
bool commaFirst;
auto nextElement = [&commaFirst]() {
if (commaFirst) {
commaFirst = false;
return "\n ";
} else {
return ",\n ";
}
};
std::stringstream meta;
meta << "{\n";
AsmConstWalker emAsmWalker = fixEmAsmConstsAndReturnWalker(wasm);
// print
commaFirst = true;
if (!emAsmWalker.asmConsts.empty()) {
meta << " \"asmConsts\": {";
for (auto& asmConst : emAsmWalker.asmConsts) {
meta << nextElement();
meta << '"' << asmConst.id << "\": [\"" << asmConst.code << "\", ";
printSignatures(meta, asmConst.sigs);
meta << ", [\"" << proxyingSuffix(asmConst.proxy) << "\"]";
meta << "]";
}
meta << "\n },\n";
}
EmJsWalker emJsWalker = fixEmJsFuncsAndReturnWalker(wasm);
if (!emJsWalker.codeByName.empty()) {
meta << " \"emJsFuncs\": {";
commaFirst = true;
for (auto& pair : emJsWalker.codeByName) {
auto& name = pair.first;
auto& code = pair.second;
meta << nextElement();
meta << '"' << name << "\": \"" << code << '"';
}
meta << "\n },\n";
}
meta << " \"staticBump\": " << staticBump << ",\n";
meta << " \"tableSize\": " << wasm.table.initial.addr << ",\n";
if (!initializerFunctions.empty()) {
meta << " \"initializers\": [";
commaFirst = true;
for (const auto& func : initializerFunctions) {
meta << nextElement();
meta << "\"" << func.c_str() << "\"";
}
meta << "\n ],\n";
}
// Avoid adding duplicate imports to `declares' or `invokeFuncs`. Even
// though we might import the same function multiple times (i.e. with
// different sigs) we only need to list is in the metadata once.
std::set<std::string> declares;
std::set<std::string> invokeFuncs;
// We use the `base` rather than the `name` of the imports here and below
// becasue this is the externally visible name that the embedder (JS) will
// see.
meta << " \"declares\": [";
commaFirst = true;
ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
if (emJsWalker.codeByName.count(import->base.str) == 0 &&
!import->base.startsWith(EM_ASM_PREFIX.str) &&
!import->base.startsWith("invoke_")) {
if (declares.insert(import->base.str).second) {
meta << nextElement() << '"' << import->base.str << '"';
}
}
});
meta << "\n ],\n";
meta << " \"externs\": [";
commaFirst = true;
ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
if (!(import->module == ENV && import->name == STACK_INIT)) {
meta << nextElement() << "\"_" << import->base.str << '"';
}
});
meta << "\n ],\n";
if (!wasm.exports.empty()) {
meta << " \"implementedFunctions\": [";
commaFirst = true;
for (const auto& ex : wasm.exports) {
if (ex->kind == ExternalKind::Function) {
meta << nextElement() << "\"_" << ex->name.str << '"';
}
}
meta << "\n ],\n";
meta << " \"exports\": [";
commaFirst = true;
for (const auto& ex : wasm.exports) {
if (ex->kind == ExternalKind::Function) {
meta << nextElement() << '"' << ex->name.str << '"';
}
}
meta << "\n ],\n";
meta << " \"namedGlobals\": {";
commaFirst = true;
for (const auto& ex : wasm.exports) {
if (ex->kind == ExternalKind::Global) {
const Global* g = wasm.getGlobal(ex->value);
assert(g->type == Type::i32);
Const* init = g->init->cast<Const>();
uint32_t addr = init->value.geti32();
meta << nextElement() << '"' << ex->name.str << "\" : \"" << addr
<< '"';
}
}
meta << "\n },\n";
}
meta << " \"invokeFuncs\": [";
commaFirst = true;
ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
if (import->base.startsWith("invoke_")) {
if (invokeFuncs.insert(import->base.str).second) {
meta << nextElement() << '"' << import->base.str << '"';
}
}
});
meta << "\n ],\n";
meta << " \"features\": [";
commaFirst = true;
wasm.features.iterFeatures([&](FeatureSet::Feature f) {
meta << nextElement() << "\"--enable-" << FeatureSet::toString(f) << '"';
});
meta << "\n ],\n";
auto mainReadsParams = false;
if (auto* exp = wasm.getExportOrNull("main")) {
if (exp->kind == ExternalKind::Function) {
auto* main = wasm.getFunction(exp->value);
mainReadsParams = true;
// If main does not read its parameters, it will just be a stub that
// calls __original_main (which has no parameters).
if (auto* call = main->body->dynCast<Call>()) {
if (call->operands.empty()) {
mainReadsParams = false;
}
}
}
}
meta << " \"mainReadsParams\": " << int(mainReadsParams) << '\n';
meta << "}\n";
return meta.str();
}
void EmscriptenGlueGenerator::separateDataSegments(Output* outfile,
Address base) {
size_t lastEnd = 0;
for (Memory::Segment& seg : wasm.memory.segments) {
if (seg.isPassive) {
Fatal() << "separating passive segments not implemented";
}
if (!seg.offset->is<Const>()) {
Fatal() << "separating relocatable segments not implemented";
}
size_t offset = seg.offset->cast<Const>()->value.geti32();
offset -= base;
size_t fill = offset - lastEnd;
if (fill > 0) {
std::vector<char> buf(fill);
outfile->write(buf.data(), fill);
}
outfile->write(seg.data.data(), seg.data.size());
lastEnd = offset + seg.data.size();
}
wasm.memory.segments.clear();
}
void EmscriptenGlueGenerator::exportWasiStart() {
// If main exists, export a function to call it per the wasi standard.
Name main = "main";
if (!wasm.getFunctionOrNull(main)) {
BYN_TRACE("exportWasiStart: main not found\n");
return;
}
Name _start = "_start";
if (wasm.getExportOrNull(_start)) {
BYN_TRACE("exportWasiStart: _start already present\n");
return;
}
BYN_TRACE("exportWasiStart\n");
Builder builder(wasm);
auto* body =
builder.makeDrop(builder.makeCall(main,
{LiteralUtils::makeZero(Type::i32, wasm),
LiteralUtils::makeZero(Type::i32, wasm)},
Type::i32));
auto* func =
builder.makeFunction(_start, Signature(Type::none, Type::none), {}, body);
wasm.addFunction(func);
wasm.addExport(builder.makeExport(_start, _start, ExternalKind::Function));
}
} // namespace wasm