summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Lively <tlively@google.com>2024-04-08 10:50:35 -0700
committerGitHub <noreply@github.com>2024-04-08 17:50:35 +0000
commitad097394dff569afb42bc4c1c4d961faad04fc81 (patch)
treee67ebefa77d27b29140160cce4111c2f34690b4a
parentf0dd9941de2df62e0a29f2faeadf007e37a425a9 (diff)
downloadbinaryen-ad097394dff569afb42bc4c1c4d961faad04fc81.tar.gz
binaryen-ad097394dff569afb42bc4c1c4d961faad04fc81.tar.bz2
binaryen-ad097394dff569afb42bc4c1c4d961faad04fc81.zip
Handle return calls correctly
This is a combined commit covering multiple PRs fixing the handling of return calls in different areas. The PRs are all landed as a single commit to ensure internal consistency and avoid problems with bisection. Original PR descriptions follow: * Fix inlining of `return_call*` (#6448) Previously we transformed return calls in inlined function bodies into normal calls followed by branches out to the caller code. Similarly, when inlining a `return_call` callsite, we simply added a `return` after the body inlined at the callsite. These transformations would have been correct if the semantics of return calls were to call and then return, but they are not correct for the actual semantics of returning and then calling. The previous implementation is observably incorrect for return calls inside try blocks, where the previous implementation would run the inlined body within the try block, but the proper semantics would be to run the inlined body outside the try block. Fix the problem by transforming inlined return calls to branches followed by calls rather than as calls followed by branches. For the case of inlined return call callsites, insert branches out of the original body of the caller and inline the body of the callee as a sibling of the original caller body. For the other case of return calls appearing in inlined bodies, translate the return calls to branches out to calls inserted as siblings of the original inlined body. In both cases, it would have been convenient to use multivalue block return to send call parameters along the branches to the calls, but unfortunately in our IR that would have required tuple-typed scratch locals to unpack the tuple of operands at the call sites. It is simpler to just use locals to propagate the operands in the first place. * Fix interpretation of `return_call*` (#6451) We previously interpreted return calls as calls followed by returns, but that is not correct both because it grows the size of the execution stack and because it runs the called functions in the wrong context, which can be observable in the case of exception handling. Update the interpreter to handle return calls correctly by adding a new `RETURN_CALL_FLOW` that behaves like a return, but carries the arguments and reference to the return-callee rather than normal return values. `callFunctionInternal` is updated to intercept this flow and call return-called functions in a loop until a function returns with some other kind of flow. Pull in the upstream spec tests return_call.wast, return_call_indirect.wast, and return_call_ref.wast with light editing so that we parse and validate them successfully. * Handle return calls in wasm-ctor-eval (#6464) When an evaluated export ends in a return call, continue evaluating the return-called function. This requires propagating the parameters, handling the case that the return-called function might be an import, and fixing up local indices in case the final function has different parameters than the original function. * Update effects.h to handle return calls correctly (#6470) As far as their surrounding code is concerned return calls are no different from normal returns. It's only from a caller's perspective that a function containing a return call also has the effects of the return-callee. To model this more precisely in EffectAnalyzer, stash the throw effect of return-callees on the side and only merge it in at the end when analyzing the effects of a full function body.
-rw-r--r--src/ir/effects.h113
-rw-r--r--src/ir/localize.h15
-rw-r--r--src/passes/Inlining.cpp210
-rw-r--r--src/tools/wasm-ctor-eval.cpp194
-rw-r--r--src/wasm-builder.h1
-rw-r--r--src/wasm-interpreter.h168
-rw-r--r--src/wasm/wasm.cpp1
-rw-r--r--test/lit/ctor-eval/return_call.wast529
-rw-r--r--test/lit/passes/global-effects.wast222
-rw-r--r--test/lit/passes/inlining-unreachable.wast19
-rw-r--r--test/lit/passes/inlining_all-features.wast7
-rw-r--r--test/lit/passes/inlining_enable-tail-call.wast519
-rw-r--r--test/lit/passes/simplify-locals-eh-old.wast51
-rw-r--r--test/lit/passes/vacuum-eh-old.wast79
-rw-r--r--test/spec/return_call.wast202
-rw-r--r--test/spec/return_call_eh.wast35
-rw-r--r--test/spec/return_call_indirect.wast536
-rw-r--r--test/spec/return_call_ref.wast377
18 files changed, 2925 insertions, 353 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 6901f99de..3ecb54641 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -67,9 +67,23 @@ public:
// noticeable from the perspective of the caller, that is, effects that are
// only noticeable during the call, but "vanish" when the call stack is
// unwound.
+ //
+ // Unlike walking just the body, walking the function will also
+ // include the effects of any return calls the function makes. For that
+ // reason, it is a bug if a user of this code calls walk(Expression*) and not
+ // walk(Function*) if their intention is to scan an entire function body.
+ // Putting it another way, a return_call is syntax sugar for a return and a
+ // call, where the call executes at the function scope, so there is a
+ // meaningful difference between scanning an expression and scanning
+ // the entire function body.
void walk(Function* func) {
walk(func->body);
+ // Effects of return-called functions will be visible to the caller.
+ if (hasReturnCallThrow) {
+ throws_ = true;
+ }
+
// We can ignore branching out of the function body - this can only be
// a return, and that is only noticeable in the function, not outside.
branchesOut = false;
@@ -143,6 +157,22 @@ public:
// or a continuation that is never continued, are examples of that.
bool mayNotReturn = false;
+ // Since return calls return out of the body of the function before performing
+ // their call, they are indistinguishable from normal returns from the
+ // perspective of their surrounding code, and the return-callee's effects only
+ // become visible when considering the effects of the whole function
+ // containing the return call. To model this correctly, stash the callee's
+ // effects on the side and only merge them in after walking a full function
+ // body.
+ //
+ // We currently do this stashing only for the throw effect, but in principle
+ // we could do it for all effects if it made a difference. (Only throw is
+ // noticeable now because the only thing that can change between doing the
+ // call here and doing it outside at the function exit is the scoping of
+ // try-catch blocks. If future wasm scoping additions are added, we may need
+ // more here.)
+ bool hasReturnCallThrow = false;
+
// Helper functions to check for various effect types
bool accessesLocal() const {
@@ -466,43 +496,63 @@ private:
return;
}
+ const EffectAnalyzer* targetEffects = nullptr;
+ if (parent.funcEffectsMap) {
+ auto iter = parent.funcEffectsMap->find(curr->target);
+ if (iter != parent.funcEffectsMap->end()) {
+ targetEffects = &iter->second;
+ }
+ }
+
if (curr->isReturn) {
parent.branchesOut = true;
+ // When EH is enabled, any call can throw.
+ if (parent.features.hasExceptionHandling() &&
+ (!targetEffects || targetEffects->throws())) {
+ parent.hasReturnCallThrow = true;
+ }
}
- if (parent.funcEffectsMap) {
- auto iter = parent.funcEffectsMap->find(curr->target);
- if (iter != parent.funcEffectsMap->end()) {
- // We have effect information for this call target, and can just use
- // that. The one change we may want to make is to remove throws_, if
- // the target function throws and we know that will be caught anyhow,
- // the same as the code below for the general path.
- const auto& targetEffects = iter->second;
- if (targetEffects.throws_ && parent.tryDepth > 0) {
- auto filteredEffects = targetEffects;
- filteredEffects.throws_ = false;
- parent.mergeIn(filteredEffects);
- } else {
- // Just merge in all the effects.
- parent.mergeIn(targetEffects);
- }
- return;
+ if (targetEffects) {
+ // We have effect information for this call target, and can just use
+ // that. The one change we may want to make is to remove throws_, if the
+ // target function throws and we know that will be caught anyhow, the
+ // same as the code below for the general path. We can always filter out
+ // throws for return calls because they are already more precisely
+ // captured by `branchesOut`, which models the return, and
+ // `hasReturnCallThrow`, which models the throw that will happen after
+ // the return.
+ if (targetEffects->throws_ && (parent.tryDepth > 0 || curr->isReturn)) {
+ auto filteredEffects = *targetEffects;
+ filteredEffects.throws_ = false;
+ parent.mergeIn(filteredEffects);
+ } else {
+ // Just merge in all the effects.
+ parent.mergeIn(*targetEffects);
}
+ return;
}
parent.calls = true;
- // When EH is enabled, any call can throw.
- if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
+ // When EH is enabled, any call can throw. Skip this for return calls
+ // because the throw is already more precisely captured by the combination
+ // of `hasReturnCallThrow` and `branchesOut`.
+ if (parent.features.hasExceptionHandling() && parent.tryDepth == 0 &&
+ !curr->isReturn) {
parent.throws_ = true;
}
}
void visitCallIndirect(CallIndirect* curr) {
parent.calls = true;
- if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws_ = true;
- }
if (curr->isReturn) {
parent.branchesOut = true;
+ if (parent.features.hasExceptionHandling()) {
+ parent.hasReturnCallThrow = true;
+ }
+ }
+ if (parent.features.hasExceptionHandling() &&
+ (parent.tryDepth == 0 && !curr->isReturn)) {
+ parent.throws_ = true;
}
}
void visitLocalGet(LocalGet* curr) {
@@ -745,21 +795,26 @@ private:
}
}
void visitCallRef(CallRef* curr) {
+ if (curr->isReturn) {
+ parent.branchesOut = true;
+ if (parent.features.hasExceptionHandling()) {
+ parent.hasReturnCallThrow = true;
+ }
+ }
if (curr->target->type.isNull()) {
parent.trap = true;
return;
}
- parent.calls = true;
- if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws_ = true;
- }
- if (curr->isReturn) {
- parent.branchesOut = true;
- }
// traps when the call target is null
if (curr->target->type.isNullable()) {
parent.implicitTrap = true;
}
+
+ parent.calls = true;
+ if (parent.features.hasExceptionHandling() &&
+ (parent.tryDepth == 0 && !curr->isReturn)) {
+ parent.throws_ = true;
+ }
}
void visitRefTest(RefTest* curr) {}
void visitRefCast(RefCast* curr) {
diff --git a/src/ir/localize.h b/src/ir/localize.h
index d44fb9be5..85e4415f5 100644
--- a/src/ir/localize.h
+++ b/src/ir/localize.h
@@ -153,17 +153,20 @@ struct ChildLocalizer {
// Nothing to add.
return parent;
}
+ auto* block = getChildrenReplacement();
+ if (!hasUnreachableChild) {
+ block->list.push_back(parent);
+ block->finalize();
+ }
+ return block;
+ }
+ // Like `getReplacement`, but the result never contains the parent.
+ Block* getChildrenReplacement() {
auto* block = Builder(wasm).makeBlock();
block->list.set(sets);
if (hasUnreachableChild) {
- // If there is an unreachable child then we do not need the parent at all,
- // and we know the type is unreachable.
block->type = Type::unreachable;
- } else {
- // Otherwise, add the parent and finalize.
- block->list.push_back(parent);
- block->finalize();
}
return block;
}
diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp
index 51dddaaa9..0643389c2 100644
--- a/src/passes/Inlining.cpp
+++ b/src/passes/Inlining.cpp
@@ -37,6 +37,7 @@
#include "ir/element-utils.h"
#include "ir/find_all.h"
#include "ir/literal-utils.h"
+#include "ir/localize.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "ir/type-updating.h"
@@ -298,22 +299,34 @@ struct Updater : public PostWalker<Updater> {
Module* module;
std::map<Index, Index> localMapping;
Name returnName;
+ Type resultType;
bool isReturn;
Builder* builder;
PassOptions& options;
+ struct ReturnCallInfo {
+ // The original `return_call` or `return_call_indirect` or `return_call_ref`
+ // with its operands replaced with `local.get`s.
+ Expression* call;
+ // The branch that is serving as the "return" part of the original
+ // `return_call`.
+ Break* branch;
+ };
+
+ // Collect information on return_calls in the inlined body. Each will be
+ // turned into branches out of the original inlined body followed by
+ // non-return version of the original `return_call`, followed by a branch out
+ // to the caller. The branch labels will be filled in at the end of the walk.
+ std::vector<ReturnCallInfo> returnCallInfos;
+
Updater(PassOptions& options) : options(options) {}
void visitReturn(Return* curr) {
replaceCurrent(builder->makeBreak(returnName, curr->value));
}
- // Return calls in inlined functions should only break out of the scope of
- // the inlined code, not the entire function they are being inlined into. To
- // achieve this, make the call a non-return call and add a break. This does
- // not cause unbounded stack growth because inlining and return calling both
- // avoid creating a new stack frame.
- template<typename T> void handleReturnCall(T* curr, Type results) {
- if (isReturn) {
+
+ template<typename T> void handleReturnCall(T* curr, Signature sig) {
+ if (isReturn || !curr->isReturn) {
// If the inlined callsite was already a return_call, then we can keep
// return_calls in the inlined function rather than downgrading them.
// That is, if A->B and B->C and both those calls are return_calls
@@ -321,45 +334,85 @@ struct Updater : public PostWalker<Updater> {
// return_call.
return;
}
+
+ // Set the children to locals as necessary, then add a branch out of the
+ // inlined body. The branch label will be set later when we create branch
+ // targets for the calls.
+ Block* childBlock = ChildLocalizer(curr, getFunction(), *module, options)
+ .getChildrenReplacement();
+ Break* branch = builder->makeBreak(Name());
+ childBlock->list.push_back(branch);
+ childBlock->type = Type::unreachable;
+ replaceCurrent(childBlock);
+
curr->isReturn = false;
- curr->type = results;
- // There might still be unreachable children causing this to be unreachable.
- curr->finalize();
- if (results.isConcrete()) {
- replaceCurrent(builder->makeBreak(returnName, curr));
- } else {
- replaceCurrent(builder->blockify(curr, builder->makeBreak(returnName)));
- }
+ curr->type = sig.results;
+ returnCallInfos.push_back({curr, branch});
}
+
void visitCall(Call* curr) {
- if (curr->isReturn) {
- handleReturnCall(curr, module->getFunction(curr->target)->getResults());
- }
+ handleReturnCall(curr, module->getFunction(curr->target)->getSig());
}
+
void visitCallIndirect(CallIndirect* curr) {
- if (curr->isReturn) {
- handleReturnCall(curr, curr->heapType.getSignature().results);
- }
+ handleReturnCall(curr, curr->heapType.getSignature());
}
+
void visitCallRef(CallRef* curr) {
Type targetType = curr->target->type;
- if (targetType.isNull()) {
- // We don't know what type the call should return, but we can't leave it
- // as a potentially-invalid return_call_ref, either.
- replaceCurrent(getDroppedChildrenAndAppend(
- curr, *module, options, Builder(*module).makeUnreachable()));
+ if (!targetType.isSignature()) {
+ // We don't know what type the call should return, but it will also never
+ // be reached, so we don't need to do anything here.
return;
}
- if (curr->isReturn) {
- handleReturnCall(curr, targetType.getHeapType().getSignature().results);
- }
+ handleReturnCall(curr, targetType.getHeapType().getSignature());
}
+
void visitLocalGet(LocalGet* curr) {
curr->index = localMapping[curr->index];
}
+
void visitLocalSet(LocalSet* curr) {
curr->index = localMapping[curr->index];
}
+
+ void walk(Expression*& curr) {
+ PostWalker<Updater>::walk(curr);
+ if (returnCallInfos.empty()) {
+ return;
+ }
+
+ Block* body = builder->blockify(curr);
+ curr = body;
+ auto blockNames = BranchUtils::BranchAccumulator::get(body);
+
+ for (Index i = 0; i < returnCallInfos.size(); ++i) {
+ auto& info = returnCallInfos[i];
+
+ // Add a block containing the previous body and a branch up to the caller.
+ // Give the block a name that will allow this return_call's original
+ // callsite to branch out of it then execute the call before returning to
+ // the caller.
+ auto name = Names::getValidName(
+ "__return_call", [&](Name test) { return !blockNames.count(test); }, i);
+ blockNames.insert(name);
+ info.branch->name = name;
+ Block* oldBody = builder->makeBlock(body->list, body->type);
+ body->list.clear();
+
+ if (resultType.isConcrete()) {
+ body->list.push_back(builder->makeBlock(
+ name, {builder->makeBreak(returnName, oldBody)}, Type::none));
+ } else {
+ oldBody->list.push_back(builder->makeBreak(returnName));
+ oldBody->name = name;
+ oldBody->type = Type::none;
+ body->list.push_back(oldBody);
+ }
+ body->list.push_back(info.call);
+ body->finalize(resultType);
+ }
+ }
};
// Core inlining logic. Modifies the outside function (adding locals as
@@ -376,8 +429,11 @@ static Expression* doInlining(Module* module,
Index nameHint = 0) {
Function* from = action.contents;
auto* call = (*action.callSite)->cast<Call>();
+
// Works for return_call, too
Type retType = module->getFunction(call->target)->getResults();
+
+ // Build the block that will contain the inlined contents.
Builder builder(*module);
auto* block = builder.makeBlock();
auto name = std::string("__inlined_func$") + from->name.toString();
@@ -385,6 +441,7 @@ static Expression* doInlining(Module* module,
name += '$' + std::to_string(nameHint);
}
block->name = Name(name);
+
// In the unlikely event that the function already has a branch target with
// this name, fix that up, as otherwise we can get unexpected capture of our
// branches, that is, we could end up with this:
@@ -407,27 +464,24 @@ static Expression* doInlining(Module* module,
//
// (In this case we could use a second block and define the named block $X
// after the call's parameters, but that adds work for an extremely rare
- // situation.)
+ // situation.) The latter case does not apply if the call is a return_call,
+ // because in that case the call's children do not appear inside the same
+ // block as the inlined body.
if (BranchUtils::hasBranchTarget(from->body, block->name) ||
- BranchUtils::BranchSeeker::has(call, block->name)) {
+ (!call->isReturn && BranchUtils::BranchSeeker::has(call, block->name))) {
auto fromNames = BranchUtils::getBranchTargets(from->body);
- auto callNames = BranchUtils::BranchAccumulator::get(call);
+ auto callNames = call->isReturn ? BranchUtils::NameSet{}
+ : BranchUtils::BranchAccumulator::get(call);
block->name = Names::getValidName(block->name, [&](Name test) {
return !fromNames.count(test) && !callNames.count(test);
});
}
- if (call->isReturn) {
- if (retType.isConcrete()) {
- *action.callSite = builder.makeReturn(block);
- } else {
- *action.callSite = builder.makeSequence(block, builder.makeReturn());
- }
- } else {
- *action.callSite = block;
- }
+
// Prepare to update the inlined code's locals and other things.
Updater updater(options);
+ updater.setFunction(into);
updater.module = module;
+ updater.resultType = from->getResults();
updater.returnName = block->name;
updater.isReturn = call->isReturn;
updater.builder = &builder;
@@ -435,31 +489,71 @@ static Expression* doInlining(Module* module,
for (Index i = 0; i < from->getNumLocals(); i++) {
updater.localMapping[i] = builder.addVar(into, from->getLocalType(i));
}
- // Assign the operands into the params
- for (Index i = 0; i < from->getParams().size(); i++) {
- block->list.push_back(
- builder.makeLocalSet(updater.localMapping[i], call->operands[i]));
- }
- // Zero out the vars (as we may be in a loop, and may depend on their
- // zero-init value
- for (Index i = 0; i < from->vars.size(); i++) {
- auto type = from->vars[i];
- if (!LiteralUtils::canMakeZero(type)) {
- // Non-zeroable locals do not need to be zeroed out. As they have no zero
- // value they by definition should not be used before being written to, so
- // any value we set here would not be observed anyhow.
- continue;
+
+ if (call->isReturn) {
+ // Wrap the existing function body in a block we can branch out of before
+ // entering the inlined function body. This block must have a name that is
+ // different from any other block name above the branch.
+ auto intoNames = BranchUtils::BranchAccumulator::get(into->body);
+ auto bodyName =
+ Names::getValidName(Name("__original_body"),
+ [&](Name test) { return !intoNames.count(test); });
+ if (retType.isConcrete()) {
+ into->body = builder.makeBlock(
+ bodyName, {builder.makeReturn(into->body)}, Type::none);
+ } else {
+ into->body = builder.makeBlock(
+ bodyName, {into->body, builder.makeReturn()}, Type::none);
+ }
+
+ // Sequence the inlined function body after the original caller body.
+ into->body = builder.makeSequence(into->body, block, retType);
+
+ // Replace the original callsite with an expression that assigns the
+ // operands into the params and branches out of the original body.
+ auto numParams = from->getParams().size();
+ if (numParams) {
+ auto* branchBlock = builder.makeBlock();
+ for (Index i = 0; i < numParams; i++) {
+ branchBlock->list.push_back(
+ builder.makeLocalSet(updater.localMapping[i], call->operands[i]));
+ }
+ branchBlock->list.push_back(builder.makeBreak(bodyName));
+ branchBlock->finalize(Type::unreachable);
+ *action.callSite = branchBlock;
+ } else {
+ *action.callSite = builder.makeBreak(bodyName);
+ }
+ } else {
+ // Assign the operands into the params
+ for (Index i = 0; i < from->getParams().size(); i++) {
+ block->list.push_back(
+ builder.makeLocalSet(updater.localMapping[i], call->operands[i]));
}
- block->list.push_back(
- builder.makeLocalSet(updater.localMapping[from->getVarIndexBase() + i],
- LiteralUtils::makeZero(type, *module)));
+ // Zero out the vars (as we may be in a loop, and may depend on their
+ // zero-init value
+ for (Index i = 0; i < from->vars.size(); i++) {
+ auto type = from->vars[i];
+ if (!LiteralUtils::canMakeZero(type)) {
+ // Non-zeroable locals do not need to be zeroed out. As they have no
+ // zero value they by definition should not be used before being written
+ // to, so any value we set here would not be observed anyhow.
+ continue;
+ }
+ block->list.push_back(
+ builder.makeLocalSet(updater.localMapping[from->getVarIndexBase() + i],
+ LiteralUtils::makeZero(type, *module)));
+ }
+ *action.callSite = block;
}
+
// Generate and update the inlined contents
auto* contents = ExpressionManipulator::copy(from->body, *module);
debug::copyDebugInfo(from->body, contents, from, into);
updater.walk(contents);
block->list.push_back(contents);
block->type = retType;
+
// The ReFinalize below will handle propagating unreachability if we need to
// do so, that is, if the call was reachable but now the inlined content we
// replaced it with was unreachable. The opposite case requires special
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
index d476bb2cc..fe3d42d09 100644
--- a/src/tools/wasm-ctor-eval.cpp
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -25,6 +25,7 @@
#include <memory>
#include "asmjs/shared-constants.h"
+#include "ir/find_all.h"
#include "ir/gc-type-utils.h"
#include "ir/global-utils.h"
#include "ir/import-utils.h"
@@ -1061,40 +1062,45 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
params.push_back(Literal::makeZero(type));
}
- // We want to handle the form of the global constructor function in LLVM. That
- // looks like this:
- //
- // (func $__wasm_call_ctors
- // (call $ctor.1)
- // (call $ctor.2)
- // (call $ctor.3)
- // )
- //
- // Some of those ctors may be inlined, however, which would mean that the
- // function could have locals, control flow, etc. However, we assume for now
- // that it does not have parameters at least (whose values we can't tell).
- // And for now we look for a toplevel block and process its children one at a
- // time. This allows us to eval some of the $ctor.* functions (or their
- // inlined contents) even if not all.
- //
- // TODO: Support complete partial evalling, that is, evaluate parts of an
- // arbitrary function, and not just a sequence in a single toplevel
- // block.
+ // After we successfully eval a line we will store the operations to set up
+ // the locals here. That is, we need to save the local state in the function,
+ // which we do by setting up at the entry. We update this list of expressions
+ // at the same time as applyToModule() - we must only do it after an entire
+ // atomic "chunk" has been processed succesfully, we do not want partial
+ // updates from an item in the block that we only partially evalled. When we
+ // construct the (partially) evalled function, we will create local.sets of
+ // these expressions at the beginning.
+ std::vector<Expression*> localExprs;
+
+ // We might have to evaluate multiple functions due to return calls.
+start_eval:
+ while (true) {
+ // We want to handle the form of the global constructor function in LLVM.
+ // That looks like this:
+ //
+ // (func $__wasm_call_ctors
+ // (call $ctor.1)
+ // (call $ctor.2)
+ // (call $ctor.3)
+ // )
+ //
+ // Some of those ctors may be inlined, however, which would mean that the
+ // function could have locals, control flow, etc. However, we assume for now
+ // that it does not have parameters at least (whose values we can't tell).
+ // And for now we look for a toplevel block and process its children one at
+ // a time. This allows us to eval some of the $ctor.* functions (or their
+ // inlined contents) even if not all.
+ //
+ // TODO: Support complete partial evalling, that is, evaluate parts of an
+ // arbitrary function, and not just a sequence in a single toplevel
+ // block.
+ Builder builder(wasm);
+ auto* block = builder.blockify(func->body);
- if (auto* block = func->body->dynCast<Block>()) {
// Go through the items in the block and try to execute them. We do all this
// in a single function scope for all the executions.
EvallingModuleRunner::FunctionScope scope(func, params, instance);
- // After we successfully eval a line we will store the operations to set up
- // the locals here. That is, we need to save the local state in the
- // function, which we do by setting up at the entry. We update this list of
- // local.sets at the same time as applyToModule() - we must only do it after
- // an entire atomic "chunk" has been processed succesfully, we do not want
- // partial updates from an item in the block that we only partially evalled.
- std::vector<Expression*> localSets;
-
- Builder builder(wasm);
Literals results;
Index successes = 0;
@@ -1116,6 +1122,22 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
break;
}
+ if (flow.breakTo == RETURN_CALL_FLOW) {
+ // The return-called function is stored in the last value.
+ func = wasm.getFunction(flow.values.back().getFunc());
+ flow.values.pop_back();
+ params = std::move(flow.values);
+
+ // Serialize the arguments for the new function and save the module
+ // state in case we fail to eval the new function.
+ localExprs.clear();
+ for (auto& param : params) {
+ localExprs.push_back(interface.getSerialization(param));
+ }
+ interface.applyToModule();
+ goto start_eval;
+ }
+
// So far so good! Serialize the values of locals, and apply to the
// module. Note that we must serialize the locals now as doing so may
// cause changes that must be applied to the module (e.g. GC data may
@@ -1128,11 +1150,9 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
// of them, and leave it to the optimizer to remove redundant or
// unnecessary operations. We just recompute the entire local
// serialization sets from scratch each time here, for all locals.
- localSets.clear();
+ localExprs.clear();
for (Index i = 0; i < func->getNumLocals(); i++) {
- auto value = scope.locals[i];
- localSets.push_back(
- builder.makeLocalSet(i, interface.getSerialization(value)));
+ localExprs.push_back(interface.getSerialization(scope.locals[i]));
}
interface.applyToModule();
successes++;
@@ -1144,41 +1164,97 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
if (flow.breaking()) {
// We are returning out of the function (either via a return, or via a
// break to |block|, which has the same outcome. That means we don't
- // need to execute any more lines, and can consider them to be executed.
+ // need to execute any more lines, and can consider them to be
+ // executed.
if (!quiet) {
std::cout << " ...stopping in block due to break\n";
}
// Mark us as having succeeded on the entire block, since we have: we
- // are skipping the rest, which means there is no problem there. We must
- // set this here so that lower down we realize that we've evalled
+ // are skipping the rest, which means there is no problem there. We
+ // must set this here so that lower down we realize that we've evalled
// everything.
successes = block->list.size();
break;
}
}
- if (successes > 0 && successes < block->list.size()) {
- // We managed to eval some but not all. That means we can't just remove
- // the entire function, but need to keep parts of it - the parts we have
- // not evalled - around. To do so, we create a copy of the function with
- // the partially-evalled contents and make the export use that (as the
- // function may be used in other places than the export, which we do not
- // want to affect).
+ // If we have not fully evaluated the current function, but we have
+ // evaluated part of it, have return-called to a different function, or have
+ // precomputed values for the current return-called function, then we can
+ // replace the export with a new function that does less work than the
+ // original.
+ if ((func->imported() || successes < block->list.size()) &&
+ (successes > 0 || func->name != funcName ||
+ (localExprs.size() && func->getParams() != Type::none))) {
+ auto originalFuncType = wasm.getFunction(funcName)->type;
auto copyName = Names::getValidFunctionName(wasm, funcName);
- auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName);
wasm.getExport(exportName)->value = copyName;
+ if (func->imported()) {
+ // We must have return-called this imported function. Generate a new
+ // function that return-calls the import with the arguments we have
+ // evalled.
+ auto copyFunc = builder.makeFunction(
+ copyName,
+ originalFuncType,
+ {},
+ builder.makeCall(func->name, localExprs, func->getResults(), true));
+ wasm.addFunction(std::move(copyFunc));
+ return EvalCtorOutcome();
+ }
+
+ // We may have managed to eval some but not all. That means we can't just
+ // remove the entire function, but need to keep parts of it - the parts we
+ // have not evalled - around. To do so, we create a copy of the function
+ // with the partially-evalled contents and make the export use that (as
+ // the function may be used in other places than the export, which we do
+ // not want to affect).
+ auto* copyBody =
+ builder.blockify(ExpressionManipulator::copy(func->body, wasm));
+
// Remove the items we've evalled.
- auto* copyBlock = copyFunc->body->cast<Block>();
for (Index i = 0; i < successes; i++) {
- copyBlock->list[i] = builder.makeNop();
+ copyBody->list[i] = builder.makeNop();
}
- // Put the local sets at the front of the block. We know there must be a
- // nop in that position (since we've evalled at least one item in the
- // block, and replaced it with a nop), so we can overwrite it.
- copyBlock->list[0] = builder.makeBlock(localSets);
+ // Put the local sets at the front of the function body.
+ auto* setsBlock = builder.makeBlock();
+ for (Index i = 0; i < localExprs.size(); ++i) {
+ setsBlock->list.push_back(builder.makeLocalSet(i, localExprs[i]));
+ }
+ copyBody = builder.makeSequence(setsBlock, copyBody, copyBody->type);
+
+ // We may have return-called into a function with different parameter
+ // types, but we ultimately need to export a function with the original
+ // signature. If there is a mismatch, shift the local indices to make room
+ // for the unused parameters.
+ std::vector<Type> localTypes;
+ auto originalParams = originalFuncType.getSignature().params;
+ if (originalParams != func->getParams()) {
+ // Add locals for the body to use instead of using the params.
+ for (auto type : func->getParams()) {
+ localTypes.push_back(type);
+ }
+
+ // Shift indices in the body so they will refer to the new locals.
+ auto localShift = originalParams.size();
+ if (localShift != 0) {
+ for (auto* get : FindAll<LocalGet>(copyBody).list) {
+ get->index += localShift;
+ }
+ for (auto* set : FindAll<LocalSet>(copyBody).list) {
+ set->index += localShift;
+ }
+ }
+ }
+
+ // Add vars from current function.
+ localTypes.insert(localTypes.end(), func->vars.begin(), func->vars.end());
+
+ // Create and add the new function.
+ auto* copyFunc = wasm.addFunction(builder.makeFunction(
+ copyName, originalFuncType, std::move(localTypes), copyBody));
// Interesting optimizations may be possible both due to removing some but
// not all of the code, and due to the locals we just added.
@@ -1196,24 +1272,6 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance,
return EvalCtorOutcome();
}
}
-
- // Otherwise, we don't recognize a pattern that allows us to do partial
- // evalling. So simply call the entire function at once and see if we can
- // optimize that.
-
- Literals results;
- try {
- results = instance.callFunction(funcName, params);
- } catch (FailToEvalException& fail) {
- if (!quiet) {
- std::cout << " ...stopping since could not eval: " << fail.why << "\n";
- }
- return EvalCtorOutcome();
- }
-
- // Success! Apply the results.
- interface.applyToModule();
- return EvalCtorOutcome(results);
}
// Eval all ctors in a module.
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index cc90a8abe..463faa04f 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -269,6 +269,7 @@ public:
call->target = target;
call->operands.set(args);
call->isReturn = isReturn;
+ call->finalize();
return call;
}
template<typename T>
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index c95f694ef..64c0bfb2d 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -51,7 +51,7 @@ std::ostream& operator<<(std::ostream& o, const WasmException& exn);
// Utilities
-extern Name WASM, RETURN_FLOW, NONCONSTANT_FLOW;
+extern Name WASM, RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW;
// Stuff that flows around during executing expressions: a literal, or a change
// in control flow.
@@ -63,6 +63,8 @@ public:
Flow(Literals&& values) : values(std::move(values)) {}
Flow(Name breakTo) : values(), breakTo(breakTo) {}
Flow(Name breakTo, Literal value) : values{value}, breakTo(breakTo) {}
+ Flow(Name breakTo, Literals&& values)
+ : values(std::move(values)), breakTo(breakTo) {}
Literals values;
Name breakTo; // if non-null, a break is going on
@@ -2965,32 +2967,33 @@ public:
Flow visitCall(Call* curr) {
NOTE_ENTER("Call");
NOTE_NAME(curr->target);
+ Name target = curr->target;
Literals arguments;
Flow flow = self()->generateArguments(curr->operands, arguments);
if (flow.breaking()) {
return flow;
}
auto* func = wasm.getFunction(curr->target);
- Flow ret;
+ auto funcType = func->type;
if (Intrinsics(*self()->getModule()).isCallWithoutEffects(func)) {
// The call.without.effects intrinsic is a call to an import that actually
// calls the given function reference that is the final argument.
- auto newArguments = arguments;
- auto target = newArguments.back();
- newArguments.pop_back();
- ret.values = callFunctionInternal(target.getFunc(), newArguments);
- } else if (func->imported()) {
- ret.values = externalInterface->callImport(func, arguments);
- } else {
- ret.values = callFunctionInternal(curr->target, arguments);
+ target = arguments.back().getFunc();
+ funcType = arguments.back().type.getHeapType();
+ arguments.pop_back();
+ }
+
+ if (curr->isReturn) {
+ // Return calls are represented by their arguments followed by a reference
+ // to the function to be called.
+ arguments.push_back(Literal::makeFunc(target, funcType));
+ return Flow(RETURN_CALL_FLOW, std::move(arguments));
}
+
+ Flow ret = callFunctionInternal(target, arguments);
#ifdef WASM_INTERPRETER_DEBUG
std::cout << "(returned to " << scope->function->name << ")\n";
#endif
- // TODO: make this a proper tail call (return first)
- if (curr->isReturn) {
- ret.breakTo = RETURN_FLOW;
- }
return ret;
}
@@ -3007,18 +3010,28 @@ public:
}
Index index = target.getSingleValue().geti32();
- Type type = curr->isReturn ? scope->function->getResults() : curr->type;
auto info = getTableInterfaceInfo(curr->table);
- Flow ret = info.interface->callTable(
- info.name, index, curr->heapType, arguments, type, *self());
- // TODO: make this a proper tail call (return first)
if (curr->isReturn) {
- ret.breakTo = RETURN_FLOW;
+ // Return calls are represented by their arguments followed by a reference
+ // to the function to be called.
+ auto funcref = info.interface->tableLoad(info.name, index);
+ if (!Type::isSubType(funcref.type, Type(curr->heapType, NonNullable))) {
+ trap("cast failure in call_indirect");
+ }
+ arguments.push_back(funcref);
+ return Flow(RETURN_CALL_FLOW, std::move(arguments));
}
+
+ Flow ret = info.interface->callTable(
+ info.name, index, curr->heapType, arguments, curr->type, *self());
+#ifdef WASM_INTERPRETER_DEBUG
+ std::cout << "(returned to " << scope->function->name << ")\n";
+#endif
return ret;
}
+
Flow visitCallRef(CallRef* curr) {
NOTE_ENTER("CallRef");
Literals arguments;
@@ -3030,24 +3043,22 @@ public:
if (target.breaking()) {
return target;
}
- if (target.getSingleValue().isNull()) {
+ auto targetRef = target.getSingleValue();
+ if (targetRef.isNull()) {
trap("null target in call_ref");
}
- Name funcName = target.getSingleValue().getFunc();
- auto* func = wasm.getFunction(funcName);
- Flow ret;
- if (func->imported()) {
- ret.values = externalInterface->callImport(func, arguments);
- } else {
- ret.values = callFunctionInternal(funcName, arguments);
+
+ if (curr->isReturn) {
+ // Return calls are represented by their arguments followed by a reference
+ // to the function to be called.
+ arguments.push_back(targetRef);
+ return Flow(RETURN_CALL_FLOW, std::move(arguments));
}
+
+ Flow ret = callFunctionInternal(targetRef.getFunc(), arguments);
#ifdef WASM_INTERPRETER_DEBUG
std::cout << "(returned to " << scope->function->name << ")\n";
#endif
- // TODO: make this a proper tail call (return first)
- if (curr->isReturn) {
- ret.breakTo = RETURN_FLOW;
- }
return ret;
}
@@ -4098,12 +4109,7 @@ public:
// Call a function, starting an invocation.
Literals callFunction(Name name, const Literals& arguments) {
- auto* func = wasm.getFunction(name);
- if (func->imported()) {
- return externalInterface->callImport(func, arguments);
- }
-
- // if the last call ended in a jump up the stack, it might have left stuff
+ // If the last call ended in a jump up the stack, it might have left stuff
// for us to clean up here
callDepth = 0;
functionStack.clear();
@@ -4112,47 +4118,79 @@ public:
// Internal function call. Must be public so that callTable implementations
// can use it (refactor?)
- Literals callFunctionInternal(Name name, const Literals& arguments) {
+ Literals callFunctionInternal(Name name, Literals arguments) {
if (callDepth > maxDepth) {
externalInterface->trap("stack limit");
}
- auto previousCallDepth = callDepth;
- callDepth++;
- auto previousFunctionStackSize = functionStack.size();
- functionStack.push_back(name);
- Function* function = wasm.getFunction(name);
- assert(function);
- FunctionScope scope(function, arguments, *self());
+ Flow flow;
+ std::optional<Type> resultType;
+
+ // We may have to call multiple functions in the event of return calls.
+ while (true) {
+ Function* function = wasm.getFunction(name);
+ assert(function);
+
+ // Return calls can only make the result type more precise.
+ if (resultType) {
+ assert(Type::isSubType(function->getResults(), *resultType));
+ }
+ resultType = function->getResults();
+
+ if (function->imported()) {
+ // TODO: Allow imported functions to tail call as well.
+ return externalInterface->callImport(function, arguments);
+ }
+
+ auto previousCallDepth = callDepth;
+ callDepth++;
+ auto previousFunctionStackSize = functionStack.size();
+ functionStack.push_back(name);
+
+ FunctionScope scope(function, arguments, *self());
#ifdef WASM_INTERPRETER_DEBUG
- std::cout << "entering " << function->name << "\n with arguments:\n";
- for (unsigned i = 0; i < arguments.size(); ++i) {
- std::cout << " $" << i << ": " << arguments[i] << '\n';
- }
+ std::cout << "entering " << function->name << "\n with arguments:\n";
+ for (unsigned i = 0; i < arguments.size(); ++i) {
+ std::cout << " $" << i << ": " << arguments[i] << '\n';
+ }
+#endif
+
+ flow = self()->visit(function->body);
+
+ // may decrease more than one, if we jumped up the stack
+ callDepth = previousCallDepth;
+ // if we jumped up the stack, we also need to pop higher frames
+ // TODO can FunctionScope handle this automatically?
+ while (functionStack.size() > previousFunctionStackSize) {
+ functionStack.pop_back();
+ }
+#ifdef WASM_INTERPRETER_DEBUG
+ std::cout << "exiting " << function->name << " with " << flow.values
+ << '\n';
#endif
- Flow flow = self()->visit(function->body);
+ if (flow.breakTo != RETURN_CALL_FLOW) {
+ break;
+ }
+
+ // There was a return call, so we need to call the next function before
+ // returning to the caller. The flow carries the function arguments and a
+ // function reference.
+ name = flow.values.back().getFunc();
+ flow.values.pop_back();
+ arguments = flow.values;
+ }
+
// cannot still be breaking, it means we missed our stop
assert(!flow.breaking() || flow.breakTo == RETURN_FLOW);
auto type = flow.getType();
- if (!Type::isSubType(type, function->getResults())) {
- std::cerr << "calling " << function->name << " resulted in " << type
- << " but the function type is " << function->getResults()
- << '\n';
+ if (!Type::isSubType(type, *resultType)) {
+ std::cerr << "calling " << name << " resulted in " << type
+ << " but the function type is " << *resultType << '\n';
WASM_UNREACHABLE("unexpected result type");
}
- // may decrease more than one, if we jumped up the stack
- callDepth = previousCallDepth;
- // if we jumped up the stack, we also need to pop higher frames
- // TODO can FunctionScope handle this automatically?
- while (functionStack.size() > previousFunctionStackSize) {
- functionStack.pop_back();
- }
-#ifdef WASM_INTERPRETER_DEBUG
- std::cout << "exiting " << function->name << " with " << flow.values
- << '\n';
-#endif
+
return flow.values;
}
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index bc707890c..c2df2c68c 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -25,6 +25,7 @@ namespace wasm {
Name WASM("wasm");
Name RETURN_FLOW("*return:)*");
+Name RETURN_CALL_FLOW("*return-call:)*");
Name NONCONSTANT_FLOW("*nonconstant:)*");
namespace BinaryConsts {
diff --git a/test/lit/ctor-eval/return_call.wast b/test/lit/ctor-eval/return_call.wast
new file mode 100644
index 000000000..3ff35ec42
--- /dev/null
+++ b/test/lit/ctor-eval/return_call.wast
@@ -0,0 +1,529 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-ctor-eval --ctors=test --kept-exports=test --ignore-external-input --quiet -all -g -S -o - | filecheck %s
+
+(module
+ ;; Simplest possible return call.
+
+ (func $test (export "test") (result i32)
+ (return_call $test2)
+ )
+
+ (func $test2 (result i32)
+ (i32.const 42)
+ )
+)
+
+;; CHECK: (type $0 (func (result i32)))
+
+;; CHECK: (export "test" (func $test_2))
+
+;; CHECK: (func $test_2 (type $0) (result i32)
+;; CHECK-NEXT: (i32.const 42)
+;; CHECK-NEXT: )
+(module
+ ;; Basic return call (followed by unreachable import call, setting global as proof it was executed)
+
+ (import "env" "import" (func $import))
+
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 2))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $test2)
+ ;; This is never executed, so it should not impede eval.
+ (call $import)
+ )
+
+ (func $test2
+ (global.set $g2
+ (i32.const 2)
+ )
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "g2" (global $g2))
+
+;; CHECK: (export "test" (func $test_3))
+
+;; CHECK: (func $test_3 (type $0)
+;; CHECK-NEXT: (nop)
+;; CHECK-NEXT: )
+(module
+ ;; Basic return call indirect
+ ;; TODO: Implement `tableLoad` to make this test work.
+
+ (import "env" "import" (func $import))
+
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 0))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ ;; CHECK: (table $t 1 1 funcref)
+ (table $t funcref (elem $test2))
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call_indirect $t
+ (i32.const 0)
+ )
+ ;; This is never executed, so it should not impede eval.
+ (call $import)
+ )
+
+ ;; CHECK: (elem $0 (i32.const 0) $test2)
+
+ ;; CHECK: (export "g1" (global $g1))
+
+ ;; CHECK: (export "g2" (global $g2))
+
+ ;; CHECK: (export "test" (func $test_3))
+
+ ;; CHECK: (func $test2 (type $0)
+ ;; CHECK-NEXT: (global.set $g2
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2
+ (global.set $g2
+ (i32.const 2)
+ )
+ )
+)
+
+;; CHECK: (func $test_3 (type $0)
+;; CHECK-NEXT: (return_call_indirect $t (type $0)
+;; CHECK-NEXT: (i32.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+(module
+ ;; Basic return call ref
+ ;; CHECK: (type $f (func))
+ (type $f (func))
+
+ (import "env" "import" (func $import))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 2))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ (elem declare $test2)
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call_ref $f
+ (ref.func $test2)
+ )
+ ;; This is never executed, so it should not impede eval.
+ (call $import)
+ )
+
+ (func $test2 (type $f)
+ (global.set $g2
+ (i32.const 2)
+ )
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "g2" (global $g2))
+
+;; CHECK: (export "test" (func $test_3))
+
+;; CHECK: (func $test_3 (type $f)
+;; CHECK-NEXT: (nop)
+;; CHECK-NEXT: )
+(module
+ ;; Return call to import
+
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (import "env" "import" (func $import (type $0)))
+ (import "env" "import" (func $import))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $import)
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "test" (func $test_2))
+
+;; CHECK: (func $test_2 (type $0)
+;; CHECK-NEXT: (return_call $import)
+;; CHECK-NEXT: )
+(module
+ ;; Return call to import with params
+
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (import "env" "import" (func $import (type $0) (param i32)))
+ (import "env" "import" (func $import (param i32)))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $import
+ (i32.add
+ (i32.const 40)
+ (i32.const 2)
+ )
+ )
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "test" (func $test_2))
+
+;; CHECK: (func $test_2 (type $1)
+;; CHECK-NEXT: (return_call $import
+;; CHECK-NEXT: (i32.const 42)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+(module
+ ;; Chain of return calls ending in import
+
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (import "env" "import" (func $import (type $0) (param i32)))
+ (import "env" "import" (func $import (param i32)))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 2))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $test2
+ (i32.const 40)
+ )
+ )
+
+ (func $test2 (param i32)
+ (global.set $g2
+ (i32.const 2)
+ )
+ (return_call $import
+ (i32.add
+ (i32.const 2)
+ (local.get 0)
+ )
+ )
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "g2" (global $g2))
+
+;; CHECK: (export "test" (func $test_3))
+
+;; CHECK: (func $test_3 (type $1)
+;; CHECK-NEXT: (return_call $import
+;; CHECK-NEXT: (i32.const 42)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+(module
+ ;; Return call to a function that can only be partially evaluated.
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (import "env" "import" (func $import (type $0)))
+ (import "env" "import" (func $import))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 2))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ (func $test (export "test")
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $test2)
+ )
+
+ (func $test2
+ (global.set $g2
+ (i32.const 2)
+ )
+ (call $import)
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "g2" (global $g2))
+
+;; CHECK: (export "test" (func $test_3))
+
+;; CHECK: (func $test_3 (type $0)
+;; CHECK-NEXT: (call $import)
+;; CHECK-NEXT: )
+(module
+ ;; Return call with parameters to a function that can only be partially evaluated.
+
+ ;; CHECK: (type $0 (func (param i32) (result i32)))
+
+ ;; CHECK: (type $1 (func (param i32)))
+
+ ;; CHECK: (import "env" "import" (func $import (type $0) (param i32) (result i32)))
+ (import "env" "import" (func $import (param i32) (result i32)))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 2))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ (func $test (export "test") (param i32)
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $test2
+ (local.get 0)
+ )
+ )
+
+ (func $test2 (param i32)
+ (local $x i32)
+ (global.set $g2
+ (i32.const 2)
+ )
+ (local.set $x
+ (call $import
+ (local.get 0)
+ )
+ )
+ (drop
+ (call $import
+ (i32.const 0)
+ )
+ )
+ (drop
+ (call $import
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "g2" (global $g2))
+
+;; CHECK: (export "test" (func $test_3))
+
+;; CHECK: (func $test_3 (type $1) (param $0 i32)
+;; CHECK-NEXT: (local.set $0
+;; CHECK-NEXT: (call $import
+;; CHECK-NEXT: (i32.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (drop
+;; CHECK-NEXT: (call $import
+;; CHECK-NEXT: (i32.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (drop
+;; CHECK-NEXT: (call $import
+;; CHECK-NEXT: (local.get $0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+(module
+ ;; Return call with parameters to a function that can only be partially
+ ;; evaluated that takes different parameters from the original.
+ ;; CHECK: (type $0 (func (param i32) (result i32)))
+
+ ;; CHECK: (type $1 (func (param i32)))
+
+ ;; CHECK: (import "env" "import" (func $import (type $0) (param i32) (result i32)))
+ (import "env" "import" (func $import (param i32) (result i32)))
+
+ ;; CHECK: (global $g1 (mut i32) (i32.const 1))
+ (global $g1 (export "g1") (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $g2 (mut i32) (i32.const 2))
+ (global $g2 (export "g2") (mut i32) (i32.const 0))
+
+ (func $test (export "test") (param i32)
+ (global.set $g1
+ (i32.const 1)
+ )
+ (return_call $test2
+ (i64.const 1)
+ (i64.const 2)
+ (i64.const 3)
+ )
+ )
+
+ (func $test2 (param i64 i64 i64)
+ (local $x i32)
+ (global.set $g2
+ (i32.const 2)
+ )
+ (local.set $x
+ (call $import
+ (i32.wrap_i64
+ (local.get 2)
+ )
+ )
+ )
+ (drop
+ (call $import
+ (i32.const 0)
+ )
+ )
+ (drop
+ (call $import
+ (local.get $x)
+ )
+ )
+ )
+)
+
+;; CHECK: (export "g1" (global $g1))
+
+;; CHECK: (export "g2" (global $g2))
+
+;; CHECK: (export "test" (func $test_3))
+
+;; CHECK: (func $test_3 (type $1) (param $0 i32)
+;; CHECK-NEXT: (local.set $0
+;; CHECK-NEXT: (call $import
+;; CHECK-NEXT: (i32.const 3)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (drop
+;; CHECK-NEXT: (call $import
+;; CHECK-NEXT: (i32.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (drop
+;; CHECK-NEXT: (call $import
+;; CHECK-NEXT: (local.get $0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+(module
+ ;; Return call to self with different params, then stop evaluating.
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (import "env" "import" (func $import (type $1)))
+ (import "env" "import" (func $import))
+
+ ;; CHECK: (global $g (mut i32) (i32.const 42))
+ (global $g (mut i32) (i32.const 0))
+
+ ;; CHECK: (export "test" (func $test_2))
+
+ ;; CHECK: (func $test (type $0) (param $0 i32)
+ ;; CHECK-NEXT: (global.set $g
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (call $import)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (return_call $test
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (export "test") (param i32)
+ (global.set $g
+ (local.get 0)
+ )
+ (if
+ (i32.eq
+ (local.get 0)
+ (i32.const 42)
+ )
+ (then
+ (call $import)
+ )
+ (else
+ (return_call $test
+ (i32.add
+ (local.get 0)
+ (i32.const 1)
+ )
+ )
+ )
+ )
+ )
+)
+
+;; CHECK: (func $test_2 (type $0) (param $0 i32)
+;; CHECK-NEXT: (if
+;; CHECK-NEXT: (i32.eq
+;; CHECK-NEXT: (local.tee $0
+;; CHECK-NEXT: (i32.const 42)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (i32.const 42)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (then
+;; CHECK-NEXT: (call $import)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (else
+;; CHECK-NEXT: (return_call $test
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $0)
+;; CHECK-NEXT: (i32.const 1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
diff --git a/test/lit/passes/global-effects.wast b/test/lit/passes/global-effects.wast
index 3f4e58e8b..38e96c6eb 100644
--- a/test/lit/passes/global-effects.wast
+++ b/test/lit/passes/global-effects.wast
@@ -8,27 +8,36 @@
;; RUN: foreach %s %t wasm-opt -all --generate-global-effects --discard-global-effects --vacuum -S -o - | filecheck %s --check-prefix WITHOUT
(module
- ;; WITHOUT: (type $0 (func))
+
+ ;; WITHOUT: (type $void (func))
+ ;; INCLUDE: (type $void (func))
+ (type $void (func))
;; WITHOUT: (type $1 (func (result i32)))
;; WITHOUT: (type $2 (func (param i32)))
- ;; WITHOUT: (import "a" "b" (func $import (type $0)))
- ;; INCLUDE: (type $0 (func))
-
+ ;; WITHOUT: (import "a" "b" (func $import (type $void)))
;; INCLUDE: (type $1 (func (result i32)))
;; INCLUDE: (type $2 (func (param i32)))
- ;; INCLUDE: (import "a" "b" (func $import (type $0)))
+ ;; INCLUDE: (import "a" "b" (func $import (type $void)))
(import "a" "b" (func $import))
+ ;; WITHOUT: (table $t 0 funcref)
+ ;; INCLUDE: (table $t 0 funcref)
+ (table $t funcref 0)
+
+ ;; WITHOUT: (elem declare func $throw)
+
;; WITHOUT: (tag $tag)
+ ;; INCLUDE: (elem declare func $throw)
+
;; INCLUDE: (tag $tag)
(tag $tag)
- ;; WITHOUT: (func $main (type $0)
+ ;; WITHOUT: (func $main (type $void)
;; WITHOUT-NEXT: (call $nop)
;; WITHOUT-NEXT: (call $unreachable)
;; WITHOUT-NEXT: (call $call-nop)
@@ -39,7 +48,7 @@
;; WITHOUT-NEXT: (call $throw)
;; WITHOUT-NEXT: (call $throw-and-import)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $main (type $0)
+ ;; INCLUDE: (func $main (type $void)
;; INCLUDE-NEXT: (call $unreachable)
;; INCLUDE-NEXT: (call $call-unreachable)
;; INCLUDE-NEXT: (call $throw)
@@ -67,10 +76,10 @@
(call $throw-and-import)
)
- ;; WITHOUT: (func $cycle (type $0)
+ ;; WITHOUT: (func $cycle (type $void)
;; WITHOUT-NEXT: (call $cycle)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $cycle (type $0)
+ ;; INCLUDE: (func $cycle (type $void)
;; INCLUDE-NEXT: (call $cycle)
;; INCLUDE-NEXT: )
(func $cycle
@@ -79,10 +88,10 @@
(call $cycle)
)
- ;; WITHOUT: (func $cycle-1 (type $0)
+ ;; WITHOUT: (func $cycle-1 (type $void)
;; WITHOUT-NEXT: (call $cycle-2)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $cycle-1 (type $0)
+ ;; INCLUDE: (func $cycle-1 (type $void)
;; INCLUDE-NEXT: (call $cycle-2)
;; INCLUDE-NEXT: )
(func $cycle-1
@@ -90,40 +99,40 @@
(call $cycle-2)
)
- ;; WITHOUT: (func $cycle-2 (type $0)
+ ;; WITHOUT: (func $cycle-2 (type $void)
;; WITHOUT-NEXT: (call $cycle-1)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $cycle-2 (type $0)
+ ;; INCLUDE: (func $cycle-2 (type $void)
;; INCLUDE-NEXT: (call $cycle-1)
;; INCLUDE-NEXT: )
(func $cycle-2
(call $cycle-1)
)
- ;; WITHOUT: (func $nop (type $0)
+ ;; WITHOUT: (func $nop (type $void)
;; WITHOUT-NEXT: (nop)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $nop (type $0)
+ ;; INCLUDE: (func $nop (type $void)
;; INCLUDE-NEXT: (nop)
;; INCLUDE-NEXT: )
(func $nop
(nop)
)
- ;; WITHOUT: (func $unreachable (type $0)
+ ;; WITHOUT: (func $unreachable (type $void)
;; WITHOUT-NEXT: (unreachable)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $unreachable (type $0)
+ ;; INCLUDE: (func $unreachable (type $void)
;; INCLUDE-NEXT: (unreachable)
;; INCLUDE-NEXT: )
(func $unreachable
(unreachable)
)
- ;; WITHOUT: (func $call-nop (type $0)
+ ;; WITHOUT: (func $call-nop (type $void)
;; WITHOUT-NEXT: (call $nop)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $call-nop (type $0)
+ ;; INCLUDE: (func $call-nop (type $void)
;; INCLUDE-NEXT: (nop)
;; INCLUDE-NEXT: )
(func $call-nop
@@ -131,10 +140,10 @@
(call $nop)
)
- ;; WITHOUT: (func $call-unreachable (type $0)
+ ;; WITHOUT: (func $call-unreachable (type $void)
;; WITHOUT-NEXT: (call $unreachable)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $call-unreachable (type $0)
+ ;; INCLUDE: (func $call-unreachable (type $void)
;; INCLUDE-NEXT: (call $unreachable)
;; INCLUDE-NEXT: )
(func $call-unreachable
@@ -172,7 +181,7 @@
)
)
- ;; WITHOUT: (func $call-throw-and-catch (type $0)
+ ;; WITHOUT: (func $call-throw-and-catch (type $void)
;; WITHOUT-NEXT: (try $try
;; WITHOUT-NEXT: (do
;; WITHOUT-NEXT: (call $throw)
@@ -190,7 +199,7 @@
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $call-throw-and-catch (type $0)
+ ;; INCLUDE: (func $call-throw-and-catch (type $void)
;; INCLUDE-NEXT: (try $try0
;; INCLUDE-NEXT: (do
;; INCLUDE-NEXT: (call $throw-and-import)
@@ -219,7 +228,158 @@
)
)
- ;; WITHOUT: (func $call-unreachable-and-catch (type $0)
+ ;; WITHOUT: (func $return-call-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (return_call $throw)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $return-call-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (return_call $throw)
+ ;; INCLUDE-NEXT: )
+ (func $return-call-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target throws. However, the
+ ;; surrounding try-catch can be removed even without global effects
+ ;; because the throw from the return_call is never observed by this
+ ;; try-catch.
+ (return_call $throw)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $return-call-indirect-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (return_call_indirect $t (type $void)
+ ;; WITHOUT-NEXT: (i32.const 0)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $return-call-indirect-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (return_call_indirect $t (type $void)
+ ;; INCLUDE-NEXT: (i32.const 0)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $return-call-indirect-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target may throw. However,
+ ;; the surrounding try-catch can be removed even without global effects
+ ;; because the throw from the return_call is never observed by this
+ ;; try-catch.
+ (return_call_indirect
+ (i32.const 0)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $return-call-ref-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (return_call_ref $void
+ ;; WITHOUT-NEXT: (ref.func $throw)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $return-call-ref-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (return_call_ref $void
+ ;; INCLUDE-NEXT: (ref.func $throw)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ (func $return-call-ref-throw-and-catch
+ (try
+ (do
+ ;; This call cannot be optimized out, as the target may throw. However,
+ ;; the surrounding try-catch can be removed even without global effects
+ ;; because the throw from the return_call is never observed by this
+ ;; try-catch.
+ (return_call_ref $void
+ (ref.func $throw)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; WITHOUT: (func $call-return-call-throw-and-catch (type $void)
+ ;; WITHOUT-NEXT: (try $try
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $return-call-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (try $try1
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (try $try2
+ ;; WITHOUT-NEXT: (do
+ ;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (catch_all
+ ;; WITHOUT-NEXT: (nop)
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: )
+ ;; WITHOUT-NEXT: (call $return-call-throw-and-catch)
+ ;; WITHOUT-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; WITHOUT-NEXT: )
+ ;; INCLUDE: (func $call-return-call-throw-and-catch (type $void)
+ ;; INCLUDE-NEXT: (try $try1
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (try $try2
+ ;; INCLUDE-NEXT: (do
+ ;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (catch_all
+ ;; INCLUDE-NEXT: (nop)
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: )
+ ;; INCLUDE-NEXT: (call $return-call-throw-and-catch)
+ ;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
+ ;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
+ ;; INCLUDE-NEXT: )
+ (func $call-return-call-throw-and-catch
+ (try
+ (do
+ ;; Even though the body of the previous function is a try-catch_all, the
+ ;; function still throws because of its return_call, so this cannot be
+ ;; optimized out, but once again the entire try-catch can be.
+ (call $return-call-throw-and-catch)
+ )
+ (catch_all)
+ )
+ (try
+ (do
+ ;; This would be the same, except since it performs an indirect call, we
+ ;; conservatively assume it could have any effect, so we can't optimize.
+ (call $return-call-indirect-throw-and-catch)
+ )
+ (catch_all)
+ )
+ (try
+ (do
+ ;; Same here.
+ (call $return-call-ref-throw-and-catch)
+ )
+ (catch_all)
+ )
+
+ ;; These cannot be optimized out at all.
+ (call $return-call-throw-and-catch)
+ (call $return-call-indirect-throw-and-catch)
+ (call $return-call-ref-throw-and-catch)
+ )
+
+ ;; WITHOUT: (func $call-unreachable-and-catch (type $void)
;; WITHOUT-NEXT: (try $try
;; WITHOUT-NEXT: (do
;; WITHOUT-NEXT: (call $unreachable)
@@ -229,7 +389,7 @@
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $call-unreachable-and-catch (type $0)
+ ;; INCLUDE: (func $call-unreachable-and-catch (type $void)
;; INCLUDE-NEXT: (call $unreachable)
;; INCLUDE-NEXT: )
(func $call-unreachable-and-catch
@@ -299,20 +459,20 @@
)
)
- ;; WITHOUT: (func $throw (type $0)
+ ;; WITHOUT: (func $throw (type $void)
;; WITHOUT-NEXT: (throw $tag)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $throw (type $0)
+ ;; INCLUDE: (func $throw (type $void)
;; INCLUDE-NEXT: (throw $tag)
;; INCLUDE-NEXT: )
(func $throw
(throw $tag)
)
- ;; WITHOUT: (func $throw-and-import (type $0)
+ ;; WITHOUT: (func $throw-and-import (type $void)
;; WITHOUT-NEXT: (throw $tag)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $throw-and-import (type $0)
+ ;; INCLUDE: (func $throw-and-import (type $void)
;; INCLUDE-NEXT: (throw $tag)
;; INCLUDE-NEXT: )
(func $throw-and-import
@@ -327,11 +487,11 @@
)
)
- ;; WITHOUT: (func $cycle-with-unknown-call (type $0)
+ ;; WITHOUT: (func $cycle-with-unknown-call (type $void)
;; WITHOUT-NEXT: (call $cycle-with-unknown-call)
;; WITHOUT-NEXT: (call $import)
;; WITHOUT-NEXT: )
- ;; INCLUDE: (func $cycle-with-unknown-call (type $0)
+ ;; INCLUDE: (func $cycle-with-unknown-call (type $void)
;; INCLUDE-NEXT: (call $cycle-with-unknown-call)
;; INCLUDE-NEXT: (call $import)
;; INCLUDE-NEXT: )
diff --git a/test/lit/passes/inlining-unreachable.wast b/test/lit/passes/inlining-unreachable.wast
index 9ad141435..2c93359fd 100644
--- a/test/lit/passes/inlining-unreachable.wast
+++ b/test/lit/passes/inlining-unreachable.wast
@@ -77,8 +77,16 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (block $__inlined_func$callee
- ;; CHECK-NEXT: (call $imported
- ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__return_call
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (br $__return_call)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $imported
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -114,7 +122,12 @@
;; CHECK-NEXT: (block $__inlined_func$0
;; CHECK-NEXT: (block
;; CHECK-NEXT: (nop)
- ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null nofunc)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/inlining_all-features.wast b/test/lit/passes/inlining_all-features.wast
index 65dabdd6c..c274e9abb 100644
--- a/test/lit/passes/inlining_all-features.wast
+++ b/test/lit/passes/inlining_all-features.wast
@@ -100,7 +100,12 @@
)
;; CHECK: (func $1 (type $none_=>_none)
;; CHECK-NEXT: (block $__inlined_func$0
- ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null nofunc)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $1
diff --git a/test/lit/passes/inlining_enable-tail-call.wast b/test/lit/passes/inlining_enable-tail-call.wast
index d2d7772cd..49ea1c3c4 100644
--- a/test/lit/passes/inlining_enable-tail-call.wast
+++ b/test/lit/passes/inlining_enable-tail-call.wast
@@ -468,74 +468,303 @@
(call $0)
)
)
+
(module
- ;; CHECK: (type $0 (func (result i32)))
+ ;; No params, no results
+ ;; CHECK: (type $0 (func))
- ;; CHECK: (func $0 (result i32)
- ;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (block $__inlined_func$1 (result i32)
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$callee
+ ;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $0 (result i32)
- (return_call $1)
+ (func $caller
+ (return_call $callee)
)
- (func $1 (result i32)
+ (func $callee
+ (drop
+ (i32.const 42)
+ )
+ )
+)
+
+(module
+ ;; No params, one result
+ ;; CHECK: (type $0 (func (result i32)))
+
+ ;; CHECK: (func $caller (result i32)
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$callee (result i32)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (result i32)
+ (return_call $callee)
+ )
+ (func $callee (result i32)
(i32.const 42)
)
)
+
(module
+ ;; One param, no results
;; CHECK: (type $0 (func))
- ;; CHECK: (func $0
+ ;; CHECK: (func $caller
;; CHECK-NEXT: (local $0 i32)
- ;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (block $__inlined_func$1
- ;; CHECK-NEXT: (local.set $0
- ;; CHECK-NEXT: (i32.const 42)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__original_body)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$callee
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $0
- (return_call $1
+ (func $caller
+ (return_call $callee
(i32.const 42)
)
)
- (func $1 (param i32)
+ (func $callee (param i32)
(drop
(local.get 0)
)
)
)
+
(module
+ ;; One param, one result
;; CHECK: (type $0 (func (result i32)))
- ;; CHECK: (func $0 (result i32)
+ ;; CHECK: (func $caller (result i32)
;; CHECK-NEXT: (local $0 i32)
- ;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (block $__inlined_func$1 (result i32)
- ;; CHECK-NEXT: (local.set $0
- ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$callee (result i32)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $0 (result i32)
- (return_call $1
+ (func $caller (result i32)
+ (return_call $callee
(i32.const 42)
)
)
- (func $1 (param i32) (result i32)
+ (func $callee (param i32) (result i32)
(local.get 0)
)
)
+
+(module
+ ;; Multiple params, no result
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local $y i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$callee
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (local $x i32)
+ (local $y i32)
+ (return_call $callee
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+ (func $callee (param i32 i32)
+ (drop
+ (local.get 0)
+ )
+ (drop
+ (local.get 1)
+ )
+ )
+)
+
+(module
+ ;; Chain of return_calls, no params, no results.
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $first
+ ;; CHECK-NEXT: (block $__original_body_0
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$second
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__original_body_0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$third$1
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $first
+ (return_call $second)
+ )
+ (func $second
+ (return_call $third)
+ )
+ (func $third
+ (drop
+ (i32.const 42)
+ )
+ )
+)
+
+(module
+ ;; Chain of return_calls with params and results.
+ ;; CHECK: (type $0 (func (result i32)))
+
+ ;; CHECK: (func $first (result i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local $y i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (local $4 i32)
+ ;; CHECK-NEXT: (local $5 i32)
+ ;; CHECK-NEXT: (block $__original_body_0
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$second
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $4
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $5
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__original_body_0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$third$1 (result i32)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $first (result i32)
+ (local $x i32)
+ (local $y i32)
+ (return_call $second
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+ (func $second (param i32 i32) (result i32)
+ (return_call $third
+ (local.get 0)
+ (local.get 1)
+ )
+ )
+ (func $third (param i32 i32) (result i32)
+ (drop
+ (local.get 0)
+ )
+ (drop
+ (local.get 1)
+ )
+ (i32.const 42)
+ )
+)
+
(module
;; CHECK: (type $0 (func))
@@ -543,14 +772,19 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (block $__inlined_func$1 (result i32)
- ;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (br $__inlined_func$1
- ;; CHECK-NEXT: (block (result i32)
- ;; CHECK-NEXT: (block $__inlined_func$2$1 (result i32)
- ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (block $__return_call
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__return_call)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (block $__inlined_func$2$1 (result i32)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -568,6 +802,7 @@
(i32.const 42)
)
)
+
(module
;; CHECK: (type $0 (func))
@@ -575,19 +810,110 @@
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (block $__inlined_func$1
;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__return_call
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__return_call)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__inlined_func$1)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__inlined_func$2$1
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $0
+ (call $1)
+ )
+ (func $1
+ (return_call $2
+ (i32.const 42)
+ )
+ )
+ (func $2 (param i32)
+ (drop
+ (local.get 0)
+ )
+ )
+)
+
+(module
+ ;; Same as above, but with nontrivial effects in the children.
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (type $1 (func (param i32)))
+
+ ;; CHECK: (func $0
+ ;; CHECK-NEXT: (block $__inlined_func$1
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__return_call
+ ;; CHECK-NEXT: (br $__inlined_func$1)
+ ;; CHECK-NEXT: (br $__return_call)
+ ;; CHECK-NEXT: (br $__inlined_func$1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $2
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $0
+ (call $1)
+ )
+ (func $1
+ (return_call $2
+ (return)
+ )
+ )
+ ;; CHECK: (func $2 (param $0 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $2 (param i32)
+ (drop
+ (local.get 0)
+ )
+ )
+)
+
+(module
+ ;; Same as above, but this time the child is not unreachable.
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (func $0
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (block $__inlined_func$1
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__return_call
;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (block $__inlined_func$2$1
- ;; CHECK-NEXT: (local.set $0
- ;; CHECK-NEXT: (i32.const 42)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (local.tee $0
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__inlined_func$1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__return_call)
;; CHECK-NEXT: )
;; CHECK-NEXT: (br $__inlined_func$1)
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__inlined_func$2$1
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -596,7 +922,9 @@
)
(func $1
(return_call $2
- (i32.const 42)
+ (block (result i32)
+ (return)
+ )
)
)
(func $2 (param i32)
@@ -605,6 +933,7 @@
)
)
)
+
(module
;; CHECK: (type $0 (func))
@@ -617,7 +946,12 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (block $__inlined_func$1 (result i32)
- ;; CHECK-NEXT: (br $__inlined_func$1
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (block $__return_call
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__return_call)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect (type $T)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
@@ -650,11 +984,14 @@
;; CHECK: (func $0
;; CHECK-NEXT: (block $__inlined_func$1
;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__return_call
+ ;; CHECK-NEXT: (br $__return_call)
+ ;; CHECK-NEXT: (br $__inlined_func$1)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect (type $T)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (br $__inlined_func$1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -703,28 +1040,31 @@
;; CHECK-NEXT: (block
;; CHECK-NEXT: (block $__inlined_func$13$1
;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (if
- ;; CHECK-NEXT: (global.get $global$0)
- ;; CHECK-NEXT: (then
- ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $global$0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__inlined_func$13$1)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block $__inlined_func$2
;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (block $__inlined_func$2
- ;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (if
- ;; CHECK-NEXT: (global.get $global$0)
- ;; CHECK-NEXT: (then
- ;; CHECK-NEXT: (br $__inlined_func$2)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (global.set $global$0
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $global$0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (br $__inlined_func$2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (br $__inlined_func$13$1)
+ ;; CHECK-NEXT: (global.set $global$0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -746,37 +1086,42 @@
(export "is_even" (func $is_even))
;; CHECK: (func $is_even (param $i i32) (result i32)
;; CHECK-NEXT: (local $1 i32)
- ;; CHECK-NEXT: (if (result i32)
- ;; CHECK-NEXT: (i32.eqz
- ;; CHECK-NEXT: (local.get $i)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (then
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (else
- ;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (block $__inlined_func$is_odd (result i32)
- ;; CHECK-NEXT: (local.set $1
- ;; CHECK-NEXT: (i32.sub
- ;; CHECK-NEXT: (local.get $i)
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (if (result i32)
- ;; CHECK-NEXT: (i32.eqz
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (then
- ;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (else
- ;; CHECK-NEXT: (return_call $is_even
- ;; CHECK-NEXT: (i32.sub
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__original_body
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (i32.eqz
+ ;; CHECK-NEXT: (local.get $i)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (i32.sub
+ ;; CHECK-NEXT: (local.get $i)
+ ;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $__original_body)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $__inlined_func$is_odd (result i32)
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (i32.eqz
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (return_call $is_even
+ ;; CHECK-NEXT: (i32.sub
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/simplify-locals-eh-old.wast b/test/lit/passes/simplify-locals-eh-old.wast
index 5e4a395d3..0bf26f34d 100644
--- a/test/lit/passes/simplify-locals-eh-old.wast
+++ b/test/lit/passes/simplify-locals-eh-old.wast
@@ -152,6 +152,57 @@
)
)
+ ;; CHECK: (func $return-call-can-be-sinked-into-try (type $3) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (try $try (result i32)
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (return_call $return-call-can-be-sinked-into-try)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e-i32
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-call-can-be-sinked-into-try (result i32)
+ (local $0 i32)
+ (drop
+ ;; This cannot throw either, so it can be sunk. Wrap the return_call in an
+ ;; if so the whole expression does not return unconditionally.
+ (local.tee $0
+ (if (result i32)
+ (i32.const 0)
+ (then
+ (return_call $return-call-can-be-sinked-into-try)
+ )
+ (else
+ (i32.const 1)
+ )
+ )
+ )
+ )
+ (try (result i32)
+ (do
+ (drop (local.get $0))
+ (i32.const 0)
+ )
+ (catch $e-i32
+ (pop i32)
+ )
+ )
+ )
+
;; CHECK: (func $equivalent-set-removal-call (type $1) (param $0 i32)
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (nop)
diff --git a/test/lit/passes/vacuum-eh-old.wast b/test/lit/passes/vacuum-eh-old.wast
index 68b5f7b1e..0215b5b25 100644
--- a/test/lit/passes/vacuum-eh-old.wast
+++ b/test/lit/passes/vacuum-eh-old.wast
@@ -2,12 +2,19 @@
;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s
(module
+ ;; CHECK: (type $void (func))
+ (type $void (func))
+
+ ;; CHECK: (table $t 0 funcref)
+
;; CHECK: (tag $e (param i32))
(tag $e (param i32))
;; CHECK: (tag $e2 (param i32))
(tag $e2 (param i32))
- ;; CHECK: (func $try-test (type $0)
+ (table $t funcref 0)
+
+ ;; CHECK: (func $try-test (type $void)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $try-test
@@ -60,7 +67,7 @@
(i32.const 2)
)
- ;; CHECK: (func $inner-try-catch-test (type $0)
+ ;; CHECK: (func $inner-try-catch-test (type $void)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (try $try
;; CHECK-NEXT: (do
@@ -108,7 +115,7 @@
)
)
- ;; CHECK: (func $br-in-catch (type $0)
+ ;; CHECK: (func $br-in-catch (type $void)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $br-in-catch
@@ -128,7 +135,7 @@
)
)
- ;; CHECK: (func $try-delegate-outer-target (type $0)
+ ;; CHECK: (func $try-delegate-outer-target (type $void)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (try $label$0
;; CHECK-NEXT: (do
@@ -179,7 +186,7 @@
)
)
- ;; CHECK: (func $trivial-catch-all-of-throw (type $0)
+ ;; CHECK: (func $trivial-catch-all-of-throw (type $void)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (try $try3
;; CHECK-NEXT: (do
@@ -225,4 +232,66 @@
(catch_all)
)
)
+
+ ;; CHECK: (func $throw (type $void)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $throw
+ ;; Helper for the tail call tests below.
+ (throw $e
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $return-call-catch (type $void)
+ ;; CHECK-NEXT: (return_call $throw)
+ ;; CHECK-NEXT: )
+ (func $return-call-catch
+ (try
+ (do
+ ;; This returns before it throws, so we can optimize out the surrounding
+ ;; try-catch.
+ (return_call $throw)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; CHECK: (func $return-call-indirect-catch (type $void)
+ ;; CHECK-NEXT: (return_call_indirect $t (type $void)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-call-indirect-catch
+ (try
+ (do
+ ;; This returns before it throws, so we can optimize out the surrounding
+ ;; try-catch.
+ (return_call_indirect
+ (i32.const 0)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; CHECK: (func $return-call-ref-catch (type $void)
+ ;; CHECK-NEXT: (return_call_ref $void
+ ;; CHECK-NEXT: (ref.func $throw)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-call-ref-catch
+ (try
+ (do
+ ;; This returns before it throws, so we can optimize out the surrounding
+ ;; try-catch.
+ (return_call_ref $void
+ (ref.func $throw)
+ )
+ )
+ (catch_all)
+ )
+ )
)
diff --git a/test/spec/return_call.wast b/test/spec/return_call.wast
new file mode 100644
index 000000000..9423159ff
--- /dev/null
+++ b/test/spec/return_call.wast
@@ -0,0 +1,202 @@
+;; Test `return_call` operator
+
+(module
+ ;; Auxiliary definitions
+ (func $const-i32 (result i32) (i32.const 0x132))
+ (func $const-i64 (result i64) (i64.const 0x164))
+ (func $const-f32 (result f32) (f32.const 0xf32))
+ (func $const-f64 (result f64) (f64.const 0xf64))
+
+ (func $id-i32 (param i32) (result i32) (local.get 0))
+ (func $id-i64 (param i64) (result i64) (local.get 0))
+ (func $id-f32 (param f32) (result f32) (local.get 0))
+ (func $id-f64 (param f64) (result f64) (local.get 0))
+
+ (func $f32-i32 (param f32 i32) (result i32) (local.get 1))
+ (func $i32-i64 (param i32 i64) (result i64) (local.get 1))
+ (func $f64-f32 (param f64 f32) (result f32) (local.get 1))
+ (func $i64-f64 (param i64 f64) (result f64) (local.get 1))
+
+ ;; Typing
+
+ (func (export "type-i32") (result i32) (return_call $const-i32))
+ (func (export "type-i64") (result i64) (return_call $const-i64))
+ (func (export "type-f32") (result f32) (return_call $const-f32))
+ (func (export "type-f64") (result f64) (return_call $const-f64))
+
+ (func (export "type-first-i32") (result i32) (return_call $id-i32 (i32.const 32)))
+ (func (export "type-first-i64") (result i64) (return_call $id-i64 (i64.const 64)))
+ (func (export "type-first-f32") (result f32) (return_call $id-f32 (f32.const 1.32)))
+ (func (export "type-first-f64") (result f64) (return_call $id-f64 (f64.const 1.64)))
+
+ (func (export "type-second-i32") (result i32)
+ (return_call $f32-i32 (f32.const 32.1) (i32.const 32))
+ )
+ (func (export "type-second-i64") (result i64)
+ (return_call $i32-i64 (i32.const 32) (i64.const 64))
+ )
+ (func (export "type-second-f32") (result f32)
+ (return_call $f64-f32 (f64.const 64) (f32.const 32))
+ )
+ (func (export "type-second-f64") (result f64)
+ (return_call $i64-f64 (i64.const 64) (f64.const 64.1))
+ )
+
+ ;; Recursion
+
+ (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call $fac-acc
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ )
+ )
+ )
+ )
+
+ (func $count (export "count") (param i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 0))
+ (else (return_call $count (i64.sub (local.get 0) (i64.const 1))))
+ )
+ )
+
+ (func $even (export "even") (param i64) (result i32)
+ (if (result i32) (i64.eqz (local.get 0))
+ (then (i32.const 44))
+ (else (return_call $odd (i64.sub (local.get 0) (i64.const 1))))
+ )
+ )
+ (func $odd (export "odd") (param i64) (result i32)
+ (if (result i32) (i64.eqz (local.get 0))
+ (then (i32.const 99))
+ (else (return_call $even (i64.sub (local.get 0) (i64.const 1))))
+ )
+ )
+)
+
+(assert_return (invoke "type-i32") (i32.const 0x132))
+(assert_return (invoke "type-i64") (i64.const 0x164))
+(assert_return (invoke "type-f32") (f32.const 0xf32))
+(assert_return (invoke "type-f64") (f64.const 0xf64))
+
+(assert_return (invoke "type-first-i32") (i32.const 32))
+(assert_return (invoke "type-first-i64") (i64.const 64))
+(assert_return (invoke "type-first-f32") (f32.const 1.32))
+(assert_return (invoke "type-first-f64") (f64.const 1.64))
+
+(assert_return (invoke "type-second-i32") (i32.const 32))
+(assert_return (invoke "type-second-i64") (i64.const 64))
+(assert_return (invoke "type-second-f32") (f32.const 32))
+(assert_return (invoke "type-second-f64") (f64.const 64.1))
+
+(assert_return (invoke "fac-acc" (i64.const 0) (i64.const 1)) (i64.const 1))
+(assert_return (invoke "fac-acc" (i64.const 1) (i64.const 1)) (i64.const 1))
+(assert_return (invoke "fac-acc" (i64.const 5) (i64.const 1)) (i64.const 120))
+(assert_return
+ (invoke "fac-acc" (i64.const 25) (i64.const 1))
+ (i64.const 7034535277573963776)
+)
+
+(assert_return (invoke "count" (i64.const 0)) (i64.const 0))
+(assert_return (invoke "count" (i64.const 1000)) (i64.const 0))
+(assert_return (invoke "count" (i64.const 1000000)) (i64.const 0))
+
+(assert_return (invoke "even" (i64.const 0)) (i32.const 44))
+(assert_return (invoke "even" (i64.const 1)) (i32.const 99))
+(assert_return (invoke "even" (i64.const 100)) (i32.const 44))
+(assert_return (invoke "even" (i64.const 77)) (i32.const 99))
+(assert_return (invoke "even" (i64.const 1000000)) (i32.const 44))
+(assert_return (invoke "even" (i64.const 1000001)) (i32.const 99))
+(assert_return (invoke "odd" (i64.const 0)) (i32.const 99))
+(assert_return (invoke "odd" (i64.const 1)) (i32.const 44))
+(assert_return (invoke "odd" (i64.const 200)) (i32.const 99))
+(assert_return (invoke "odd" (i64.const 77)) (i32.const 44))
+(assert_return (invoke "odd" (i64.const 1000000)) (i32.const 99))
+(assert_return (invoke "odd" (i64.const 999999)) (i32.const 44))
+
+
+;; Invalid typing
+
+(assert_invalid
+ (module
+ (func $type-void-vs-num (result i32) (return_call 1) (i32.const 0))
+ (func)
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (func $type-num-vs-num (result i32) (return_call 1) (i32.const 0))
+ (func (result i64) (i64.const 1))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (func $arity-0-vs-1 (return_call 1))
+ (func (param i32))
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (func $arity-0-vs-2 (return_call 1))
+ (func (param f64 i32))
+ )
+ "type mismatch"
+)
+
+;; (module
+;; (func $arity-1-vs-0 (i32.const 1) (return_call 1))
+;; (func)
+;; )
+
+;; (module
+;; (func $arity-2-vs-0 (f64.const 2) (i32.const 1) (return_call 1))
+;; (func)
+;; )
+
+(assert_invalid
+ (module
+ (func $type-first-void-vs-num (return_call 1 (nop) (i32.const 1)))
+ (func (param i32 i32))
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (func $type-second-void-vs-num (return_call 1 (i32.const 1) (nop)))
+ (func (param i32 i32))
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (func $type-first-num-vs-num (return_call 1 (f64.const 1) (i32.const 1)))
+ (func (param i32 f64))
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (func $type-second-num-vs-num (return_call 1 (i32.const 1) (f64.const 1)))
+ (func (param f64 i32))
+ )
+ "type mismatch"
+)
+
+
+;; Unbound function
+
+(assert_invalid
+ (module (func $unbound-func (return_call 1)))
+ "unknown function"
+)
+(assert_invalid
+ (module (func $large-func (return_call 1012321300)))
+ "unknown function"
+)
diff --git a/test/spec/return_call_eh.wast b/test/spec/return_call_eh.wast
new file mode 100644
index 000000000..e85fdf7c6
--- /dev/null
+++ b/test/spec/return_call_eh.wast
@@ -0,0 +1,35 @@
+;; Test the combination of 'return_call' with exception handling
+
+(module
+ (tag $t)
+
+ (func $test (export "test") (result i32)
+ (try (result i32)
+ (do
+ (call $return-call-in-try)
+ )
+ (catch_all
+ ;; Catch the exception thrown from $return-callee
+ (i32.const 42)
+ )
+ )
+
+ )
+
+ (func $return-call-in-try (result i32)
+ (try (result i32)
+ (do
+ (return_call $return-callee)
+ )
+ (catch_all
+ (unreachable)
+ )
+ )
+ )
+
+ (func $return-callee (result i32)
+ (throw $t)
+ )
+)
+
+(assert_return (invoke "test") (i32.const 42))
diff --git a/test/spec/return_call_indirect.wast b/test/spec/return_call_indirect.wast
new file mode 100644
index 000000000..27f1dcdbf
--- /dev/null
+++ b/test/spec/return_call_indirect.wast
@@ -0,0 +1,536 @@
+;; Test `return_call_indirect` operator
+
+(module
+ ;; Auxiliary definitions
+ (type $proc (func))
+ (type $out-i32 (func (result i32)))
+ (type $out-i64 (func (result i64)))
+ (type $out-f32 (func (result f32)))
+ (type $out-f64 (func (result f64)))
+ (type $over-i32 (func (param i32) (result i32)))
+ (type $over-i64 (func (param i64) (result i64)))
+ (type $over-f32 (func (param f32) (result f32)))
+ (type $over-f64 (func (param f64) (result f64)))
+ (type $f32-i32 (func (param f32 i32) (result i32)))
+ (type $i32-i64 (func (param i32 i64) (result i64)))
+ (type $f64-f32 (func (param f64 f32) (result f32)))
+ (type $i64-f64 (func (param i64 f64) (result f64)))
+ (type $over-i32-duplicate (func (param i32) (result i32)))
+ (type $over-i64-duplicate (func (param i64) (result i64)))
+ (type $over-f32-duplicate (func (param f32) (result f32)))
+ (type $over-f64-duplicate (func (param f64) (result f64)))
+
+ (func $const-i32 (type $out-i32) (i32.const 0x132))
+ (func $const-i64 (type $out-i64) (i64.const 0x164))
+ (func $const-f32 (type $out-f32) (f32.const 0xf32))
+ (func $const-f64 (type $out-f64) (f64.const 0xf64))
+
+ (func $id-i32 (type $over-i32) (local.get 0))
+ (func $id-i64 (type $over-i64) (local.get 0))
+ (func $id-f32 (type $over-f32) (local.get 0))
+ (func $id-f64 (type $over-f64) (local.get 0))
+
+ (func $i32-i64 (type $i32-i64) (local.get 1))
+ (func $i64-f64 (type $i64-f64) (local.get 1))
+ (func $f32-i32 (type $f32-i32) (local.get 1))
+ (func $f64-f32 (type $f64-f32) (local.get 1))
+
+ (func $over-i32-duplicate (type $over-i32-duplicate) (local.get 0))
+ (func $over-i64-duplicate (type $over-i64-duplicate) (local.get 0))
+ (func $over-f32-duplicate (type $over-f32-duplicate) (local.get 0))
+ (func $over-f64-duplicate (type $over-f64-duplicate) (local.get 0))
+
+ (table funcref
+ (elem
+ $const-i32 $const-i64 $const-f32 $const-f64
+ $id-i32 $id-i64 $id-f32 $id-f64
+ $f32-i32 $i32-i64 $f64-f32 $i64-f64
+ $fac $fac-acc $even $odd
+ $over-i32-duplicate $over-i64-duplicate
+ $over-f32-duplicate $over-f64-duplicate
+ )
+ )
+
+ ;; Syntax
+
+ (func
+ (return_call_indirect (i32.const 0))
+ (return_call_indirect (param i64) (i64.const 0) (i32.const 0))
+ (return_call_indirect (param i64) (param) (param f64 i32 i64)
+ (i64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (i32.const 0)
+ )
+ (return_call_indirect (result) (i32.const 0))
+ )
+
+ (func (result i32)
+ (return_call_indirect (result i32) (i32.const 0))
+ (return_call_indirect (result i32) (result) (i32.const 0))
+ (return_call_indirect (param i64) (result i32) (i64.const 0) (i32.const 0))
+ (return_call_indirect
+ (param) (param i64) (param) (param f64 i32 i64) (param) (param)
+ (result) (result i32) (result) (result)
+ (i64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (i32.const 0)
+ )
+ )
+
+ (func (result i64)
+ (return_call_indirect (type $over-i64) (param i64) (result i64)
+ (i64.const 0) (i32.const 0)
+ )
+ )
+
+ ;; Typing
+
+ (func (export "type-i32") (result i32)
+ (return_call_indirect (type $out-i32) (i32.const 0))
+ )
+ (func (export "type-i64") (result i64)
+ (return_call_indirect (type $out-i64) (i32.const 1))
+ )
+ (func (export "type-f32") (result f32)
+ (return_call_indirect (type $out-f32) (i32.const 2))
+ )
+ (func (export "type-f64") (result f64)
+ (return_call_indirect (type $out-f64) (i32.const 3))
+ )
+
+ (func (export "type-index") (result i64)
+ (return_call_indirect (type $over-i64) (i64.const 100) (i32.const 5))
+ )
+
+ (func (export "type-first-i32") (result i32)
+ (return_call_indirect (type $over-i32) (i32.const 32) (i32.const 4))
+ )
+ (func (export "type-first-i64") (result i64)
+ (return_call_indirect (type $over-i64) (i64.const 64) (i32.const 5))
+ )
+ (func (export "type-first-f32") (result f32)
+ (return_call_indirect (type $over-f32) (f32.const 1.32) (i32.const 6))
+ )
+ (func (export "type-first-f64") (result f64)
+ (return_call_indirect (type $over-f64) (f64.const 1.64) (i32.const 7))
+ )
+
+ (func (export "type-second-i32") (result i32)
+ (return_call_indirect (type $f32-i32)
+ (f32.const 32.1) (i32.const 32) (i32.const 8)
+ )
+ )
+ (func (export "type-second-i64") (result i64)
+ (return_call_indirect (type $i32-i64)
+ (i32.const 32) (i64.const 64) (i32.const 9)
+ )
+ )
+ (func (export "type-second-f32") (result f32)
+ (return_call_indirect (type $f64-f32)
+ (f64.const 64) (f32.const 32) (i32.const 10)
+ )
+ )
+ (func (export "type-second-f64") (result f64)
+ (return_call_indirect (type $i64-f64)
+ (i64.const 64) (f64.const 64.1) (i32.const 11)
+ )
+ )
+
+ ;; Dispatch
+
+ (func (export "dispatch") (param i32 i64) (result i64)
+ (return_call_indirect (type $over-i64) (local.get 1) (local.get 0))
+ )
+
+ (func (export "dispatch-structural") (param i32) (result i64)
+ (return_call_indirect (type $over-i64-duplicate)
+ (i64.const 9) (local.get 0)
+ )
+ )
+
+ ;; Multiple tables
+
+ (table $tab2 funcref (elem $tab-f1))
+ (table $tab3 funcref (elem $tab-f2))
+
+ (func $tab-f1 (result i32) (i32.const 0x133))
+ (func $tab-f2 (result i32) (i32.const 0x134))
+
+ (func (export "call-tab") (param $i i32) (result i32)
+ (if (i32.eq (local.get $i) (i32.const 0))
+ (then (return_call_indirect (type $out-i32) (i32.const 0)))
+ )
+ (if (i32.eq (local.get $i) (i32.const 1))
+ (then (return_call_indirect $tab2 (type $out-i32) (i32.const 0)))
+ )
+ (if (i32.eq (local.get $i) (i32.const 2))
+ (then (return_call_indirect $tab3 (type $out-i32) (i32.const 0)))
+ )
+ (i32.const 0)
+ )
+
+ ;; Recursion
+
+ (func $fac (export "fac") (type $over-i64)
+ (return_call_indirect (param i64 i64) (result i64)
+ (local.get 0) (i64.const 1) (i32.const 13)
+ )
+ )
+
+ (func $fac-acc (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call_indirect (param i64 i64) (result i64)
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ (i32.const 13)
+ )
+ )
+ )
+ )
+
+ (func $even (export "even") (param i32) (result i32)
+ (if (result i32) (i32.eqz (local.get 0))
+ (then (i32.const 44))
+ (else
+ (return_call_indirect (type $over-i32)
+ (i32.sub (local.get 0) (i32.const 1))
+ (i32.const 15)
+ )
+ )
+ )
+ )
+ (func $odd (export "odd") (param i32) (result i32)
+ (if (result i32) (i32.eqz (local.get 0))
+ (then (i32.const 99))
+ (else
+ (return_call_indirect (type $over-i32)
+ (i32.sub (local.get 0) (i32.const 1))
+ (i32.const 14)
+ )
+ )
+ )
+ )
+)
+
+(assert_return (invoke "type-i32") (i32.const 0x132))
+(assert_return (invoke "type-i64") (i64.const 0x164))
+(assert_return (invoke "type-f32") (f32.const 0xf32))
+(assert_return (invoke "type-f64") (f64.const 0xf64))
+
+(assert_return (invoke "type-index") (i64.const 100))
+
+(assert_return (invoke "type-first-i32") (i32.const 32))
+(assert_return (invoke "type-first-i64") (i64.const 64))
+(assert_return (invoke "type-first-f32") (f32.const 1.32))
+(assert_return (invoke "type-first-f64") (f64.const 1.64))
+
+(assert_return (invoke "type-second-i32") (i32.const 32))
+(assert_return (invoke "type-second-i64") (i64.const 64))
+(assert_return (invoke "type-second-f32") (f32.const 32))
+(assert_return (invoke "type-second-f64") (f64.const 64.1))
+
+(assert_return (invoke "dispatch" (i32.const 5) (i64.const 2)) (i64.const 2))
+(assert_return (invoke "dispatch" (i32.const 5) (i64.const 5)) (i64.const 5))
+(assert_return (invoke "dispatch" (i32.const 12) (i64.const 5)) (i64.const 120))
+(assert_return (invoke "dispatch" (i32.const 17) (i64.const 2)) (i64.const 2))
+(assert_trap (invoke "dispatch" (i32.const 0) (i64.const 2)) "indirect call type mismatch")
+(assert_trap (invoke "dispatch" (i32.const 15) (i64.const 2)) "indirect call type mismatch")
+(assert_trap (invoke "dispatch" (i32.const 20) (i64.const 2)) "undefined element")
+(assert_trap (invoke "dispatch" (i32.const -1) (i64.const 2)) "undefined element")
+(assert_trap (invoke "dispatch" (i32.const 1213432423) (i64.const 2)) "undefined element")
+
+(assert_return (invoke "dispatch-structural" (i32.const 5)) (i64.const 9))
+(assert_return (invoke "dispatch-structural" (i32.const 5)) (i64.const 9))
+(assert_return (invoke "dispatch-structural" (i32.const 12)) (i64.const 362880))
+(assert_return (invoke "dispatch-structural" (i32.const 17)) (i64.const 9))
+(assert_trap (invoke "dispatch-structural" (i32.const 11)) "indirect call type mismatch")
+(assert_trap (invoke "dispatch-structural" (i32.const 16)) "indirect call type mismatch")
+
+(assert_return (invoke "call-tab" (i32.const 0)) (i32.const 0x132))
+(assert_return (invoke "call-tab" (i32.const 1)) (i32.const 0x133))
+(assert_return (invoke "call-tab" (i32.const 2)) (i32.const 0x134))
+
+(assert_return (invoke "fac" (i64.const 0)) (i64.const 1))
+(assert_return (invoke "fac" (i64.const 1)) (i64.const 1))
+(assert_return (invoke "fac" (i64.const 5)) (i64.const 120))
+(assert_return (invoke "fac" (i64.const 25)) (i64.const 7034535277573963776))
+
+(assert_return (invoke "even" (i32.const 0)) (i32.const 44))
+(assert_return (invoke "even" (i32.const 1)) (i32.const 99))
+(assert_return (invoke "even" (i32.const 100)) (i32.const 44))
+(assert_return (invoke "even" (i32.const 77)) (i32.const 99))
+(assert_return (invoke "even" (i32.const 100000)) (i32.const 44))
+(assert_return (invoke "even" (i32.const 111111)) (i32.const 99))
+(assert_return (invoke "odd" (i32.const 0)) (i32.const 99))
+(assert_return (invoke "odd" (i32.const 1)) (i32.const 44))
+(assert_return (invoke "odd" (i32.const 200)) (i32.const 99))
+(assert_return (invoke "odd" (i32.const 77)) (i32.const 44))
+(assert_return (invoke "odd" (i32.const 200002)) (i32.const 99))
+(assert_return (invoke "odd" (i32.const 300003)) (i32.const 44))
+
+
+;; Invalid syntax
+
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (type $sig) (result i32) (param i32)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "unexpected token"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (param i32) (type $sig) (result i32)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "unexpected token"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (param i32) (result i32) (type $sig)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "unexpected token"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (result i32) (type $sig) (param i32)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "unexpected token"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (result i32) (param i32) (type $sig)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "unexpected token"
+)
+(assert_malformed
+ (module quote
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (result i32) (param i32)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "unexpected token"
+)
+
+(assert_malformed
+ (module quote
+ "(table 0 funcref)"
+ "(func (return_call_indirect (param $x i32) (i32.const 0) (i32.const 0)))"
+ )
+ "unexpected token"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (type $sig) (result i32) (i32.const 0))"
+ ")"
+ )
+ "inline function type"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (type $sig) (result i32) (i32.const 0))"
+ ")"
+ )
+ "inline function type"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func"
+ " (return_call_indirect (type $sig) (param i32)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "inline function type"
+)
+(assert_malformed
+ (module quote
+ "(type $sig (func (param i32 i32) (result i32)))"
+ "(table 0 funcref)"
+ "(func (result i32)"
+ " (return_call_indirect (type $sig) (param i32) (result i32)"
+ " (i32.const 0) (i32.const 0)"
+ " )"
+ ")"
+ )
+ "inline function type"
+)
+
+;; Invalid typing
+
+(assert_invalid
+ (module
+ (type (func))
+ (func $no-table (return_call_indirect (type 0) (i32.const 0)))
+ )
+ "unknown table"
+)
+
+;; (assert_invalid
+;; (module
+;; (type (func))
+;; (table 0 funcref)
+;; (func $type-void-vs-num (i32.eqz (return_call_indirect (type 0) (i32.const 0))))
+;; )
+;; "type mismatch"
+;; )
+(assert_invalid
+ (module
+ (type (func (result i64)))
+ (table 0 funcref)
+ (func $type-num-vs-num (i32.eqz (return_call_indirect (type 0) (i32.const 0))))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type (func (param i32)))
+ (table 0 funcref)
+ (func $arity-0-vs-1 (return_call_indirect (type 0) (i32.const 0)))
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (type (func (param f64 i32)))
+ (table 0 funcref)
+ (func $arity-0-vs-2 (return_call_indirect (type 0) (i32.const 0)))
+ )
+ "type mismatch"
+)
+
+;; (module
+;; (type (func))
+;; (table 0 funcref)
+;; (func $arity-1-vs-0 (return_call_indirect (type 0) (i32.const 1) (i32.const 0)))
+;; )
+
+;; (module
+;; (type (func))
+;; (table 0 funcref)
+;; (func $arity-2-vs-0
+;; (return_call_indirect (type 0) (f64.const 2) (i32.const 1) (i32.const 0))
+;; )
+;; )
+
+(assert_invalid
+ (module
+ (type (func (param i32)))
+ (table 0 funcref)
+ (func $type-func-void-vs-i32 (return_call_indirect (type 0) (i32.const 1) (nop)))
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (type (func (param i32)))
+ (table 0 funcref)
+ (func $type-func-num-vs-i32 (return_call_indirect (type 0) (i32.const 0) (i64.const 1)))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type (func (param i32 i32)))
+ (table 0 funcref)
+ (func $type-first-void-vs-num
+ (return_call_indirect (type 0) (nop) (i32.const 1) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (type (func (param i32 i32)))
+ (table 0 funcref)
+ (func $type-second-void-vs-num
+ (return_call_indirect (type 0) (i32.const 1) (nop) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (type (func (param i32 f64)))
+ (table 0 funcref)
+ (func $type-first-num-vs-num
+ (return_call_indirect (type 0) (f64.const 1) (i32.const 1) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (type (func (param f64 i32)))
+ (table 0 funcref)
+ (func $type-second-num-vs-num
+ (return_call_indirect (type 0) (i32.const 1) (f64.const 1) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
+
+
+;; Unbound type
+
+(assert_invalid
+ (module
+ (table 0 funcref)
+ (func $unbound-type (return_call_indirect (type 1) (i32.const 0)))
+ )
+ "unknown type"
+)
+(assert_invalid
+ (module
+ (table 0 funcref)
+ (func $large-type (return_call_indirect (type 1012321300) (i32.const 0)))
+ )
+ "unknown type"
+)
+
+
+;; Unbound function in table
+
+(assert_invalid
+ (module (table funcref (elem 0 0)))
+ "unknown function 0"
+)
diff --git a/test/spec/return_call_ref.wast b/test/spec/return_call_ref.wast
new file mode 100644
index 000000000..6bb5d2a14
--- /dev/null
+++ b/test/spec/return_call_ref.wast
@@ -0,0 +1,377 @@
+;; Test `return_call_ref` operator
+
+(module
+ ;; Auxiliary definitions
+ (type $proc (func))
+ (type $-i32 (func (result i32)))
+ (type $-i64 (func (result i64)))
+ (type $-f32 (func (result f32)))
+ (type $-f64 (func (result f64)))
+
+ (type $i32-i32 (func (param i32) (result i32)))
+ (type $i64-i64 (func (param i64) (result i64)))
+ (type $f32-f32 (func (param f32) (result f32)))
+ (type $f64-f64 (func (param f64) (result f64)))
+
+ (type $f32-i32 (func (param f32 i32) (result i32)))
+ (type $i32-i64 (func (param i32 i64) (result i64)))
+ (type $f64-f32 (func (param f64 f32) (result f32)))
+ (type $i64-f64 (func (param i64 f64) (result f64)))
+
+ (type $i64i64-i64 (func (param i64 i64) (result i64)))
+
+ (func $const-i32 (result i32) (i32.const 0x132))
+ (func $const-i64 (result i64) (i64.const 0x164))
+ (func $const-f32 (result f32) (f32.const 0xf32))
+ (func $const-f64 (result f64) (f64.const 0xf64))
+
+ (func $id-i32 (param i32) (result i32) (local.get 0))
+ (func $id-i64 (param i64) (result i64) (local.get 0))
+ (func $id-f32 (param f32) (result f32) (local.get 0))
+ (func $id-f64 (param f64) (result f64) (local.get 0))
+
+ (func $f32-i32 (param f32 i32) (result i32) (local.get 1))
+ (func $i32-i64 (param i32 i64) (result i64) (local.get 1))
+ (func $f64-f32 (param f64 f32) (result f32) (local.get 1))
+ (func $i64-f64 (param i64 f64) (result f64) (local.get 1))
+
+ (global $const-i32 (ref $-i32) (ref.func $const-i32))
+ (global $const-i64 (ref $-i64) (ref.func $const-i64))
+ (global $const-f32 (ref $-f32) (ref.func $const-f32))
+ (global $const-f64 (ref $-f64) (ref.func $const-f64))
+
+ (global $id-i32 (ref $i32-i32) (ref.func $id-i32))
+ (global $id-i64 (ref $i64-i64) (ref.func $id-i64))
+ (global $id-f32 (ref $f32-f32) (ref.func $id-f32))
+ (global $id-f64 (ref $f64-f64) (ref.func $id-f64))
+
+ (global $f32-i32 (ref $f32-i32) (ref.func $f32-i32))
+ (global $i32-i64 (ref $i32-i64) (ref.func $i32-i64))
+ (global $f64-f32 (ref $f64-f32) (ref.func $f64-f32))
+ (global $i64-f64 (ref $i64-f64) (ref.func $i64-f64))
+
+ (elem declare func
+ $const-i32 $const-i64 $const-f32 $const-f64
+ $id-i32 $id-i64 $id-f32 $id-f64
+ $f32-i32 $i32-i64 $f64-f32 $i64-f64
+ )
+
+ ;; Typing
+
+ (func (export "type-i32") (result i32)
+ (return_call_ref $-i32 (global.get $const-i32))
+ )
+ (func (export "type-i64") (result i64)
+ (return_call_ref $-i64 (global.get $const-i64))
+ )
+ (func (export "type-f32") (result f32)
+ (return_call_ref $-f32 (global.get $const-f32))
+ )
+ (func (export "type-f64") (result f64)
+ (return_call_ref $-f64 (global.get $const-f64))
+ )
+
+ (func (export "type-first-i32") (result i32)
+ (return_call_ref $i32-i32 (i32.const 32) (global.get $id-i32))
+ )
+ (func (export "type-first-i64") (result i64)
+ (return_call_ref $i64-i64 (i64.const 64) (global.get $id-i64))
+ )
+ (func (export "type-first-f32") (result f32)
+ (return_call_ref $f32-f32 (f32.const 1.32) (global.get $id-f32))
+ )
+ (func (export "type-first-f64") (result f64)
+ (return_call_ref $f64-f64 (f64.const 1.64) (global.get $id-f64))
+ )
+
+ (func (export "type-second-i32") (result i32)
+ (return_call_ref $f32-i32 (f32.const 32.1) (i32.const 32) (global.get $f32-i32))
+ )
+ (func (export "type-second-i64") (result i64)
+ (return_call_ref $i32-i64 (i32.const 32) (i64.const 64) (global.get $i32-i64))
+ )
+ (func (export "type-second-f32") (result f32)
+ (return_call_ref $f64-f32 (f64.const 64) (f32.const 32) (global.get $f64-f32))
+ )
+ (func (export "type-second-f64") (result f64)
+ (return_call_ref $i64-f64 (i64.const 64) (f64.const 64.1) (global.get $i64-f64))
+ )
+
+ ;; Null
+
+ (func (export "null")
+ (return_call_ref $proc (ref.null $proc))
+ )
+
+ ;; Recursion
+
+ (global $fac-acc (ref $i64i64-i64) (ref.func $fac-acc))
+
+ (elem declare func $fac-acc)
+ (func $fac-acc (export "fac-acc") (param i64 i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 1))
+ (else
+ (return_call_ref $i64i64-i64
+ (i64.sub (local.get 0) (i64.const 1))
+ (i64.mul (local.get 0) (local.get 1))
+ (global.get $fac-acc)
+ )
+ )
+ )
+ )
+
+ (global $count (ref $i64-i64) (ref.func $count))
+
+ (elem declare func $count)
+ (func $count (export "count") (param i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (local.get 0))
+ (else
+ (return_call_ref $i64-i64
+ (i64.sub (local.get 0) (i64.const 1))
+ (global.get $count)
+ )
+ )
+ )
+ )
+
+ (global $even (ref $i64-i64) (ref.func $even))
+ (global $odd (ref $i64-i64) (ref.func $odd))
+
+ (elem declare func $even)
+ (func $even (export "even") (param i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (i64.const 44))
+ (else
+ (return_call_ref $i64-i64
+ (i64.sub (local.get 0) (i64.const 1))
+ (global.get $odd)
+ )
+ )
+ )
+ )
+ (elem declare func $odd)
+ (func $odd (export "odd") (param i64) (result i64)
+ (if (result i64) (i64.eqz (local.get 0))
+ (then (i64.const 99))
+ (else
+ (return_call_ref $i64-i64
+ (i64.sub (local.get 0) (i64.const 1))
+ (global.get $even)
+ )
+ )
+ )
+ )
+)
+
+(assert_return (invoke "type-i32") (i32.const 0x132))
+(assert_return (invoke "type-i64") (i64.const 0x164))
+(assert_return (invoke "type-f32") (f32.const 0xf32))
+(assert_return (invoke "type-f64") (f64.const 0xf64))
+
+(assert_return (invoke "type-first-i32") (i32.const 32))
+(assert_return (invoke "type-first-i64") (i64.const 64))
+(assert_return (invoke "type-first-f32") (f32.const 1.32))
+(assert_return (invoke "type-first-f64") (f64.const 1.64))
+
+(assert_return (invoke "type-second-i32") (i32.const 32))
+(assert_return (invoke "type-second-i64") (i64.const 64))
+(assert_return (invoke "type-second-f32") (f32.const 32))
+(assert_return (invoke "type-second-f64") (f64.const 64.1))
+
+(assert_trap (invoke "null") "null function reference")
+
+(assert_return (invoke "fac-acc" (i64.const 0) (i64.const 1)) (i64.const 1))
+(assert_return (invoke "fac-acc" (i64.const 1) (i64.const 1)) (i64.const 1))
+(assert_return (invoke "fac-acc" (i64.const 5) (i64.const 1)) (i64.const 120))
+(assert_return
+ (invoke "fac-acc" (i64.const 25) (i64.const 1))
+ (i64.const 7034535277573963776)
+)
+
+(assert_return (invoke "count" (i64.const 0)) (i64.const 0))
+(assert_return (invoke "count" (i64.const 1000)) (i64.const 0))
+(assert_return (invoke "count" (i64.const 1000000)) (i64.const 0))
+
+(assert_return (invoke "even" (i64.const 0)) (i64.const 44))
+(assert_return (invoke "even" (i64.const 1)) (i64.const 99))
+(assert_return (invoke "even" (i64.const 100)) (i64.const 44))
+(assert_return (invoke "even" (i64.const 77)) (i64.const 99))
+(assert_return (invoke "even" (i64.const 1000000)) (i64.const 44))
+(assert_return (invoke "even" (i64.const 1000001)) (i64.const 99))
+(assert_return (invoke "odd" (i64.const 0)) (i64.const 99))
+(assert_return (invoke "odd" (i64.const 1)) (i64.const 44))
+(assert_return (invoke "odd" (i64.const 200)) (i64.const 99))
+(assert_return (invoke "odd" (i64.const 77)) (i64.const 44))
+(assert_return (invoke "odd" (i64.const 1000000)) (i64.const 99))
+(assert_return (invoke "odd" (i64.const 999999)) (i64.const 44))
+
+
+;; More typing
+
+(module
+ (type $t (func))
+ (type $t1 (func (result (ref $t))))
+ (type $t2 (func (result (ref null $t))))
+ (type $t3 (func (result (ref func))))
+ (type $t4 (func (result (ref null func))))
+ (elem declare func $f11 $f22 $f33 $f44)
+ (func $f11 (result (ref $t)) (return_call_ref $t1 (ref.func $f11)))
+ (func $f21 (result (ref null $t)) (return_call_ref $t1 (ref.func $f11)))
+ (func $f22 (result (ref null $t)) (return_call_ref $t2 (ref.func $f22)))
+ (func $f31 (result (ref func)) (return_call_ref $t1 (ref.func $f11)))
+ (func $f33 (result (ref func)) (return_call_ref $t3 (ref.func $f33)))
+ (func $f41 (result (ref null func)) (return_call_ref $t1 (ref.func $f11)))
+ (func $f42 (result (ref null func)) (return_call_ref $t2 (ref.func $f22)))
+ (func $f43 (result (ref null func)) (return_call_ref $t3 (ref.func $f33)))
+ (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44)))
+)
+
+(assert_invalid
+ (module
+ (type $t (func))
+ (type $t2 (func (result (ref null $t))))
+ (elem declare func $f22)
+ (func $f12 (result (ref $t)) (return_call_ref $t2 (ref.func $f22)))
+ (func $f22 (result (ref null $t)) (return_call_ref $t2 (ref.func $f22)))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type $t (func))
+ (type $t3 (func (result (ref func))))
+ (elem declare func $f33)
+ (func $f13 (result (ref $t)) (return_call_ref $t3 (ref.func $f33)))
+ (func $f33 (result (ref func)) (return_call_ref $t3 (ref.func $f33)))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type $t (func))
+ (type $t4 (func (result (ref null func))))
+ (elem declare func $f44)
+ (func $f14 (result (ref $t)) (return_call_ref $t4 (ref.func $f44)))
+ (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44)))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type $t (func))
+ (type $t3 (func (result (ref func))))
+ (elem declare func $f33)
+ (func $f23 (result (ref null $t)) (return_call_ref $t3 (ref.func $f33)))
+ (func $f33 (result (ref func)) (return_call_ref $t3 (ref.func $f33)))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type $t (func))
+ (type $t4 (func (result (ref null func))))
+ (elem declare func $f44)
+ (func $f24 (result (ref null $t)) (return_call_ref $t4 (ref.func $f44)))
+ (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44)))
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type $t4 (func (result (ref null func))))
+ (elem declare func $f44)
+ (func $f34 (result (ref func)) (return_call_ref $t4 (ref.func $f44)))
+ (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44)))
+ )
+ "type mismatch"
+)
+
+
+;; Unreachable typing.
+
+(module
+ (type $t (func (result i32)))
+ (func (export "unreachable") (result i32)
+ (return_call_ref $t (unreachable))
+ )
+)
+(assert_trap (invoke "unreachable") "unreachable")
+
+(module
+ (elem declare func $f)
+ (type $t (func (param i32) (result i32)))
+ (func $f (param i32) (result i32) (local.get 0))
+
+ (func (export "unreachable") (result i32)
+ (return_call_ref $t
+ (unreachable)
+ (ref.func $f)
+ )
+ )
+)
+(assert_trap (invoke "unreachable") "unreachable")
+
+(module
+ (elem declare func $f)
+ (type $t (func (param i32) (result i32)))
+ (func $f (param i32) (result i32) (local.get 0))
+
+ (func (export "unreachable") (result i32)
+ (unreachable)
+ (return_call_ref $t
+ (i32.const 0)
+ (ref.func $f)
+ )
+ (i32.const 0)
+ )
+)
+(assert_trap (invoke "unreachable") "unreachable")
+
+(assert_invalid
+ (module
+ (elem declare func $f)
+ (type $t (func (param i32) (result i32)))
+ (func $f (param i32) (result i32) (local.get 0))
+
+ (func (export "unreachable") (result i32)
+ (unreachable)
+ (i64.const 0)
+ (ref.func $f)
+ (return_call_ref $t)
+ )
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (elem declare func $f)
+ (type $t (func (param i32) (result i32)))
+ (func $f (param i32) (result i32) (local.get 0))
+
+ (func (export "unreachable") (result i32)
+ (unreachable)
+ (ref.func $f)
+ (return_call_ref $t)
+ (i64.const 0)
+ )
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (type $t (func))
+ (func $f (param $r externref)
+ (return_call_ref $t (local.get $r))
+ )
+ )
+ "type mismatch"
+)