diff options
55 files changed, 294 insertions, 264 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b6a725d..17a69e053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Current Trunk - Add --side-module option to wasm-emscripten-finalize. - Add `segmentPassive` argument to `BinaryenSetMemory` for marking segments passive. +- Make `-o -` print to stdout instead of a file named "-". v73 --- diff --git a/build-js.sh b/build-js.sh index f4f93a469..1ca2a1129 100755 --- a/build-js.sh +++ b/build-js.sh @@ -144,6 +144,7 @@ echo "building shared bitcode" $BINARYEN_SRC/passes/SSAify.cpp \ $BINARYEN_SRC/passes/StackIR.cpp \ $BINARYEN_SRC/passes/Strip.cpp \ + $BINARYEN_SRC/passes/StripTargetFeatures.cpp \ $BINARYEN_SRC/passes/TrapMode.cpp \ $BINARYEN_SRC/passes/Untee.cpp \ $BINARYEN_SRC/passes/Vacuum.cpp \ diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 7723f7cee..227b7b65e 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -312,10 +312,7 @@ class Py2CompletedProcess: output=self.stdout, stderr=self.stderr) -def run_process(cmd, check=True, input=None, universal_newlines=True, - capture_output=False, *args, **kw): - kw.setdefault('universal_newlines', True) - +def run_process(cmd, check=True, input=None, capture_output=False, *args, **kw): if hasattr(subprocess, "run"): ret = subprocess.run(cmd, check=check, input=input, *args, **kw) return ret @@ -419,9 +416,7 @@ def minify_check(wast, verify_final_result=True): print ' (minify check)' cmd = WASM_OPT + [wast, '--print-minified'] print ' ', ' '.join(cmd) - subprocess.check_call( - WASM_OPT + [wast, '--print-minified'], - stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) + subprocess.check_call(cmd, stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) assert os.path.exists('a.wast') subprocess.check_call( WASM_OPT + ['a.wast', '--print-minified'], diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 9a68542a5..a981cb660 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -1450,7 +1450,6 @@ void Asm2WasmBuilder::processAsm(Ref ast) { PassRunner passRunner(&wasm, passOptions); passRunner.options.lowMemoryUnused = true; - passRunner.setFeatures(passOptions.features); if (debug) { passRunner.setDebug(true); passRunner.setValidateGlobally(false); diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 2ae3da650..aa94d1d30 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -2486,9 +2486,9 @@ int BinaryenModuleValidate(BinaryenModuleRef module) { } Module* wasm = (Module*)module; - // TODO add feature selection support to C API - FeatureSet features = FeatureSet::All; - return WasmValidator().validate(*wasm, features) ? 1 : 0; + // TODO(tlively): Add C API for managing features + wasm->features = FeatureSet::All; + return WasmValidator().validate(*wasm) ? 1 : 0; } void BinaryenModuleOptimize(BinaryenModuleRef module) { diff --git a/src/ir/memory-utils.h b/src/ir/memory-utils.h index 0bad50c5a..fbf1f646c 100644 --- a/src/ir/memory-utils.h +++ b/src/ir/memory-utils.h @@ -69,6 +69,12 @@ namespace MemoryUtils { return true; } + // Conservatively refuse to change segments if there might be memory.init + // and data.drop instructions. + if (module.features.hasBulkMemory()) { + return false; + } + auto isEmpty = [](Memory::Segment& segment) { return segment.data.size() == 0; }; diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 259ec9fea..fe99a458f 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -69,75 +69,6 @@ struct BinaryIndexes { } }; -// Read the features section into the out param, if it is present. If it is not -// present, return false -inline bool readFeaturesSection(const Module& module, FeatureSet& features) try { - features = FeatureSet::MVP; - - const UserSection* section = nullptr; - for (auto &s : module.userSections) { - if (s.name == BinaryConsts::UserSections::TargetFeatures) { - section = &s; - break; - } - } - - if (!section) { - return false; - } - - // read target features section - size_t index = 0; - auto next_byte = [&]() { - return section->data.at(index++); - }; - - size_t num_feats = U32LEB().read(next_byte).value; - for (size_t i = 0; i < num_feats; ++i) { - uint8_t prefix = section->data.at(index++); - if (prefix != '=' && prefix != '+' && prefix != '-') { - // unrecognized prefix, silently fail - features = FeatureSet::MVP; - return false; - } - - size_t len = U32LEB().read(next_byte).value; - if (index + len > section->data.size()) { - // ill-formed string, silently fail - features = FeatureSet::MVP; - return false; - } - std::string name(§ion->data[index], len); - index += len; - - if (prefix == '-') { - continue; - } - - if (name == BinaryConsts::UserSections::AtomicsFeature) { - features.setAtomics(); - } else if (name == BinaryConsts::UserSections::BulkMemoryFeature) { - features.setBulkMemory(); - } else if (name == BinaryConsts::UserSections::ExceptionHandlingFeature) { - WASM_UNREACHABLE(); // TODO: exception handling - } else if (name == BinaryConsts::UserSections::TruncSatFeature) { - features.setTruncSat(); - } else if (name == BinaryConsts::UserSections::SignExtFeature) { - features.setSignExt(); - } else if (name == BinaryConsts::UserSections::SIMD128Feature) { - features.setSIMD(); - } - } - - return true; - -// silently fail to read features. Maybe this should warn? -} catch (std::out_of_range& e) { - features = FeatureSet::MVP; - return false; -} - - inline Function* copyFunction(Function* func, Module& out) { auto* ret = new Function(); ret->name = func->name; diff --git a/src/pass.h b/src/pass.h index 6116a9cca..65bf7bf52 100644 --- a/src/pass.h +++ b/src/pass.h @@ -74,8 +74,6 @@ struct PassOptions { enum { LowMemoryBound = 1024 }; // Whether to try to preserve debug info through, which are special calls. bool debugInfo = false; - // Which wasm features to accept, and be allowed to use. - FeatureSet features = FeatureSet::All; // Arbitrary string arguments from the commandline, which we forward to passes. std::map<std::string, std::string> arguments; @@ -129,9 +127,6 @@ struct PassRunner { void setValidateGlobally(bool validate) { options.validateGlobally = validate; } - void setFeatures(FeatureSet features) { - options.features = features; - } void add(std::string passName) { auto pass = PassRegistry::get()->createPass(passName); diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index d2e713e2b..10fdc7fe4 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -42,6 +42,7 @@ SET(passes_SOURCES PrintCallGraph.cpp StackIR.cpp Strip.cpp + StripTargetFeatures.cpp RedundantSetElimination.cpp RelooperJumpThreading.cpp ReReloop.cpp diff --git a/src/passes/MemoryPacking.cpp b/src/passes/MemoryPacking.cpp index ef150e2de..704f57d89 100644 --- a/src/passes/MemoryPacking.cpp +++ b/src/passes/MemoryPacking.cpp @@ -32,7 +32,7 @@ struct MemoryPacking : public Pass { // avoid invalidating segment indices or segment contents referenced from // memory.init instructions. // TODO: optimize in the presence of memory.init instructions - if (!module->memory.exists || runner->options.features.hasBulkMemory()) { + if (!module->memory.exists || module->features.hasBulkMemory()) { return; } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index ebfb6c311..29d867d76 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1456,7 +1456,6 @@ Pass* createPrintStackIRPass() { std::ostream& WasmPrinter::printModule(Module* module, std::ostream& o) { PassRunner passRunner(module); - passRunner.setFeatures(FeatureSet::All); passRunner.setIsNested(true); passRunner.add<Printer>(&o); passRunner.run(); diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp index 852af0c16..738a201ee 100644 --- a/src/passes/SafeHeap.cpp +++ b/src/passes/SafeHeap.cpp @@ -113,7 +113,7 @@ struct SafeHeap : public Pass { instrumenter.add<AccessInstrumenter>(); instrumenter.run(); // add helper checking funcs and imports - addGlobals(module, runner->options.features); + addGlobals(module, module->features); } Name dynamicTopPtr, segfault, alignfault; diff --git a/src/passes/Strip.cpp b/src/passes/Strip.cpp index 40e5a9e9d..edc171ab3 100644 --- a/src/passes/Strip.cpp +++ b/src/passes/Strip.cpp @@ -75,9 +75,4 @@ Pass *createStripProducersPass() { }); } -Pass *createStripTargetFeaturesPass() { - return new Strip([&](const UserSection& curr) { - return curr.name == BinaryConsts::UserSections::TargetFeatures; - }); -} } // namespace wasm diff --git a/src/passes/StripTargetFeatures.cpp b/src/passes/StripTargetFeatures.cpp new file mode 100644 index 000000000..645fbb11c --- /dev/null +++ b/src/passes/StripTargetFeatures.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#include "pass.h" + +using namespace std; + +namespace wasm { + +struct StripTargetFeatures : public Pass { + void run(PassRunner* runner, Module* module) override { + module->hasFeaturesSection = false; + } +}; + +Pass *createStripTargetFeaturesPass() { + return new StripTargetFeatures(); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 786a39474..fd5359206 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -306,7 +306,7 @@ void PassRunner::run() { if (options.validate) { // validate, ignoring the time std::cerr << "[PassRunner] (validating)\n"; - if (!WasmValidator().validate(*wasm, options.features, validationFlags)) { + if (!WasmValidator().validate(*wasm, validationFlags)) { WasmPrinter::printModule(wasm); if (passDebug >= 2) { std::cerr << "Last pass (" << pass->name << ") broke validation. Here is the module before: \n" << moduleBefore.str() << "\n"; @@ -323,7 +323,7 @@ void PassRunner::run() { std::cerr << "[PassRunner] passes took " << totalTime.count() << " seconds." << std::endl; if (options.validate) { std::cerr << "[PassRunner] (final validation)\n"; - if (!WasmValidator().validate(*wasm, options.features, validationFlags)) { + if (!WasmValidator().validate(*wasm, validationFlags)) { WasmPrinter::printModule(wasm); std::cerr << "final module does not validate\n"; abort(); diff --git a/src/support/file.cpp b/src/support/file.cpp index 2fe636cfd..c10646720 100644 --- a/src/support/file.cpp +++ b/src/support/file.cpp @@ -68,6 +68,9 @@ template std::vector<char> wasm::read_file<>(const std::string& , Flags::BinaryO wasm::Output::Output(const std::string& filename, Flags::BinaryOption binary, Flags::DebugOption debug) : outfile(), out([this, filename, binary, debug]() { + if (filename == "-") { + return std::cout.rdbuf(); + } std::streambuf *buffer; if (filename.size()) { if (debug == Flags::Debug) std::cerr << "Opening '" << filename << "'" << std::endl; diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp index 9ec083db2..547bf4ab1 100644 --- a/src/tools/asm2wasm.cpp +++ b/src/tools/asm2wasm.cpp @@ -177,6 +177,9 @@ int main(int argc, const char *argv[]) { wasm.memory.segments.emplace_back(init, data); } + // set up the module's features, needed by optimization and validation passes + options.applyFeatures(wasm); + // compile the code Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, trapMode, options.passOptions, legalizeJavaScriptFFI, options.runningDefaultOptimizationPasses(), wasmOnly); asm2wasm.processAsm(asmjs); @@ -185,7 +188,6 @@ int main(int argc, const char *argv[]) { if (memInit != options.extra.end()) { if (options.runningDefaultOptimizationPasses()) { PassRunner runner(&wasm); - runner.setFeatures(options.passOptions.features); runner.add("memory-packing"); runner.run(); } @@ -213,7 +215,7 @@ int main(int argc, const char *argv[]) { } if (options.passOptions.validate) { - if (!WasmValidator().validate(wasm, options.passOptions.features)) { + if (!WasmValidator().validate(wasm)) { WasmPrinter::printModule(&wasm); Fatal() << "error in validating output"; } diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 7b47dea8f..b7056f964 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -124,10 +124,6 @@ public: std::cout << "shrink level: " << options.passOptions.shrinkLevel << '\n'; } - void setFeatures(FeatureSet features_) { - features = features_; - } - void setAllowNaNs(bool allowNaNs_) { allowNaNs = allowNaNs_; } @@ -197,9 +193,6 @@ private: // Whether to emit memory operations like loads and stores. bool allowMemory = true; - // Features allowed to be emitted - FeatureSet features = FeatureSet::All; - // Whether to emit atomic waits (which in single-threaded mode, may hang...) static const bool ATOMIC_WAITS = false; @@ -257,7 +250,7 @@ private: void setupMemory() { // Add memory itself MemoryUtils::ensureExists(wasm.memory); - if (features.hasBulkMemory()) { + if (wasm.features.hasBulkMemory()) { size_t memCovered = 0; // need at least one segment for memory.inits size_t numSegments = upTo(8) + 1; @@ -1198,7 +1191,7 @@ private: return builder.makeLoad(8, false, offset, pick(1, 2, 4, 8), ptr, type); } case v128: { - if (!features.hasSIMD()) { + if (!wasm.features.hasSIMD()) { return makeTrivial(type); } return builder.makeLoad(16, false, offset, pick(1, 2, 4, 8, 16), ptr, type); @@ -1213,7 +1206,7 @@ private: if (!allowMemory) return makeTrivial(type); auto* ret = makeNonAtomicLoad(type); if (type != i32 && type != i64) return ret; - if (!features.hasAtomics() || oneIn(2)) return ret; + if (!wasm.features.hasAtomics() || oneIn(2)) return ret; // make it atomic auto* load = ret->cast<Load>(); wasm.memory.shared = true; @@ -1270,7 +1263,7 @@ private: return builder.makeStore(8, offset, pick(1, 2, 4, 8), ptr, value, type); } case v128: { - if (!features.hasSIMD()) { + if (!wasm.features.hasSIMD()) { return makeTrivial(type); } return builder.makeStore(16, offset, pick(1, 2, 4, 8, 16), ptr, value, type); @@ -1287,7 +1280,7 @@ private: auto* store = ret->dynCast<Store>(); if (!store) return ret; if (store->value->type != i32 && store->value->type != i64) return store; - if (!features.hasAtomics() || oneIn(2)) return store; + if (!wasm.features.hasAtomics() || oneIn(2)) return store; // make it atomic wasm.memory.shared = true; store->isAtomic = true; @@ -1482,7 +1475,7 @@ private: return buildUnary({ op, make(f64) }); } case v128: { - assert(features.hasSIMD()); + assert(wasm.features.hasSIMD()); return buildUnary({ pick(AnyTrueVecI8x16, AllTrueVecI8x16, AnyTrueVecI16x8, AllTrueVecI16x8, AnyTrueVecI32x4, AllTrueVecI32x4, AnyTrueVecI64x2, AllTrueVecI64x2), make(v128) }); @@ -1541,7 +1534,7 @@ private: WASM_UNREACHABLE(); } case v128: { - assert(features.hasSIMD()); + assert(wasm.features.hasSIMD()); switch (upTo(5)) { case 0: return buildUnary({ pick(SplatVecI8x16, SplatVecI16x8, SplatVecI32x4), make(i32) }); case 1: return buildUnary({ SplatVecI64x2, make(i64) }); @@ -1594,7 +1587,7 @@ private: return makeDeNanOp(buildBinary({ pick(AddFloat64, SubFloat64, MulFloat64, DivFloat64, CopySignFloat64, MinFloat64, MaxFloat64), make(f64), make(f64) })); } case v128: { - assert(features.hasSIMD()); + assert(wasm.features.hasSIMD()); return buildBinary({ pick(EqVecI8x16, NeVecI8x16, LtSVecI8x16, LtUVecI8x16, GtSVecI8x16, GtUVecI8x16, LeSVecI8x16, LeUVecI8x16, GeSVecI8x16, GeUVecI8x16, EqVecI16x8, NeVecI16x8, LtSVecI16x8, LtUVecI16x8, GtSVecI16x8, GtUVecI16x8, LeSVecI16x8, LeUVecI16x8, GeSVecI16x8, GeUVecI16x8, @@ -1669,7 +1662,7 @@ private: } Expression* makeAtomic(Type type) { - assert(features.hasAtomics()); + assert(wasm.features.hasAtomics()); if (!allowMemory) return makeTrivial(type); wasm.memory.shared = true; if (type == i32 && oneIn(2)) { @@ -1722,7 +1715,7 @@ private: } Expression* makeSIMD(Type type) { - assert(features.hasSIMD()); + assert(wasm.features.hasSIMD()); if (type != v128) { return makeSIMDExtract(type); } @@ -1809,7 +1802,7 @@ private: Expression* makeBulkMemory(Type type) { if (!allowMemory) return makeTrivial(type); - assert(features.hasBulkMemory()); + assert(wasm.features.hasBulkMemory()); assert(type == none); switch (upTo(4)) { case 0: return makeMemoryInit(); @@ -1988,7 +1981,7 @@ private: const T pick(FeatureOptions<T>& picker) { std::vector<T> matches; for (const auto& item : picker.options) { - if (features.has(item.first)) { + if (wasm.features.has(item.first)) { matches.reserve(matches.size() + item.second.size()); matches.insert(matches.end(), item.second.begin(), item.second.end()); } diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index dfc61b0e3..cf7c612ea 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -141,7 +141,6 @@ struct OptimizationOptions : public ToolOptions { void runPasses(Module& wasm) { PassRunner passRunner(&wasm, passOptions); if (debug) passRunner.setDebug(true); - passRunner.setFeatures(passOptions.features); for (auto& pass : passes) { if (pass == DEFAULT_OPT_PASSES) { passRunner.addDefaultOptimizationPasses(); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 6c2613407..dd585612f 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -34,24 +34,18 @@ struct ToolOptions : public Options { Arguments::Zero, [this](Options*, const std::string&) { hasFeatureOptions = true; - passOptions.features.makeMVP(); enabledFeatures.makeMVP(); disabledFeatures.setAll(); }) - .add("--all-features", "-all", "Enable all features " - "(default if there is no target features section" - " or if any feature options are provided)", + .add("--all-features", "-all", "Enable all features", Arguments::Zero, [this](Options*, const std::string&) { hasFeatureOptions = true; - passOptions.features.setAll(); enabledFeatures.setAll(); disabledFeatures.makeMVP(); }) .add("--detect-features", "", - "Use features from the target features section" - "(default if there is a target features section" - " and no feature options are provided)", + "Use features from the target features section, or MVP (default)", Arguments::Zero, [this](Options*, const std::string&) { hasFeatureOptions = true; @@ -76,13 +70,11 @@ struct ToolOptions : public Options { ToolOptions& addFeature(FeatureSet::Feature feature, const std::string& description) { - (*this) .add(std::string("--enable-") + FeatureSet::toString(feature), "", std::string("Enable ") + description, Arguments::Zero, [=](Options*, const std::string&) { hasFeatureOptions = true; - passOptions.features.set(feature, true); enabledFeatures.set(feature, true); disabledFeatures.set(feature, false); }) @@ -91,36 +83,32 @@ struct ToolOptions : public Options { std::string("Disable ") + description, Arguments::Zero, [=](Options*, const std::string&) { hasFeatureOptions = true; - passOptions.features.set(feature, false); enabledFeatures.set(feature, false); disabledFeatures.set(feature, true); }); return *this; } - void calculateFeatures(const Module& module) { - FeatureSet wasmFeatures; - bool wasmHasFeatures = - ModuleUtils::readFeaturesSection(module, wasmFeatures); - + void applyFeatures(Module& module) { if (hasFeatureOptions) { - if (detectFeatures) { - wasmFeatures.enable(enabledFeatures); - wasmFeatures.disable(disabledFeatures); - passOptions.features = wasmFeatures; - } else if (!(wasmFeatures <= passOptions.features)) { - Fatal() << "module uses features not explicitly specified, " - << "use --detect-features to resolve"; + if (!detectFeatures && module.hasFeaturesSection) { + FeatureSet optionsFeatures = FeatureSet::All; + optionsFeatures.enable(enabledFeatures); + optionsFeatures.disable(disabledFeatures); + if (!(module.features <= optionsFeatures)) { + Fatal() << "module uses features not explicitly specified, " + << "use --detect-features to resolve"; + } } - } else if (wasmHasFeatures) { - passOptions.features = wasmFeatures; + module.features.enable(enabledFeatures); + module.features.disable(disabledFeatures); } } private: bool hasFeatureOptions = false; bool detectFeatures = false; - FeatureSet enabledFeatures = FeatureSet::MVP; + FeatureSet enabledFeatures = FeatureSet::All; FeatureSet disabledFeatures = FeatureSet::MVP; }; diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index c504c2a81..fb79eb7cf 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -91,9 +91,11 @@ int main(int argc, const char *argv[]) { Fatal() << "error in parsing input"; } + wasm.features = FeatureSet::All; + if (options.extra["validate"] != "none") { if (options.debug) std::cerr << "Validating..." << std::endl; - if (!wasm::WasmValidator().validate(wasm, FeatureSet::All, + if (!wasm::WasmValidator().validate(wasm, WasmValidator::Globally | (options.extra["validate"] == "web" ? WasmValidator::Web : 0))) { WasmPrinter::printModule(&wasm); Fatal() << "Error: input module is not valid.\n"; diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index be1de7684..7509eb84f 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -425,7 +425,7 @@ int main(int argc, const char* argv[]) { } } - options.calculateFeatures(wasm); + options.applyFeatures(wasm); if (!WasmValidator().validate(wasm)) { WasmPrinter::printModule(&wasm); @@ -444,7 +444,6 @@ int main(int argc, const char* argv[]) { // Do some useful optimizations after the evalling { PassRunner passRunner(&wasm); - passRunner.setFeatures(options.passOptions.features); passRunner.add("memory-packing"); // we flattened it, so re-optimize passRunner.add("remove-unused-names"); passRunner.add("dce"); diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index e39d9e2d6..46fb0533b 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -130,7 +130,7 @@ int main(int argc, const char *argv[]) { Fatal() << "error in parsing wasm source map"; } - options.calculateFeatures(wasm); + options.applyFeatures(wasm); if (options.debug) { std::cerr << "Module before:\n"; @@ -210,7 +210,7 @@ int main(int argc, const char *argv[]) { } // Substantial changes to the wasm are done, enough to create the metadata. - std::string metadata = generator.generateEmscriptenMetadata(dataSize, initializerFunctions, options.passOptions.features); + 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/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index d36459a4a..6fce0f89b 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -162,26 +162,26 @@ int main(int argc, const char* argv[]) { Fatal() << "error in building module, std::bad_alloc (possibly invalid request for silly amounts of memory)"; } - options.calculateFeatures(wasm); + options.applyFeatures(wasm); if (options.passOptions.validate) { - if (!WasmValidator().validate(wasm, options.passOptions.features)) { + if (!WasmValidator().validate(wasm)) { WasmPrinter::printModule(&wasm); Fatal() << "error in validating input"; } } } else { // translate-to-fuzz + options.applyFeatures(wasm); TranslateToFuzzReader reader(wasm, options.extra["infile"]); if (fuzzPasses) { reader.pickPasses(options); } - reader.setFeatures(options.passOptions.features); reader.setAllowNaNs(fuzzNaNs); reader.setAllowMemory(fuzzMemory); reader.build(); if (options.passOptions.validate) { - if (!WasmValidator().validate(wasm, options.passOptions.features)) { + if (!WasmValidator().validate(wasm)) { WasmPrinter::printModule(&wasm); std::cerr << "translate-to-fuzz must always generate a valid module"; abort(); @@ -241,7 +241,7 @@ int main(int argc, const char* argv[]) { WasmBinaryBuilder parser(other, input, false); parser.read(); if (options.passOptions.validate) { - bool valid = WasmValidator().validate(other, options.passOptions.features); + bool valid = WasmValidator().validate(other); if (!valid) { WasmPrinter::printModule(&other); } @@ -256,7 +256,7 @@ int main(int argc, const char* argv[]) { options.runPasses(*curr); if (options.passOptions.validate) { bool valid = - WasmValidator().validate(*curr, options.passOptions.features); + WasmValidator().validate(*curr); if (!valid) { WasmPrinter::printModule(&*curr); } diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 36a44b056..1f58da0d5 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -819,7 +819,7 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< FunctionReferenceRemover referenceRemover(names); referenceRemover.walkModule(module.get()); - if (WasmValidator().validate(*module, FeatureSet::All, WasmValidator::Globally | WasmValidator::Quiet) && + if (WasmValidator().validate(*module, WasmValidator::Globally | WasmValidator::Quiet) && writeAndTestReduction()) { std::cerr << "| removed " << names.size() << " functions\n"; return true; diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 0d7ff61de..0c95b39ea 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -25,12 +25,12 @@ #include "execution-results.h" #include "pass.h" #include "shell-interface.h" +#include "support/command-line.h" #include "support/file.h" #include "wasm-interpreter.h" #include "wasm-printing.h" #include "wasm-s-parser.h" #include "wasm-validator.h" -#include "tool-options.h" using namespace cashew; using namespace wasm; @@ -233,7 +233,7 @@ int main(int argc, const char* argv[]) { Name entry; std::set<size_t> skipped; - ToolOptions options("wasm-shell", "Execute .wast files"); + Options options("wasm-shell", "Execute .wast files"); options .add( "--entry", "-e", "Call the entry point after parsing the module", @@ -292,7 +292,8 @@ int main(int argc, const char* argv[]) { builders[moduleName].swap(builder); modules[moduleName].swap(module); i++; - bool valid = WasmValidator().validate(*modules[moduleName], options.passOptions.features); + modules[moduleName]->features = FeatureSet::All; + bool valid = WasmValidator().validate(*modules[moduleName]); if (!valid) { WasmPrinter::printModule(modules[moduleName].get()); } diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index 8704dbf49..67a5d33b6 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -407,7 +407,7 @@ int main(int argc, const char *argv[]) { ModuleReader reader; reader.setDebug(options.debug); reader.read(input, wasm, ""); - options.calculateFeatures(wasm); + options.applyFeatures(wasm); } else { auto input( read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); @@ -426,7 +426,7 @@ int main(int argc, const char *argv[]) { } if (options.passOptions.validate) { - if (!WasmValidator().validate(wasm, options.passOptions.features)) { + if (!WasmValidator().validate(wasm)) { WasmPrinter::printModule(&wasm); Fatal() << "error in validating input"; } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index f13b79654..50b78fbae 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -365,8 +365,7 @@ enum Subsection { NameLocal = 2, }; - -} +} // namespace UserSections enum ASTNodes { Unreachable = 0x00, @@ -819,6 +818,12 @@ enum MemoryFlags { IsShared = 1 << 1 }; +enum FeaturePrefix { + FeatureUsed = '+', + FeatureRequired = '=', + FeatureDisallowed = '-' +}; + } // namespace BinaryConsts @@ -901,6 +906,7 @@ public: void writeEarlyUserSections(); void writeLateUserSections(); void writeUserSection(const UserSection& section); + void writeFeaturesSection(); void initializeDebugInfo(); void writeSourceMapProlog(); @@ -1085,6 +1091,7 @@ public: void readFunctionTableDeclaration(); void readTableElements(); void readNames(size_t); + void readFeatures(size_t); // Debug information reading helpers void setDebugLocations(std::istream* sourceMap_) { diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h index 36c730be8..7d86031c7 100644 --- a/src/wasm-emscripten.h +++ b/src/wasm-emscripten.h @@ -49,8 +49,8 @@ public: void replaceStackPointerGlobal(); std::string generateEmscriptenMetadata( - Address staticBump, std::vector<Name> const& initializerFunctions, - FeatureSet features); + Address staticBump, std::vector<Name> const& initializerFunctions); + void fixInvokeFunctionNames(); diff --git a/src/wasm-features.h b/src/wasm-features.h index 6c58f1642..a1e7a321d 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -77,14 +77,14 @@ struct FeatureSet { template<typename F> void iterFeatures(F f) { if (hasAtomics()) f(Atomics); + if (hasBulkMemory()) f(BulkMemory); if (hasMutableGlobals()) f(MutableGlobals); if (hasTruncSat()) f(TruncSat); - if (hasSIMD()) f(SIMD); - if (hasBulkMemory()) f(BulkMemory); if (hasSignExt()) f(SignExt); + if (hasSIMD()) f(SIMD); } - bool operator<=(const FeatureSet& other) { + bool operator<=(const FeatureSet& other) const { return !(features & ~other.features); } diff --git a/src/wasm-validator.h b/src/wasm-validator.h index 3c0b6018b..9f9cddb02 100644 --- a/src/wasm-validator.h +++ b/src/wasm-validator.h @@ -57,7 +57,7 @@ struct WasmValidator { }; typedef uint32_t Flags; - bool validate(Module& module, FeatureSet features = FeatureSet::MVP, Flags flags = Globally); + bool validate(Module& module, Flags flags = Globally); }; } // namespace wasm diff --git a/src/wasm.h b/src/wasm.h index 700938ef9..2171eeb12 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -924,6 +924,13 @@ public: std::vector<UserSection> userSections; std::vector<std::string> debugInfoFileNames; + // `features` are the features allowed to be used in this module and should be + // respected regardless of the value of`hasFeaturesSection`. + // `hasFeaturesSection` means we read a features section and will emit one + // too. + FeatureSet features = FeatureSet::All; + bool hasFeaturesSection = false; + MixedArena allocator; private: diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2ee528392..b351a46e5 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -69,6 +69,7 @@ void WasmBinaryWriter::write() { } writeLateUserSections(); + writeFeaturesSection(); finishUp(); } @@ -356,7 +357,9 @@ void WasmBinaryWriter::writeFunctionTableDeclaration() { } void WasmBinaryWriter::writeTableElements() { - if (!wasm->table.exists) return; + if (!wasm->table.exists || wasm->table.segments.size() == 0) { + return; + } if (debug) std::cerr << "== writeTableElements" << std::endl; auto start = startSection(BinaryConsts::Section::Element); @@ -486,7 +489,7 @@ void WasmBinaryWriter::writeLateUserSections() { } void WasmBinaryWriter::writeUserSection(const UserSection& section) { - auto start = startSection(0); + auto start = startSection(BinaryConsts::User); writeInlineString(section.name.c_str()); for (size_t i = 0; i < section.data.size(); i++) { o << uint8_t(section.data[i]); @@ -494,6 +497,41 @@ void WasmBinaryWriter::writeUserSection(const UserSection& section) { finishSection(start); } +void WasmBinaryWriter::writeFeaturesSection() { + if (!wasm->hasFeaturesSection || wasm->features.isMVP()) { + return; + } + + // TODO(tlively): unify feature names with rest of toolchain and use + // FeatureSet::toString() + auto toString = [](FeatureSet::Feature f) { + switch (f) { + case FeatureSet::Atomics: return "atomics"; + case FeatureSet::MutableGlobals: return "mutable-globals"; + case FeatureSet::TruncSat: return "nontrapping-fptoint"; + case FeatureSet::SIMD: return "simd128"; + case FeatureSet::BulkMemory: return "bulk-memory"; + case FeatureSet::SignExt: return "sign-ext"; + default: WASM_UNREACHABLE(); + } + }; + + std::vector<const char*> features; + wasm->features.iterFeatures([&](FeatureSet::Feature f) { + features.push_back(toString(f)); + }); + + auto start = startSection(BinaryConsts::User); + writeInlineString(BinaryConsts::UserSections::TargetFeatures); + o << U32LEB(features.size()); + for (auto& f : features) { + o << uint8_t(BinaryConsts::FeatureUsed); + writeInlineString(f); + } + finishSection(start); +} + + void WasmBinaryWriter::writeDebugLocation(const Function::DebugLocation& loc) { if (loc == lastDebugLocation) { return; @@ -648,6 +686,8 @@ void WasmBinaryBuilder::readUserSection(size_t payloadLen) { payloadLen -= read; if (sectionName.equals(BinaryConsts::UserSections::Name)) { readNames(payloadLen); + } else if (sectionName.equals(BinaryConsts::UserSections::TargetFeatures)) { + readFeatures(payloadLen); } else { // an unfamiliar custom section if (sectionName.equals(BinaryConsts::UserSections::Linking)) { @@ -1600,6 +1640,48 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) { } } +void WasmBinaryBuilder::readFeatures(size_t payloadLen) { + wasm.hasFeaturesSection = true; + wasm.features = FeatureSet::MVP; + + auto sectionPos = pos; + size_t num_feats = getU32LEB(); + for (size_t i = 0; i < num_feats; ++i) { + uint8_t prefix = getInt8(); + if (prefix != BinaryConsts::FeatureUsed) { + if (prefix == BinaryConsts::FeatureRequired) { + throwError("Required features not supported"); + } else if (prefix == BinaryConsts::FeatureDisallowed) { + throwError("Disallowed features not supported"); + } else { + throwError("Unrecognized feature policy prefix"); + } + } + + Name name = getInlineString(); + if (pos > sectionPos + payloadLen) { + throwError("ill-formed string extends beyond section"); + } + + if (name == BinaryConsts::UserSections::AtomicsFeature) { + wasm.features.setAtomics(); + } else if (name == BinaryConsts::UserSections::BulkMemoryFeature) { + wasm.features.setBulkMemory(); + } else if (name == BinaryConsts::UserSections::ExceptionHandlingFeature) { + WASM_UNREACHABLE(); // TODO: exception handling + } else if (name == BinaryConsts::UserSections::TruncSatFeature) { + wasm.features.setTruncSat(); + } else if (name == BinaryConsts::UserSections::SignExtFeature) { + wasm.features.setSignExt(); + } else if (name == BinaryConsts::UserSections::SIMD128Feature) { + wasm.features.setSIMD(); + } + } + if (pos != sectionPos + payloadLen) { + throwError("bad features section size"); + } +} + BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { if (pos == endOfFunction) { throwError("Reached function end without seeing End opcode"); diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index 1b40b871e..da2604383 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -823,8 +823,7 @@ void printSet(std::ostream& o, C& c) { } std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( - Address staticBump, std::vector<Name> const& initializerFunctions, - FeatureSet features) { + Address staticBump, std::vector<Name> const& initializerFunctions) { bool commaFirst; auto nextElement = [&commaFirst]() { if (commaFirst) { @@ -965,7 +964,7 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( meta << " \"features\": ["; commaFirst = true; meta << nextElement() << "\"--mvp-features\""; - features.iterFeatures([&](FeatureSet::Feature f) { + wasm.features.iterFeatures([&](FeatureSet::Feature f) { meta << nextElement() << "\"--enable-" << FeatureSet::toString(f) << '"'; }); meta << "\n ]\n"; diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a32dd5b0c..1e94ead0a 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -50,7 +50,6 @@ inline std::ostream& printModuleComponent(Expression* curr, std::ostream& stream struct ValidationInfo { bool validateWeb; bool validateGlobally; - FeatureSet features; bool quiet; std::atomic<bool> valid; @@ -477,7 +476,7 @@ void FunctionValidator::visitCallIndirect(CallIndirect* curr) { } void FunctionValidator::visitConst(Const* curr) { - shouldBeTrue(getFeatures(curr->type) <= info.features, curr, + shouldBeTrue(getFeatures(curr->type) <= getModule()->features, curr, "all used features should be allowed"); } @@ -514,10 +513,10 @@ void FunctionValidator::visitSetGlobal(SetGlobal* curr) { void FunctionValidator::visitLoad(Load* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); if (curr->isAtomic) { - shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); + shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); shouldBeTrue(curr->type == i32 || curr->type == i64 || curr->type == unreachable, curr, "Atomic load should be i32 or i64"); } - if (curr->type == v128) shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + if (curr->type == v128) shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeFalse(curr->isAtomic && !getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->type, curr); validateAlignment(curr->align, curr->type, curr->bytes, curr->isAtomic, curr); @@ -531,10 +530,10 @@ void FunctionValidator::visitLoad(Load* curr) { void FunctionValidator::visitStore(Store* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); if (curr->isAtomic) { - shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); + shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); shouldBeTrue(curr->valueType == i32 || curr->valueType == i64 || curr->valueType == unreachable, curr, "Atomic store should be i32 or i64"); } - if (curr->valueType == v128) shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + if (curr->valueType == v128) shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeFalse(curr->isAtomic && !getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->valueType, curr); validateAlignment(curr->align, curr->valueType, curr->bytes, curr->isAtomic, curr); @@ -548,7 +547,7 @@ void FunctionValidator::visitStore(Store* curr) { void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); + shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->type, curr); shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicRMW pointer type must be i32"); @@ -558,7 +557,7 @@ void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) { void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); + shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->type, curr); shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "cmpxchg pointer type must be i32"); @@ -572,7 +571,7 @@ void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) { void FunctionValidator::visitAtomicWait(AtomicWait* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); + shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); shouldBeEqualOrFirstIsUnreachable(curr->type, i32, curr, "AtomicWait must have type i32"); shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicWait pointer type must be i32"); @@ -583,7 +582,7 @@ void FunctionValidator::visitAtomicWait(AtomicWait* curr) { void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); + shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); shouldBeEqualOrFirstIsUnreachable(curr->type, i32, curr, "AtomicNotify must have type i32"); shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicNotify pointer type must be i32"); @@ -591,7 +590,7 @@ void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) { } void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) { - shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->vec->type, v128, curr, "extract_lane must operate on a v128"); Type lane_t = none; size_t lanes = 0; @@ -610,7 +609,7 @@ void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) { } void FunctionValidator::visitSIMDReplace(SIMDReplace* curr) { - shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "replace_lane must have type v128"); shouldBeEqualOrFirstIsUnreachable(curr->vec->type, v128, curr, "replace_lane must operate on a v128"); Type lane_t = none; @@ -628,7 +627,7 @@ void FunctionValidator::visitSIMDReplace(SIMDReplace* curr) { } void FunctionValidator::visitSIMDShuffle(SIMDShuffle* curr) { - shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "v128.shuffle must have type v128"); shouldBeEqualOrFirstIsUnreachable(curr->left->type, v128, curr, "expected operand of type v128"); shouldBeEqualOrFirstIsUnreachable(curr->right->type, v128, curr, "expected operand of type v128"); @@ -638,7 +637,7 @@ void FunctionValidator::visitSIMDShuffle(SIMDShuffle* curr) { } void FunctionValidator::visitSIMDBitselect(SIMDBitselect* curr) { - shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "v128.bitselect must have type v128"); shouldBeEqualOrFirstIsUnreachable(curr->left->type, v128, curr, "expected operand of type v128"); shouldBeEqualOrFirstIsUnreachable(curr->right->type, v128, curr, "expected operand of type v128"); @@ -646,7 +645,7 @@ void FunctionValidator::visitSIMDBitselect(SIMDBitselect* curr) { } void FunctionValidator::visitSIMDShift(SIMDShift* curr) { - shouldBeTrue(info.features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "vector shift must have type v128"); shouldBeEqualOrFirstIsUnreachable(curr->vec->type, v128, curr, "expected operand of type v128"); shouldBeEqualOrFirstIsUnreachable(curr->shift->type, i32, curr, "expected shift amount to have type i32"); @@ -654,7 +653,7 @@ void FunctionValidator::visitSIMDShift(SIMDShift* curr) { void FunctionValidator::visitMemoryInit(MemoryInit* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); + shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "memory.init must have type none"); shouldBeEqualOrFirstIsUnreachable(curr->dest->type, i32, curr, "memory.init dest must be an i32"); shouldBeEqualOrFirstIsUnreachable(curr->offset->type, i32, curr, "memory.init offset must be an i32"); @@ -664,14 +663,14 @@ void FunctionValidator::visitMemoryInit(MemoryInit* curr) { void FunctionValidator::visitDataDrop(DataDrop* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); + shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "data.drop must have type none"); shouldBeTrue(curr->segment < getModule()->memory.segments.size(), curr, "data.drop segment index out of bounds"); } void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); + shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "memory.copy must have type none"); shouldBeEqualOrFirstIsUnreachable(curr->dest->type, i32, curr, "memory.copy dest must be an i32"); shouldBeEqualOrFirstIsUnreachable(curr->source->type, i32, curr, "memory.copy source must be an i32"); @@ -680,7 +679,7 @@ void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) { void FunctionValidator::visitMemoryFill(MemoryFill* curr) { shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(info.features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); + shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "memory.fill must have type none"); shouldBeEqualOrFirstIsUnreachable(curr->dest->type, i32, curr, "memory.fill dest must be an i32"); shouldBeEqualOrFirstIsUnreachable(curr->value->type, i32, curr, "memory.fill value must be an i32"); @@ -874,7 +873,7 @@ void FunctionValidator::visitBinary(Binary* curr) { } case InvalidBinary: WASM_UNREACHABLE(); } - shouldBeTrue(Features::get(curr->op) <= info.features, curr, "all used features should be allowed"); + shouldBeTrue(Features::get(curr->op) <= getModule()->features, curr, "all used features should be allowed"); } void FunctionValidator::visitUnary(Unary* curr) { @@ -1057,7 +1056,7 @@ void FunctionValidator::visitUnary(Unary* curr) { break; case InvalidUnary: WASM_UNREACHABLE(); } - shouldBeTrue(Features::get(curr->op) <= info.features, curr, "all used features should be allowed"); + shouldBeTrue(Features::get(curr->op) <= getModule()->features, curr, "all used features should be allowed"); } void FunctionValidator::visitSelect(Select* curr) { @@ -1106,7 +1105,7 @@ void FunctionValidator::visitFunction(Function* curr) { typeFeatures |= getFeatures(type); shouldBeTrue(isConcreteType(type), curr, "vars must be concretely typed"); } - shouldBeTrue(typeFeatures <= info.features, curr, + shouldBeTrue(typeFeatures <= getModule()->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 @@ -1241,7 +1240,7 @@ static void validateImports(Module& module, ValidationInfo& info) { } } }); - if (!info.features.hasMutableGlobals()) { + if (!module.features.hasMutableGlobals()) { ModuleUtils::iterImportedGlobals(module, [&](Global* curr) { info.shouldBeFalse(curr->mutable_, curr->name, "Imported global cannot be mutable"); }); @@ -1258,7 +1257,7 @@ static void validateExports(Module& module, ValidationInfo& info) { info.shouldBeUnequal(param, i64, f->name, "Exported function must not have i64 parameters"); } } - } else if (curr->kind == ExternalKind::Global && !info.features.hasMutableGlobals()) { + } else if (curr->kind == ExternalKind::Global && !module.features.hasMutableGlobals()) { if (Global* g = module.getGlobalOrNull(curr->value)) { info.shouldBeFalse(g->mutable_, g->name, "Exported global cannot be mutable"); } @@ -1286,7 +1285,7 @@ static void validateExports(Module& module, ValidationInfo& info) { static void validateGlobals(Module& module, ValidationInfo& info) { ModuleUtils::iterDefinedGlobals(module, [&](Global* curr) { - info.shouldBeTrue(getFeatures(curr->type) <= info.features, curr->name, + info.shouldBeTrue(getFeatures(curr->type) <= module.features, curr->name, "all used types should be allowed"); info.shouldBeTrue(curr->init != nullptr, curr->name, "global init must be non-null"); info.shouldBeTrue(curr->init->is<Const>() || curr->init->is<GetGlobal>(), curr->name, "global init must be valid"); @@ -1302,11 +1301,11 @@ static void validateMemory(Module& module, ValidationInfo& info) { info.shouldBeTrue(curr.initial <= Memory::kMaxSize, "memory", "initial memory must be <= 4GB"); info.shouldBeTrue(!curr.hasMax() || curr.max <= Memory::kMaxSize, "memory", "max memory must be <= 4GB, or unlimited"); info.shouldBeTrue(!curr.shared || curr.hasMax(), "memory", "shared memory must have max size"); - if (curr.shared) info.shouldBeTrue(info.features.hasAtomics(), "memory", "memory is shared, but atomics are disabled"); + if (curr.shared) info.shouldBeTrue(module.features.hasAtomics(), "memory", "memory is shared, but atomics are disabled"); for (auto& segment : curr.segments) { Index size = segment.data.size(); if (segment.isPassive) { - info.shouldBeTrue(info.features.hasBulkMemory(), segment.offset, "nonzero segment flags (bulk memory is disabled)"); + info.shouldBeTrue(module.features.hasBulkMemory(), segment.offset, "nonzero segment flags (bulk memory is disabled)"); info.shouldBeEqual(segment.offset, (Expression*)nullptr, segment.offset, "passive segment should not have an offset"); } else { if (!info.shouldBeEqual(segment.offset->type, i32, segment.offset, "segment offset should be i32")) continue; @@ -1351,11 +1350,10 @@ static void validateModule(Module& module, ValidationInfo& info) { // TODO: If we want the validator to be part of libwasm rather than libpasses, then // Using PassRunner::getPassDebug causes a circular dependence. We should fix that, // perhaps by moving some of the pass infrastructure into libsupport. -bool WasmValidator::validate(Module& module, FeatureSet features, Flags flags) { +bool WasmValidator::validate(Module& module, Flags flags) { ValidationInfo info; info.validateWeb = (flags & Web) != 0; info.validateGlobally = (flags & Globally) != 0; - info.features = features; info.quiet = (flags & Quiet) != 0; // parallel wasm logic validation PassRunner runner(&module); diff --git a/test/debugInfo.fromasm.clamp.no-opts.map b/test/debugInfo.fromasm.clamp.no-opts.map index 0b394eb99..091513249 100644 --- a/test/debugInfo.fromasm.clamp.no-opts.map +++ b/test/debugInfo.fromasm.clamp.no-opts.map @@ -1 +1 @@ -{"version":3,"sources":["tests/hello_world.c","tests/other_file.cpp","return.cpp","even-opted.cpp","fib.c","/tmp/emscripten_test_binaryen2_28hnAe/src.c","(unknown)"],"names":[],"mappings":"yLAIA,IACA,ICyylTA,aC7vlTA,OAkDA,0BCnGA,OACA,OACA,uBCAA,4BAKA,QAJA,OADA,8CAKA,0ICsi1DA,MCrvyDA"}
\ No newline at end of file +{"version":3,"sources":["tests/hello_world.c","tests/other_file.cpp","return.cpp","even-opted.cpp","fib.c","/tmp/emscripten_test_binaryen2_28hnAe/src.c","(unknown)"],"names":[],"mappings":"sLAIA,IACA,ICyylTA,aC7vlTA,OAkDA,0BCnGA,OACA,OACA,uBCAA,4BAKA,QAJA,OADA,8CAKA,0ICsi1DA,MCrvyDA"}
\ No newline at end of file diff --git a/test/debugInfo.fromasm.imprecise.no-opts.map b/test/debugInfo.fromasm.imprecise.no-opts.map index 75f8f7d42..bce1d3419 100644 --- a/test/debugInfo.fromasm.imprecise.no-opts.map +++ b/test/debugInfo.fromasm.imprecise.no-opts.map @@ -1 +1 @@ -{"version":3,"sources":["tests/hello_world.c","tests/other_file.cpp","return.cpp","even-opted.cpp","fib.c","/tmp/emscripten_test_binaryen2_28hnAe/src.c","(unknown)"],"names":[],"mappings":"wLAIA,IACA,ICyylTA,aC7vlTA,OAkDA,SCnGA,OACA,OACA,sBCAA,4BAKA,QAJA,OADA,8CAKA,0ICsi1DA,MCrvyDA"}
\ No newline at end of file +{"version":3,"sources":["tests/hello_world.c","tests/other_file.cpp","return.cpp","even-opted.cpp","fib.c","/tmp/emscripten_test_binaryen2_28hnAe/src.c","(unknown)"],"names":[],"mappings":"qLAIA,IACA,ICyylTA,aC7vlTA,OAkDA,SCnGA,OACA,OACA,sBCAA,4BAKA,QAJA,OADA,8CAKA,0ICsi1DA,MCrvyDA"}
\ No newline at end of file diff --git a/test/debugInfo.fromasm.no-opts.map b/test/debugInfo.fromasm.no-opts.map index 0b394eb99..091513249 100644 --- a/test/debugInfo.fromasm.no-opts.map +++ b/test/debugInfo.fromasm.no-opts.map @@ -1 +1 @@ -{"version":3,"sources":["tests/hello_world.c","tests/other_file.cpp","return.cpp","even-opted.cpp","fib.c","/tmp/emscripten_test_binaryen2_28hnAe/src.c","(unknown)"],"names":[],"mappings":"yLAIA,IACA,ICyylTA,aC7vlTA,OAkDA,0BCnGA,OACA,OACA,uBCAA,4BAKA,QAJA,OADA,8CAKA,0ICsi1DA,MCrvyDA"}
\ No newline at end of file +{"version":3,"sources":["tests/hello_world.c","tests/other_file.cpp","return.cpp","even-opted.cpp","fib.c","/tmp/emscripten_test_binaryen2_28hnAe/src.c","(unknown)"],"names":[],"mappings":"sLAIA,IACA,ICyylTA,aC7vlTA,OAkDA,0BCnGA,OACA,OACA,uBCAA,4BAKA,QAJA,OADA,8CAKA,0ICsi1DA,MCrvyDA"}
\ No newline at end of file diff --git a/test/lld/duplicate_imports.wast.out b/test/lld/duplicate_imports.wast.out index 88f60d664..cbb1db44a 100644 --- a/test/lld/duplicate_imports.wast.out +++ b/test/lld/duplicate_imports.wast.out @@ -140,11 +140,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/em_asm.wast.mem.out b/test/lld/em_asm.wast.mem.out index c8c193574..13484ff3d 100644 --- a/test/lld/em_asm.wast.mem.out +++ b/test/lld/em_asm.wast.mem.out @@ -264,11 +264,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/em_asm.wast.out b/test/lld/em_asm.wast.out index bbacde8b7..7986466cd 100644 --- a/test/lld/em_asm.wast.out +++ b/test/lld/em_asm.wast.out @@ -265,11 +265,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/em_asm_O0.wast.out b/test/lld/em_asm_O0.wast.out index 38074f724..45868e567 100644 --- a/test/lld/em_asm_O0.wast.out +++ b/test/lld/em_asm_O0.wast.out @@ -125,11 +125,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/em_asm_shared.wast.out b/test/lld/em_asm_shared.wast.out index 725ef618d..19eabedc8 100644 --- a/test/lld/em_asm_shared.wast.out +++ b/test/lld/em_asm_shared.wast.out @@ -257,11 +257,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/em_asm_table.wast.out b/test/lld/em_asm_table.wast.out index 72002d34e..1ed2cd5bd 100644 --- a/test/lld/em_asm_table.wast.out +++ b/test/lld/em_asm_table.wast.out @@ -95,11 +95,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/em_js_O0.wast.out b/test/lld/em_js_O0.wast.out index 26e4ce5af..3db4c83d7 100644 --- a/test/lld/em_js_O0.wast.out +++ b/test/lld/em_js_O0.wast.out @@ -75,11 +75,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/hello_world.wast.mem.out b/test/lld/hello_world.wast.mem.out index 133e66aa7..79ef1342a 100644 --- a/test/lld/hello_world.wast.mem.out +++ b/test/lld/hello_world.wast.mem.out @@ -100,11 +100,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/hello_world.wast.out b/test/lld/hello_world.wast.out index f964276d9..c4a54667b 100644 --- a/test/lld/hello_world.wast.out +++ b/test/lld/hello_world.wast.out @@ -101,11 +101,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/init.wast.out b/test/lld/init.wast.out index 5f46bee3c..676f00327 100644 --- a/test/lld/init.wast.out +++ b/test/lld/init.wast.out @@ -112,11 +112,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/recursive.wast.out b/test/lld/recursive.wast.out index 8ac3105d4..277d3cf1f 100644 --- a/test/lld/recursive.wast.out +++ b/test/lld/recursive.wast.out @@ -158,11 +158,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/reserved_func_ptr.wast.out b/test/lld/reserved_func_ptr.wast.out index ee58ef846..5a5a6f29d 100644 --- a/test/lld/reserved_func_ptr.wast.out +++ b/test/lld/reserved_func_ptr.wast.out @@ -195,11 +195,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/lld/shared.wast.out b/test/lld/shared.wast.out index 076a91aab..8d44279b8 100644 --- a/test/lld/shared.wast.out +++ b/test/lld/shared.wast.out @@ -99,11 +99,11 @@ "features": [ "--mvp-features", "--enable-threads", + "--enable-bulk-memory", "--enable-mutable-globals", "--enable-nontrapping-float-to-int", - "--enable-simd", - "--enable-bulk-memory", - "--enable-sign-ext" + "--enable-sign-ext", + "--enable-simd" ] } -- END METADATA -- diff --git a/test/passes/limit-segments.txt b/test/passes/limit-segments_disable-bulk-memory.txt index 3ef5da445..3ef5da445 100644 --- a/test/passes/limit-segments.txt +++ b/test/passes/limit-segments_disable-bulk-memory.txt diff --git a/test/passes/limit-segments.wast b/test/passes/limit-segments_disable-bulk-memory.wast index 7771cacde..7771cacde 100644 --- a/test/passes/limit-segments.wast +++ b/test/passes/limit-segments_disable-bulk-memory.wast diff --git a/test/spec/globals.wast b/test/spec/globals.wast index 02ef4dc04..ca3100a64 100644 --- a/test/spec/globals.wast +++ b/test/spec/globals.wast @@ -52,26 +52,6 @@ ) (assert_invalid - (module (import "m" "a" (global (mut i32)))) - "mutable globals cannot be imported" -) - -(assert_invalid - (module (global (import "m" "a") (mut i32))) - "mutable globals cannot be imported" -) - -(assert_invalid - (module (global (mut f32) (f32.const 0)) (export "a" (global 0))) - "mutable globals cannot be exported" -) - -(assert_invalid - (module (global (export "a") (mut f32) (f32.const 0))) - "mutable globals cannot be exported" -) - -(assert_invalid (module (global f32 (f32.neg (f32.const 0)))) "constant expression required" ) diff --git a/test/unit/test_features.py b/test/unit/test_features.py index f9c410414..f130e1408 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -124,23 +124,37 @@ class TargetFeaturesSectionTest(unittest.TestCase): self.assertEqual(p.stderr, '') return p.stdout + def roundtrip(self, filename): + path = os.path.join(options.binaryen_test, 'unit', 'input', filename) + p = run_process(WASM_OPT + ['-g', '-o', '-', path], check=False, + capture_output=True) + self.assertEqual(p.returncode, 0) + self.assertEqual(p.stderr, '') + with open(path, 'rb') as f: + self.assertEqual(str(p.stdout), str(f.read())) + def test_atomics(self): + self.roundtrip('atomics_target_feature.wasm') module = self.disassemble('atomics_target_feature.wasm') self.assertIn('i32.atomic.rmw.add', module) def test_bulk_memory(self): + self.roundtrip('bulkmem_target_feature.wasm') module = self.disassemble('bulkmem_target_feature.wasm') self.assertIn('memory.copy', module) def test_nontrapping_fptoint(self): + self.roundtrip('truncsat_target_feature.wasm') module = self.disassemble('truncsat_target_feature.wasm') self.assertIn('i32.trunc_sat_f32_u', module) def test_sign_ext(self): + self.roundtrip('signext_target_feature.wasm') module = self.disassemble('signext_target_feature.wasm') self.assertIn('i32.extend8_s', module) def test_simd(self): + self.roundtrip('simd_target_feature.wasm') module = self.disassemble('simd_target_feature.wasm') self.assertIn('i32x4.splat', module) |