/* * 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 #include "asm_v_wasm.h" #include "asmjs/shared-constants.h" #include "shared-constants.h" #include "wasm-builder.h" #include "wasm-linker.h" #include "wasm-traversal.h" #include "wasm.h" namespace wasm { cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); cashew::IString EM_JS_PREFIX("__em_js__"); static constexpr const char* dummyFunction = "__wasm_nullptr"; 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_); } Global* EmscriptenGlueGenerator::getStackPointerGlobal() { // Assumption: first global is __stack_pointer return wasm.globals[0].get(); } Expression* EmscriptenGlueGenerator::generateLoadStackPointer() { if (!useStackPointerGlobal) { return builder.makeLoad( /* bytes =*/ 4, /* signed =*/ false, /* offset =*/ stackPointerOffset, /* align =*/ 4, /* ptr =*/ builder.makeConst(Literal(0)), /* type =*/ i32 ); } Global* stackPointer = getStackPointerGlobal(); return builder.makeGetGlobal(stackPointer->name, i32); } Expression* EmscriptenGlueGenerator::generateStoreStackPointer(Expression* value) { if (!useStackPointerGlobal) { return builder.makeStore( /* bytes =*/ 4, /* offset =*/ stackPointerOffset, /* align =*/ 4, /* ptr =*/ builder.makeConst(Literal(0)), /* value =*/ value, /* type =*/ i32 ); } Global* stackPointer = getStackPointerGlobal(); return builder.makeSetGlobal(stackPointer->name, value); } void EmscriptenGlueGenerator::generateStackSaveFunction() { Name name("stackSave"); std::vector params { }; Function* function = builder.makeFunction( name, std::move(params), i32, {} ); function->body = generateLoadStackPointer(); addExportedFunction(wasm, function); } void EmscriptenGlueGenerator::generateStackAllocFunction() { Name name("stackAlloc"); std::vector params { { "0", i32 } }; Function* function = builder.makeFunction( name, std::move(params), i32, { { "1", i32 } } ); Expression* loadStack = generateLoadStackPointer(); GetLocal* getSizeArg = builder.makeGetLocal(0, 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); SetLocal* teeStackLocal = builder.makeTeeLocal(1, maskedSub); Expression* storeStack = generateStoreStackPointer(teeStackLocal); Block* block = builder.makeBlock(); block->list.push_back(storeStack); GetLocal* getStackLocal2 = builder.makeGetLocal(1, i32); block->list.push_back(getStackLocal2); block->type = i32; function->body = block; addExportedFunction(wasm, function); } void EmscriptenGlueGenerator::generateStackRestoreFunction() { Name name("stackRestore"); std::vector params { { "0", i32 } }; Function* function = builder.makeFunction( name, std::move(params), none, {} ); GetLocal* getArg = builder.makeGetLocal(0, i32); Expression* store = generateStoreStackPointer(getArg); function->body = store; addExportedFunction(wasm, function); } void EmscriptenGlueGenerator::generateRuntimeFunctions() { generateStackSaveFunction(); generateStackAllocFunction(); generateStackRestoreFunction(); } Function* EmscriptenGlueGenerator::generateMemoryGrowthFunction() { Name name(GROW_WASM_MEMORY); std::vector params { { NEW_SIZE, i32 } }; Function* growFunction = builder.makeFunction( name, std::move(params), i32, {} ); growFunction->body = builder.makeHost( GrowMemory, Name(), { builder.makeGetLocal(0, i32) } ); addExportedFunction(wasm, growFunction); return growFunction; } static bool hasI64ResultOrParam(FunctionType* ft) { if (ft->result == i64) return true; for (auto ty : ft->params) { if (ty == i64) return true; } return false; } void EmscriptenGlueGenerator::generateDynCallThunks() { std::unordered_set sigs; Builder builder(wasm); std::vector tableSegmentData; if (wasm.table.segments.size() > 0) { tableSegmentData = wasm.table.segments[0].data; } for (const auto& indirectFunc : tableSegmentData) { if (indirectFunc == dummyFunction) { continue; } std::string sig; if (auto import = wasm.getImportOrNull(indirectFunc)) { sig = getSig(wasm.getFunctionType(import->functionType)); } else { sig = getSig(wasm.getFunction(indirectFunc)); } auto* funcType = ensureFunctionType(sig, &wasm); if (hasI64ResultOrParam(funcType)) continue; // Can't export i64s on the web. if (!sigs.insert(sig).second) continue; // Sig is already in the set std::vector params; params.emplace_back("fptr", i32); // function pointer param int p = 0; for (const auto& ty : funcType->params) params.emplace_back(std::to_string(p++), ty); Function* f = builder.makeFunction(std::string("dynCall_") + sig, std::move(params), funcType->result, {}); Expression* fptr = builder.makeGetLocal(0, i32); std::vector args; for (unsigned i = 0; i < funcType->params.size(); ++i) { args.push_back(builder.makeGetLocal(i + 1, funcType->params[i])); } Expression* call = builder.makeCallIndirect(funcType, fptr, args); f->body = call; wasm.addFunction(f); exportFunction(wasm, f->name, true); } } struct JSCallWalker : public PostWalker { Module &wasm; JSCallWalker(Module &_wasm) : wasm(_wasm) { if (wasm.table.segments.size() == 0) { auto emptySegment = wasm.allocator.alloc()->set(Literal(uint32_t(0))); wasm.table.segments.emplace_back(emptySegment); } const auto& tableSegmentData = wasm.table.segments[0].data; // Check if jsCalls have already been created for (Index i = 0; i < tableSegmentData.size(); ++i) { if (tableSegmentData[i].startsWith("jsCall_")) { jsCallStartIndex = i; return; } } jsCallStartIndex = wasm.table.segments[0].offset->cast()->value.getInteger() + tableSegmentData.size(); } // Gather all function signatures used in call_indirect, because any of them // can be used to call function pointers created by emscripten's addFunction. void visitCallIndirect(CallIndirect *curr) { // dynCall thunks are generated in binaryen and call_indirect instructions // within them cannot be used to call function pointers returned by // emscripten's addFunction. if (!getFunction()->name.startsWith("dynCall_")) { indirectlyCallableSigs.insert( getSig(wasm.getFunctionType(curr->fullType))); } } bool createJSCallThunks; Index jsCallStartIndex; // Function type signatures used in call_indirect instructions std::set indirectlyCallableSigs; }; JSCallWalker getJSCallWalker(Module& wasm) { JSCallWalker walker(wasm); walker.walkModule(&wasm); return walker; } void EmscriptenGlueGenerator::generateJSCallThunks( unsigned numReservedFunctionPointers) { if (numReservedFunctionPointers == 0) return; JSCallWalker walker = getJSCallWalker(wasm); auto& tableSegmentData = wasm.table.segments[0].data; for (std::string sig : walker.indirectlyCallableSigs) { // Add imports for jsCall_sig (e.g. jsCall_vi). // Imported jsCall_sig functions have their first parameter as an index to // the function table, so we should prepend an 'i' to parameters' signature // (e.g. If the signature of the callee is 'vi', the imported jsCall_vi // function would have signature 'vii'.) std::string importSig = std::string(1, sig[0]) + 'i' + sig.substr(1); FunctionType *importType = ensureFunctionType(importSig, &wasm); auto import = new Import; import->name = import->base = "jsCall_" + sig; import->module = ENV; import->functionType = importType->name; import->kind = ExternalKind::Function; wasm.addImport(import); FunctionType *funcType = ensureFunctionType(sig, &wasm); // Create jsCall_sig_index thunks (e.g. jsCall_vi_0, jsCall_vi_1, ...) // e.g. If # of reserved function pointers (given by a command line // argument) is 3 and there are two possible signature 'vi' and 'ii', the // genereated thunks will be jsCall_vi_0, jsCall_vi_1, jsCall_vi_2, // jsCall_ii_0, jsCall_ii_1, and jsCall_ii_2. for (unsigned fp = 0; fp < numReservedFunctionPointers; ++fp) { std::vector params; int p = 0; for (const auto& ty : funcType->params) { params.emplace_back(std::to_string(p++), ty); } Function* f = builder.makeFunction( std::string("jsCall_") + sig + "_" + std::to_string(fp), std::move(params), funcType->result, {}); std::vector args; args.push_back(builder.makeConst(Literal(fp))); for (unsigned i = 0; i < funcType->params.size(); ++i) { args.push_back(builder.makeGetLocal(i, funcType->params[i])); } Expression* call = builder.makeCallImport(import->name, args, funcType->result); f->body = call; wasm.addFunction(f); tableSegmentData.push_back(f->name); } } wasm.table.initial = wasm.table.max = wasm.table.segments[0].offset->cast()->value.getInteger() + tableSegmentData.size(); } std::vector
getSegmentOffsets(Module& wasm) { std::vector
segmentOffsets; for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) { Const* addrConst = wasm.memory.segments[i].offset->cast(); auto address = addrConst->value.geti32(); segmentOffsets.push_back(address); } 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
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 (address >= offset && address < offset + segment.data.size()) { return &segment.data[address - offset]; } } return nullptr; } std::string codeForConstAddr(Module& wasm, std::vector
const& segmentOffsets, Const* addrConst) { auto address = addrConst->value.geti32(); 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); } struct AsmConstWalker : public PostWalker { Module& wasm; std::vector
segmentOffsets; // segment index => address offset std::map> sigsForCode; std::map ids; std::set allSigs; AsmConstWalker(Module& _wasm) : wasm(_wasm), segmentOffsets(getSegmentOffsets(wasm)) { } void visitCallImport(CallImport* curr); private: Literal idLiteralForCode(std::string code); std::string asmConstSig(std::string baseSig); Name nameForImportWithSig(std::string sig); void addImport(Name importName, std::string baseSig); }; void AsmConstWalker::visitCallImport(CallImport* curr) { Import* import = wasm.getImport(curr->target); if (import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { auto arg = curr->operands[0]->cast(); auto code = codeForConstAddr(wasm, segmentOffsets, arg); arg->value = idLiteralForCode(code); auto baseSig = getSig(curr); auto sig = asmConstSig(baseSig); sigsForCode[code].insert(sig); auto importName = nameForImportWithSig(sig); curr->target = importName; if (allSigs.count(sig) == 0) { allSigs.insert(sig); addImport(importName, baseSig); } } } Literal AsmConstWalker::idLiteralForCode(std::string code) { int32_t id; if (ids.count(code) == 0) { id = ids.size(); ids[code] = id; } else { id = ids[code]; } return Literal(id); } std::string AsmConstWalker::asmConstSig(std::string baseSig) { std::string sig = ""; for (size_t i = 0; i < baseSig.size(); ++i) { // Omit the signature of the "code" parameter, taken as a string, as the first argument if (i != 1) { sig += baseSig[i]; } } return sig; } Name AsmConstWalker::nameForImportWithSig(std::string sig) { std::string fixedTarget = EMSCRIPTEN_ASM_CONST.str + std::string("_") + sig; return Name(fixedTarget.c_str()); } void AsmConstWalker::addImport(Name importName, std::string baseSig) { auto import = new Import; import->name = import->base = importName; import->module = ENV; import->functionType = ensureFunctionType(baseSig, &wasm)->name; import->kind = ExternalKind::Function; wasm.addImport(import); } AsmConstWalker fixEmAsmConstsAndReturnWalker(Module& wasm) { // Collect imports to remove // This would find our generated functions if we ran it later std::vector toRemove; for (auto& import : wasm.imports) { if (import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { toRemove.push_back(import->name); } } // Walk the module, generate _sig versions of EM_ASM functions AsmConstWalker walker(wasm); walker.walkModule(&wasm); // Remove the base functions that we didn't generate for (auto importName : toRemove) { wasm.removeImport(importName); } return walker; } struct EmJsWalker : public PostWalker { Module& wasm; std::vector
segmentOffsets; // segment index => address offset std::map codeByName; EmJsWalker(Module& _wasm) : wasm(_wasm), segmentOffsets(getSegmentOffsets(wasm)) { } void visitFunction(Function* curr) { if (!curr->name.startsWith(EM_JS_PREFIX.str)) { return; } auto funcName = std::string(curr->name.stripPrefix(EM_JS_PREFIX.str)); auto addrConst = curr->body->dynCast(); if (addrConst == nullptr) { auto block = curr->body->dynCast(); Expression* first = nullptr; if (block && block->list.size() > 0) { first = block->list[0]; } if (first) { addrConst = first->dynCast(); } } if (addrConst == nullptr) { Fatal() << "Unexpected generated __em_js__ function body: " << curr; } auto code = codeForConstAddr(wasm, segmentOffsets, addrConst); codeByName[funcName] = code; } }; EmJsWalker fixEmJsFuncsAndReturnWalker(Module& wasm) { EmJsWalker walker(wasm); walker.walkModule(&wasm); std::vector 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 EmscriptenGlueGenerator::fixEmAsmConsts() { fixEmAsmConstsAndReturnWalker(wasm); fixEmJsFuncsAndReturnWalker(wasm); } template void printSet(std::ostream& o, C& c) { o << "["; bool first = true; for (auto& item : c) { if (first) first = false; else o << ","; o << '"' << item << '"'; } o << "]"; } std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( Address staticBump, std::vector const& initializerFunctions, unsigned numReservedFunctionPointers) { bool commaFirst; auto maybeComma = [&commaFirst]() { if (commaFirst) { commaFirst = false; return ""; } else { return ","; } }; std::stringstream meta; meta << "{ "; AsmConstWalker emAsmWalker = fixEmAsmConstsAndReturnWalker(wasm); // print commaFirst = true; meta << "\"asmConsts\": {"; for (auto& pair : emAsmWalker.sigsForCode) { auto& code = pair.first; auto& sigs = pair.second; meta << maybeComma(); meta << '"' << emAsmWalker.ids[code] << "\": [\"" << code << "\", "; printSet(meta, sigs); meta << "]"; } meta << "},"; EmJsWalker emJsWalker = fixEmJsFuncsAndReturnWalker(wasm); if (emJsWalker.codeByName.size() > 0) { meta << "\"emJsFuncs\": {"; commaFirst = true; for (auto& pair : emJsWalker.codeByName) { auto& name = pair.first; auto& code = pair.second; meta << maybeComma(); meta << '"' << name << "\": \"" << code << '"'; } meta << "},"; } meta << "\"staticBump\": " << staticBump << ", "; meta << "\"initializers\": ["; commaFirst = true; for (const auto& func : initializerFunctions) { meta << maybeComma(); meta << "\"" << func.c_str() << "\""; } meta << "]"; if (numReservedFunctionPointers) { JSCallWalker jsCallWalker = getJSCallWalker(wasm); meta << ", "; meta << "\"jsCallStartIndex\": " << jsCallWalker.jsCallStartIndex << ", "; meta << "\"jsCallFuncType\": ["; commaFirst = true; for (std::string sig : jsCallWalker.indirectlyCallableSigs) { meta << maybeComma(); meta << "\"" << sig << "\""; } meta << "]"; } meta << ", \"declares\": ["; commaFirst = true; for (const auto& import : wasm.imports) { if (import->kind == ExternalKind::Function && (emJsWalker.codeByName.count(import->name.str) == 0) && !import->name.startsWith(EMSCRIPTEN_ASM_CONST.str) && !import->name.startsWith("invoke_") && !import->name.startsWith("jsCall_")) { meta << maybeComma() << '"' << import->name.str << '"'; } } meta << "]"; meta << ", \"externs\": ["; commaFirst = true; for (const auto& import : wasm.imports) { if (import->kind == ExternalKind::Global) { meta << maybeComma() << "\"_" << import->name.str << '"'; } } meta << "]"; meta << ", \"implementedFunctions\": ["; commaFirst = true; for (const auto& func : wasm.functions) { meta << maybeComma() << "\"_" << func->name.str << '"'; } meta << "]"; meta << ", \"exports\": ["; commaFirst = true; for (const auto& ex : wasm.exports) { meta << maybeComma() << '"' << ex->name.str << '"'; } meta << "]"; meta << ", \"invokeFuncs\": ["; commaFirst = true; for (const auto& import : wasm.imports) { if (import->name.startsWith("invoke_")) { meta << maybeComma() << '"' << import->name.str << '"'; } } meta << "]"; meta << " }\n"; return meta.str(); } std::string emscriptenGlue( Module& wasm, bool allowMemoryGrowth, Address stackPointer, Address staticBump, std::vector const& initializerFunctions, unsigned numReservedFunctionPointers) { EmscriptenGlueGenerator generator(wasm, stackPointer); generator.generateRuntimeFunctions(); if (allowMemoryGrowth) { generator.generateMemoryGrowthFunction(); } generator.generateDynCallThunks(); if (numReservedFunctionPointers) { generator.generateJSCallThunks(numReservedFunctionPointers); } return generator.generateEmscriptenMetadata(staticBump, initializerFunctions, numReservedFunctionPointers); } } // namespace wasm