| /* |
| * 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 "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<NameType> params { }; |
| Function* function = builder.makeFunction( |
| name, std::move(params), i32, {} |
| ); |
| |
| function->body = generateLoadStackPointer(); |
| |
| addExportedFunction(wasm, function); |
| } |
| |
| void EmscriptenGlueGenerator::generateStackAllocFunction() { |
| Name name("stackAlloc"); |
| std::vector<NameType> 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<NameType> 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<NameType> 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<std::string> sigs; |
| Builder builder(wasm); |
| std::vector<Name> 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<NameType> 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<Expression*> 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<JSCallWalker> { |
| Module &wasm; |
| JSCallWalker(Module &_wasm) : wasm(_wasm) { |
| if (wasm.table.segments.size() == 0) { |
| auto emptySegment = |
| wasm.allocator.alloc<Const>()->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<Const>()->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<std::string> 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<NameType> 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<Expression*> 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<Const>()->value.getInteger() + |
| tableSegmentData.size(); |
| } |
| |
| std::vector<Address> getSegmentOffsets(Module& wasm) { |
| std::vector<Address> segmentOffsets; |
| for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) { |
| Const* addrConst = wasm.memory.segments[i].offset->cast<Const>(); |
| 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<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 (address >= offset && address < offset + segment.data.size()) { |
| return &segment.data[address - offset]; |
| } |
| } |
| return nullptr; |
| } |
| |
| std::string codeForConstAddr(Module& wasm, |
| std::vector<Address> 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<AsmConstWalker> { |
| Module& wasm; |
| std::vector<Address> segmentOffsets; // segment index => address offset |
| |
| std::map<std::string, std::set<std::string>> sigsForCode; |
| std::map<std::string, Address> ids; |
| std::set<std::string> 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<Const>(); |
| 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<Name> 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<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 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<Const>(); |
| if (addrConst == nullptr) { |
| auto block = curr->body->dynCast<Block>(); |
| Expression* first = nullptr; |
| if (block && block->list.size() > 0) { |
| first = block->list[0]; |
| } |
| if (first) { |
| addrConst = first->dynCast<Const>(); |
| } |
| } |
| 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<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 EmscriptenGlueGenerator::fixEmAsmConsts() { |
| fixEmAsmConstsAndReturnWalker(wasm); |
| fixEmJsFuncsAndReturnWalker(wasm); |
| } |
| |
| template<class C> |
| 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<Name> 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<Name> 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 |