diff --git a/README.md b/README.md index b3809263edc..0f48cf7c838 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,15 @@ There are a few differences between Binaryen IR and the WebAssembly language: rare cases (we avoid this overhead in the common case where the `br_if` value is unused). + * Strings + + * When the string builtins feature is enabled (`--enable-string-builtins`), + string operations are optimized. First, string imports are lifted into + stringref operations, before any default optimization passes. Those + stringref operations can then be optimized (e.g., a concat of constants + turns into a concatenated constant). When we are about to finish running + default optimizations, we lower stringref back into string builtins. + As a result, you might notice that round-trip conversions (wasm => Binaryen IR => wasm) change code a little in some corner cases. diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index b074106b271..8f84ae3fc00 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -19,10 +19,13 @@ unfuzzable = [ # Float16 is still experimental. 'f16.wast', - # TODO: fuzzer and interpreter support for strings + # TODO: fuzzer and interpreter support for strings, including limitations + # like the fuzzer not handling (ref extern) imports (there is no way + # to create a replacement value) 'strings.wast', 'simplify-locals-strings.wast', 'string-lowering-instructions.wast', + 'string-builtins.wast', # TODO: fuzzer and interpreter support for extern conversions 'extern-conversions.wast', # ignore DWARF because it is incompatible with multivalue atm diff --git a/src/pass.h b/src/pass.h index 0ebd7e6d6bd..5b255e6a6ad 100644 --- a/src/pass.h +++ b/src/pass.h @@ -324,18 +324,30 @@ struct PassRunner { // warning. void addIfNoDWARFIssues(std::string passName); - // Adds the default set of optimization passes; this is - // what -O does. - void addDefaultOptimizationPasses(); + // By default, we do not know if we are running first in the ordering of + // optimization passes, or last - we could be anywhere. + struct Ordering { + bool first; + bool last; + }; + static constexpr Ordering UnknownOrdering = {false, false}; + + // Adds the default set of optimization passes; this is what -O does. + // + // The ordering indicates our position relative to other default + // optimizations, that is, if ordering.first then we are first. + void addDefaultOptimizationPasses(Ordering ordering = UnknownOrdering); // Adds the default optimization passes that work on // individual functions. - void addDefaultFunctionOptimizationPasses(); + void + addDefaultFunctionOptimizationPasses(Ordering ordering = UnknownOrdering); // Adds the default optimization passes that work on // entire modules as a whole, and make sense to // run before function passes. - void addDefaultGlobalOptimizationPrePasses(); + void + addDefaultGlobalOptimizationPrePasses(Ordering ordering = UnknownOrdering); // Adds the default optimization passes that work on // entire modules as a whole, and make sense to @@ -343,7 +355,8 @@ struct PassRunner { // This is run at the very end of the optimization // process - you can assume no other opts will be run // afterwards. - void addDefaultGlobalOptimizationPostPasses(); + void + addDefaultGlobalOptimizationPostPasses(Ordering ordering = UnknownOrdering); // Run the passes on the module void run(); diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 2042bc71d3a..4a8b87f6484 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -605,13 +605,13 @@ void PassRunner::addIfNoDWARFIssues(std::string passName) { } } -void PassRunner::addDefaultOptimizationPasses() { - addDefaultGlobalOptimizationPrePasses(); - addDefaultFunctionOptimizationPasses(); - addDefaultGlobalOptimizationPostPasses(); +void PassRunner::addDefaultOptimizationPasses(Ordering ordering) { + addDefaultGlobalOptimizationPrePasses(ordering); + addDefaultFunctionOptimizationPasses(ordering); + addDefaultGlobalOptimizationPostPasses(ordering); } -void PassRunner::addDefaultFunctionOptimizationPasses() { +void PassRunner::addDefaultFunctionOptimizationPasses(Ordering ordering) { // All the additions here are optional if DWARF must be preserved. That is, // when DWARF is relevant we run fewer optimizations. // FIXME: support DWARF in all of them. @@ -723,7 +723,17 @@ void PassRunner::addDefaultFunctionOptimizationPasses() { addIfNoDWARFIssues("vacuum"); // just to be safe } -void PassRunner::addDefaultGlobalOptimizationPrePasses() { +void PassRunner::addDefaultGlobalOptimizationPrePasses(Ordering ordering) { + // If we are optimizing string builtins then we lift at the very start of the + // optimization pipeline, not just at the beginning here, but only when we are + // ordered before other bundles of passes. + // + // We check for GC for symmetry with the lowering pass, see comment in + // addDefaultGlobalOptimizationPostPasses() below. + if (wasm->features.hasStringBuiltins() && wasm->features.hasGC() && + options.optimizeLevel >= 2 && ordering.first) { + addIfNoDWARFIssues("string-lifting"); + } // Removing duplicate functions is fast and saves work later. addIfNoDWARFIssues("duplicate-function-elimination"); // Do a global cleanup before anything heavy, as it is fairly fast and can @@ -772,7 +782,7 @@ void PassRunner::add(std::string passName, std::optional passArg) { doAdd(std::move(pass)); } -void PassRunner::addDefaultGlobalOptimizationPostPasses() { +void PassRunner::addDefaultGlobalOptimizationPostPasses(Ordering ordering) { if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) { addIfNoDWARFIssues("dae-optimizing"); } @@ -794,6 +804,20 @@ void PassRunner::addDefaultGlobalOptimizationPostPasses() { } else { addIfNoDWARFIssues("simplify-globals"); } + + // Lower away strings at the very very end. We do this before + // remove-unused-module-elements so we don't add unused imports, and also + // before reorder-globals, which will sort the new globals. + // + // Note we also test for GC here, as the pass adds imports that use GC arrays + // (and externref). Those imports may be unused, but they exist until + // remove-unused-module-elements cleans them up, which would cause an error in + // between. + if (wasm->features.hasStringBuiltins() && wasm->features.hasGC() && + options.optimizeLevel >= 2 && ordering.last) { + addIfNoDWARFIssues("string-lowering-magic-imports"); + } + addIfNoDWARFIssues("remove-unused-module-elements"); if (options.optimizeLevel >= 2 && wasm->features.hasStrings()) { // Gather strings to globals right before reorder-globals, which will then diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index 333380d0490..03ccc0fd53e 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -398,7 +398,19 @@ struct OptimizationOptions : public ToolOptions { passRunner.clear(); }; - for (auto& pass : passes) { + // Find the first and last default opt passes, so we can tell them they are + // first/last. + Index firstDefault = passes.size(); + Index lastDefault = passes.size(); + for (Index i = 0; i < passes.size(); i++) { + if (passes[i].name == DEFAULT_OPT_PASSES) { + firstDefault = std::min(firstDefault, i); + lastDefault = i; + } + } + + for (Index i = 0; i < passes.size(); i++) { + auto& pass = passes[i]; if (pass.name == DEFAULT_OPT_PASSES) { // This is something like -O3 or -Oz. We must run this now, in order to // set the proper opt and shrink levels. To do that, first reset the @@ -416,8 +428,13 @@ struct OptimizationOptions : public ToolOptions { passRunner.options.optimizeLevel = *pass.optimizeLevel; passRunner.options.shrinkLevel = *pass.shrinkLevel; + // Note the ordering of these default passes. + PassRunner::Ordering ordering; + ordering.first = (i == firstDefault); + ordering.last = (i == lastDefault); + // Run our optimizations now with the custom levels. - passRunner.addDefaultOptimizationPasses(); + passRunner.addDefaultOptimizationPasses(ordering); flush(); // Restore the default optimize/shrinkLevels. diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 3c42b6b1f13..eec2dd30a50 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -108,6 +108,8 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::FP16, "float 16 operations") .addFeature(FeatureSet::CustomDescriptors, "custom descriptors (RTTs) and exact references") + .addFeature(FeatureSet::StringBuiltins, + "string builtins (imported JS strings)") .add("--enable-typed-function-references", "", "Deprecated compatibility flag", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5ee7452060a..c21590bd278 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -454,6 +454,7 @@ extern const char* FP16Feature; extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; extern const char* CustomDescriptorsFeature; +extern const char* StringBuiltinsFeature; enum Subsection { NameModule = 0, diff --git a/src/wasm-features.h b/src/wasm-features.h index a7c3ce0c4f5..f928f78445e 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -55,11 +55,12 @@ struct FeatureSet { // it does nothing. Binaryen always accepts LEB call-indirect encodings. CallIndirectOverlong = 1 << 20, CustomDescriptors = 1 << 21, + StringBuiltins = 1 << 22, MVP = None, // Keep in sync with llvm default features: // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153 Default = SignExt | MutableGlobals, - All = (1 << 22) - 1, + All = (1 << 23) - 1, }; static std::string toString(Feature f) { @@ -108,6 +109,8 @@ struct FeatureSet { return "call-indirect-overlong"; case CustomDescriptors: return "custom-descriptors"; + case StringBuiltins: + return "string-builtins"; case MVP: case Default: case All: @@ -168,6 +171,7 @@ struct FeatureSet { bool hasCustomDescriptors() const { return (features & CustomDescriptors) != 0; } + bool hasStringBuiltins() const { return (features & StringBuiltins) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -194,6 +198,7 @@ struct FeatureSet { void setFP16(bool v = true) { set(FP16, v); } void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); } void setCustomDescriptors(bool v = true) { set(CustomDescriptors, v); } + void setStringBuiltins(bool v = true) { set(StringBuiltins, v); } void setMVP() { features = MVP; } void setAll() { features = All; } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 4421a4f471c..7f76231ccb9 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1395,6 +1395,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::CallIndirectOverlongFeature; case FeatureSet::CustomDescriptors: return BinaryConsts::CustomSections::CustomDescriptorsFeature; + case FeatureSet::StringBuiltins: + return BinaryConsts::CustomSections::StringBuiltinsFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 655a3156382..6a96db33850 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -61,6 +61,7 @@ const char* FP16Feature = "fp16"; const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; const char* CustomDescriptorsFeature = "custom-descriptors"; +const char* StringBuiltinsFeature = "string-builtins"; } // namespace BinaryConsts::CustomSections diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 45c7417dcda..b9fdf381739 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -33,7 +33,7 @@ Features.RelaxedSIMD: 4096 Features.ExtendedConst: 8192 Features.Strings: 16384 Features.MultiMemory: 32768 -Features.All: 4194303 +Features.All: 8388607 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 1b798654d1a..1839597deca 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -47,7 +47,7 @@ BinaryenFeatureMemory64: 2048 BinaryenFeatureRelaxedSIMD: 4096 BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 -BinaryenFeatureAll: 4194303 +BinaryenFeatureAll: 8388607 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 77ae850e4df..f5d57fc46bb 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -134,6 +134,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 9a5fbdcda26..60b1c175598 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -141,6 +141,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index a10b41a4f3c..b2ca9cfbc15 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -127,6 +127,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 4cb9ef940a0..06f5bc556b2 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -169,6 +169,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index a8b95194950..25dbc0723ba 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -157,6 +157,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 5870b5c9d45..c4ba77e67a6 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -787,6 +787,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported +;; CHECK-NEXT: JS strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins +;; CHECK-NEXT: (imported JS strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index ac98198d2dd..cdc91c15333 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -799,6 +799,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported +;; CHECK-NEXT: JS strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins +;; CHECK-NEXT: (imported JS strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 2739394e3ed..739388333c8 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -217,6 +217,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 095c66047ac..abcc9607482 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -266,6 +266,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins (imported JS +;; CHECK-NEXT: strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index ac68667554b..ec69865fa22 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -751,6 +751,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-string-builtins Enable string builtins (imported +;; CHECK-NEXT: JS strings) +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-string-builtins Disable string builtins +;; CHECK-NEXT: (imported JS strings) +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/string-builtins.wast b/test/lit/string-builtins.wast new file mode 100644 index 00000000000..68a82f48d08 --- /dev/null +++ b/test/lit/string-builtins.wast @@ -0,0 +1,60 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Run normal -O2, which should lift, optimize, and lower strings, if the +;; string builtins feature is enabled. We optimize in the last two here. + +;; RUN: foreach %s %t wasm-opt -O2 --enable-reference-types -S -o - | filecheck %s --check-prefix=MVP +;; RUN: foreach %s %t wasm-opt -O2 -all -S -o - | filecheck %s --check-prefix=ALL +;; RUN: foreach %s %t wasm-opt -O2 --enable-reference-types --enable-string-builtins -S -o - | filecheck %s --check-prefix=ESB + +(module + (type $array16 (array (mut i16))) + + ;; MVP: (type $0 (func (param externref externref) (result (ref extern)))) + + ;; MVP: (type $1 (func (result (ref extern)))) + + ;; MVP: (import "\'" "foo" (global $foo (ref extern))) + (import "\'" "foo" (global $foo (ref extern))) + + ;; MVP: (import "\'" "bar" (global $bar (ref extern))) + (import "\'" "bar" (global $bar (ref extern))) + + ;; MVP: (import "wasm:js-string" "concat" (func $concat (param externref externref) (result (ref extern)))) + (import "wasm:js-string" "concat" (func $concat (param externref externref) (result (ref extern)))) + + ;; MVP: (export "string.concat" (func $string.concat)) + + ;; MVP: (func $string.concat (result (ref extern)) + ;; MVP-NEXT: (call $concat + ;; MVP-NEXT: (global.get $foo) + ;; MVP-NEXT: (global.get $bar) + ;; MVP-NEXT: ) + ;; MVP-NEXT: ) + ;; ALL: (type $0 (func (result (ref extern)))) + + ;; ALL: (import "\'" "foobar" (global $"string.const_\"foobar\"" (ref extern))) + + ;; ALL: (export "string.concat" (func $string.concat)) + + ;; ALL: (func $string.concat (type $0) (result (ref extern)) + ;; ALL-NEXT: (global.get $"string.const_\"foobar\"") + ;; ALL-NEXT: ) + ;; ESB: (type $0 (func (result (ref extern)))) + + ;; ESB: (import "\'" "foobar" (global $"string.const_\"foobar\"" (ref extern))) + + ;; ESB: (export "string.concat" (func $string.concat)) + + ;; ESB: (func $string.concat (result (ref extern)) + ;; ESB-NEXT: (global.get $"string.const_\"foobar\"") + ;; ESB-NEXT: ) + (func $string.concat (export "string.concat") (result (ref extern)) + ;; When we optimize, we concatenate "foo" and "bar" here to "foobar". A new + ;; imported global will appear for that, and we will get it here. + (call $concat + (global.get $foo) + (global.get $bar) + ) + ) +) 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 34976434e9d..2f012471e1e 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 @@ -20,6 +20,7 @@ --enable-bulk-memory-opt --enable-call-indirect-overlong --enable-custom-descriptors +--enable-string-builtins (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) diff --git a/test/reduce/gc.wast.txt b/test/reduce/gc.wast.txt index 3af5287ce99..e19d3da34ac 100644 --- a/test/reduce/gc.wast.txt +++ b/test/reduce/gc.wast.txt @@ -1,7 +1,7 @@ (module (type $0 (struct (field (mut i32)) (field funcref))) (type $1 (func (result i32))) - (global $global$0 (ref null $0) (struct.new_default $0)) + (global $global$0 (ref (exact $0)) (struct.new_default $0)) (export "use-global" (func $0)) (func $0 (result i32) (struct.set $0 0 diff --git a/test/unit/test_features.py b/test/unit/test_features.py index 2647ff84717..aede708dc67 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -453,4 +453,5 @@ def test_emit_all_features(self): '--enable-bulk-memory-opt', '--enable-call-indirect-overlong', '--enable-custom-descriptors', + '--enable-string-builtins', ], p2.stdout.splitlines()) diff --git a/test/unit/test_passes.py b/test/unit/test_passes.py index 838f1d2f73f..446ed377ab7 100644 --- a/test/unit/test_passes.py +++ b/test/unit/test_passes.py @@ -61,3 +61,18 @@ def test_O3_O1(self): self.assertNotIn(PASS_IN_O3_ONLY, self.get_passes_run(['-O1'])) self.assertNotIn(PASS_IN_O3_ONLY, self.get_passes_run(['-O1', '-O1'])) + + def test_string_builtins(self): + # When we enable string builtins, we lift early and lower late, and + # only do each once even if there are multiple -O2 operations. + passes = self.get_passes_run(['-O2', '-O2', '-all']) + self.assertEqual(passes.count('string-lifting'), 1) + self.assertEqual(passes.count('string-lowering'), 1) + + # Other passes appear twice, when -O2 is repeated + self.assertEqual(passes.count('directize'), 2) + + # Without the feature, we do not lift or lower. + passes = self.get_passes_run(['-O2', '-O2', '-all', '--disable-string-builtins']) + self.assertEqual(passes.count('string-lifting'), 0) + self.assertEqual(passes.count('string-lowering'), 0)