diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 5908a63a569..5365181619d 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2865,6 +2865,9 @@ def get_random_opts(): # value (the same used in j2wasm atm) if random.random() < 0.5: ret += ['-pii=4'] + # fuzz both with and without expecting later toolchain opts + if random.random() < 0.5: + ret += ['--expect-later-toolchain-opts'] # test both closed and open world if CLOSED_WORLD: ret += [CLOSED_WORLD_FLAG] diff --git a/src/ir/function-utils.h b/src/ir/function-utils.h index 2d3cf9d94fd..92e814c6434 100644 --- a/src/ir/function-utils.h +++ b/src/ir/function-utils.h @@ -26,7 +26,7 @@ namespace wasm::FunctionUtils { // Checks if two functions are equal in all functional aspects, // everything but their name (which can't be the same, in the same // module!) - same params, vars, body, result, etc. -inline bool equal(Function* left, Function* right) { +inline bool equal(Function* left, Function* right, const PassOptions& options) { if (left->type != right->type) { return false; } @@ -44,10 +44,16 @@ inline bool equal(Function* left, Function* right) { // only in metadata (following LLVM's example). If we have a non-optimization // reason for comparing metadata here then we could add a flag for it. We do, // however, compare metadata that changes semantics (not debug info, but - // things like jsCalled). We do that check conservatively, erasing the non- - // semantic-altering ones and then comparing (so that new annotations + // things like removableIfUnused). auto leftAnnotations = Intrinsics::getAnnotations(left); auto rightAnnotations = Intrinsics::getAnnotations(right); + if (options.lastOpts) { + // If these are the last toolchain opts, we do not need to worry about + // inhibiting others by merging a jsCalled function with a non-jsCalled one + // (after the merge, both will be annotated, preventing optimizations that + // would be a problem in the presence of JS calls). + leftAnnotations.jsCalled = rightAnnotations.jsCalled = false; + } if (!leftAnnotations.equalOnSemanticsAltering(rightAnnotations)) { return false; } diff --git a/src/pass.h b/src/pass.h index 8bbf9612a90..b92926c076a 100644 --- a/src/pass.h +++ b/src/pass.h @@ -239,6 +239,11 @@ struct PassOptions { // that needs lowering, then we lower it, then we can optimize it again to the // original form). bool targetJS = false; + // Whether we are in the last major sequence of optimizations being run. If so + // then we can perform optimizations that only make sense to do at the very + // end (because they might inhibit further ones after). For example, if we run + // -O3 -O3 then in the second -O3 we might do more things. + bool lastOpts = false; // Arbitrary string arguments from the commandline, which we forward to // passes. std::unordered_map arguments; diff --git a/src/passes/DuplicateFunctionElimination.cpp b/src/passes/DuplicateFunctionElimination.cpp index 88253e00583..b004c6a3e71 100644 --- a/src/passes/DuplicateFunctionElimination.cpp +++ b/src/passes/DuplicateFunctionElimination.cpp @@ -83,10 +83,13 @@ struct DuplicateFunctionElimination : public Pass { if (duplicates.contains(second->name)) { continue; } - if (FunctionUtils::equal(first, second)) { - // great, we can replace the second with the first! + if (FunctionUtils::equal(first, second, options)) { + // Great, we can replace the second with the first! replacements[second->name] = first->name; duplicates.insert(second->name); + if (second->funcAnnotations.jsCalled) { + first->funcAnnotations.jsCalled = true; + } } } } diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index a43c92c3fe7..fe8af0e82d6 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -67,6 +67,11 @@ struct OptimizationOptions : public ToolOptions { std::vector passes; + // Normally we assume no wasm-opt or other toolchain opts happen after us, + // but the user can tell us otherwise, which inhibits some opts (ones that + // limit subsequent opts). + bool expectLaterToolchainOpts = false; + // Add a request to run all the default opt passes. They are run with the // current opt and shrink levels specified, which are read from passOptions. // @@ -319,6 +324,15 @@ struct OptimizationOptions : public ToolOptions { [this](Options*, const std::string&) { passOptions.zeroFilledMemory = true; }) + .add("--expect-later-toolchain-opts", + "", + "Expect further invocations of wasm-opt later, avoiding " + "optimizations that might inhibit them", + OptimizationOptionsCategory, + Options::Arguments::Zero, + [this](Options*, const std::string&) { + expectLaterToolchainOpts = true; + }) .add("--skip-pass", "-sp", "Skip a pass (do not run it)", @@ -410,7 +424,45 @@ struct OptimizationOptions : public ToolOptions { passRunner.clear(); }; - for (auto& pass : passes) { + // If we do not expect later toolchain opts, then this invocation of + // wasm-opt is the last one, and the last invocation of the default opts is + // the last one. Find that invocation and mark lastOpts=true there, which + // will then remain true until the end. + // + // That is, imagine we are called with this: + // + // wasm-opt --dce -O3 --rse -Oz --licm + // ^^ + // Then lastOpts is set here --^^ + // And it applies from then on: [.......] + // That is, we mark lastOpts on the -Oz (after --dce -O3 --rse) and it + // applies to the -Oz and the --licm. + // + // The idea is that the last -O3/-Oz/etc. is the last big cycle of opts, and + // we can assume it is the last (and other specific passes are assumed to be + // "last" if they are after it). + // + // TODO: We could maybe add a --begin-last-opts flag (whose position on the + // commandline matters) if we needed more fine-grained control here. + passRunner.options.lastOpts = false; + Index lastDefaultOptsIndex = Index(-1); + if (!expectLaterToolchainOpts) { + for (Index i = 0; i < passes.size(); ++i) { + if (passes[i].name == DEFAULT_OPT_PASSES) { + lastDefaultOptsIndex = i; + } + } + + if (lastDefaultOptsIndex == Index(-1)) { + // No default opts run at all. Just mark lastOpts now, so it applies to + // the non-default opts (same as it would apply to all non-default opts + // after the last default ones). + passRunner.options.lastOpts = true; + } + } + + 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 @@ -428,6 +480,11 @@ struct OptimizationOptions : public ToolOptions { passRunner.options.optimizeLevel = *pass.optimizeLevel; passRunner.options.shrinkLevel = *pass.shrinkLevel; + // If these are the last default opts, mark that. + if (i == lastDefaultOptsIndex) { + passRunner.options.lastOpts = true; + } + // Run our optimizations now with the custom levels. passRunner.addDefaultOptimizationPasses(); flush(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 38b112e5820..ea45a3ce6fe 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -696,6 +696,11 @@ ;; CHECK-NEXT: --zero-filled-memory,-uim Assume that an imported memory ;; CHECK-NEXT: will be zero-initialized ;; CHECK-EMPTY: +;; CHECK-NEXT: --expect-later-toolchain-opts Expect further invocations of +;; CHECK-NEXT: wasm-opt later, avoiding +;; CHECK-NEXT: optimizations that might inhibit +;; CHECK-NEXT: them +;; CHECK-EMPTY: ;; CHECK-NEXT: --skip-pass,-sp Skip a pass (do not run it) ;; CHECK-EMPTY: ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 058e63d144d..90c3b2990a3 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -741,6 +741,11 @@ ;; CHECK-NEXT: --zero-filled-memory,-uim Assume that an imported memory ;; CHECK-NEXT: will be zero-initialized ;; CHECK-EMPTY: +;; CHECK-NEXT: --expect-later-toolchain-opts Expect further invocations of +;; CHECK-NEXT: wasm-opt later, avoiding +;; CHECK-NEXT: optimizations that might inhibit +;; CHECK-NEXT: them +;; CHECK-EMPTY: ;; CHECK-NEXT: --skip-pass,-sp Skip a pass (do not run it) ;; CHECK-EMPTY: ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index fa0be35fa25..fdce36ac338 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -660,6 +660,11 @@ ;; CHECK-NEXT: --zero-filled-memory,-uim Assume that an imported memory ;; CHECK-NEXT: will be zero-initialized ;; CHECK-EMPTY: +;; CHECK-NEXT: --expect-later-toolchain-opts Expect further invocations of +;; CHECK-NEXT: wasm-opt later, avoiding +;; CHECK-NEXT: optimizations that might inhibit +;; CHECK-NEXT: them +;; CHECK-EMPTY: ;; CHECK-NEXT: --skip-pass,-sp Skip a pass (do not run it) ;; CHECK-EMPTY: ;; CHECK-EMPTY: diff --git a/test/lit/passes/duplicate-function-elimination_annotations.wast b/test/lit/passes/duplicate-function-elimination_annotations.wast index 64ed8c0f471..804eb6c6c57 100644 --- a/test/lit/passes/duplicate-function-elimination_annotations.wast +++ b/test/lit/passes/duplicate-function-elimination_annotations.wast @@ -1,18 +1,17 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. -;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --all-features -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --all-features -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --all-features --expect-later-toolchain-opts -S -o - | filecheck %s --check-prefix=LATER -;; We should not merge functions differing in semantics-altering annotations -;; like js.called and removable.if.unused. - -;; One function has the js.called hint, so we do not merge. +;; One function has the js.called hint, so we merge only if we do not expect +;; later toolchain opts (and keep the hint on the merged function). (module ;; CHECK: (type $0 (func (param i32))) ;; CHECK: (export "a" (func $a)) - ;; CHECK: (export "b" (func $b)) + ;; CHECK: (export "b" (func $a)) ;; CHECK: (@binaryen.js.called) ;; CHECK-NEXT: (func $a (type $0) (param $x i32) @@ -23,6 +22,21 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (@binaryen.js.called) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.js.called) (func $a (export "a") (param $x i32) (if @@ -33,14 +47,14 @@ ) ) - ;; CHECK: (func $b (type $0) (param $x i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; LATER: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (func $b (export "b") (param $x i32) (if (local.get $x) @@ -51,15 +65,17 @@ ) ) -;; Flipped, now other has it. We still do not merge. +;; Flipped, now other has it. Again, we merge only if we do not expect later +;; toolchain opts (and keep the hint on the merged function). (module ;; CHECK: (type $0 (func (param i32))) ;; CHECK: (export "a" (func $a)) - ;; CHECK: (export "b" (func $b)) + ;; CHECK: (export "b" (func $a)) - ;; CHECK: (func $a (type $0) (param $x i32) + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $a (type $0) (param $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then @@ -67,6 +83,20 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (func $a (export "a") (param $x i32) (if (local.get $x) @@ -76,15 +106,15 @@ ) ) - ;; CHECK: (@binaryen.js.called) - ;; CHECK-NEXT: (func $b (type $0) (param $x i32) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; LATER: (@binaryen.js.called) + ;; LATER-NEXT: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.js.called) (func $b (export "b") (param $x i32) (if @@ -96,7 +126,7 @@ ) ) -;; Both have it, so we do merge. +;; Both have it, so we always merge. (module ;; CHECK: (type $0 (func (param i32))) @@ -113,6 +143,21 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $a)) + + ;; LATER: (@binaryen.js.called) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.js.called) (func $a (export "a") (param $x i32) (if @@ -134,7 +179,11 @@ ) ) -;; Just one function has removable.if.unused, so we do not merge. +;; Just one function has removable.if.unused, so we do not merge. Unlike +;; js.called, we cannot merge and apply the annotation, as the annotation alters +;; semantics (whereas js.called just warns about something we should not break, +;; so applying it to more places can inhibit opts, but not break things). We +;; could *remove* it after merging, however TODO (module ;; CHECK: (type $0 (func (param i32))) @@ -151,6 +200,21 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (@binaryen.removable.if.unused) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.removable.if.unused) (func $a (export "a") (param $x i32) (if @@ -169,6 +233,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (func $b (export "b") (param $x i32) (if (local.get $x) @@ -196,6 +268,21 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (@binaryen.removable.if.unused) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.removable.if.unused) (func $a (export "a") (param $x i32) (if @@ -215,6 +302,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (@binaryen.js.called) + ;; LATER-NEXT: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.js.called) (func $b (export "b") (param $x i32) (if @@ -244,6 +340,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (@binaryen.removable.if.unused) + ;; LATER-NEXT: (@binaryen.js.called) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.removable.if.unused) (@binaryen.js.called) (func $a (export "a") (param $x i32) @@ -264,6 +376,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (@binaryen.js.called) + ;; LATER-NEXT: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.js.called) (func $b (export "b") (param $x i32) (if @@ -294,6 +415,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $a)) + + ;; LATER: (@binaryen.removable.if.unused) + ;; LATER-NEXT: (@binaryen.js.called) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.removable.if.unused) (@binaryen.js.called) (func $a (export "a") (param $x i32) @@ -334,6 +471,21 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (@binaryen.idempotent) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.idempotent) (func $a (export "a") (param $x i32) (if @@ -352,6 +504,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (func $b (export "b") (param $x i32) (if (local.get $x) @@ -379,6 +539,21 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (type $0 (func (param i32))) + + ;; LATER: (export "a" (func $a)) + + ;; LATER: (export "b" (func $b)) + + ;; LATER: (@binaryen.noreturn) + ;; LATER-NEXT: (func $a (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (@binaryen.noreturn) (func $a (export "a") (param $x i32) (if @@ -397,6 +572,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LATER: (func $b (type $0) (param $x i32) + ;; LATER-NEXT: (if + ;; LATER-NEXT: (local.get $x) + ;; LATER-NEXT: (then + ;; LATER-NEXT: (unreachable) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) + ;; LATER-NEXT: ) (func $b (export "b") (param $x i32) (if (local.get $x) diff --git a/test/lit/passes/last-opts.wast b/test/lit/passes/last-opts.wast new file mode 100644 index 00000000000..095c39e4d89 --- /dev/null +++ b/test/lit/passes/last-opts.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Run DFE early, before the last set of opts. At that early time, we do not +;; ignore the js.called intrinsic, so we do not merge a and b. We then extract b, +;; which will remain without the annotation (if we merged, the single remaining +;; function would be annotated). We then run -O3, which is the last set of opts +;; (this defines which the last set of opts is). As this is after the +;; extraction, so there is nothing to merge at that point, so we end up with +;; b, without the js.called annotation. +;; +;; If we considered the first DFE to be in the last set of opts, we'd merge a +;; and b, and the extraction would error on not finding b. +;; +;; RUN: foreach %s %t wasm-opt --duplicate-function-elimination --extract-function=b -O3 -S -o - | filecheck %s + +;; The same thing happens with an -O3 instead of the DFE (-O3 runs that DFE, and +;; it is not in the set of last opts). +;; RUN: foreach %s %t wasm-opt -O3 --extract-function=b -O3 -S -o - | filecheck %s + +(module + (@binaryen.js.called) + (func $a (export "a") (param $x i32) + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) + + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (export "b" (func $b)) + + ;; CHECK: (func $b (param $0 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (export "b") (param $x i32) + ;; b survives, and does not have the js.called annotation. + (if + (local.get $x) + (then + (unreachable) + ) + ) + ) +) +