diff options
author | Alon Zakai <alonzakai@gmail.com> | 2018-03-13 09:29:38 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-13 09:29:38 -0700 |
commit | d52213c3f5e96bb3450721d96aa68d3c5e0865b6 (patch) | |
tree | c585377fb842da0c4b87657f5047272196af9e6c /src/wasm | |
parent | 0008b795d11d65d0759c3c0a71ee74b536f1ecf8 (diff) | |
download | binaryen-d52213c3f5e96bb3450721d96aa68d3c5e0865b6.tar.gz binaryen-d52213c3f5e96bb3450721d96aa68d3c5e0865b6.tar.bz2 binaryen-d52213c3f5e96bb3450721d96aa68d3c5e0865b6.zip |
Function pointer cast emulation (#1468)
This adds a pass that implements "function pointer cast emulation" - allows indirect calls to go through even if the number of arguments or their types is incorrect. That is undefined behavior in C/C++ but in practice somehow works in native archs. It is even relied upon in e.g. Python.
Emscripten already has such emulation for asm.js, which also worked for asm2wasm. This implements something like it in binaryen which also allows the wasm backend to use it. As a result, Python should now be portable using the wasm backend.
The mechanism used for the emulation is to make all indirect calls use a fixed number of arguments, all of type i64, and a return type of also i64. Thunks are then placed in the table which translate the arguments properly for the target, basically by reinterpreting to i64 and back. As a result, receiving an i64 when an i32 is sent will have the upper bits all zero, and the reverse would truncate the upper bits, etc. (Note that this is different than emscripten's existing emulation, which converts (as signed) to a double. That makes sense for JS where double's can contain all numeric values, but in wasm we have i64s. Also, bitwise conversion may be more like what native archs do anyhow. It is enough for Python.)
Also adds validation for a function's type matching the function's actual params and result (surprised we didn't have that before, but we didn't, and there was even a place in the test suite where that was wrong).
Also simplifies the build script by moving two cpp files into the wasm/ subdir, so they can be built once and shared between the various tools.
Diffstat (limited to 'src/wasm')
-rw-r--r-- | src/wasm/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 10 | ||||
-rw-r--r-- | src/wasm/wasm-emscripten.cpp | 664 | ||||
-rw-r--r-- | src/wasm/wasm-interpreter.cpp | 24 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 6 |
5 files changed, 701 insertions, 5 deletions
diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index 1a8a9b8ba..da876b56f 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -2,6 +2,8 @@ SET(wasm_SOURCES literal.cpp wasm.cpp wasm-binary.cpp + wasm-emscripten.cpp + wasm-interpreter.cpp wasm-io.cpp wasm-s-parser.cpp wasm-type.cpp diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 56b35b712..69e939c44 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1730,11 +1730,11 @@ void WasmBinaryBuilder::readFunctions() { } } auto func = Builder(wasm).makeFunction( - Name::fromInt(i), - std::move(params), - type->result, - std::move(vars) - ); + Name::fromInt(i), + std::move(params), + type->result, + std::move(vars) + ); func->type = type->name; currFunction = func; { diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp new file mode 100644 index 000000000..9a393db43 --- /dev/null +++ b/src/wasm/wasm-emscripten.cpp @@ -0,0 +1,664 @@ +/* + * 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 diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp new file mode 100644 index 000000000..e7df785ac --- /dev/null +++ b/src/wasm/wasm-interpreter.cpp @@ -0,0 +1,24 @@ +#include "wasm-interpreter.h" + +namespace wasm { + +#ifdef WASM_INTERPRETER_DEBUG +int Indenter::indentLevel = 0; + +Indenter::Indenter(const char* entry) : entryName(entry) { + ++indentLevel; +} +Indenter::~Indenter() { + print(); + std::cout << "exit " << entryName << '\n'; + --indentLevel; +} +void Indenter::print() { + std::cout << indentLevel << ':'; + for (int i = 0; i <= indentLevel; ++i) { + std::cout << ' '; + } +} +#endif // WASM_INTERPRETER_DEBUG + +} // namespace wasm diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 8ff2fb9b8..d6c6e7bd0 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -806,6 +806,12 @@ void FunctionValidator::visitFunction(Function* curr) { shouldBeTrue(breakTargets.empty(), curr->body, "all named break targets must exist"); returnType = unreachable; labelNames.clear(); + // if function has a named type, it must match up with the function's params and result + if (info.validateGlobally && curr->type.is()) { + auto* ft = getModule()->getFunctionType(curr->type); + shouldBeTrue(ft->params == curr->params, curr->name, "function params must match its declared type"); + shouldBeTrue(ft->result == curr->result, curr->name, "function result must match its declared type"); + } // expressions must not be seen more than once struct Walker : public PostWalker<Walker, UnifiedExpressionVisitor<Walker>> { std::unordered_set<Expression*>& seen; |