diff options
Diffstat (limited to 'src')
32 files changed, 330 insertions, 267 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index c554b0a23..71cd92da4 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1616,6 +1616,15 @@ double BinaryenConstGetValueF64(BinaryenExpressionRef expr) { assert(expression->is<Const>()); return static_cast<Const*>(expression)->value.getf64(); } +void BinaryenConstGetValueV128(BinaryenExpressionRef expr, uint8_t* out) { + if (tracing) { + std::cout << " BinaryenConstGetValueV128(expressions[" << expressions[expr] << "], " << out << ");\n"; + } + + auto* expression = (Expression*)expr; + assert(expression->is<Const>()); + memcpy(out, static_cast<Const*>(expression)->value.getv128().data(), 16); +} // Unary BinaryenOp BinaryenUnaryGetOp(BinaryenExpressionRef expr) { if (tracing) { @@ -1945,7 +1954,7 @@ BinaryenExpressionRef BinaryenSIMDShuffleGetRight(BinaryenExpressionRef expr) { } void BinaryenSIMDShuffleGetMask(BinaryenExpressionRef expr, uint8_t *mask) { if (tracing) { - std::cout << " BinaryenSIMDShuffleGetMask(expressions[" << expressions[expr] << "]);\n"; + std::cout << " BinaryenSIMDShuffleGetMask(expressions[" << expressions[expr] << "], " << mask << ");\n"; } auto* expression = (Expression*)expr; @@ -3067,6 +3076,12 @@ size_t BinaryenSizeofLiteral(void) { return sizeof(Literal); } +// Returns the size of an allocate and write result object. +EMSCRIPTEN_KEEPALIVE +size_t BinaryenSizeofAllocateAndWriteResult(void) { + return sizeof(BinaryenModuleAllocateAndWriteResult); +} + #endif } // extern "C" diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 5355ceeeb..787bfb242 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -618,6 +618,7 @@ int32_t BinaryenConstGetValueI64Low(BinaryenExpressionRef expr); int32_t BinaryenConstGetValueI64High(BinaryenExpressionRef expr); float BinaryenConstGetValueF32(BinaryenExpressionRef expr); double BinaryenConstGetValueF64(BinaryenExpressionRef expr); +void BinaryenConstGetValueV128(BinaryenExpressionRef expr, uint8_t* out); BinaryenOp BinaryenUnaryGetOp(BinaryenExpressionRef expr); BinaryenExpressionRef BinaryenUnaryGetValue(BinaryenExpressionRef expr); diff --git a/src/ir/block-utils.h b/src/ir/block-utils.h index e3ad370db..968332212 100644 --- a/src/ir/block-utils.h +++ b/src/ir/block-utils.h @@ -59,7 +59,7 @@ namespace BlockUtils { inline Expression* simplifyToContentsWithPossibleTypeChange(Block* block, T* parent) { return simplifyToContents(block, parent, true); } -}; +} } // namespace wasm diff --git a/src/ir/features.h b/src/ir/features.h index ed7fb6ff5..505e239a5 100644 --- a/src/ir/features.h +++ b/src/ir/features.h @@ -76,6 +76,14 @@ inline FeatureSet get(UnaryOp op) { ret.setSIMD(); break; } + case ExtendS8Int32: + case ExtendS16Int32: + case ExtendS8Int64: + case ExtendS16Int64: + case ExtendS32Int64: { + ret.setSignExt(); + break; + } default: {} } return ret; diff --git a/src/ir/global-utils.h b/src/ir/global-utils.h index 02bbbf2d2..fa4cdc44a 100644 --- a/src/ir/global-utils.h +++ b/src/ir/global-utils.h @@ -48,7 +48,7 @@ namespace GlobalUtils { }); return ret; } -}; +} } // namespace wasm diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 1eef5757e..aee41255c 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -365,6 +365,13 @@ Module['ConvertUVecI32x4ToVecF32x4'] = Module['_BinaryenConvertUVecI32x4ToVecF32 Module['ConvertSVecI64x2ToVecF64x2'] = Module['_BinaryenConvertSVecI64x2ToVecF64x2'](); Module['ConvertUVecI64x2ToVecF64x2'] = Module['_BinaryenConvertUVecI64x2ToVecF64x2'](); +// The size of a single literal in memory as used in Const creation, +// which is a little different: we don't want users to need to make +// their own Literals, as the C API handles them by value, which means +// we would leak them. Instead, Const creation is fused together with +// an intermediate stack allocation of this size to pass the value. +var sizeOfLiteral = _BinaryenSizeofLiteral(); + // 'Module' interface Module['Module'] = function(module) { assert(!module); // guard against incorrect old API usage @@ -482,13 +489,6 @@ function wrapModule(module, self) { } } - // The Const creation API is a little different: we don't want users to - // need to make their own Literals, as the C API handles them by value, - // which means we would leak them. Instead, this is the only API that - // accepts Literals, so fuse it with Literal creation - var temp = _malloc(Module['_BinaryenSizeofLiteral']()); // a single literal in memory. the LLVM C ABI - // makes us pass pointers to this. - self['i32'] = { 'load': function(offset, align, ptr) { return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i32'], ptr); @@ -515,8 +515,11 @@ function wrapModule(module, self) { return Module['_BinaryenStore'](module, 2, offset, align, ptr, value, Module['i32']); }, 'const': function(x) { - Module['_BinaryenLiteralInt32'](temp, x); - return Module['_BinaryenConst'](module, temp); + return preserveStack(function() { + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralInt32'](tempLiteral, x); + return Module['_BinaryenConst'](module, tempLiteral); + }); }, 'clz': function(value) { return Module['_BinaryenUnary'](module, Module['ClzInt32'], value); @@ -778,8 +781,11 @@ function wrapModule(module, self) { return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['i64']); }, 'const': function(x, y) { - Module['_BinaryenLiteralInt64'](temp, x, y); - return Module['_BinaryenConst'](module, temp); + return preserveStack(function() { + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralInt64'](tempLiteral, x, y); + return Module['_BinaryenConst'](module, tempLiteral); + }); }, 'clz': function(value) { return Module['_BinaryenUnary'](module, Module['ClzInt64'], value); @@ -1049,12 +1055,18 @@ function wrapModule(module, self) { return Module['_BinaryenStore'](module, 4, offset, align, ptr, value, Module['f32']); }, 'const': function(x) { - Module['_BinaryenLiteralFloat32'](temp, x); - return Module['_BinaryenConst'](module, temp); + return preserveStack(function() { + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralFloat32'](tempLiteral, x); + return Module['_BinaryenConst'](module, tempLiteral); + }); }, 'const_bits': function(x) { - Module['_BinaryenLiteralFloat32Bits'](temp, x); - return Module['_BinaryenConst'](module, temp); + return preserveStack(function() { + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralFloat32Bits'](tempLiteral, x); + return Module['_BinaryenConst'](module, tempLiteral); + }); }, 'neg': function(value) { return Module['_BinaryenUnary'](module, Module['NegFloat32'], value); @@ -1148,12 +1160,18 @@ function wrapModule(module, self) { return Module['_BinaryenStore'](module, 8, offset, align, ptr, value, Module['f64']); }, 'const': function(x) { - Module['_BinaryenLiteralFloat64'](temp, x); - return Module['_BinaryenConst'](module, temp); + return preserveStack(function() { + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralFloat64'](tempLiteral, x); + return Module['_BinaryenConst'](module, tempLiteral); + }); }, 'const_bits': function(x, y) { - Module['_BinaryenLiteralFloat64Bits'](temp, x, y); - return Module['_BinaryenConst'](module, temp); + return preserveStack(function() { + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralFloat64Bits'](tempLiteral, x, y); + return Module['_BinaryenConst'](module, tempLiteral); + }); }, 'neg': function(value) { return Module['_BinaryenUnary'](module, Module['NegFloat64'], value); @@ -1248,8 +1266,9 @@ function wrapModule(module, self) { }, 'const': function(i8s) { return preserveStack(function() { - Module['_BinaryenLiteralVec128'](temp, i8sToStack(i8s)); - return Module['_BinaryenConst'](module, temp); + var tempLiteral = stackAlloc(sizeOfLiteral); + Module['_BinaryenLiteralVec128'](tempLiteral, i8sToStack(i8s)); + return Module['_BinaryenConst'](module, tempLiteral); }); }, 'not': function(value) { @@ -1729,7 +1748,7 @@ function wrapModule(module, self) { }); }; self['removeFunctionType'] = function(name) { - return preserveStack(function () { + return preserveStack(function() { return Module['_BinaryenRemoveFunctionType'](module, strToStack(name)); }); }; @@ -1754,7 +1773,7 @@ function wrapModule(module, self) { }); } self['removeGlobal'] = function(name) { - return preserveStack(function () { + return preserveStack(function() { return Module['_BinaryenRemoveGlobal'](module, strToStack(name)); }); } @@ -1900,10 +1919,11 @@ function wrapModule(module, self) { }; self['emitBinary'] = function(sourceMapUrl) { return preserveStack(function() { - Module['_BinaryenModuleAllocateAndWrite'](temp, module, strToStack(sourceMapUrl)); - var binaryPtr = HEAPU32[ temp >>> 2 ]; - var binaryBytes = HEAPU32[(temp >>> 2) + 1]; - var sourceMapPtr = HEAPU32[(temp >>> 2) + 2]; + var tempBuffer = stackAlloc(_BinaryenSizeofAllocateAndWriteResult()); + Module['_BinaryenModuleAllocateAndWrite'](tempBuffer, module, strToStack(sourceMapUrl)); + var binaryPtr = HEAPU32[ tempBuffer >>> 2 ]; + var binaryBytes = HEAPU32[(tempBuffer >>> 2) + 1]; + var sourceMapPtr = HEAPU32[(tempBuffer >>> 2) + 2]; try { var buffer = new Uint8Array(binaryBytes); buffer.set(HEAPU8.subarray(binaryPtr, binaryPtr + binaryBytes)); @@ -2090,7 +2110,18 @@ Module['getExpressionInfo'] = function(expr) { case Module['i32']: value = Module['_BinaryenConstGetValueI32'](expr); break; case Module['i64']: value = { 'low': Module['_BinaryenConstGetValueI64Low'](expr), 'high': Module['_BinaryenConstGetValueI64High'](expr) }; break; case Module['f32']: value = Module['_BinaryenConstGetValueF32'](expr); break; - case Module['f64']: value = Module['_BinaryenConstGetValueF64'](expr); break; + case Module['f64']: value = Module['_BinaryenConstGetValueF64'](expr); break; + case Module['v128']: { + preserveStack(function() { + var tempBuffer = stackAlloc(16); + Module['_BinaryenConstGetValueV128'](expr, tempBuffer); + value = new Array(16); + for (var i = 0 ; i < 16; i++) { + value[i] = HEAPU8[tempBuffer + i]; + } + }); + break; + } default: throw Error('unexpected type: ' + type); } return { @@ -2203,11 +2234,11 @@ Module['getExpressionInfo'] = function(expr) { }; case Module['SIMDShuffleId']: return preserveStack(function() { - var ret = stackAlloc(16); - Module['_BinaryenSIMDShuffleGetMask'](expr, ret); - var mask = []; + var tempBuffer = stackAlloc(16); + Module['_BinaryenSIMDShuffleGetMask'](expr, tempBuffer); + var mask = new Array(16); for (var i = 0 ; i < 16; i++) { - mask[i] = HEAP8[ret + i]; + mask[i] = HEAPU8[tempBuffer + i]; } return { 'id': id, diff --git a/src/parsing.h b/src/parsing.h index 6eecb38f2..97fc88432 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -40,7 +40,7 @@ struct ParseException { ParseException(std::string text) : text(text), line(-1), col(-1) {} ParseException(std::string text, size_t line, size_t col) : text(text), line(line), col(col) {} - void dump(std::ostream& o) { + void dump(std::ostream& o) const { Colors::magenta(o); o << "["; Colors::red(o); @@ -63,7 +63,7 @@ struct MapParseException { MapParseException() : text("unknown parse error") {} MapParseException(std::string text) : text(text) {} - void dump(std::ostream& o) { + void dump(std::ostream& o) const { Colors::magenta(o); o << "["; Colors::red(o); diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 2835744d3..621383ca4 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -275,7 +275,7 @@ std::vector<Index> adjustOrderByPriorities(std::vector<Index>& baseline, std::ve return priorities[x] > priorities[y] || (priorities[x] == priorities[y] && reversed[x] < reversed[y]); }); return ret; -}; +} void CoalesceLocals::pickIndices(std::vector<Index>& indices) { if (numLocals == 0) return; diff --git a/src/passes/Metrics.cpp b/src/passes/Metrics.cpp index b794ea32d..8717a86b7 100644 --- a/src/passes/Metrics.cpp +++ b/src/passes/Metrics.cpp @@ -66,6 +66,7 @@ struct Metrics : public WalkerPass<PostWalker<Metrics, UnifiedExpressionVisitor< counts["[imports]"] = imports.getNumImports(); // add functions counts["[funcs]"] = imports.getNumDefinedFunctions(); + counts["[exports]"] = module->exports.size(); // add memory and table if (module->memory.exists) { Index size = 0; @@ -172,7 +173,7 @@ struct Metrics : public WalkerPass<PostWalker<Metrics, UnifiedExpressionVisitor< o << title << "\n"; for (auto* key : keys) { auto value = counts[key]; - if (value == 0) continue; + if (value == 0 && key[0] != '[') continue; o << " " << left << setw(15) << key << ": " << setw(8) << value; if (lastCounts.count(key)) { diff --git a/src/passes/Strip.cpp b/src/passes/Strip.cpp index 8ab712cb1..40e5a9e9d 100644 --- a/src/passes/Strip.cpp +++ b/src/passes/Strip.cpp @@ -47,11 +47,15 @@ struct Strip : public Pass { ), sections.end() ); - // Clean up internal data structures. - module->clearDebugInfo(); - for (auto& func : module->functions) { - func->clearNames(); - func->clearDebugInfo(); + // If we're cleaning up debug info, clear on the function and module too. + UserSection temp; + temp.name = BinaryConsts::UserSections::Name; + if (decider(temp)) { + module->clearDebugInfo(); + for (auto& func : module->functions) { + func->clearNames(); + func->clearDebugInfo(); + } } } }; @@ -71,4 +75,9 @@ Pass *createStripProducersPass() { }); } +Pass *createStripTargetFeaturesPass() { + return new Strip([&](const UserSection& curr) { + return curr.name == BinaryConsts::UserSections::TargetFeatures; + }); +} } // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 3712bf19e..5d8b8d2c8 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -138,6 +138,7 @@ void PassRegistry::registerPasses() { registerPass("strip", "deprecated; same as strip-debug", createStripDebugPass); registerPass("strip-debug", "strip debug info (including the names section)", createStripDebugPass); registerPass("strip-producers", "strip the wasm producers section", createStripProducersPass); + registerPass("strip-target-features", "strip the wasm target features section", createStripTargetFeaturesPass); registerPass("trap-mode-clamp", "replace trapping operations with clamping semantics", createTrapModeClamp); registerPass("trap-mode-js", "replace trapping operations with js semantics", createTrapModeJS); registerPass("untee", "removes local.tees, replacing them with sets and gets", createUnteePass); diff --git a/src/passes/passes.h b/src/passes/passes.h index ab11721fb..ac7126bd4 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -88,6 +88,7 @@ Pass* createSimplifyLocalsNoStructurePass(); Pass* createSimplifyLocalsNoTeeNoStructurePass(); Pass* createStripDebugPass(); Pass* createStripProducersPass(); +Pass* createStripTargetFeaturesPass(); Pass* createSouperifyPass(); Pass* createSouperifySingleUsePass(); Pass* createSpillPointersPass(); diff --git a/src/support/colors.h b/src/support/colors.h index fb5267ce1..6761639d9 100644 --- a/src/support/colors.h +++ b/src/support/colors.h @@ -52,6 +52,6 @@ inline void green(std::ostream& stream) {} inline void blue(std::ostream& stream) {} inline void bold(std::ostream& stream) {} #endif -}; +} #endif // wasm_support_color_h diff --git a/src/support/file.cpp b/src/support/file.cpp index 19401b21d..2fe636cfd 100644 --- a/src/support/file.cpp +++ b/src/support/file.cpp @@ -21,6 +21,17 @@ #include <cstdint> #include <limits> +std::vector<char> wasm::read_stdin(Flags::DebugOption debug) { + if (debug == Flags::Debug) std::cerr << "Loading stdin..." << std::endl; + std::vector<char> input; + char c; + while (std::cin.get(c) && !std::cin.eof()) { + input.push_back(c); + } + return input; +} + + template<typename T> T wasm::read_file(const std::string& filename, Flags::BinaryOption binary, Flags::DebugOption debug) { if (debug == Flags::Debug) std::cerr << "Loading '" << filename << "'..." << std::endl; @@ -84,4 +95,3 @@ size_t wasm::file_size(std::string filename) { std::ifstream infile(filename, std::ifstream::ate | std::ifstream::binary); return infile.tellg(); } - diff --git a/src/support/file.h b/src/support/file.h index e94d23fad..cb9c82ca9 100644 --- a/src/support/file.h +++ b/src/support/file.h @@ -39,6 +39,8 @@ namespace Flags { }; } +std::vector<char> read_stdin(Flags::DebugOption); + template<typename T> T read_file(const std::string& filename, Flags::BinaryOption binary, Flags::DebugOption debug); // Declare the valid explicit specializations. diff --git a/src/tools/spec-wrapper.h b/src/tools/spec-wrapper.h index a42230fc1..77db8a0f4 100644 --- a/src/tools/spec-wrapper.h +++ b/src/tools/spec-wrapper.h @@ -34,7 +34,7 @@ static std::string generateSpecWrapper(Module& wasm) { case i64: ret += "(i64.const 0)"; break; case f32: ret += "(f32.const 0)"; break; case f64: ret += "(f64.const 0)"; break; - case v128: ret += "(v128.const i32 0 0 0 0)"; break; + case v128: ret += "(v128.const i32x4 0 0 0 0)"; break; case none: case unreachable: WASM_UNREACHABLE(); } diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 5620883ec..671085a4e 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -39,6 +39,16 @@ struct ToolOptions : public Options { [this](Options *o, const std::string& arguments) { passOptions.features = FeatureSet::All; }) + .add("--enable-sign-ext", "", "Enable sign extension operations", + Options::Arguments::Zero, + [this](Options *o, const std::string& arguments) { + passOptions.features.setSignExt(); + }) + .add("--disable-sign-ext", "", "Disable sign extension operations", + Options::Arguments::Zero, + [this](Options *o, const std::string& arguments) { + passOptions.features.setSignExt(false); + }) .add("--enable-threads", "", "Enable atomic operations", Options::Arguments::Zero, [this](Options *o, const std::string& arguments) { diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index 42fc1e4db..2aa5de46f 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -47,7 +47,6 @@ int main(int argc, const char *argv[]) { bool emitBinary = true; bool debugInfo = false; bool legalizeJavaScriptFFI = true; - unsigned numReservedFunctionPointers = 0; uint64_t globalBase = INVALID_BASE; uint64_t initialStackPointer = INVALID_BASE; Options options("wasm-emscripten-finalize", @@ -70,14 +69,6 @@ int main(int argc, const char *argv[]) { [&emitBinary](Options*, const std::string& ) { emitBinary = false; }) - .add("--emscripten-reserved-function-pointers", "", - "Number of reserved function pointers for emscripten addFunction " - "support", - Options::Arguments::One, - [&numReservedFunctionPointers](Options *, - const std::string &argument) { - numReservedFunctionPointers = std::stoi(argument); - }) .add("--global-base", "", "The address at which static globals were placed", Options::Arguments::One, [&globalBase](Options*, const std::string&argument ) { @@ -203,7 +194,6 @@ int main(int argc, const char *argv[]) { } generator.generateDynCallThunks(); - generator.generateJSCallThunks(numReservedFunctionPointers); // Legalize the wasm. { @@ -214,11 +204,12 @@ int main(int argc, const char *argv[]) { legalizeJavaScriptFFI ? ABI::LegalizationLevel::Full : ABI::LegalizationLevel::Minimal )); + passRunner.add("strip-target-features"); passRunner.run(); } // Substantial changes to the wasm are done, enough to create the metadata. - std::string metadata = generator.generateEmscriptenMetadata(dataSize, initializerFunctions, numReservedFunctionPointers); + std::string metadata = generator.generateEmscriptenMetadata(dataSize, initializerFunctions); // Finally, separate out data segments if relevant (they may have been needed // for metadata). diff --git a/src/wasm-binary.h b/src/wasm-binary.h index a875db791..32166af00 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -345,6 +345,7 @@ extern const char* SourceMapUrl; extern const char* Dylink; extern const char* Linking; extern const char* Producers; +extern const char* TargetFeatures; enum Subsection { NameFunction = 1, diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h index 275c809fd..acb2994ad 100644 --- a/src/wasm-emscripten.h +++ b/src/wasm-emscripten.h @@ -46,14 +46,8 @@ public: // and restore functions. void replaceStackPointerGlobal(); - // Create thunks to support emscripten's addFunction functionality. Creates (# - // of reserved function pointers) thunks for each indirectly called function - // signature. - void generateJSCallThunks(unsigned numReservedFunctionPointers); - std::string generateEmscriptenMetadata( - Address staticBump, std::vector<Name> const& initializerFunctions, - unsigned numReservedFunctionPointers); + Address staticBump, std::vector<Name> const& initializerFunctions); void fixInvokeFunctionNames(); diff --git a/src/wasm-features.h b/src/wasm-features.h new file mode 100644 index 000000000..4dd806e28 --- /dev/null +++ b/src/wasm-features.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019 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. + */ + +#ifndef wasm_features_h +#define wasm_features_h + +#include <stdint.h> + +struct FeatureSet { + enum Feature : uint32_t { + MVP = 0, + Atomics = 1 << 0, + MutableGlobals = 1 << 1, + TruncSat = 1 << 2, + SIMD = 1 << 3, + BulkMemory = 1 << 4, + SignExt = 1 << 5, + All = Atomics | MutableGlobals | TruncSat | SIMD | BulkMemory | SignExt + }; + + FeatureSet() : features(MVP) {} + FeatureSet(uint32_t features) : features(features) {} + + bool isMVP() const { return features == MVP; } + bool has(Feature f) { return (features & f) == f; } + bool hasAtomics() const { return features & Atomics; } + bool hasMutableGlobals() const { return features & MutableGlobals; } + bool hasTruncSat() const { return features & TruncSat; } + bool hasSIMD() const { return features & SIMD; } + bool hasBulkMemory() const { return features & BulkMemory; } + bool hasSignExt() const { return features & SignExt; } + bool hasAll() const { return features & All; } + + void makeMVP() { features = MVP; } + void set(Feature f, bool v = true) { features = v ? (features | f) : (features & ~f); } + void setAtomics(bool v = true) { set(Atomics, v); } + void setMutableGlobals(bool v = true) { set(MutableGlobals, v); } + void setTruncSat(bool v = true) { set(TruncSat, v); } + void setSIMD(bool v = true) { set(SIMD, v); } + void setBulkMemory(bool v = true) { set(BulkMemory, v); } + void setSignExt(bool v = true) { set(SignExt, v); } + void setAll(bool v = true) { features = v ? All : MVP; } + + bool operator<=(const FeatureSet& other) { + return !(features & ~other.features); + } + + FeatureSet& operator|=(const FeatureSet& other) { + features |= other.features; + return *this; + } + +private: + uint32_t features; +}; + +#endif // wasm_features_h diff --git a/src/wasm-io.h b/src/wasm-io.h index 2dfdbae6c..6d8dcc6e5 100644 --- a/src/wasm-io.h +++ b/src/wasm-io.h @@ -42,11 +42,15 @@ public: // read binary void readBinary(std::string filename, Module& wasm, std::string sourceMapFilename=""); - // read text or binary, checking the contents for what it is + // read text or binary, checking the contents for what it is. If `filename` is + // empty, read from stdin. void read(std::string filename, Module& wasm, std::string sourceMapFilename=""); // check whether a file is a wasm binary bool isBinaryFile(std::string filename); + +private: + void readStdin(Module& wasm, std::string sourceMapFilename); }; class ModuleWriter : public ModuleIO { diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index f0ed79409..07de235a2 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -152,6 +152,7 @@ private: return stringToType(str.str, allowError, prefix); } Type stringToType(const char* str, bool allowError=false, bool prefix=false); + Type stringToLaneType(const char* str); bool isType(cashew::IString str) { return stringToType(str, true) != none; } diff --git a/src/wasm-type.h b/src/wasm-type.h index 0b99fa53a..60253f6ab 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -17,6 +17,8 @@ #ifndef wasm_wasm_type_h #define wasm_wasm_type_h +#include "wasm-features.h" + namespace wasm { enum Type { @@ -33,6 +35,7 @@ enum Type { const char* printType(Type type); unsigned getTypeSize(Type type); +FeatureSet getFeatures(Type type); Type getType(unsigned size, bool float_); Type getReachableType(Type a, Type b); bool isConcreteType(Type type); diff --git a/src/wasm.h b/src/wasm.h index a16dab478..ab9d7c816 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -35,49 +35,10 @@ #include "mixed_arena.h" #include "support/name.h" #include "wasm-type.h" +#include "wasm-features.h" namespace wasm { -struct FeatureSet { - enum Feature : uint32_t { - MVP = 0, - Atomics = 1 << 0, - MutableGlobals = 1 << 1, - TruncSat = 1 << 2, - SIMD = 1 << 3, - BulkMemory = 1 << 4, - All = Atomics | MutableGlobals | TruncSat | SIMD | BulkMemory - }; - - FeatureSet() : features(MVP) {} - FeatureSet(uint32_t features) : features(features) {} - - bool isMVP() const { return features == MVP; } - bool has(Feature f) { return (features & f) == f; } - bool hasAtomics() const { return features & Atomics; } - bool hasMutableGlobals() const { return features & MutableGlobals; } - bool hasTruncSat() const { return features & TruncSat; } - bool hasSIMD() const { return features & SIMD; } - bool hasBulkMemory() const { return features & BulkMemory; } - bool hasAll() const { return features & All; } - - void makeMVP() { features = MVP; } - void set(Feature f, bool v = true) { features = v ? (features | f) : (features & ~f); } - void setAtomics(bool v = true) { set(Atomics, v); } - void setMutableGlobals(bool v = true) { set(MutableGlobals, v); } - void setTruncSat(bool v = true) { set(TruncSat, v); } - void setSIMD(bool v = true) { set(SIMD, v); } - void setBulkMemory(bool v = true) { set(BulkMemory, v); } - void setAll(bool v = true) { features = v ? All : MVP; } - - bool operator<=(const FeatureSet& other) { - return !(features & ~other.features); - } - -private: - uint32_t features; -}; - // An index in a wasm module typedef uint32_t Index; @@ -114,7 +75,6 @@ enum UnaryOp { PromoteFloat32, // f32 to f64 DemoteFloat64, // f64 to f32 ReinterpretInt32, ReinterpretInt64, // reinterpret bits to float - // The following sign-extention operators go along with wasm atomics support. // Extend signed subword-sized integer. This differs from e.g. ExtendSInt32 // because the input integer is in an i64 value insetad of an i32 value. ExtendS8Int32, ExtendS16Int32, ExtendS8Int64, ExtendS16Int64, ExtendS32Int64, @@ -268,7 +228,7 @@ public: void finalize() {} template<class T> - bool is() { + bool is() const { return int(_id) == int(T::SpecificId); } @@ -277,11 +237,22 @@ public: return int(_id) == int(T::SpecificId) ? (T*)this : nullptr; } + template <class T> + const T* dynCast() const { + return int(_id) == int(T::SpecificId) ? (const T*)this : nullptr; + } + template<class T> T* cast() { assert(int(_id) == int(T::SpecificId)); return (T*)this; } + + template<class T> + const T* cast() const { + assert(int(_id) == int(T::SpecificId)); + return (const T*)this; + } }; const char* getExpressionName(Expression* curr); @@ -956,7 +927,7 @@ private: std::map<Name, Global*> globalsMap; public: - Module() = default;; + Module() = default; FunctionType* getFunctionType(Name name); Export* getExport(Name name); diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index bc45f5834..b374566d1 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -243,7 +243,7 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { case Type::i64: o << literal.i64; break; case Type::f32: literal.printFloat(o, literal.getf32()); break; case Type::f64: literal.printDouble(o, literal.getf64()); break; - case Type::v128: o << "i32 "; literal.printVec128(o, literal.getv128()); break; + case Type::v128: o << "i32x4 "; literal.printVec128(o, literal.getv128()); break; case Type::unreachable: WASM_UNREACHABLE(); } restoreNormalColor(o); diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index 390266d44..ea24b945d 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -47,10 +47,15 @@ void addExportedFunction(Module& wasm, Function* function) { } Global* EmscriptenGlueGenerator::getStackPointerGlobal() { - // Assumption: first global is __stack_pointer - // TODO(sbc): Once mutable globals are a thing we shouldn't need this - // at all since we can simply export __stack_pointer. - return wasm.globals[0].get(); + // Assumption: The first non-imported global is global is __stack_pointer + // 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()) { + return g.get(); + } + } + Fatal() << "stack pointer global not found"; } Expression* EmscriptenGlueGenerator::generateLoadStackPointer() { @@ -157,6 +162,9 @@ Function* EmscriptenGlueGenerator::generateMemoryGrowthFunction() { void EmscriptenGlueGenerator::generateStackInitialization(Address addr) { auto* stackPointer = getStackPointerGlobal(); + assert(!stackPointer->imported()); + if (!stackPointer->init || !stackPointer->init->is<Const>()) + Fatal() << "stack pointer global is not assignable"; stackPointer->init->cast<Const>()->value = Literal(int32_t(addr)); } @@ -270,109 +278,6 @@ void EmscriptenGlueGenerator::replaceStackPointerGlobal() { wasm.removeGlobal(stackPointer->name); } -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; - - jsCallStartIndex = - wasm.table.segments[0].offset->cast<Const>()->value.getInteger(); - // Check if jsCalls have already been created - for (Index i = 0; i < tableSegmentData.size(); ++i) { - if (tableSegmentData[i].startsWith("jsCall_")) { - jsCallStartIndex += i; - return; - } - } - jsCallStartIndex += 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; - unsigned numEntriesAdded = 0; - 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 Function; - import->name = import->base = "jsCall_" + sig; - import->module = ENV; - import->type = importType->name; - FunctionTypeUtils::fillFunction(import, importType); - wasm.addFunction(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.makeCall(import->name, args, funcType->result); - f->body = call; - wasm.addFunction(f); - tableSegmentData.push_back(f->name); - numEntriesAdded++; - } - } - wasm.table.initial.addr += numEntriesAdded; - if (wasm.table.max != Table::kUnlimitedSize) { - wasm.table.max.addr += numEntriesAdded; - } -} - std::vector<Address> getSegmentOffsets(Module& wasm) { std::vector<Address> segmentOffsets; for (unsigned i = 0; i < wasm.memory.segments.size(); ++i) { @@ -780,8 +685,7 @@ void printSet(std::ostream& o, C& c) { } std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( - Address staticBump, std::vector<Name> const& initializerFunctions, - unsigned numReservedFunctionPointers) { + Address staticBump, std::vector<Name> const& initializerFunctions) { bool commaFirst; auto nextElement = [&commaFirst]() { if (commaFirst) { @@ -844,18 +748,6 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( meta << "\n ],\n"; } - if (numReservedFunctionPointers) { - JSCallWalker jsCallWalker = getJSCallWalker(wasm); - meta << " \"jsCallStartIndex\": " << jsCallWalker.jsCallStartIndex << ",\n"; - meta << " \"jsCallFuncType\": ["; - commaFirst = true; - for (std::string sig : jsCallWalker.indirectlyCallableSigs) { - meta << nextElement(); - meta << "\"" << sig << "\""; - } - 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. @@ -870,8 +762,7 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { if (emJsWalker.codeByName.count(import->base.str) == 0 && !import->base.startsWith(EMSCRIPTEN_ASM_CONST.str) && - !import->base.startsWith("invoke_") && - !import->base.startsWith("jsCall_")) { + !import->base.startsWith("invoke_")) { if (declares.insert(import->base.str).second) { meta << nextElement() << '"' << import->base.str << '"'; } diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index 95ffe6e89..057798d2e 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -30,18 +30,21 @@ namespace wasm { -void ModuleReader::readText(std::string filename, Module& wasm) { - if (debug) std::cerr << "reading text from " << filename << "\n"; - auto input(read_file<std::string>(filename, Flags::Text, debug ? Flags::Debug : Flags::Release)); +static void readTextData(std::string& input, Module& wasm) { SExpressionParser parser(const_cast<char*>(input.c_str())); Element& root = *parser.root; SExpressionWasmBuilder builder(wasm, *root[0]); + } -void ModuleReader::readBinary(std::string filename, Module& wasm, - std::string sourceMapFilename) { - if (debug) std::cerr << "reading binary from " << filename << "\n"; - auto input(read_file<std::vector<char>>(filename, Flags::Binary, debug ? Flags::Debug : Flags::Release)); +void ModuleReader::readText(std::string filename, Module& wasm) { + if (debug) std::cerr << "reading text from " << filename << "\n"; + auto input(read_file<std::string>(filename, Flags::Text, debug ? Flags::Debug : Flags::Release)); + readTextData(input, wasm); +} + +static void readBinaryData(std::vector<char>& input, Module& wasm, + std::string sourceMapFilename, bool debug) { std::unique_ptr<std::ifstream> sourceMapStream; WasmBinaryBuilder parser(wasm, input, debug); if (sourceMapFilename.size()) { @@ -55,6 +58,13 @@ void ModuleReader::readBinary(std::string filename, Module& wasm, } } +void ModuleReader::readBinary(std::string filename, Module& wasm, + std::string sourceMapFilename) { + if (debug) std::cerr << "reading binary from " << filename << "\n"; + auto input(read_file<std::vector<char>>(filename, Flags::Binary, debug ? Flags::Debug : Flags::Release)); + readBinaryData(input, wasm, sourceMapFilename, debug); +} + bool ModuleReader::isBinaryFile(std::string filename) { std::ifstream infile; std::ios_base::openmode flags = std::ifstream::in | std::ifstream::binary; @@ -67,6 +77,11 @@ bool ModuleReader::isBinaryFile(std::string filename) { void ModuleReader::read(std::string filename, Module& wasm, std::string sourceMapFilename) { + // empty filename means read from stdin + if (!filename.size()) { + readStdin(wasm, sourceMapFilename); + return; + } if (isBinaryFile(filename)) { readBinary(filename, wasm, sourceMapFilename); } else { @@ -78,6 +93,23 @@ void ModuleReader::read(std::string filename, Module& wasm, } } +// TODO: reading into a vector<char> then copying into a string is unnecessarily +// inefficient. It would be better to read just once into a stringstream. +void ModuleReader::readStdin(Module& wasm, std::string sourceMapFilename) { + std::vector<char> input = read_stdin(debug ? Flags::Debug : Flags::Release); + if (input.size() >= 4 && input[0] == '\0' && input[1] == 'a' && + input[2] == 's' && input[3] == 'm') { + readBinaryData(input, wasm, sourceMapFilename, debug); + } else { + std::ostringstream s; + s.write(input.data(), input.size()); + s << '\0'; + std::string input_str = s.str(); + readTextData(input_str, wasm); + } +} + + void ModuleWriter::writeText(Module& wasm, Output& output) { WasmPrinter::printModule(&wasm, output.getStream()); } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 7fd4679b1..1067264f7 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -641,6 +641,16 @@ Type SExpressionWasmBuilder::stringToType(const char* str, bool allowError, bool throw ParseException("invalid wasm type"); } +Type SExpressionWasmBuilder::stringToLaneType(const char* str) { + if (strcmp(str, "i8x16") == 0) return i32; + if (strcmp(str, "i16x8") == 0) return i32; + if (strcmp(str, "i32x4") == 0) return i32; + if (strcmp(str, "i64x2") == 0) return i64; + if (strcmp(str, "f32x4") == 0) return f32; + if (strcmp(str, "f64x2") == 0) return f64; + return none; +} + Function::DebugLocation SExpressionWasmBuilder::getDebugLocation(const SourceLocation& loc) { IString file = loc.filename; auto& debugInfoFileNames = wasm.debugInfoFileNames; @@ -864,6 +874,20 @@ Expression* SExpressionWasmBuilder::makeThenOrElse(Element& s) { return ret; } +template<int Lanes> +static Literal makeLanes(Element& s, MixedArena& allocator, Type lane_t) { + std::array<Literal, Lanes> lanes; + for (size_t i = 0; i < Lanes; ++i) { + Expression* lane = parseConst(s[i+2]->str(), lane_t, allocator); + if (lane) { + lanes[i] = lane->cast<Const>()->value; + } else { + throw ParseException("Could not parse v128 lane"); + } + } + return Literal(lanes); +} + Expression* SExpressionWasmBuilder::makeConst(Element& s, Type type) { if (type != v128) { auto ret = parseConst(s[1]->str(), type, allocator); @@ -872,57 +896,35 @@ Expression* SExpressionWasmBuilder::makeConst(Element& s, Type type) { } auto ret = allocator.alloc<Const>(); - auto getLiteral = [](Expression* expr) { - if (expr == nullptr) { - throw ParseException("Could not parse v128 lane"); - } - return expr->cast<Const>()->value; - }; - Type lane_t = stringToType(s[1]->str()); + Type lane_t = stringToLaneType(s[1]->str().str); size_t lanes = s.size() - 2; switch (lanes) { case 2: { if (lane_t != i64 && lane_t != f64) { throw ParseException("Unexpected v128 literal lane type"); } - std::array<Literal, 2> lanes; - for (size_t i = 0; i < 2; ++i) { - lanes[i] = getLiteral(parseConst(s[i+2]->str(), lane_t, allocator)); - } - ret->value = Literal(lanes); + ret->value = makeLanes<2>(s, allocator, lane_t); break; } case 4: { if (lane_t != i32 && lane_t != f32) { throw ParseException("Unexpected v128 literal lane type"); } - std::array<Literal, 4> lanes; - for (size_t i = 0; i < 4; ++i) { - lanes[i] = getLiteral(parseConst(s[i+2]->str(), lane_t, allocator)); - } - ret->value = Literal(lanes); + ret->value = makeLanes<4>(s, allocator, lane_t); break; } case 8: { if (lane_t != i32) { throw ParseException("Unexpected v128 literal lane type"); } - std::array<Literal, 8> lanes; - for (size_t i = 0; i < 8; ++i) { - lanes[i] = getLiteral(parseConst(s[i+2]->str(), lane_t, allocator)); - } - ret->value = Literal(lanes); + ret->value = makeLanes<8>(s, allocator, lane_t); break; } case 16: { if (lane_t != i32) { throw ParseException("Unexpected v128 literal lane type"); } - std::array<Literal, 16> lanes; - for (size_t i = 0; i < 16; ++i) { - lanes[i] = getLiteral(parseConst(s[i+2]->str(), lane_t, allocator)); - } - ret->value = Literal(lanes); + ret->value = makeLanes<16>(s, allocator, lane_t); break; } default: throw ParseException("Unexpected number of lanes in v128 literal"); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index f9371ffab..fc393ed98 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -15,6 +15,7 @@ */ #include "wasm-type.h" +#include "wasm-features.h" #include <cstdlib> #include "compiler-support.h" @@ -47,6 +48,13 @@ unsigned getTypeSize(Type type) { WASM_UNREACHABLE(); } +FeatureSet getFeatures(Type type) { + if (type == v128) { + return FeatureSet::SIMD; + } + return FeatureSet(); +} + Type getType(unsigned size, bool float_) { if (size < 4) return Type::i32; if (size == 4) return float_ ? Type::f32 : Type::i32; diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 84a0efbff..91e7e7398 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -1081,12 +1081,17 @@ void FunctionValidator::visitHost(Host* curr) { } void FunctionValidator::visitFunction(Function* curr) { + FeatureSet typeFeatures = getFeatures(curr->result); for (auto type : curr->params) { + typeFeatures |= getFeatures(type); shouldBeTrue(isConcreteType(type), curr, "params must be concretely typed"); } for (auto type : curr->vars) { + typeFeatures |= getFeatures(type); shouldBeTrue(isConcreteType(type), curr, "vars must be concretely typed"); } + shouldBeTrue(typeFeatures <= info.features, curr, + "all used types should be allowed"); // if function has no result, it is ignored // if body is unreachable, it might be e.g. a return if (curr->body->type != unreachable) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c12481760..c99bb0994 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -32,6 +32,7 @@ const char* SourceMapUrl = "sourceMappingURL"; const char* Dylink = "dylink"; const char* Linking = "linking"; const char* Producers = "producers"; +const char* TargetFeatures = "target_features"; } } |