summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md40
-rw-r--r--src/ir/CMakeLists.txt1
-rw-r--r--src/ir/effects.h10
-rw-r--r--src/ir/intrinsics.cpp42
-rw-r--r--src/ir/intrinsics.h95
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/Intrinsics.cpp50
-rw-r--r--src/passes/pass.cpp3
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/wasm/wasm-validator.cpp11
-rw-r--r--test/lit/help/optimization-opts.test2
-rw-r--r--test/lit/passes/intrinsic-lowering.wast53
-rw-r--r--test/lit/passes/vacuum-intrinsics.wast233
13 files changed, 540 insertions, 2 deletions
diff --git a/README.md b/README.md
index 1ad54a183..a35518c27 100644
--- a/README.md
+++ b/README.md
@@ -141,6 +141,46 @@ Notes when working with Binaryen IR:
incorrectly.
* For similar reasons, nodes should not appear in more than one functions.
+### Intrinsics
+
+Binaryen intrinsic functions look like calls to imports, e.g.,
+
+```wat
+(import "binaryen-intrinsics" "foo" (func $foo))
+```
+
+Implementing them that way allows them to be read and written by other tools,
+and it avoids confusing errors on a binary format error that could happen in
+those tools if we had a custom binary format extension.
+
+An intrinsic method may be optimized away by the optimizer. If it is not, it
+must be **lowered** before shipping the wasm, as otherwise it will look like a
+call to an import that does not exist (and VMs will show an error on not having
+a proper value for that import). That final lowering is *not* done
+automatically. A user of intrinsics must run the pass for that explicitly,
+because the tools do not know when the user intends to finish optimizing, as the
+user may have a pipeline of multiple optimization steps, or may be doing local
+experimentation, or fuzzing/reducing, etc. Only the user knows when the final
+optimization happens before the wasm is "final" and ready to be shipped. Note
+that, in general, some additional optimizations may be possible after the final
+lowering, and so a useful pattern is to optimize once normally with intrinsics,
+then lower them away, then optimize after that, e.g.:
+
+```
+wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -O
+```
+
+Each intrinsic defines its semantics, which includes what the optimizer is
+allowed to do with it and what the final lowering will turn it to. See
+[intrinsics.h](https://github.com/WebAssembly/binaryen/blob/main/src/ir/intrinsics.h)
+for the detailed definitions. A quick summary appears here:
+
+* `call.without.effects`: Similar to a `call_ref` in that it receives
+ parameters, and a reference to a function to call, and calls that function
+ with those parameters, except that the optimizer can assume the call has no
+ side effects, and may be able to optimize it out (if it does not have a
+ result that is used, generally).
+
## Tools
This repository contains code that builds the following tools in `bin/`:
diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt
index c17a48ac0..f598f3d72 100644
--- a/src/ir/CMakeLists.txt
+++ b/src/ir/CMakeLists.txt
@@ -2,6 +2,7 @@ FILE(GLOB ir_HEADERS *.h)
set(ir_SOURCES
ExpressionAnalyzer.cpp
ExpressionManipulator.cpp
+ intrinsics.cpp
names.cpp
properties.cpp
LocalGraph.cpp
diff --git a/src/ir/effects.h b/src/ir/effects.h
index ad8f68efa..ee45d8205 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -17,6 +17,7 @@
#ifndef wasm_ir_effects_h
#define wasm_ir_effects_h
+#include "ir/intrinsics.h"
#include "pass.h"
#include "wasm-traversal.h"
@@ -31,7 +32,7 @@ public:
Expression* ast = nullptr)
: ignoreImplicitTraps(passOptions.ignoreImplicitTraps),
trapsNeverHappen(passOptions.trapsNeverHappen),
- debugInfo(passOptions.debugInfo), module(&module),
+ debugInfo(passOptions.debugInfo), module(module),
features(module.features) {
if (ast) {
walk(ast);
@@ -41,7 +42,7 @@ public:
bool ignoreImplicitTraps;
bool trapsNeverHappen;
bool debugInfo;
- Module* module;
+ Module& module;
FeatureSet features;
// Walk an expression and all its children.
@@ -393,6 +394,11 @@ private:
}
void visitCall(Call* curr) {
+ // call.without.effects has no effects.
+ if (Intrinsics(parent.module).isCallWithoutEffects(curr)) {
+ return;
+ }
+
parent.calls = true;
// When EH is enabled, any call can throw.
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
diff --git a/src/ir/intrinsics.cpp b/src/ir/intrinsics.cpp
new file mode 100644
index 000000000..c2318cabf
--- /dev/null
+++ b/src/ir/intrinsics.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 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 "ir/intrinsics.h"
+#include "wasm-builder.h"
+
+namespace wasm {
+
+static Name BinaryenIntrinsics("binaryen-intrinsics"),
+ CallWithoutEffects("call.without.effects");
+
+bool Intrinsics::isCallWithoutEffects(Function* func) {
+ return func->module == BinaryenIntrinsics && func->base == CallWithoutEffects;
+}
+
+Call* Intrinsics::isCallWithoutEffects(Expression* curr) {
+ if (auto* call = curr->dynCast<Call>()) {
+ // The target function may not exist if the module is still being
+ // constructed.
+ if (auto* func = module.getFunctionOrNull(call->target)) {
+ if (isCallWithoutEffects(func)) {
+ return call;
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace wasm
diff --git a/src/ir/intrinsics.h b/src/ir/intrinsics.h
new file mode 100644
index 000000000..9a1356029
--- /dev/null
+++ b/src/ir/intrinsics.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2021 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_intrinsics_h
+#define wasm_ir_intrinsics_h
+
+#include "pass.h"
+#include "wasm-traversal.h"
+
+//
+// See the README.md for background on intrinsic functions.
+//
+// Intrinsics can be recognized by Intrinsics::isFoo() methods, that check if a
+// function is a particular intrinsic, or if a call to a function is so. The
+// latter returns nullptr if the input is not that intrinsic, and otherwise the
+// intrinsic itself cast to a Call*.
+//
+
+namespace wasm {
+
+class Intrinsics {
+ Module& module;
+
+public:
+ Intrinsics(Module& module) : module(module) {}
+
+ //
+ // Check if an instruction is the call.without.effects intrinsic.
+ //
+ // (import "binaryen-intrinsics" "call.without.effects"
+ // (func (..params..) (param $target funcref) (..results..)))
+ //
+ // call.without.effects can take any parameters, and in addition a funcref,
+ // and return any result.
+ //
+ // Precise semantics:
+ //
+ // * The optimizer will assume this instruction has no side effects.
+ // * Final lowering turns a call.without.effects into a call of the given
+ // function with the given parameters. (This will either be a direct call,
+ // or a call_ref; note that either way, the function reference that appears
+ // here must have the proper type - if not, you will get an error.)
+ //
+ // call.without.effects is useful to be able to get rid of an unused result
+ // that has side effects. For example,
+ //
+ // (drop (call $get-something))
+ //
+ // cannot be removed, as a call has side effects. But if a code generator
+ // knows that it is fine to not make the call given that the result is
+ // dropped (perhaps the side effects are to initialize a global cache, for
+ // example) then instead of emitting
+ //
+ // (call $get-something)
+ //
+ // it can emit
+ //
+ // (call $call.without.effects (ref.func $get-something))
+ //
+ // which will have this behavior in the optimizer if it is dropped:
+ //
+ // (drop (call $call.without.effects (ref.func $get-something)))
+ // =>
+ // (drop (ref.func $get-something))
+ //
+ // Later optimizations can remove the dropped ref.func. Or, if the result is
+ // actually used,
+ //
+ // (local.set $x (call $call.without.effects (ref.func $get-something)))
+ // =>
+ // (local.set $x (call $get-something))
+ //
+ // Later passes will then turn that into a direct call and further optimize
+ // things.
+ //
+ bool isCallWithoutEffects(Function* func);
+ Call* isCallWithoutEffects(Expression* curr);
+};
+
+} // namespace wasm
+
+#endif // wasm_ir_intrinsics_h
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index b61b895ef..a49ae8980 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -35,6 +35,7 @@ set(passes_SOURCES
Inlining.cpp
InstrumentLocals.cpp
InstrumentMemory.cpp
+ Intrinsics.cpp
LegalizeJSInterface.cpp
LimitSegments.cpp
LocalCSE.cpp
diff --git a/src/passes/Intrinsics.cpp b/src/passes/Intrinsics.cpp
new file mode 100644
index 000000000..1b5a50bbb
--- /dev/null
+++ b/src/passes/Intrinsics.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 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 "ir/intrinsics.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct IntrinsicLowering : public WalkerPass<PostWalker<IntrinsicLowering>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new IntrinsicLowering; }
+
+ void visitCall(Call* curr) {
+ if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
+ // Turn into a call, by using the final operand as the function to call.
+ auto& operands = curr->operands;
+ auto* target = operands.back();
+ operands.pop_back();
+ // We could rely on later optimizations here, but at least ensure we emit
+ // a direct call when we can, to avoid a performance cliff if the user
+ // forgets to optimize.
+ Builder builder(*getModule());
+ if (auto* refFunc = target->dynCast<RefFunc>()) {
+ replaceCurrent(builder.makeCall(refFunc->func, operands, curr->type));
+ } else {
+ replaceCurrent(builder.makeCallRef(target, operands, curr->type));
+ }
+ }
+ }
+};
+
+Pass* createIntrinsicLoweringPass() { return new IntrinsicLowering(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 878ecb3cb..5f53a1d78 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -164,6 +164,9 @@ void PassRegistry::registerPasses() {
registerPass("inlining-optimizing",
"inline functions and optimizes where we inlined",
createInliningOptimizingPass);
+ registerPass("intrinsic-lowering",
+ "lower away binaryen intrinsics",
+ createIntrinsicLoweringPass);
registerPass("legalize-js-interface",
"legalizes i64 types on the import/export boundary",
createLegalizeJSInterfacePass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 715e5c443..47b246bd4 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -62,6 +62,7 @@ Pass* createLimitSegmentsPass();
Pass* createLocalCSEPass();
Pass* createLocalSubtypingPass();
Pass* createLogExecutionPass();
+Pass* createIntrinsicLoweringPass();
Pass* createInstrumentLocalsPass();
Pass* createInstrumentMemoryPass();
Pass* createLoopInvariantCodeMotionPass();
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index eaeea7f1a..0de417ed7 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -21,6 +21,7 @@
#include "ir/features.h"
#include "ir/global-utils.h"
+#include "ir/intrinsics.h"
#include "ir/module-utils.h"
#include "ir/stack-utils.h"
#include "ir/utils.h"
@@ -2705,6 +2706,16 @@ static void validateImports(Module& module, ValidationInfo& info) {
"Imported function must not have i64 results");
}
}
+
+ if (Intrinsics(module).isCallWithoutEffects(curr)) {
+ auto lastParam = curr->getParams();
+ if (lastParam.isTuple()) {
+ lastParam = lastParam.getTuple().types.back();
+ }
+ info.shouldBeTrue(lastParam.isFunction(),
+ curr->name,
+ "call.if.used's last param must be a function");
+ }
});
ModuleUtils::iterImportedGlobals(module, [&](Global* curr) {
if (!module.features.hasMutableGlobals()) {
diff --git a/test/lit/help/optimization-opts.test b/test/lit/help/optimization-opts.test
index 18f8970c2..177a1b620 100644
--- a/test/lit/help/optimization-opts.test
+++ b/test/lit/help/optimization-opts.test
@@ -284,6 +284,8 @@
;; CHECK-NEXT: to intercept all loads and
;; CHECK-NEXT: stores
;; CHECK-NEXT:
+;; CHECK-NEXT: --intrinsic-lowering lower away binaryen intrinsics
+;; CHECK-NEXT:
;; CHECK-NEXT: --legalize-js-interface legalizes i64 types on the
;; CHECK-NEXT: import/export boundary
;; CHECK-NEXT:
diff --git a/test/lit/passes/intrinsic-lowering.wast b/test/lit/passes/intrinsic-lowering.wast
new file mode 100644
index 000000000..01bafbf86
--- /dev/null
+++ b/test/lit/passes/intrinsic-lowering.wast
@@ -0,0 +1,53 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --intrinsic-lowering -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $none (func))
+ (type $none (func))
+
+ ;; call.without.effects with no params.
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe-v (param funcref) (result i32)))
+ (import "binaryen-intrinsics" "call.without.effects" (func $cwe-v (param funcref) (result i32)))
+
+ ;; call.without.effects with some params.
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe-dif (param f64 i32 funcref) (result f32)))
+ (import "binaryen-intrinsics" "call.without.effects" (func $cwe-dif (param f64) (param i32) (param funcref) (result f32)))
+
+ ;; call.without.effects with no result.
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe-n (param funcref)))
+ (import "binaryen-intrinsics" "call.without.effects" (func $cwe-n (param funcref)))
+
+ ;; CHECK: (func $test (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $test)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $dif
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref
+ ;; CHECK-NEXT: (ref.null $none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ (func $test (result i32)
+ ;; These will be lowered into calls.
+ (drop (call $cwe-v (ref.func $test)))
+ (drop (call $cwe-dif (f64.const 3.14159) (i32.const 42) (ref.func $dif)))
+ ;; The last must be a call_ref, as we don't see a constant ref.func
+ (call $cwe-n
+ (ref.null $none)
+ )
+ (i32.const 1)
+ )
+
+ ;; CHECK: (func $dif (param $0 f64) (param $1 i32) (result f32)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $dif (param f64) (param i32) (result f32)
+ ;; Helper function for the above.
+ (unreachable)
+ )
+)
diff --git a/test/lit/passes/vacuum-intrinsics.wast b/test/lit/passes/vacuum-intrinsics.wast
new file mode 100644
index 000000000..bd0f8ed60
--- /dev/null
+++ b/test/lit/passes/vacuum-intrinsics.wast
@@ -0,0 +1,233 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32)))
+ (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32)))
+
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-fj (param f32 funcref) (result i64)))
+ (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-fj (param f32) (param funcref) (result i64)))
+
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-ref (param funcref) (result (ref any))))
+ (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-ref (param funcref) (result (ref any))))
+
+ ;; CHECK: (func $used
+ ;; CHECK-NEXT: (local $i32 i32)
+ ;; CHECK-NEXT: (local.set $i32
+ ;; CHECK-NEXT: (call $call.without.effects
+ ;; CHECK-NEXT: (ref.func $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $used
+ (local $i32 i32)
+ ;; The result is used (by the local.set), so we cannot do anything here.
+ (local.set $i32
+ (call $call.without.effects (ref.func $i))
+ )
+ )
+
+ ;; CHECK: (func $unused
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unused
+ ;; The result is unused, so we can remove the call.
+ (drop
+ (call $call.without.effects (ref.func $i))
+ )
+ )
+
+ ;; CHECK: (func $unused-fj
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $unused-fj
+ ;; As above, but with an extra float param and a different result type.
+ (drop
+ (call $call.without.effects-fj (f32.const 2.71828) (ref.func $fj))
+ )
+ )
+
+ ;; CHECK: (func $unused-fj-side-effects
+ ;; CHECK-NEXT: (local $f32 f32)
+ ;; CHECK-NEXT: (local.set $f32
+ ;; CHECK-NEXT: (f32.const 2.718280076980591)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unused-fj-side-effects
+ (local $f32 f32)
+ ;; As above, but side effects in the param. We must keep the params around
+ ;; and drop them.
+ (drop
+ (call $call.without.effects-fj
+ (local.tee $f32
+ (f32.const 2.71828)
+ )
+ (ref.func $fj)
+ )
+ )
+ )
+
+ ;; CHECK: (func $unused-unreachable
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $call.without.effects-fj
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (ref.func $fj)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unused-unreachable
+ ;; An unused result, but the call is unreachable and so we ignore it (and
+ ;; leave it for DCE).
+ (drop
+ (call $call.without.effects-fj (unreachable) (ref.func $fj))
+ )
+ )
+
+ ;; CHECK: (func $used-fallthrough
+ ;; CHECK-NEXT: (local $i32 i32)
+ ;; CHECK-NEXT: (local.set $i32
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (block $condition (result i32)
+ ;; CHECK-NEXT: (call $nop)
+ ;; CHECK-NEXT: (call $call.without.effects
+ ;; CHECK-NEXT: (ref.func $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $ifTrue (result i32)
+ ;; CHECK-NEXT: (call $nop)
+ ;; CHECK-NEXT: (call $call.without.effects
+ ;; CHECK-NEXT: (ref.func $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $ifFalse (result i32)
+ ;; CHECK-NEXT: (call $nop)
+ ;; CHECK-NEXT: (call $call.without.effects
+ ;; CHECK-NEXT: (ref.func $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $used-fallthrough
+ (local $i32 i32)
+ (local.set $i32
+ (if (result i32)
+ ;; The block falls through a value that is used as the if condition.
+ (block $condition (result i32)
+ ;; Add a call to $nop so that the blocks are not optimized away.
+ (call $nop)
+ (call $call.without.effects (ref.func $i))
+ )
+ ;; The arms fall through their blocks and also through the if, and end
+ ;; up used by the set.
+ (block $ifTrue (result i32)
+ (call $nop)
+ (call $call.without.effects (ref.func $i))
+ )
+ (block $ifFalse (result i32)
+ (call $nop)
+ (call $call.without.effects (ref.func $i))
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $unused-fallthrough
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (block $condition (result i32)
+ ;; CHECK-NEXT: (call $nop)
+ ;; CHECK-NEXT: (call $call.without.effects
+ ;; CHECK-NEXT: (ref.func $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $ifTrue (result i32)
+ ;; CHECK-NEXT: (call $nop)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $ifFalse (result i32)
+ ;; CHECK-NEXT: (call $nop)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unused-fallthrough
+ (drop
+ (if (result i32)
+ (block $condition (result i32)
+ (call $nop)
+ (call $call.without.effects (ref.func $i))
+ )
+ ;; As above, but now there is a drop outside the if, so the arms are
+ ;; unused and we can optimize them.
+ (block $ifTrue (result i32)
+ (call $nop)
+ (call $call.without.effects (ref.func $i))
+ )
+ (block $ifFalse (result i32)
+ (call $nop)
+ (call $call.without.effects (ref.func $i))
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $unused-fallthrough-bad-type
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (if (result (ref any))
+ ;; CHECK-NEXT: (call $i)
+ ;; CHECK-NEXT: (call $call.without.effects-ref
+ ;; CHECK-NEXT: (ref.func $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $call.without.effects-ref
+ ;; CHECK-NEXT: (ref.func $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unused-fallthrough-bad-type
+ (drop
+ (if (result (ref any))
+ (call $i)
+ ;; As above, but the type of these unused values prevents us from
+ ;; optimizing as we cannot create a "zero" for them.
+ (call $call.without.effects-ref (ref.func $ref))
+ (call $call.without.effects-ref (ref.func $ref))
+ )
+ )
+ )
+
+ ;; CHECK: (func $i (result i32)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $i (result i32)
+ ;; Helper function for the above.
+ (unreachable)
+ )
+
+ ;; CHECK: (func $fj (param $0 f32) (result i64)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $fj (param f32) (result i64)
+ ;; Helper function for the above.
+ (unreachable)
+ )
+
+ ;; CHECK: (func $ref (result (ref any))
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $ref (result (ref any))
+ ;; Helper function for the above.
+ (unreachable)
+ )
+
+
+ ;; CHECK: (func $nop
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $nop
+ ;; Helper function for the above.
+ (nop)
+ )
+)