summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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"
+)