#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 "ir/table-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_INIT("stack$init");
static Name POST_INSTANTIATE("__post_instantiate");
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_);
}
bool isExported(Module& wasm, Name name) {
for (auto& ex : wasm.exports) {
if (ex->value == name) {
return true;
}
}
return false;
}
Global* getStackPointerGlobal(Module& wasm) {
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;
}
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 = wasm.getFunctionOrNull(ASSIGN_GOT_ENTRIES)) {
Expression* call = builder.makeCall(F->name, {}, Type::none);
post_instantiate->body = builder.blockify(post_instantiate->body, call);
}
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);
}
void EmscriptenGlueGenerator::internalizeStackPointerGlobal() {
Global* stackPointer = getStackPointerGlobal(wasm);
if (!stackPointer || !stackPointer->imported() || !stackPointer->mutable_) {
return;
}
Name internalName = stackPointer->name;
Name externalName = internalName.c_str() + std::string("_import");
stackPointer->name = externalName;
stackPointer->mutable_ = false;
wasm.updateMaps();
Builder builder(wasm);
auto* init = builder.makeGlobalGet(externalName, stackPointer->type);
auto* sp = builder.makeGlobal(
internalName, stackPointer->type, init, Builder::Mutable);
wasm.addGlobal(sp);
}
const Address UNKNOWN_OFFSET(uint32_t(-1));
std::vector<Address> getSegmentOffsets(Module& wasm) {
std::unordered_map<Index, Address> passiveOffsets;
if (wasm.features.hasBulkMemory()) {
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 {
segmentOffsets.push_back(UNKNOWN_OFFSET);
}
} else if (auto* addrConst = segment.offset->dynCast<Const>()) {
auto address = addrConst->value.geti32();
segmentOffsets.push_back(address);
} else {
segmentOffsets.push_back(0);
}
}
return segmentOffsets;
}
std::string escape(const char* input) {
std::string code = input;
size_t curr = 0;
while ((curr = code.find("\\n", curr)) != std::string::npos) {
code = code.replace(curr, 2, "\\\\n");
curr += 3; }
curr = 0;
while ((curr = code.find('"', curr)) != std::string::npos) {
if (curr == 0 || code[curr - 1] != '\\') {
code = code.replace(curr,
1,
"\\"
"\"");
curr += 2; } else { code = code.replace(curr,
1,
"\\"
"\\"
"\"");
curr += 3; }
}
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) {
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;
bool minimizeWasmChanges;
std::vector<Address> segmentOffsets;
struct AsmConst {
std::set<Signature> sigs;
Address id;
std::string code;
Proxying proxy;
};
std::vector<AsmConst> asmConsts;
std::set<std::pair<Signature, Proxying>> allSigs;
std::map<Index, LocalSet*> sets;
AsmConstWalker(Module& _wasm, bool minimizeWasmChanges)
: wasm(_wasm), minimizeWasmChanges(minimizeWasmChanges),
segmentOffsets(getSegmentOffsets(wasm)) {}
void noteNonLinear(Expression* curr);
void visitLocalSet(LocalSet* curr);
void visitCall(Call* curr);
void process();
private:
void 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) {
sets.clear();
}
void AsmConstWalker::visitLocalSet(LocalSet* curr) { sets[curr->index] = curr; }
void AsmConstWalker::visitCall(Call* curr) {
auto* import = wasm.getFunction(curr->target);
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>()) {
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>()) {
if (setlocal->isTee()) {
arg = setlocal->value;
continue;
}
}
if (auto* bin = arg->dynCast<Binary>()) {
if (bin->op == AddInt32) {
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);
createAsmConst(address, code, sig, importName);
}
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::process() {
walkModule(&wasm);
addImports();
}
void 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);
}
Signature AsmConstWalker::asmConstSig(Signature baseSig) {
assert(baseSig.params.size() >= 1);
return Signature(
Type(std::vector<Type>(baseSig.params.begin() + 1, baseSig.params.end())),
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());
}
}
static AsmConstWalker fixEmAsmConstsAndReturnWalker(Module& wasm,
bool minimizeWasmChanges) {
AsmConstWalker walker(wasm, minimizeWasmChanges);
walker.process();
return walker;
}
struct EmJsWalker : public PostWalker<EmJsWalker> {
Module& wasm;
std::vector<Address> segmentOffsets;
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));
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;
}
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, minimizeWasmChanges);
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";
}
std::set<std::string> declares;
std::set<std::string> invokeFuncs;
meta << " \"declares\": [";
commaFirst = true;
ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
if (emJsWalker.codeByName.count(import->base.str) == 0 &&
!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 << " \"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->module == ENV && import->base.startsWith("invoke_")) {
if (invokeFuncs.insert(import->base.str).second) {
meta << nextElement() << '"' << import->base.str << '"';
}
}
});
meta << "\n ],\n";
if (!standalone) {
auto mainReadsParams = false;
auto* exp = wasm.getExportOrNull("main");
if (!exp) {
exp = wasm.getExportOrNull("__main_argc_argv");
}
if (exp) {
if (exp->kind == ExternalKind::Function) {
auto* main = wasm.getFunction(exp->value);
mainReadsParams = true;
if (auto* call = main->body->dynCast<Call>()) {
if (call->operands.empty()) {
mainReadsParams = false;
}
}
}
}
meta << " \"mainReadsParams\": " << int(mainReadsParams) << ",\n";
}
meta << " \"features\": [";
commaFirst = true;
wasm.features.iterFeatures([&](FeatureSet::Feature f) {
meta << nextElement() << "\"--enable-" << FeatureSet::toString(f) << '"';
});
meta << "\n ]\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::renameMainArgcArgv() {
Export* ex = wasm.getExportOrNull("__main_argc_argv");
if (!ex) {
BYN_TRACE("renameMain: __main_argc_argv not found\n");
return;
}
ex->name = "main";
wasm.updateMaps();
ModuleUtils::renameFunction(wasm, "__main_argc_argv", "main");
}
}