diff options
-rw-r--r-- | README.md | 40 | ||||
-rw-r--r-- | src/ir/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/ir/effects.h | 10 | ||||
-rw-r--r-- | src/ir/intrinsics.cpp | 42 | ||||
-rw-r--r-- | src/ir/intrinsics.h | 95 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/Intrinsics.cpp | 50 | ||||
-rw-r--r-- | src/passes/pass.cpp | 3 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 11 | ||||
-rw-r--r-- | test/lit/help/optimization-opts.test | 2 | ||||
-rw-r--r-- | test/lit/passes/intrinsic-lowering.wast | 53 | ||||
-rw-r--r-- | test/lit/passes/vacuum-intrinsics.wast | 233 |
13 files changed, 540 insertions, 2 deletions
@@ -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) + ) +) |