From d2f54b40e736cdee2a0ff9fc15e9323ccb9d7c20 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Sat, 19 Mar 2022 02:29:35 +0000 Subject: [PATCH] Add support for extended-const proposal (#4529) See https://github.com/WebAssembly/extended-const --- src/binaryen-c.cpp | 3 ++ src/binaryen-c.h | 1 + src/ir/global-utils.h | 10 +++---- src/ir/properties.h | 20 +++++++++++++ src/js/binaryen.js-post.js | 1 + src/tools/tool-options.h | 1 + src/wasm-binary.h | 1 + src/wasm-features.h | 7 ++++- src/wasm/wasm-binary.cpp | 4 +++ src/wasm/wasm-validator.cpp | 29 +++++++++++++------ src/wasm/wasm.cpp | 1 + test/binaryen.js/kitchen-sink.js | 1 + test/binaryen.js/kitchen-sink.js.txt | 3 +- test/example/c-api-kitchen-sink.c | 1 + test/example/c-api-kitchen-sink.txt | 3 +- test/lit/help/wasm-as.test | 4 +++ test/lit/help/wasm-ctor-eval.test | 4 +++ test/lit/help/wasm-dis.test | 4 +++ test/lit/help/wasm-emscripten-finalize.test | 4 +++ test/lit/help/wasm-metadce.test | 4 +++ test/lit/help/wasm-opt.test | 6 ++++ test/lit/help/wasm-reduce.test | 4 +++ test/lit/help/wasm-split.test | 4 +++ test/lit/help/wasm2js.test | 6 ++++ test/lit/validation/extended-const.wast | 24 +++++++++++++++ ..._roundtrip_print-features_all-features.txt | 1 + test/unit/test_features.py | 1 + 27 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 test/lit/validation/extended-const.wast diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index df868dc62..d884626b2 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -252,6 +252,9 @@ BinaryenFeatures BinaryenFeatureTypedFunctionReferences(void) { BinaryenFeatures BinaryenFeatureRelaxedSIMD(void) { return static_cast(FeatureSet::RelaxedSIMD); } +BinaryenFeatures BinaryenFeatureExtendedConst(void) { + return static_cast(FeatureSet::ExtendedConst); +} BinaryenFeatures BinaryenFeatureAll(void) { return static_cast(FeatureSet::All); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 7511a65a4..e5a02d556 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -162,6 +162,7 @@ BINARYEN_API BinaryenFeatures BinaryenFeatureGC(void); BINARYEN_API BinaryenFeatures BinaryenFeatureMemory64(void); BINARYEN_API BinaryenFeatures BinaryenFeatureTypedFunctionReferences(void); BINARYEN_API BinaryenFeatures BinaryenFeatureRelaxedSIMD(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureExtendedConst(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void); // Modules diff --git a/src/ir/global-utils.h b/src/ir/global-utils.h index 26aec7790..ca00047a0 100644 --- a/src/ir/global-utils.h +++ b/src/ir/global-utils.h @@ -53,20 +53,18 @@ getGlobalInitializedToImport(Module& wasm, Name module, Name base) { return ret; } -inline bool canInitializeGlobal(Expression* curr) { +inline bool canInitializeGlobal(Expression* curr, FeatureSet features) { if (auto* tuple = curr->dynCast()) { for (auto* op : tuple->operands) { - if (!canInitializeGlobal(op)) { + if (!canInitializeGlobal(op, features)) { return false; } } return true; } - if (Properties::isSingleConstantExpression(curr) || curr->is() || - curr->is() || curr->is() || curr->is() || - curr->is() || curr->is() || curr->is()) { + if (Properties::isValidInConstantExpression(curr, features)) { for (auto* child : ChildIterator(curr)) { - if (!canInitializeGlobal(child)) { + if (!canInitializeGlobal(child, features)) { return false; } } diff --git a/src/ir/properties.h b/src/ir/properties.h index d2c5affd2..07898169f 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -414,6 +414,26 @@ inline bool canEmitSelectWithArms(Expression* ifTrue, Expression* ifFalse) { // bool isGenerative(Expression* curr, FeatureSet features); +inline bool isValidInConstantExpression(Expression* expr, FeatureSet features) { + if (isSingleConstantExpression(expr) || expr->is() || + expr->is() || expr->is() || expr->is() || + expr->is() || expr->is() || expr->is()) { + return true; + } + + if (features.hasExtendedConst()) { + if (expr->is()) { + auto bin = static_cast(expr); + if (bin->op == AddInt64 || bin->op == SubInt64 || bin->op == MulInt64 || + bin->op == AddInt32 || bin->op == SubInt32 || bin->op == MulInt32) { + return true; + } + } + } + + return false; +} + } // namespace wasm::Properties #endif // wasm_ir_properties_h diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index ee90aa1fc..7dfeee26c 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -149,6 +149,7 @@ function initializeConstants() { 'Memory64', 'TypedFunctionReferences', 'RelaxedSIMD', + 'ExtendedConst', 'All' ].forEach(name => { Module['Features'][name] = Module['_BinaryenFeature' + name](); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index f8278e950..bc948fc97 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -93,6 +93,7 @@ struct ToolOptions : public Options { "typed function references") .addFeature(FeatureSet::GCNNLocals, "GC non-null locals") .addFeature(FeatureSet::RelaxedSIMD, "relaxed SIMD") + .addFeature(FeatureSet::ExtendedConst, "extended const expressions") .add("--no-validation", "-n", "Disables validation, assumes inputs are correct", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index b31447ed9..051f77f58 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -425,6 +425,7 @@ extern const char* GCFeature; extern const char* Memory64Feature; extern const char* TypedFunctionReferencesFeature; extern const char* RelaxedSIMDFeature; +extern const char* ExtendedConstFeature; enum Subsection { NameModule = 0, diff --git a/src/wasm-features.h b/src/wasm-features.h index 02e1660bb..df4397e78 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -42,7 +42,8 @@ struct FeatureSet { // TODO: Remove this feature when the wasm spec stabilizes. GCNNLocals = 1 << 13, RelaxedSIMD = 1 << 14, - All = (1 << 15) - 1 + ExtendedConst = 1 << 15, + All = (1 << 16) - 1 }; static std::string toString(Feature f) { @@ -77,6 +78,8 @@ struct FeatureSet { return "gc-nn-locals"; case RelaxedSIMD: return "relaxed-simd"; + case ExtendedConst: + return "extended-const"; default: WASM_UNREACHABLE("unexpected feature"); } @@ -122,6 +125,7 @@ struct FeatureSet { } bool hasGCNNLocals() const { return (features & GCNNLocals) != 0; } bool hasRelaxedSIMD() const { return (features & RelaxedSIMD) != 0; } + bool hasExtendedConst() const { return (features & ExtendedConst) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -144,6 +148,7 @@ struct FeatureSet { } void setGCNNLocals(bool v = true) { set(GCNNLocals, v); } void setRelaxedSIMD(bool v = true) { set(RelaxedSIMD, v); } + void setExtendedConst(bool v = true) { set(ExtendedConst, v); } void setMVP() { features = MVP; } void setAll() { // Do not set GCNNLocals, which forces the user to opt in to that feature diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 1ddb009cf..f43346fdb 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1084,6 +1084,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::UserSections::TypedFunctionReferencesFeature; case FeatureSet::RelaxedSIMD: return BinaryConsts::UserSections::RelaxedSIMDFeature; + case FeatureSet::ExtendedConst: + return BinaryConsts::UserSections::ExtendedConstFeature; default: WASM_UNREACHABLE("unexpected feature flag"); } @@ -3350,6 +3352,8 @@ void WasmBinaryBuilder::readFeatures(size_t payloadLen) { feature = FeatureSet::TypedFunctionReferences; } else if (name == BinaryConsts::UserSections::RelaxedSIMDFeature) { feature = FeatureSet::RelaxedSIMD; + } else if (name == BinaryConsts::UserSections::ExtendedConstFeature) { + feature = FeatureSet::ExtendedConst; } else { // Silently ignore unknown features (this may be and old binaryen running // on a new wasm). diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 65fcc7a9d..86fc4811b 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2767,13 +2767,21 @@ void FunctionValidator::visitFunction(Function* curr) { } } -static bool checkSegmentOffset(Expression* curr, Address add, Address max) { - if (curr->is()) { - return true; +static bool checkSegmentOffset(Expression* curr, + Address add, + Address max, + FeatureSet features) { + if (!Properties::isValidInConstantExpression(curr, features)) { + return false; } auto* c = curr->dynCast(); if (!c) { - return false; + // Unless the instruction is actually a const instruction, we don't + // currently try to evaluate it. + // TODO: Attempt to evaluate other expressions that might also be const + // such as `global.get` or more complex instruction sequences involving + // add/sub/mul/etc. + return true; } uint64_t raw = c->value.getInteger(); if (raw > std::numeric_limits::max()) { @@ -2999,9 +3007,10 @@ static void validateGlobals(Module& module, ValidationInfo& info) { info.shouldBeTrue( curr->init != nullptr, curr->name, "global init must be non-null"); assert(curr->init); - info.shouldBeTrue(GlobalUtils::canInitializeGlobal(curr->init), - curr->name, - "global init must be valid"); + info.shouldBeTrue( + GlobalUtils::canInitializeGlobal(curr->init, module.features), + curr->name, + "global init must be valid"); if (!info.shouldBeSubType(curr->init->type, curr->type, @@ -3066,7 +3075,8 @@ static void validateMemory(Module& module, ValidationInfo& info) { } info.shouldBeTrue(checkSegmentOffset(segment.offset, segment.data.size(), - curr.initial * Memory::kPageSize), + curr.initial * Memory::kPageSize, + module.features), segment.offset, "memory segment offset should be reasonable"); if (segment.offset->is()) { @@ -3171,7 +3181,8 @@ static void validateTables(Module& module, ValidationInfo& info) { "element segment offset should be i32"); info.shouldBeTrue(checkSegmentOffset(segment->offset, segment->data.size(), - table->initial * Table::kPageSize), + table->initial * Table::kPageSize, + module.features), segment->offset, "table segment offset should be reasonable"); if (module.features.hasTypedFunctionReferences()) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index aac8b44fe..ca9691346 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -49,6 +49,7 @@ const char* GCFeature = "gc"; const char* Memory64Feature = "memory64"; const char* TypedFunctionReferencesFeature = "typed-function-references"; const char* RelaxedSIMDFeature = "relaxed-simd"; +const char* ExtendedConstFeature = "extended-const"; } // namespace UserSections } // namespace BinaryConsts diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 30495d239..d72ffcec0 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -116,6 +116,7 @@ function test_features() { console.log("Features.Memory64: " + binaryen.Features.Memory64); console.log("Features.TypedFunctionReferences: " + binaryen.Features.TypedFunctionReferences); console.log("Features.RelaxedSIMD: " + binaryen.Features.RelaxedSIMD); + console.log("Features.ExtendedConst: " + binaryen.Features.ExtendedConst); console.log("Features.All: " + binaryen.Features.All); } diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index ed45591ea..376eee7f0 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -43,7 +43,8 @@ Features.GC: 1024 Features.Memory64: 2048 Features.TypedFunctionReferences: 4096 Features.RelaxedSIMD: 16384 -Features.All: 32767 +Features.ExtendedConst: 32768 +Features.All: 65535 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index fb3fc87b0..90784d7ac 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -303,6 +303,7 @@ void test_features() { printf("BinaryenFeatureTypedFunctionReferences: %d\n", BinaryenFeatureTypedFunctionReferences()); printf("BinaryenFeatureRelaxedSIMD: %d\n", BinaryenFeatureRelaxedSIMD()); + printf("BinaryenFeatureExtendedConst: %d\n", BinaryenFeatureExtendedConst()); printf("BinaryenFeatureAll: %d\n", BinaryenFeatureAll()); } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index f5e5d6d67..f759864a8 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -27,7 +27,8 @@ BinaryenFeatureGC: 1024 BinaryenFeatureMemory64: 2048 BinaryenFeatureTypedFunctionReferences: 4096 BinaryenFeatureRelaxedSIMD: 16384 -BinaryenFeatureAll: 32767 +BinaryenFeatureExtendedConst: 32768 +BinaryenFeatureAll: 65535 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index cc3b350a7..50ab57355 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -100,6 +100,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 183094f8f..518d536e3 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -104,6 +104,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index b4573d1a6..c2b4af271 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -93,6 +93,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 15ae1c176..2f9ae3a31 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -143,6 +143,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 80dfc3183..99a830065 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -141,6 +141,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 183106e4d..caf6437fc 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -593,6 +593,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const +;; CHECK-NEXT: expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const +;; CHECK-NEXT: expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes ;; CHECK-NEXT: inputs are correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 5c6cb05cc..2360ed508 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -129,6 +129,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 6319f475f..f79d1c0c7 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -187,6 +187,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes inputs are ;; CHECK-NEXT: correct ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 0b03efa27..ab169a1a3 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -555,6 +555,12 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-relaxed-simd Disable relaxed SIMD ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-extended-const Enable extended const +;; CHECK-NEXT: expressions +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-extended-const Disable extended const +;; CHECK-NEXT: expressions +;; CHECK-NEXT: ;; CHECK-NEXT: --no-validation,-n Disables validation, assumes ;; CHECK-NEXT: inputs are correct ;; CHECK-NEXT: diff --git a/test/lit/validation/extended-const.wast b/test/lit/validation/extended-const.wast new file mode 100644 index 000000000..4269ca2b7 --- /dev/null +++ b/test/lit/validation/extended-const.wast @@ -0,0 +1,24 @@ +;; Test that shared memory requires atomics + +;; RUN: not wasm-opt %s 2>&1 | filecheck %s --check-prefix NO-EXTENDED +;; RUN: wasm-opt %s --enable-extended-const -o - -S | filecheck %s --check-prefix EXTENDED + +;; NO-EXTENDED: unexpected false: global init must be valid +;; NO-EXTENDED: unexpected false: memory segment offset should be reasonable + +;; EXTENDED: (import "env" "global" (global $gimport$0 i32)) +;; EXTENDED: (global $1 i32 (i32.add +;; EXTENDED: (global.get $gimport$0) +;; EXTENDED: (i32.const 42) +;; EXTENDED: )) +;; EXTENDED: (data (i32.sub +;; EXTENDED: (global.get $gimport$0) +;; EXTENDED: (i32.const 10) +;; EXTENDED: ) "hello world") + +(module + (memory 1 1) + (import "env" "global" (global i32)) + (global i32 (i32.add (global.get 0) (i32.const 42))) + (data (i32.sub (global.get 0) (i32.const 10)) "hello world") +) diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 5feeecc20..53fd2b237 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -12,6 +12,7 @@ --enable-memory64 --enable-typed-function-references --enable-relaxed-simd +--enable-extended-const (module (type $none_=>_v128_externref (func (result v128 externref))) (func $foo (result v128 externref) diff --git a/test/unit/test_features.py b/test/unit/test_features.py index 93f715ef9..408084086 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -397,4 +397,5 @@ class TargetFeaturesSectionTest(utils.BinaryenTestCase): '--enable-memory64', '--enable-typed-function-references', '--enable-relaxed-simd', + '--enable-extended-const', ], p2.stdout.splitlines())