diff options
-rw-r--r-- | src/passes/Monomorphize.cpp | 288 | ||||
-rw-r--r-- | test/lit/passes/monomorphize-consts.wast | 213 | ||||
-rw-r--r-- | test/lit/passes/monomorphize-context.wast | 1805 | ||||
-rw-r--r-- | test/lit/passes/monomorphize-mvp.wast | 26 | ||||
-rw-r--r-- | test/lit/passes/monomorphize-types.wast | 268 | ||||
-rw-r--r-- | test/lit/passes/no-inline-monomorphize-inlining.wast | 64 |
6 files changed, 2331 insertions, 333 deletions
diff --git a/src/passes/Monomorphize.cpp b/src/passes/Monomorphize.cpp index 8c08db55b..1fd81b9d1 100644 --- a/src/passes/Monomorphize.cpp +++ b/src/passes/Monomorphize.cpp @@ -85,13 +85,18 @@ // compute the LUB of a bunch of calls to a target and then investigate // that one case and use it in all those callers. // TODO: Not just direct calls? But updating vtables is complex. +// TODO: Should we look at no-inline flags? We do move code between functions, +// but it isn't normal inlining. // #include "ir/cost.h" +#include "ir/effects.h" #include "ir/find_all.h" +#include "ir/iteration.h" #include "ir/manipulation.h" #include "ir/module-utils.h" #include "ir/names.h" +#include "ir/properties.h" #include "ir/return-utils.h" #include "ir/type-updating.h" #include "ir/utils.h" @@ -212,33 +217,204 @@ struct CallContext { // remaining values by updating |newOperands| (for example, if all the values // sent are constants, then |newOperands| will end up empty, as we have // nothing left to send). + // + // The approach implemented here tries to move as much code into the call + // context as possible. That may not always be helpful, say in situations like + // this: + // + // (call $foo + // (i32.add + // (local.get $x) + // (local.get $y) + // ) + // ) + // + // If we move the i32.add into $foo then it will still be adding two unknown + // values (which will be parameters rather than locals). Moving the add might + // just increase code size if so. However, there are many other situations + // where the more code, the better: + // + // (call $foo + // (i32.eqz + // (local.get $x) + // ) + // ) + // + // While the value remains unknown, after moving the i32.eqz into the target + // function we may be able to use the fact that it has at most 1 bit set. + // Even larger benefits can happen in WasmGC: + // + // (call $foo + // (struct.new $T + // (local.get $x) + // (local.get $y) + // ) + // ) + // + // If the struct never escapes then we may be able to remove the allocation + // after monomorphization, even if we know nothing about the values in its + // fields. + // + // TODO: Explore other options that are more careful about how much code is + // moved. void buildFromCall(CallInfo& info, std::vector<Expression*>& newOperands, - Module& wasm) { + Module& wasm, + const PassOptions& options) { Builder builder(wasm); + // First, find the things we can move into the context and the things we + // cannot. Some things simply cannot be moved out of the calling function, + // such as a local.set, but also we need to handle effect interactions among + // the operands, because each time we move code into the context we are + // pushing it into the called function, which changes the order of + // operations, for example: + // + // (call $foo + // (first + // (a) + // ) + // (second + // (b) + // ) + // ) + // + // (func $foo (param $first) (param $second) + // ) + // + // If we move |first| and |a| into the context then we get this: + // + // (call $foo + // ;; |first| and |a| were removed from here. + // (second + // (b) + // ) + // ) + // + // (func $foo (param $second) + // ;; |first| is now a local, and we assign it inside the called func. + // (local $first) + // (local.set $first + // (first + // (a) + // ) + // ) + // ) + // + // After this code motion we execute |second| and |b| *before* the call, and + // |first| and |a| after, so we cannot do this transformation if the order + // of operations between them matters. + // + // The key property here is that all things that are moved into the context + // (moved into the monomorphized function) remain ordered with respect to + // each other, but must be moved past all non-moving things after them. For + // example, say we want to move B and D in this list (of expressions in + // execution order): + // + // A, B, C, D, E + // + // After moving B and D we end up with this: + // + // A, C, E and executing later in the monomorphized function: B, D + // + // Then we must be able to move B past C and E, and D past E. It is simplest + // to compute this in reverse order, starting from E and going back, and + // then each time we want to move something we can check if it can cross + // over all the non-moving effects we've seen so far. To compute this, first + // list out the post-order of the expressions, and then we'll iterate in + // reverse. + struct Lister + : public PostWalker<Lister, UnifiedExpressionVisitor<Lister>> { + std::vector<Expression*> list; + void visitExpression(Expression* curr) { list.push_back(curr); } + } lister; + // As a quick estimate, we need space for at least the operands. + lister.list.reserve(operands.size()); + + for (auto* operand : info.call->operands) { + lister.walk(operand); + } + + // Go in reverse post-order as explained earlier, noting what cannot be + // moved into the context, and while accumulating the effects that are not + // moving. + std::unordered_set<Expression*> immovable; + EffectAnalyzer nonMovingEffects(options, wasm); + for (auto i = int64_t(lister.list.size()) - 1; i >= 0; i--) { + auto* curr = lister.list[i]; + + // This may have been marked as immovable because of the parent. We do + // that because if a parent is immovable then we can't move the children + // into the context (if we did, they would execute after the parent, but + // it needs their values). + bool currImmovable = immovable.count(curr) > 0; + if (!currImmovable) { + // This might be movable or immovable. Check both effect interactions + // (as described before, we want to move this past immovable code) and + // reasons intrinsic to the expression itself that might prevent moving. + ShallowEffectAnalyzer currEffects(options, wasm, curr); + if (currEffects.invalidates(nonMovingEffects) || + !canBeMovedIntoContext(curr, currEffects)) { + immovable.insert(curr); + currImmovable = true; + } + } + + if (currImmovable) { + // Regardless of whether this was marked immovable because of the + // parent, or because we just found it cannot be moved, accumulate the + // effects, and also mark its immediate children (so that we do the same + // when we get to them). + nonMovingEffects.visit(curr); + for (auto* child : ChildIterator(curr)) { + immovable.insert(child); + } + } + } + + // We now know which code can be moved and which cannot, so we can do the + // final processing of the call operands. We do this as a copy operation, + // copying as much as possible into the call context. Code that cannot be + // moved ends up as values sent to the monomorphized function. + // + // The copy operation works in pre-order, which allows us to override + // entire children as needed: + // + // (call $foo + // (problem + // (a) + // ) + // (later) + // ) + // + // We visit |problem| first, and if there is a problem that prevents us + // moving it into the context then we override the copy and then it and + // its child |a| remain in the caller (and |a| is never visited in the + // copy). for (auto* operand : info.call->operands) { - // Process the operand. This is a copy operation, as we are trying to move - // (copy) code from the callsite into the called function. When we find we - // can copy then we do so, and when we cannot that value remains as a - // value sent from the call. operands.push_back(ExpressionManipulator::flexibleCopy( operand, wasm, [&](Expression* child) -> Expression* { - if (canBeMovedIntoContext(child)) { - // This can be moved, great: let the copy happen. + if (!child) { + // This is an optional child that is not present. Let the copy of + // the nullptr happen. + return nullptr; + } + + if (!immovable.count(child)) { + // This can be moved; let the copy happen. return nullptr; } - // This cannot be moved, so we stop here: this is a value that is sent - // into the monomorphized function. It is a new operand in the call, - // and in the context operands it is a local.get, that reads that - // value. + // This cannot be moved. Do not copy it into the call context. In the + // example above, |problem| remains as an operand on the call (so we + // add it to |newOperands|), and in the call context all we have is a + // local.get that reads that sent value. auto paramIndex = newOperands.size(); newOperands.push_back(child); // TODO: If one operand is a tee and another a get, we could actually // reuse the local, effectively showing the monomorphized - // function that the values are the same. (But then the checks - // later down to is<LocalGet> would need to check index too.) + // function that the values are the same. EquivalentSets may + // help here. return builder.makeLocalGet(paramIndex, child->type); })); } @@ -247,12 +423,49 @@ struct CallContext { } // Checks whether an expression can be moved into the context. - bool canBeMovedIntoContext(Expression* curr) { - // Constant numbers, funcs, strings, etc. can all be copied, so it is ok to - // add them to the context. - // TODO: Allow global.get as well, and anything else that is purely - // copyable. - return Properties::isSingleConstantExpression(curr); + bool canBeMovedIntoContext(Expression* curr, + const ShallowEffectAnalyzer& effects) { + // Pretty much everything can be moved into the context if we can copy it + // between functions, such as constants, globals, etc. The things we cannot + // copy are now checked for. + if (effects.branchesOut || effects.hasExternalBreakTargets()) { + // This branches or returns. We can't move control flow between functions. + return false; + } + if (effects.accessesLocal()) { + // Reads/writes to local state cannot be moved around. + return false; + } + if (effects.calls) { + // We can in principle move calls, but for simplicity we avoid such + // situations (which might involve recursion etc.). + return false; + } + if (Properties::isControlFlowStructure(curr)) { + // We can in principle move entire control flow structures with their + // children, but for simplicity stop when we see one rather than look + // inside to see if we could transfer all its contents. (We would also + // need to be careful when handling If arms, etc.) + return false; + } + for (auto* child : ChildIterator(curr)) { + if (child->type.isTuple()) { + // Consider this: + // + // (call $target + // (tuple.extract 2 1 + // (local.get $tuple) + // ) + // ) + // + // We cannot move the tuple.extract into the context, because then the + // call would have a tuple param. While it is possible to split up the + // tuple, or to check if we can also move the children with the parent, + // for simplicity just ignore this rare situation. + return false; + } + } + return true; } // Check if a context is trivial relative to a call, that is, the context @@ -389,7 +602,7 @@ struct Monomorphize : public Pass { // if we use that context. CallContext context; std::vector<Expression*> newOperands; - context.buildFromCall(info, newOperands, wasm); + context.buildFromCall(info, newOperands, wasm, getPassOptions()); // See if we've already evaluated this function + call context. If so, then // we've memoized the result. @@ -447,8 +660,22 @@ struct Monomorphize : public Pass { doOpts(func); doOpts(monoFunc.get()); + // The cost before monomorphization is the old body + the context + // operands. The operands will be *removed* from the calling code if we + // optimize, and moved into the monomorphized function, so the proper + // comparison is the context + the old body, versus the new body (which + // includes the reverse-inlined call context). auto costBefore = CostAnalyzer(func->body).cost; + for (auto* operand : context.operands) { + // Note that a slight oddity is that we have *not* optimized the + // operands before. We optimize func before and after, but the operands + // are in the calling function, which we are not modifying here. In + // theory that might lead to false positives, if the call's operands are + // very unoptimized. + costBefore += CostAnalyzer(operand).cost; + } auto costAfter = CostAnalyzer(monoFunc->body).cost; + // TODO: We should probably only accept improvements above some minimum, // to avoid optimizing cases where we duplicate a huge function but // only optimize a tiny part of it compared to the original. @@ -486,14 +713,19 @@ struct Monomorphize : public Pass { // Copy the function as the base for the new one. auto newFunc = ModuleUtils::copyFunctionWithoutAdd(func, wasm, newName); - // Generate the new signature, and apply it to the new function. + // A local.get is a value that arrives in a parameter. Anything else is + // something that we are reverse-inlining into the function, so we don't + // need a param for it. Note that we might have multiple gets nested here, + // if we are copying part of the original parameter but not all children, so + // we scan each operand for all such local.gets. + // + // Use this information to generate the new signature, and apply it to the + // new function. std::vector<Type> newParams; for (auto* operand : context.operands) { - // A local.get is a value that arrives in a parameter. Anything else is - // something that we are reverse-inlining into the function, so we don't - // need a param for it. - if (operand->is<LocalGet>()) { - newParams.push_back(operand->type); + FindAll<LocalGet> gets(operand); + for (auto* get : gets.list) { + newParams.push_back(get->type); } } // If we were dropped then we are pulling the drop into the monomorphized @@ -501,7 +733,7 @@ struct Monomorphize : public Pass { auto newResults = context.dropped ? Type::none : func->getResults(); newFunc->type = Signature(Type(newParams), newResults); - // We must update local indexes: the new function has a potentially + // We must update local indexes: the new function has a potentially // different number of parameters, and parameters are at the very bottom of // the local index space. We are also replacing old params with vars. To // track this, map each old index to the new one. @@ -559,7 +791,7 @@ struct Monomorphize : public Pass { // (local.get $param) ;; copied old body // ) // - // We need to add such an local.set in the prelude of the function for each + // We need to add such a local.set in the prelude of the function for each // operand in the context. std::vector<Expression*> pre; for (Index i = 0; i < context.operands.size(); i++) { diff --git a/test/lit/passes/monomorphize-consts.wast b/test/lit/passes/monomorphize-consts.wast index ec59edfea..d71367569 100644 --- a/test/lit/passes/monomorphize-consts.wast +++ b/test/lit/passes/monomorphize-consts.wast @@ -12,85 +12,59 @@ ;; ALWAYS: (type $0 (func (param i32))) - ;; ALWAYS: (type $1 (func)) + ;; ALWAYS: (type $1 (func (param i32 i32 funcref stringref))) - ;; ALWAYS: (type $2 (func (param i32 i32 funcref stringref))) + ;; ALWAYS: (type $2 (func (param i32) (result i32))) - ;; ALWAYS: (type $3 (func (param i32) (result i32))) + ;; ALWAYS: (type $3 (func (result i32))) - ;; ALWAYS: (type $4 (func (result i32))) + ;; ALWAYS: (type $4 (func (param i32 i32))) ;; ALWAYS: (import "a" "b" (func $import (type $0) (param i32))) - ;; CAREFUL: (type $0 (func)) + ;; CAREFUL: (type $0 (func (param i32))) ;; CAREFUL: (type $1 (func (param i32 i32 funcref stringref))) - ;; CAREFUL: (type $2 (func (param i32))) + ;; CAREFUL: (type $2 (func (param i32) (result i32))) - ;; CAREFUL: (type $3 (func (param i32) (result i32))) + ;; CAREFUL: (type $3 (func (result i32))) - ;; CAREFUL: (type $4 (func (result i32))) + ;; CAREFUL: (type $4 (func (param i32 i32))) - ;; CAREFUL: (import "a" "b" (func $import (type $2) (param i32))) + ;; CAREFUL: (import "a" "b" (func $import (type $0) (param i32))) (import "a" "b" (func $import (param i32))) ;; ALWAYS: (elem declare func $calls) - ;; ALWAYS: (func $calls (type $1) + ;; ALWAYS: (func $calls (type $4) (param $x i32) (param $y i32) ;; ALWAYS-NEXT: (call $target_9 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 2) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $target_9 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 3) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $y) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $target_10 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 2) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; CAREFUL: (elem declare func $calls) - ;; CAREFUL: (func $calls (type $0) - ;; CAREFUL-NEXT: (call $target - ;; CAREFUL-NEXT: (i32.const 1) - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 2) - ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (ref.func $calls) - ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL: (func $calls (type $4) (param $x i32) (param $y i32) + ;; CAREFUL-NEXT: (call $target_9 + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (call $target - ;; CAREFUL-NEXT: (i32.const 1) - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 3) - ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (ref.func $calls) - ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: (call $target_9 + ;; CAREFUL-NEXT: (local.get $y) ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (call $target - ;; CAREFUL-NEXT: (i32.const 3) - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 2) - ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (ref.func $calls) - ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: (call $target_10 + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls - ;; All but the eqz parameter are constants that can be handled. In ALWAYS - ;; mode we optimize and remove all but that one. In CAREFUL mode we end up - ;; not doing anything, as the target function's body optimizes out anyhow - ;; (so there is no benefit from monomorphization, after opts). + (func $calls (param $x i32) (param $y i32) + ;; All but the local.get are constants that can be handled. (call $target (i32.const 1) - (i32.eqz - (i32.const 2) - ) + (local.get $x) (ref.func $calls) (string.const "foo") ) @@ -100,9 +74,7 @@ ;; This will call the same refined function as the previous call. (call $target (i32.const 1) - (i32.eqz - (i32.const 3) ;; this changed - ) + (local.get $y) ;; this changed (ref.func $calls) (string.const "foo") ) @@ -111,64 +83,42 @@ ;; call a different refined function. (call $target (i32.const 3) ;; this changed - (i32.eqz - (i32.const 2) - ) + (local.get $x) (ref.func $calls) (string.const "foo") ) ) - ;; ALWAYS: (func $more-calls (type $1) + ;; ALWAYS: (func $more-calls (type $0) (param $x i32) ;; ALWAYS-NEXT: (call $target_9 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 999) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $other-target_11 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 999) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $work_12 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 999) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $more-calls (type $0) - ;; CAREFUL-NEXT: (call $target - ;; CAREFUL-NEXT: (i32.const 1) - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 999) - ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (ref.func $calls) - ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL: (func $more-calls (type $0) (param $x i32) + ;; CAREFUL-NEXT: (call $target_9 + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (call $other-target - ;; CAREFUL-NEXT: (i32.const 1) - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 999) - ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (ref.func $calls) - ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: (call $other-target_11 + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (call $work_9 - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 999) - ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $work_12 + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $more-calls + (func $more-calls (param $x i32) ;; Identical to the first call in the previous function (except for the non- ;; constant second param, which is ok to be different). We should call the ;; same refined function before, even though we are in a different ;; function here. (call $target (i32.const 1) - (i32.eqz - (i32.const 999) - ) + (local.get $x) (ref.func $calls) (string.const "foo") ) @@ -178,9 +128,7 @@ ;; a different refined function than before (call $other-target (i32.const 1) - (i32.eqz - (i32.const 999) - ) + (local.get $x) (ref.func $calls) (string.const "foo") ) @@ -190,22 +138,16 @@ ;; unlock actual work. (call $work (i32.const 3) - (i32.eqz - (i32.const 999) - ) + (local.get $x) (ref.func $calls) (string.const "foo") ) ) - ;; ALWAYS: (func $fail (type $1) + ;; ALWAYS: (func $fail (type $0) (param $x i32) ;; ALWAYS-NEXT: (call $target - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 1) - ;; ALWAYS-NEXT: ) - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 999) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: (block (result funcref) ;; ALWAYS-NEXT: (ref.func $calls) ;; ALWAYS-NEXT: ) @@ -214,14 +156,10 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $fail (type $0) + ;; CAREFUL: (func $fail (type $0) (param $x i32) ;; CAREFUL-NEXT: (call $target - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 1) - ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 999) - ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: (block (result funcref) ;; CAREFUL-NEXT: (ref.func $calls) ;; CAREFUL-NEXT: ) @@ -230,15 +168,11 @@ ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $fail + (func $fail (param $x i32) ;; No operand is a constant here, so we do nothing. (call $target - (i32.eqz - (i32.const 1) - ) - (i32.eqz - (i32.const 999) - ) + (local.get $x) + (local.get $x) (block (result funcref) (ref.func $calls) ) @@ -248,7 +182,7 @@ ) ) - ;; ALWAYS: (func $mutual-recursion-a (type $3) (param $x i32) (result i32) + ;; ALWAYS: (func $mutual-recursion-a (type $2) (param $x i32) (result i32) ;; ALWAYS-NEXT: (if (result i32) ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: (then @@ -264,7 +198,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $mutual-recursion-a (type $3) (param $0 i32) (result i32) + ;; CAREFUL: (func $mutual-recursion-a (type $2) (param $0 i32) (result i32) ;; CAREFUL-NEXT: (if (result i32) ;; CAREFUL-NEXT: (local.get $0) ;; CAREFUL-NEXT: (then @@ -272,9 +206,7 @@ ;; CAREFUL-NEXT: (call $mutual-recursion-b ;; CAREFUL-NEXT: (local.get $0) ;; CAREFUL-NEXT: ) - ;; CAREFUL-NEXT: (call $mutual-recursion-b - ;; CAREFUL-NEXT: (i32.const 0) - ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $mutual-recursion-b_13) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (else @@ -308,15 +240,15 @@ ) ) - ;; ALWAYS: (func $mutual-recursion-b (type $3) (param $x i32) (result i32) + ;; ALWAYS: (func $mutual-recursion-b (type $2) (param $x i32) (result i32) ;; ALWAYS-NEXT: (i32.add ;; ALWAYS-NEXT: (call $mutual-recursion-a_14) ;; ALWAYS-NEXT: (i32.const 1337) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $mutual-recursion-b (type $3) (param $0 i32) (result i32) + ;; CAREFUL: (func $mutual-recursion-b (type $2) (param $0 i32) (result i32) ;; CAREFUL-NEXT: (i32.add - ;; CAREFUL-NEXT: (call $mutual-recursion-a_10) + ;; CAREFUL-NEXT: (call $mutual-recursion-a_14) ;; CAREFUL-NEXT: (i32.const 1337) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) @@ -330,7 +262,7 @@ ) ) - ;; ALWAYS: (func $target (type $2) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; ALWAYS: (func $target (type $1) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) @@ -362,7 +294,7 @@ ) ) - ;; ALWAYS: (func $other-target (type $2) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; ALWAYS: (func $other-target (type $1) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $func) ;; ALWAYS-NEXT: ) @@ -395,7 +327,7 @@ ) ) - ;; ALWAYS: (func $work (type $2) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; ALWAYS: (func $work (type $1) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) ;; ALWAYS-NEXT: (call $import ;; ALWAYS-NEXT: (i32.add ;; ALWAYS-NEXT: (local.get $x) @@ -590,7 +522,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS: (func $mutual-recursion-b_13 (type $4) (result i32) +;; ALWAYS: (func $mutual-recursion-b_13 (type $3) (result i32) ;; ALWAYS-NEXT: (local $x i32) ;; ALWAYS-NEXT: (local.set $x ;; ALWAYS-NEXT: (i32.const 0) @@ -603,7 +535,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS: (func $mutual-recursion-a_14 (type $4) (result i32) +;; ALWAYS: (func $mutual-recursion-a_14 (type $3) (result i32) ;; ALWAYS-NEXT: (local $x i32) ;; ALWAYS-NEXT: (local.set $x ;; ALWAYS-NEXT: (i32.const 0) @@ -624,7 +556,19 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; CAREFUL: (func $work_9 (type $2) (param $0 i32) +;; CAREFUL: (func $target_9 (type $0) (param $0 i32) +;; CAREFUL-NEXT: (nop) +;; CAREFUL-NEXT: ) + +;; CAREFUL: (func $target_10 (type $0) (param $0 i32) +;; CAREFUL-NEXT: (nop) +;; CAREFUL-NEXT: ) + +;; CAREFUL: (func $other-target_11 (type $0) (param $0 i32) +;; CAREFUL-NEXT: (nop) +;; CAREFUL-NEXT: ) + +;; CAREFUL: (func $work_12 (type $0) (param $0 i32) ;; CAREFUL-NEXT: (call $import ;; CAREFUL-NEXT: (i32.const 3) ;; CAREFUL-NEXT: ) @@ -633,6 +577,15 @@ ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) -;; CAREFUL: (func $mutual-recursion-a_10 (type $4) (result i32) +;; CAREFUL: (func $mutual-recursion-b_13 (type $3) (result i32) +;; CAREFUL-NEXT: (i32.add +;; CAREFUL-NEXT: (call $mutual-recursion-a +;; CAREFUL-NEXT: (i32.const 0) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (i32.const 1337) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) + +;; CAREFUL: (func $mutual-recursion-a_14 (type $3) (result i32) ;; CAREFUL-NEXT: (i32.const 42) ;; CAREFUL-NEXT: ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast new file mode 100644 index 000000000..d3bc4a242 --- /dev/null +++ b/test/lit/passes/monomorphize-context.wast @@ -0,0 +1,1805 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; As in monomorphize-types.wast, test in both "always" mode, which always +;; monomorphizes, and in "careful" mode which does it only when it appears to +;; actually help. + +;; RUN: foreach %s %t wasm-opt --monomorphize-always -all -S -o - | filecheck %s --check-prefix ALWAYS +;; RUN: foreach %s %t wasm-opt --monomorphize -all -S -o - | filecheck %s --check-prefix CAREFUL + +(module + ;; ALWAYS: (type $0 (func (param i32) (result i32))) + + ;; ALWAYS: (type $1 (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 anyref funcref i32 f64 i32 anyref anyref))) + + ;; ALWAYS: (type $2 (func (param i32 i32 i32 i32 i32 i32))) + + ;; ALWAYS: (type $struct (struct)) + (type $struct (struct)) + + (memory 10 20) + + ;; ALWAYS: (global $imm i32 (i32.const 10)) + ;; CAREFUL: (type $0 (func (param i32) (result i32))) + + ;; CAREFUL: (type $1 (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 anyref funcref i32 f64 i32 anyref anyref))) + + ;; CAREFUL: (type $2 (func (param i32 i32 i32 i32 i32 i32))) + + ;; CAREFUL: (global $imm i32 (i32.const 10)) + (global $imm i32 (i32.const 10)) + + ;; ALWAYS: (global $mut (mut i32) (i32.const 20)) + ;; CAREFUL: (global $mut (mut i32) (i32.const 20)) + (global $mut (mut i32) (i32.const 20)) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (elem declare func $target) + + ;; ALWAYS: (func $caller (type $0) (param $x i32) (result i32) + ;; ALWAYS-NEXT: (block $out (result i32) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (br_if $out + ;; ALWAYS-NEXT: (i32.const 12) + ;; ALWAYS-NEXT: (i32.const 13) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (if (result i32) + ;; ALWAYS-NEXT: (i32.const 1) + ;; ALWAYS-NEXT: (then + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (else + ;; ALWAYS-NEXT: (i32.const 3) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $caller + ;; ALWAYS-NEXT: (i32.const 4) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (local.tee $x + ;; ALWAYS-NEXT: (i32.const 5) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 14) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) (param $x i32) (result i32) + ;; CAREFUL-NEXT: (block $out (result i32) + ;; CAREFUL-NEXT: (call $target_2 + ;; CAREFUL-NEXT: (br_if $out + ;; CAREFUL-NEXT: (i32.const 12) + ;; CAREFUL-NEXT: (i32.const 13) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (if (result i32) + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: (then + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (else + ;; CAREFUL-NEXT: (i32.const 3) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $caller + ;; CAREFUL-NEXT: (i32.const 4) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: (local.tee $x + ;; CAREFUL-NEXT: (i32.const 5) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 14) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller (param $x i32) (result i32) + ;; Show the variety of things we can and cannot move into the call context. + ;; + ;; Note that in CAREFUL mode we only optimize here if we properly take into + ;; account the call context in the cost. The function we are calling has + ;; an empty body, so the monomorphized function will contain basically just + ;; the moved code from the call context. If we didn't measure that in the + ;; cost before monomorphization then it would seem like we went from cost 0 + ;; (empty body) to the cost of the operations that remain after we + ;; optimize (which is the i32.load, which might trap so it remains). But if + ;; we take into account the context, then monomorphization definitely helps + ;; as it removes a bunch of constants. + (block $out (result i32) + (call $target + ;; We can't move control flow. + (br_if $out + (i32.const 12) + (i32.const 13) + ) + ;; We can't move control flow structures. + (block (result i32) + (i32.const 0) + ) + (if (result i32) + (i32.const 1) + (then + (i32.const 2) + ) + (else + (i32.const 3) + ) + ) + ;; We don't move calls. + (call $caller + (i32.const 4) + ) + ;; We can't move local operations. + (local.get $x) + (local.tee $x + (i32.const 5) + ) + ;; We can move globals, even mutable. + (global.get $imm) + (global.get $mut) + ;; We can move loads and other options that might trap. + (i32.load + (i32.const 6) + ) + ;; We can move constants. + (i32.const 7) + (ref.null any) + (ref.func $target) + ;; We can move math operations. + (i32.eqz + (i32.const 8) + ) + (f64.add + (f64.const 2.71828) + (f64.const 3.14159) + ) + ;; We can move selects. + (select + (i32.const 9) + (i32.const 10) + (i32.const 11) + ) + ;; We can move GC operations. + (ref.cast (ref null none) + (ref.null none) + ) + (struct.new $struct) + ) + (i32.const 14) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 anyref) (param $11 funcref) (param $12 i32) (param $13 f64) (param $14 i32) (param $15 anyref) (param $16 anyref) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 anyref) (param $11 funcref) (param $12 i32) (param $13 f64) (param $14 i32) (param $15 anyref) (param $16 anyref) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param anyref) + (param funcref) + (param i32) + (param f64) + (param i32) + (param anyref) + (param anyref) + ) +) + +;; ALWAYS: (func $target_2 (type $2) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) +;; ALWAYS-NEXT: (local $6 i32) +;; ALWAYS-NEXT: (local $7 i32) +;; ALWAYS-NEXT: (local $8 i32) +;; ALWAYS-NEXT: (local $9 i32) +;; ALWAYS-NEXT: (local $10 i32) +;; ALWAYS-NEXT: (local $11 i32) +;; ALWAYS-NEXT: (local $12 i32) +;; ALWAYS-NEXT: (local $13 i32) +;; ALWAYS-NEXT: (local $14 i32) +;; ALWAYS-NEXT: (local $15 i32) +;; ALWAYS-NEXT: (local $16 anyref) +;; ALWAYS-NEXT: (local $17 funcref) +;; ALWAYS-NEXT: (local $18 i32) +;; ALWAYS-NEXT: (local $19 f64) +;; ALWAYS-NEXT: (local $20 i32) +;; ALWAYS-NEXT: (local $21 anyref) +;; ALWAYS-NEXT: (local $22 anyref) +;; ALWAYS-NEXT: (local.set $6 +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $7 +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $8 +;; ALWAYS-NEXT: (local.get $2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $9 +;; ALWAYS-NEXT: (local.get $3) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $10 +;; ALWAYS-NEXT: (local.get $4) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $11 +;; ALWAYS-NEXT: (local.get $5) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $12 +;; ALWAYS-NEXT: (global.get $imm) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $13 +;; ALWAYS-NEXT: (global.get $mut) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $14 +;; ALWAYS-NEXT: (i32.load +;; ALWAYS-NEXT: (i32.const 6) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $15 +;; ALWAYS-NEXT: (i32.const 7) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $16 +;; ALWAYS-NEXT: (ref.null none) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $17 +;; ALWAYS-NEXT: (ref.func $target) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $18 +;; ALWAYS-NEXT: (i32.eqz +;; ALWAYS-NEXT: (i32.const 8) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $19 +;; ALWAYS-NEXT: (f64.add +;; ALWAYS-NEXT: (f64.const 2.71828) +;; ALWAYS-NEXT: (f64.const 3.14159) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $20 +;; ALWAYS-NEXT: (select +;; ALWAYS-NEXT: (i32.const 9) +;; ALWAYS-NEXT: (i32.const 10) +;; ALWAYS-NEXT: (i32.const 11) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $21 +;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.null none) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $22 +;; ALWAYS-NEXT: (struct.new_default $struct) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_2 (type $2) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) +;; CAREFUL-NEXT: (drop +;; CAREFUL-NEXT: (i32.load +;; CAREFUL-NEXT: (i32.const 6) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +(module + ;; ALWAYS: (type $0 (func (param i32) (result i32))) + + ;; ALWAYS: (type $1 (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 anyref funcref i32 f64 i32 anyref anyref) (result i32))) + + ;; ALWAYS: (type $2 (func (param i32 i32 i32 i32 i32 i32))) + + ;; ALWAYS: (type $struct (struct)) + (type $struct (struct)) + + (memory 10 20) + + ;; ALWAYS: (global $imm i32 (i32.const 10)) + ;; CAREFUL: (type $0 (func (param i32) (result i32))) + + ;; CAREFUL: (type $1 (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 anyref funcref i32 f64 i32 anyref anyref) (result i32))) + + ;; CAREFUL: (type $2 (func (param i32 i32 i32 i32 i32 i32))) + + ;; CAREFUL: (global $imm i32 (i32.const 10)) + (global $imm i32 (i32.const 10)) + + ;; ALWAYS: (global $mut (mut i32) (i32.const 20)) + ;; CAREFUL: (global $mut (mut i32) (i32.const 20)) + (global $mut (mut i32) (i32.const 20)) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (elem declare func $target) + + ;; ALWAYS: (func $caller (type $0) (param $x i32) (result i32) + ;; ALWAYS-NEXT: (block $out (result i32) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (br_if $out + ;; ALWAYS-NEXT: (i32.const 12) + ;; ALWAYS-NEXT: (i32.const 13) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (if (result i32) + ;; ALWAYS-NEXT: (i32.const 1) + ;; ALWAYS-NEXT: (then + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (else + ;; ALWAYS-NEXT: (i32.const 3) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $caller + ;; ALWAYS-NEXT: (i32.const 4) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (local.tee $x + ;; ALWAYS-NEXT: (i32.const 5) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 14) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) (param $x i32) (result i32) + ;; CAREFUL-NEXT: (block $out (result i32) + ;; CAREFUL-NEXT: (call $target_2 + ;; CAREFUL-NEXT: (br_if $out + ;; CAREFUL-NEXT: (i32.const 12) + ;; CAREFUL-NEXT: (i32.const 13) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (if (result i32) + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: (then + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (else + ;; CAREFUL-NEXT: (i32.const 3) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $caller + ;; CAREFUL-NEXT: (i32.const 4) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: (local.tee $x + ;; CAREFUL-NEXT: (i32.const 5) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 14) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller (param $x i32) (result i32) + ;; As above, but now the call is dropped, so the context can include the + ;; drop. + (block $out (result i32) + (drop + (call $target + (br_if $out + (i32.const 12) + (i32.const 13) + ) + (block (result i32) + (i32.const 0) + ) + (if (result i32) + (i32.const 1) + (then + (i32.const 2) + ) + (else + (i32.const 3) + ) + ) + (call $caller + (i32.const 4) + ) + (local.get $x) + (local.tee $x + (i32.const 5) + ) + (global.get $imm) + (global.get $mut) + (i32.load + (i32.const 6) + ) + (i32.const 7) + (ref.null any) + (ref.func $target) + (i32.eqz + (i32.const 8) + ) + (f64.add + (f64.const 2.71828) + (f64.const 3.14159) + ) + (select + (i32.const 9) + (i32.const 10) + (i32.const 11) + ) + (ref.cast (ref null none) + (ref.null none) + ) + (struct.new $struct) + ) + ) + (i32.const 14) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 anyref) (param $11 funcref) (param $12 i32) (param $13 f64) (param $14 i32) (param $15 anyref) (param $16 anyref) (result i32) + ;; ALWAYS-NEXT: (local.get $7) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) (param $6 i32) (param $7 i32) (param $8 i32) (param $9 i32) (param $10 anyref) (param $11 funcref) (param $12 i32) (param $13 f64) (param $14 i32) (param $15 anyref) (param $16 anyref) (result i32) + ;; CAREFUL-NEXT: (local.get $7) + ;; CAREFUL-NEXT: ) + (func $target + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param anyref) + (param funcref) + (param i32) + (param f64) + (param i32) + (param anyref) + (param anyref) + (result i32) + (local.get 7) + ) +) + + + + +;; ALWAYS: (func $target_2 (type $2) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) +;; ALWAYS-NEXT: (local $6 i32) +;; ALWAYS-NEXT: (local $7 i32) +;; ALWAYS-NEXT: (local $8 i32) +;; ALWAYS-NEXT: (local $9 i32) +;; ALWAYS-NEXT: (local $10 i32) +;; ALWAYS-NEXT: (local $11 i32) +;; ALWAYS-NEXT: (local $12 i32) +;; ALWAYS-NEXT: (local $13 i32) +;; ALWAYS-NEXT: (local $14 i32) +;; ALWAYS-NEXT: (local $15 i32) +;; ALWAYS-NEXT: (local $16 anyref) +;; ALWAYS-NEXT: (local $17 funcref) +;; ALWAYS-NEXT: (local $18 i32) +;; ALWAYS-NEXT: (local $19 f64) +;; ALWAYS-NEXT: (local $20 i32) +;; ALWAYS-NEXT: (local $21 anyref) +;; ALWAYS-NEXT: (local $22 anyref) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (block (result i32) +;; ALWAYS-NEXT: (local.set $6 +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $7 +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $8 +;; ALWAYS-NEXT: (local.get $2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $9 +;; ALWAYS-NEXT: (local.get $3) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $10 +;; ALWAYS-NEXT: (local.get $4) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $11 +;; ALWAYS-NEXT: (local.get $5) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $12 +;; ALWAYS-NEXT: (global.get $imm) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $13 +;; ALWAYS-NEXT: (global.get $mut) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $14 +;; ALWAYS-NEXT: (i32.load +;; ALWAYS-NEXT: (i32.const 6) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $15 +;; ALWAYS-NEXT: (i32.const 7) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $16 +;; ALWAYS-NEXT: (ref.null none) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $17 +;; ALWAYS-NEXT: (ref.func $target) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $18 +;; ALWAYS-NEXT: (i32.eqz +;; ALWAYS-NEXT: (i32.const 8) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $19 +;; ALWAYS-NEXT: (f64.add +;; ALWAYS-NEXT: (f64.const 2.71828) +;; ALWAYS-NEXT: (f64.const 3.14159) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $20 +;; ALWAYS-NEXT: (select +;; ALWAYS-NEXT: (i32.const 9) +;; ALWAYS-NEXT: (i32.const 10) +;; ALWAYS-NEXT: (i32.const 11) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $21 +;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.null none) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $22 +;; ALWAYS-NEXT: (struct.new_default $struct) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.get $13) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_2 (type $2) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) +;; CAREFUL-NEXT: (drop +;; CAREFUL-NEXT: (i32.load +;; CAREFUL-NEXT: (i32.const 6) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +(module + (memory 10 20) + + ;; ALWAYS: (type $0 (func)) + + ;; ALWAYS: (type $1 (func (param f32))) + + ;; ALWAYS: (type $2 (func (param f64))) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (func $caller (type $0) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (block $label$1 (result f64) + ;; ALWAYS-NEXT: (f64.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $target_3) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param f32))) + + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (f32.demote_f64 + ;; CAREFUL-NEXT: (block $label$1 (result f64) + ;; CAREFUL-NEXT: (f64.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $target_2) + ;; CAREFUL-NEXT: ) + (func $caller + ;; Nesting: the f32.demote_f64 operation can be moved into the context, but + ;; its child cannot, so we stop there. (In CAREFUL mode, we end up doing + ;; nothing here, as the benefit of monomorphization is not worth it.) + (call $target + (f32.demote_f64 + (block $label$1 (result f64) + (f64.const 0) + ) + ) + ) + + ;; Now the child is an f64.abs, which can be moved into the context, so it + ;; all is moved. This ends up worthwhile in CAREFUL mode (since we can + ;; optimize all the math here). + (call $target + (f32.demote_f64 + (f64.abs ;; this changed + (f64.const 0) + ) + ) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $f32 f32) + ;; ALWAYS-NEXT: (f32.store + ;; ALWAYS-NEXT: (i32.const 42) + ;; ALWAYS-NEXT: (local.get $f32) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 f32) + ;; CAREFUL-NEXT: (f32.store + ;; CAREFUL-NEXT: (i32.const 42) + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $target (param $f32 f32) + ;; When monomorphized the first time, the param here will be f64 and not + ;; i32, showing we handle a type change. + ;; + ;; When monomorphized the second time, the param will go away entirely. + (f32.store + (i32.const 42) + (local.get $f32) + ) + ) +) + + +;; ALWAYS: (func $target_2 (type $2) (param $0 f64) +;; ALWAYS-NEXT: (local $f32 f32) +;; ALWAYS-NEXT: (local.set $f32 +;; ALWAYS-NEXT: (f32.demote_f64 +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f32.store +;; ALWAYS-NEXT: (i32.const 42) +;; ALWAYS-NEXT: (local.get $f32) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $target_3 (type $0) +;; ALWAYS-NEXT: (local $f32 f32) +;; ALWAYS-NEXT: (local.set $f32 +;; ALWAYS-NEXT: (f32.demote_f64 +;; ALWAYS-NEXT: (f64.abs +;; ALWAYS-NEXT: (f64.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f32.store +;; ALWAYS-NEXT: (i32.const 42) +;; ALWAYS-NEXT: (local.get $f32) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_2 (type $0) +;; CAREFUL-NEXT: (f32.store +;; CAREFUL-NEXT: (i32.const 42) +;; CAREFUL-NEXT: (f32.const 0) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +(module + ;; ALWAYS: (type $struct (struct (field (mut f32)) (field (mut f64)))) + ;; CAREFUL: (type $struct (struct (field (mut f32)) (field (mut f64)))) + (type $struct (struct (field (mut f32)) (field (mut f64)))) + + ;; ALWAYS: (type $1 (func (param f32) (result anyref))) + + ;; ALWAYS: (type $2 (func (param f64) (result anyref))) + + ;; ALWAYS: (type $3 (func (param f64))) + + ;; ALWAYS: (type $4 (func (param (ref $struct)) (result anyref))) + + ;; ALWAYS: (func $caller (type $1) (param $x f32) (result anyref) + ;; ALWAYS-NEXT: (call $target_4 + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $1 (func (param f64))) + + ;; CAREFUL: (type $2 (func (param f32) (result anyref))) + + ;; CAREFUL: (type $3 (func (param f64) (result anyref))) + + ;; CAREFUL: (type $4 (func (param (ref $struct)) (result anyref))) + + ;; CAREFUL: (func $caller (type $2) (param $x f32) (result anyref) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (struct.new $struct + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: (f64.const 4.2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller (param $x f32) (result anyref) + ;; We can reverse-inline the struct.new and the nested constant, leaving + ;; only the local.get as a remaining param. (In CAREFUL mode, however, this + ;; does not look promising enough to optimize.) + (call $target + (struct.new $struct + (local.get $x) + (f64.const 4.2) + ) + ) + ) + + ;; ALWAYS: (func $caller-flip (type $2) (param $x f64) (result anyref) + ;; ALWAYS-NEXT: (call $target_5 + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $caller-flip (type $3) (param $x f64) (result anyref) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (struct.new $struct + ;; CAREFUL-NEXT: (f32.const 13.369999885559082) + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller-flip (param $x f64) (result anyref) + ;; As above, but with struct.new's children flipped (which does not change + ;; anything). + (call $target + (struct.new $struct + (f32.const 13.37) + (local.get $x) + ) + ) + ) + + ;; ALWAYS: (func $dropped-caller (type $3) (param $x f64) + ;; ALWAYS-NEXT: (call $target_6 + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $dropped-caller (type $1) (param $x f64) + ;; CAREFUL-NEXT: (call $target_4 + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $dropped-caller (param $x f64) + ;; As above, but the outcome is dropped. As the target function only does + ;; some struct.sets, escape analysis after monomorphization can prove that + ;; we can remove all the code, so this is optimized in CAREFUL mode. + (drop + (call $target + (struct.new $struct + (f32.const 13.37) + (local.get $x) + ) + ) + ) + ) + + ;; ALWAYS: (func $target (type $4) (param $ref (ref $struct)) (result anyref) + ;; ALWAYS-NEXT: (struct.set $struct 0 + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: (f32.add + ;; ALWAYS-NEXT: (struct.get $struct 0 + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (f32.const 1.100000023841858) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (struct.set $struct 1 + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: (f64.max + ;; ALWAYS-NEXT: (struct.get $struct 1 + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (f64.const -1.2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $4) (param $0 (ref $struct)) (result anyref) + ;; CAREFUL-NEXT: (struct.set $struct 0 + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: (f32.add + ;; CAREFUL-NEXT: (struct.get $struct 0 + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (f32.const 1.100000023841858) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (struct.set $struct 1 + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: (f64.max + ;; CAREFUL-NEXT: (struct.get $struct 1 + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (f64.const -1.2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + (func $target (param $ref (ref $struct)) (result anyref) + ;; Do some operations on the reference, but do not escape it. + (struct.set $struct 0 + (local.get $ref) + (f32.add + (struct.get $struct 0 + (local.get $ref) + ) + (f32.const 1.1) + ) + ) + (struct.set $struct 1 + (local.get $ref) + (f64.max + (struct.get $struct 1 + (local.get $ref) + ) + (f64.const -1.2) + ) + ) + (local.get $ref) + ) +) + +;; ALWAYS: (func $target_4 (type $1) (param $0 f32) (result anyref) +;; ALWAYS-NEXT: (local $ref (ref $struct)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: (f64.const 4.2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block (result anyref) +;; ALWAYS-NEXT: (struct.set $struct 0 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (f32.add +;; ALWAYS-NEXT: (struct.get $struct 0 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f32.const 1.100000023841858) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (struct.set $struct 1 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (f64.max +;; ALWAYS-NEXT: (struct.get $struct 1 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f64.const -1.2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $target_5 (type $2) (param $0 f64) (result anyref) +;; ALWAYS-NEXT: (local $ref (ref $struct)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (f32.const 13.369999885559082) +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block (result anyref) +;; ALWAYS-NEXT: (struct.set $struct 0 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (f32.add +;; ALWAYS-NEXT: (struct.get $struct 0 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f32.const 1.100000023841858) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (struct.set $struct 1 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (f64.max +;; ALWAYS-NEXT: (struct.get $struct 1 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f64.const -1.2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $target_6 (type $3) (param $0 f64) +;; ALWAYS-NEXT: (local $ref (ref $struct)) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (block (result anyref) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (f32.const 13.369999885559082) +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block (result anyref) +;; ALWAYS-NEXT: (struct.set $struct 0 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (f32.add +;; ALWAYS-NEXT: (struct.get $struct 0 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f32.const 1.100000023841858) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (struct.set $struct 1 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (f64.max +;; ALWAYS-NEXT: (struct.get $struct 1 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (f64.const -1.2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_4 (type $1) (param $0 f64) +;; CAREFUL-NEXT: (nop) +;; CAREFUL-NEXT: ) +(module + ;; ALWAYS: (type $struct (struct (field i16) (field (mut i8)) (field (mut f64)))) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $struct (struct (field i16) (field (mut i8)) (field (mut f64)))) + (type $struct (struct (field i16) (field (mut i8)) (field (mut f64)))) + + ;; ALWAYS: (type $1 (func)) + + ;; ALWAYS: (type $2 (func (param (ref $struct)))) + + ;; ALWAYS: (type $3 (func (param i32 f64))) + + ;; ALWAYS: (func $caller (type $1) + ;; ALWAYS-NEXT: (local $i32 i32) + ;; ALWAYS-NEXT: (local $f64 f64) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (local.get $i32) + ;; ALWAYS-NEXT: (local.get $f64) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $2 (func (param (ref $struct)))) + + ;; CAREFUL: (type $3 (func (param i32 f64))) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (local $i32 i32) + ;; CAREFUL-NEXT: (local $f64 f64) + ;; CAREFUL-NEXT: (call $target_2 + ;; CAREFUL-NEXT: (local.get $i32) + ;; CAREFUL-NEXT: (local.get $f64) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller + (local $i32 i32) + (local $f64 f64) + ;; The first operand can be moved to the context, but not the other two. Of + ;; those two, the order of iteration matters, as they have different types + ;; (if we got mixed up and reordered them, we'd error). + (call $target + (struct.new $struct + (i32.const 0) + (local.get $i32) + (local.get $f64) + ) + ) + ) + + ;; ALWAYS: (func $target (type $2) (param $0 (ref $struct)) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $2) (param $0 (ref $struct)) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param (ref $struct)) + (nop) + ) +) + +;; ALWAYS: (func $target_2 (type $3) (param $0 i32) (param $1 f64) +;; ALWAYS-NEXT: (local $2 (ref $struct)) +;; ALWAYS-NEXT: (local.set $2 +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_2 (type $3) (param $0 i32) (param $1 f64) +;; CAREFUL-NEXT: (nop) +;; CAREFUL-NEXT: ) +(module + ;; ALWAYS: (type $array (sub (array (mut i8)))) + (type $array (sub (array (mut i8)))) + + ;; ALWAYS: (type $1 (func)) + + ;; ALWAYS: (type $2 (func (param i32))) + + ;; ALWAYS: (type $3 (func (param anyref anyref) (result i32))) + + ;; ALWAYS: (type $4 (func (param i32 i32 i32))) + + ;; ALWAYS: (func $caller (type $1) + ;; ALWAYS-NEXT: (call $target_3) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param i32))) + + ;; CAREFUL: (type $2 (func (param anyref anyref) (result i32))) + + ;; CAREFUL: (type $3 (func (param i32 i32 i32))) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (call $target_3) + ;; CAREFUL-NEXT: ) + (func $caller + ;; Call the target with array.new which has an optional child, the initial + ;; value. Set it in one and leave it as nullptr in the other to see we + ;; handle both properly when we move the array.new + children into the + ;; monomorphized function. + (drop + (call $target + (array.new_default $array + (i32.const 1) + ) + (array.new $array + (i32.const 2) + (i32.const 3) + ) + ) + ) + ) + + ;; ALWAYS: (func $caller-unknown (type $2) (param $x i32) + ;; ALWAYS-NEXT: (call $target_4 + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $caller-unknown (type $1) (param $x i32) + ;; CAREFUL-NEXT: (call $target_4 + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller-unknown (param $x i32) + ;; As above, but now there are unknown children, which are not moved. + (drop + (call $target + (array.new_default $array + (local.get $x) + ) + (array.new $array + (local.get $x) + (local.get $x) + ) + ) + ) + ) + + ;; ALWAYS: (func $target (type $3) (param $0 anyref) (param $1 anyref) (result i32) + ;; ALWAYS-NEXT: (unreachable) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $2) (param $0 anyref) (param $1 anyref) (result i32) + ;; CAREFUL-NEXT: (unreachable) + ;; CAREFUL-NEXT: ) + (func $target (param anyref) (param anyref) (result i32) + (unreachable) + ) +) + + +;; ALWAYS: (func $target_3 (type $1) +;; ALWAYS-NEXT: (local $0 anyref) +;; ALWAYS-NEXT: (local $1 anyref) +;; ALWAYS-NEXT: (local.set $0 +;; ALWAYS-NEXT: (array.new_default $array +;; ALWAYS-NEXT: (i32.const 1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $1 +;; ALWAYS-NEXT: (array.new $array +;; ALWAYS-NEXT: (i32.const 2) +;; ALWAYS-NEXT: (i32.const 3) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (unreachable) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $target_4 (type $4) (param $0 i32) (param $1 i32) (param $2 i32) +;; ALWAYS-NEXT: (local $3 anyref) +;; ALWAYS-NEXT: (local $4 anyref) +;; ALWAYS-NEXT: (local.set $3 +;; ALWAYS-NEXT: (array.new_default $array +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $4 +;; ALWAYS-NEXT: (array.new $array +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: (local.get $2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (unreachable) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_3 (type $0) +;; CAREFUL-NEXT: (unreachable) +;; CAREFUL-NEXT: ) + +;; CAREFUL: (func $target_4 (type $3) (param $0 i32) (param $1 i32) (param $2 i32) +;; CAREFUL-NEXT: (unreachable) +;; CAREFUL-NEXT: ) +(module + ;; ALWAYS: (type $struct (struct (field anyref))) + (type $struct (struct (field anyref))) + + ;; ALWAYS: (type $1 (func (param anyref) (result anyref))) + + ;; ALWAYS: (func $caller (type $1) (param $x anyref) (result anyref) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func (param anyref) (result anyref))) + + ;; CAREFUL: (func $caller (type $0) (param $x anyref) (result anyref) + ;; CAREFUL-NEXT: (call $target_2 + ;; CAREFUL-NEXT: (local.get $x) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller (param $x anyref) (result anyref) + ;; A call with a deeply nested param. We can move all of it but the + ;; local.get into the monomorphized function. + (call $target + (struct.new $struct + (struct.new $struct + (struct.new $struct + (struct.new $struct + (struct.new $struct + (struct.new $struct + (local.get $x) + ) + ) + ) + ) + ) + ) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $0 anyref) (result anyref) + ;; ALWAYS-NEXT: (unreachable) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $0) (param $0 anyref) (result anyref) + ;; CAREFUL-NEXT: (unreachable) + ;; CAREFUL-NEXT: ) + (func $target (param anyref) (result anyref) + (unreachable) + ) +) + +;; ALWAYS: (func $target_2 (type $1) (param $0 anyref) (result anyref) +;; ALWAYS-NEXT: (local $1 anyref) +;; ALWAYS-NEXT: (local.set $1 +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (unreachable) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_2 (type $0) (param $0 anyref) (result anyref) +;; CAREFUL-NEXT: (unreachable) +;; CAREFUL-NEXT: ) +(module + (memory 10 20) + + ;; ALWAYS: (type $0 (func)) + + ;; ALWAYS: (type $1 (func (param i32 i32))) + + ;; ALWAYS: (type $2 (func (param i32))) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (func $caller (type $0) + ;; ALWAYS-NEXT: (call $target + ;; ALWAYS-NEXT: (i32.load + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const -1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 11) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param i32 i32))) + + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const -1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 11) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller + ;; The two operands here cannot be reordered: the first loads, and the + ;; second stores. If we move the first into the context but not the second + ;; (which is what we'd normally do: the first can be copied, while the + ;; second is a control flow structure) then we'd execute the second before + ;; the first, as we'd execute the first only after doing the call, which + ;; would be wrong. + (call $target + (i32.load + (i32.const 0) + ) + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 0xffffffff) + ) + (i32.const 11) + ) + ) + ) + + ;; ALWAYS: (func $caller-flip (type $0) + ;; ALWAYS-NEXT: (call $target_3 + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const -1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 11) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $caller-flip (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const -1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 11) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller-flip + ;; With the order reversed, there is no problem: the load can be moved into + ;; the context (in ALWAYS, at least). + (call $target + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 0xffffffff) + ) + (i32.const 11) + ) + (i32.load + (i32.const 0) + ) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param i32) (param i32) + ) +) + +;; ALWAYS: (func $target_3 (type $2) (param $0 i32) +;; ALWAYS-NEXT: (local $1 i32) +;; ALWAYS-NEXT: (local $2 i32) +;; ALWAYS-NEXT: (local.set $1 +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $2 +;; ALWAYS-NEXT: (i32.load +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) +(module + (memory 10 20) + + ;; ALWAYS: (type $0 (func (param i32))) + + ;; ALWAYS: (type $1 (func)) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (func $caller (type $1) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.load + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param i32))) + + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.atomic.rmw8.cmpxchg_u + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller + ;; Effect interaction between a parent and child: the child reads while the + ;; parent writes (and also reads), so they cannot be reordered. But the + ;; parent executes later anyhow, so it is fine to optimize the atomic + ;; operation into the context. + (call $target + (i32.atomic.rmw8.cmpxchg_u + (i32.const 0) + (block (result i32) ;; Use a block to prevent the child from being + ;; moved into the context. + (i32.load + (i32.const 0) + ) + ) + (i32.const 1) + ) + ) + ;; Note that we cannot test the reverse, since if the block were on the + ;; outside then anything inside it would not be moved. + ) + + ;; ALWAYS: (func $target (type $0) (param $0 i32) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 i32) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param i32) + ) +) + +;; ALWAYS: (func $target_2 (type $0) (param $0 i32) +;; ALWAYS-NEXT: (local $1 i32) +;; ALWAYS-NEXT: (local.set $1 +;; ALWAYS-NEXT: (i32.atomic.rmw8.cmpxchg_u +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: (i32.const 1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) +(module + ;; ALWAYS: (type $0 (func)) + + ;; ALWAYS: (type $struct (struct (field i32) (field i32))) + (type $struct (struct (field i32) (field i32))) + + (memory 10 20) + + ;; ALWAYS: (type $2 (func (param anyref))) + + ;; ALWAYS: (type $3 (func (param i32 i32))) + + ;; ALWAYS: (type $4 (func (param i32))) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (func $caller (type $0) + ;; ALWAYS-NEXT: (call $target_3 + ;; ALWAYS-NEXT: (i32.load + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const 1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param anyref))) + + ;; CAREFUL: (type $2 (func (param i32 i32))) + + ;; CAREFUL: (type $3 (func (param i32))) + + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (call $target_3 + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller + ;; Effect interaction between children of a call operand. The read and write + ;; cannot be reordered, so all we can move into the call context is the + ;; struct.new. + (call $target + (struct.new $struct + (i32.load + (i32.const 0) + ) + (block (result i32) ;; Use a block to prevent the child from being + ;; moved into the context. + (i32.store + (i32.const 0) + (i32.const 1) + ) + (i32.const 2) + ) + ) + ) + ) + + ;; ALWAYS: (func $caller-flip (type $0) + ;; ALWAYS-NEXT: (call $target_4 + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const 1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $caller-flip (type $0) + ;; CAREFUL-NEXT: (call $target_4 + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller-flip + ;; With the order reversed, there is no problem, and the load can also be + ;; optimized into the context. + (call $target + (struct.new $struct + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 1) + ) + (i32.const 2) + ) + (i32.load + (i32.const 0) + ) + ) + ) + ) + + ;; ALWAYS: (func $target (type $2) (param $0 anyref) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 anyref) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param anyref) + ) +) + + +;; ALWAYS: (func $target_3 (type $3) (param $0 i32) (param $1 i32) +;; ALWAYS-NEXT: (local $2 anyref) +;; ALWAYS-NEXT: (local.set $2 +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $target_4 (type $4) (param $0 i32) +;; ALWAYS-NEXT: (local $1 anyref) +;; ALWAYS-NEXT: (local.set $1 +;; ALWAYS-NEXT: (struct.new $struct +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: (i32.load +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_3 (type $2) (param $0 i32) (param $1 i32) +;; CAREFUL-NEXT: (nop) +;; CAREFUL-NEXT: ) + +;; CAREFUL: (func $target_4 (type $3) (param $0 i32) +;; CAREFUL-NEXT: (drop +;; CAREFUL-NEXT: (i32.load +;; CAREFUL-NEXT: (i32.const 0) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +(module + (memory 10 20) + + ;; ALWAYS: (type $0 (func)) + + ;; ALWAYS: (type $1 (func (param i32 i32 i32 i32 i32 i32))) + + ;; ALWAYS: (type $2 (func (param i32 i32 i32 i32 i32))) + + ;; ALWAYS: (memory $0 10 20) + + ;; ALWAYS: (func $caller (type $0) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const -1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 11) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.load + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const -1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 11) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.load + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result i32) + ;; ALWAYS-NEXT: (i32.store + ;; ALWAYS-NEXT: (i32.const 0) + ;; ALWAYS-NEXT: (i32.const -1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.const 11) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param i32 i32 i32 i32 i32 i32))) + + ;; CAREFUL: (memory $0 10 20) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const -1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 11) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const -1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 11) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result i32) + ;; CAREFUL-NEXT: (i32.store + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: (i32.const -1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 11) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.load + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller + ;; Similar to before, but with a sequence of interleaved things with + ;; interactions. The same two items repeat three times. Any load cannot be + ;; moved into the context if there is a store after it, which means that the + ;; last load can be optimized and nothing else (in CAREFUL mode, however, + ;; that ends up not worthwhile). + (call $target + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 0xffffffff) + ) + (i32.const 11) + ) + (i32.load + (i32.const 0) + ) + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 0xffffffff) + ) + (i32.const 11) + ) + (i32.load + (i32.const 0) + ) + (block (result i32) + (i32.store + (i32.const 0) + (i32.const 0xffffffff) + ) + (i32.const 11) + ) + (i32.load + (i32.const 0) + ) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) (param $5 i32) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) + ) +) + + +;; ALWAYS: (func $target_2 (type $2) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i32) (param $4 i32) +;; ALWAYS-NEXT: (local $5 i32) +;; ALWAYS-NEXT: (local $6 i32) +;; ALWAYS-NEXT: (local $7 i32) +;; ALWAYS-NEXT: (local $8 i32) +;; ALWAYS-NEXT: (local $9 i32) +;; ALWAYS-NEXT: (local $10 i32) +;; ALWAYS-NEXT: (local.set $5 +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $6 +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $7 +;; ALWAYS-NEXT: (local.get $2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $8 +;; ALWAYS-NEXT: (local.get $3) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $9 +;; ALWAYS-NEXT: (local.get $4) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $10 +;; ALWAYS-NEXT: (i32.load +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (nop) +;; ALWAYS-NEXT: ) +(module + ;; ALWAYS: (type $0 (func)) + + ;; ALWAYS: (type $1 (func (param f32))) + + ;; ALWAYS: (func $caller (type $0) + ;; ALWAYS-NEXT: (local $tuple (tuple i32 f32)) + ;; ALWAYS-NEXT: (call $target + ;; ALWAYS-NEXT: (tuple.extract 2 1 + ;; ALWAYS-NEXT: (local.get $tuple) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param f32))) + + ;; CAREFUL: (func $caller (type $0) + ;; CAREFUL-NEXT: (local $tuple (tuple i32 f32)) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (tuple.extract 2 1 + ;; CAREFUL-NEXT: (local.get $tuple) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $caller + (local $tuple (tuple i32 f32)) + ;; We cannot move the tuple.extract into the context, as that would mean the + ;; new call has a tuple param. Rather than handle that somehow, ignore it. + (call $target + (tuple.extract 2 1 + (local.get $tuple) + ) + ) + ) + + ;; ALWAYS: (func $target (type $1) (param $0 f32) + ;; ALWAYS-NEXT: (nop) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 f32) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param $0 f32) + ) +) + diff --git a/test/lit/passes/monomorphize-mvp.wast b/test/lit/passes/monomorphize-mvp.wast index 567a4c2ce..ade1f6895 100644 --- a/test/lit/passes/monomorphize-mvp.wast +++ b/test/lit/passes/monomorphize-mvp.wast @@ -12,38 +12,28 @@ ;; RUN: foreach %s %t wasm-opt --monomorphize -S -o - | filecheck %s --check-prefix CAREFUL (module - ;; ALWAYS: (type $0 (func (result i32))) + ;; ALWAYS: (type $0 (func (param i32) (result i32))) ;; ALWAYS: (type $1 (func (param i32 i32) (result i32))) - ;; ALWAYS: (type $2 (func (param i32) (result i32))) - - ;; ALWAYS: (func $call (result i32) + ;; ALWAYS: (func $call (param $x i32) (result i32) ;; ALWAYS-NEXT: (call $target_2 - ;; ALWAYS-NEXT: (i32.eqz - ;; ALWAYS-NEXT: (i32.const 2) - ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.get $x) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (type $0 (func (result i32))) + ;; CAREFUL: (type $0 (func (param i32) (result i32))) ;; CAREFUL: (type $1 (func (param i32 i32) (result i32))) - ;; CAREFUL: (type $2 (func (param i32) (result i32))) - - ;; CAREFUL: (func $call (result i32) + ;; CAREFUL: (func $call (param $x i32) (result i32) ;; CAREFUL-NEXT: (call $target_2 - ;; CAREFUL-NEXT: (i32.eqz - ;; CAREFUL-NEXT: (i32.const 2) - ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (local.get $x) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $call (result i32) + (func $call (param $x i32) (result i32) ;; The second parameter can be monomorphized. (call $target - (i32.eqz - (i32.const 2) - ) + (local.get $x) (i32.const 1) ) ) diff --git a/test/lit/passes/monomorphize-types.wast b/test/lit/passes/monomorphize-types.wast index 49ae8353a..7d90a0f87 100644 --- a/test/lit/passes/monomorphize-types.wast +++ b/test/lit/passes/monomorphize-types.wast @@ -10,95 +10,97 @@ ;; ALWAYS: (type $A (sub (struct))) ;; CAREFUL: (type $A (sub (struct))) (type $A (sub (struct))) + ;; ALWAYS: (type $1 (func (param (ref $A)))) + ;; ALWAYS: (type $B (sub $A (struct))) + ;; CAREFUL: (type $1 (func (param (ref $A)))) + ;; CAREFUL: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; ALWAYS: (type $2 (func (param (ref $A)))) - - ;; ALWAYS: (type $3 (func)) + ;; ALWAYS: (type $3 (func (param (ref $B)))) - ;; ALWAYS: (type $4 (func (param (ref $B)))) + ;; ALWAYS: (type $4 (func (param (ref $A) (ref $B)))) - ;; ALWAYS: (import "a" "b" (func $import (type $2) (param (ref $A)))) - ;; CAREFUL: (type $2 (func (param (ref $A)))) + ;; ALWAYS: (import "a" "b" (func $import (type $1) (param (ref $A)))) + ;; CAREFUL: (type $3 (func (param (ref $A) (ref $B)))) - ;; CAREFUL: (type $3 (func)) + ;; CAREFUL: (type $4 (func (param (ref $B)))) - ;; CAREFUL: (import "a" "b" (func $import (type $2) (param (ref $A)))) + ;; CAREFUL: (import "a" "b" (func $import (type $1) (param (ref $A)))) (import "a" "b" (func $import (param (ref $A)))) - ;; ALWAYS: (func $calls (type $3) + ;; ALWAYS: (func $calls (type $4) (param $A (ref $A)) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $refinable - ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: (local.get $A) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable - ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: (local.get $A) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable_4 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable_4 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $calls (type $3) + ;; CAREFUL: (func $calls (type $3) (param $A (ref $A)) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: (local.get $A) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: (local.get $A) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls + (func $calls (param $A (ref $A)) (param $B (ref $B)) ;; Two calls with $A, two with $B. The calls to $B should both go to the ;; same new function which has a refined parameter of $B. ;; ;; However, in CAREFUL mode we won't do that, as there is no helpful ;; improvement in the target functions even with the refined types. (call $refinable - (struct.new $A) + (local.get $A) ) (call $refinable - (struct.new $A) + (local.get $A) ) (call $refinable - (struct.new $B) + (local.get $B) ) (call $refinable - (struct.new $B) + (local.get $B) ) ) - ;; ALWAYS: (func $call-import (type $3) + ;; ALWAYS: (func $call-import (type $3) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $import - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $call-import (type $3) + ;; CAREFUL: (func $call-import (type $4) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $import - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $call-import + (func $call-import (param $B (ref $B)) ;; Calls to imports are left as they are. (call $import - (struct.new $B) + (local.get $B) ) ) - ;; ALWAYS: (func $refinable (type $2) (param $ref (ref $A)) + ;; ALWAYS: (func $refinable (type $1) (param $ref (ref $A)) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $refinable (type $2) (param $0 (ref $A)) + ;; CAREFUL: (func $refinable (type $1) (param $0 (ref $A)) ;; CAREFUL-NEXT: (nop) ;; CAREFUL-NEXT: ) (func $refinable (param $ref (ref $A)) @@ -115,7 +117,7 @@ ) -;; ALWAYS: (func $refinable_4 (type $4) (param $0 (ref $B)) +;; ALWAYS: (func $refinable_4 (type $3) (param $0 (ref $B)) ;; ALWAYS-NEXT: (local $ref (ref $A)) ;; ALWAYS-NEXT: (local.set $ref ;; ALWAYS-NEXT: (local.get $0) @@ -129,8 +131,6 @@ ;; requires a fixup. ;; ALWAYS: (type $A (sub (struct))) - ;; CAREFUL: (type $0 (func)) - ;; CAREFUL: (type $A (sub (struct))) (type $A (sub (struct))) ;; ALWAYS: (type $B (sub $A (struct))) @@ -139,27 +139,27 @@ - ;; ALWAYS: (type $2 (func)) + ;; ALWAYS: (type $2 (func (param (ref $B)))) ;; ALWAYS: (type $3 (func (param (ref $A)))) - ;; ALWAYS: (type $4 (func (param (ref $B)))) - - ;; ALWAYS: (func $calls (type $2) + ;; ALWAYS: (func $calls (type $2) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $refinable_2 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $2 (func (param (ref $B)))) + ;; CAREFUL: (type $3 (func (param (ref $A)))) - ;; CAREFUL: (func $calls (type $0) + ;; CAREFUL: (func $calls (type $2) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls + (func $calls (param $B (ref $B)) (call $refinable - (struct.new $B) + (local.get $B) ) ) @@ -190,7 +190,7 @@ ) -;; ALWAYS: (func $refinable_2 (type $4) (param $0 (ref $B)) +;; ALWAYS: (func $refinable_2 (type $2) (param $0 (ref $B)) ;; ALWAYS-NEXT: (local $unref (ref $A)) ;; ALWAYS-NEXT: (local $ref (ref $A)) ;; ALWAYS-NEXT: (local.set $ref @@ -209,84 +209,88 @@ ;; Multiple refinings of the same function, and of different functions. ;; ALWAYS: (type $A (sub (struct))) - ;; CAREFUL: (type $0 (func)) - ;; CAREFUL: (type $A (sub (struct))) (type $A (sub (struct))) ;; ALWAYS: (type $B (sub $A (struct))) + ;; CAREFUL: (type $1 (func (param (ref $A)))) + ;; CAREFUL: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; ALWAYS: (type $2 (func)) + ;; ALWAYS: (type $2 (func (param (ref $A)))) + + ;; ALWAYS: (type $3 (func (param (ref $B)))) ;; ALWAYS: (type $C (sub $B (struct))) - ;; CAREFUL: (type $3 (func (param (ref $A)))) + ;; CAREFUL: (type $3 (func (param (ref $A) (ref $B)))) ;; CAREFUL: (type $C (sub $B (struct))) (type $C (sub $B (struct))) - ;; ALWAYS: (type $4 (func (param (ref $A)))) + ;; ALWAYS: (type $5 (func (param (ref $A) (ref $B)))) - ;; ALWAYS: (type $5 (func (param (ref $B)))) + ;; ALWAYS: (type $6 (func (param (ref $C) (ref $B)))) - ;; ALWAYS: (type $6 (func (param (ref $C)))) + ;; ALWAYS: (type $7 (func (param (ref $C)))) - ;; ALWAYS: (func $calls1 (type $2) + ;; ALWAYS: (func $calls1 (type $5) (param $A (ref $A)) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $refinable1 - ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: (local.get $A) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable1_4 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $calls1 (type $0) + ;; CAREFUL: (type $5 (func (param (ref $C) (ref $B)))) + + ;; CAREFUL: (func $calls1 (type $3) (param $A (ref $A)) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $refinable1 - ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: (local.get $A) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable1 - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls1 + (func $calls1 (param $A (ref $A)) (param $B (ref $B)) (call $refinable1 - (struct.new $A) + (local.get $A) ) (call $refinable1 - (struct.new $B) + (local.get $B) ) ) - ;; ALWAYS: (func $calls2 (type $2) + ;; ALWAYS: (func $calls2 (type $6) (param $C (ref $C)) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $refinable1_5 - ;; ALWAYS-NEXT: (struct.new_default $C) + ;; ALWAYS-NEXT: (local.get $C) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable2_6 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $calls2 (type $0) + ;; CAREFUL: (func $calls2 (type $5) (param $C (ref $C)) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $refinable1 - ;; CAREFUL-NEXT: (struct.new_default $C) + ;; CAREFUL-NEXT: (local.get $C) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable2 - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls2 + (func $calls2 (param $C (ref $C)) (param $B (ref $B)) (call $refinable1 - (struct.new $C) + (local.get $C) ) (call $refinable2 - (struct.new $B) + (local.get $B) ) ) - ;; ALWAYS: (func $refinable1 (type $4) (param $ref (ref $A)) + ;; ALWAYS: (func $refinable1 (type $2) (param $ref (ref $A)) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $refinable1 (type $3) (param $0 (ref $A)) + ;; CAREFUL: (func $refinable1 (type $1) (param $0 (ref $A)) ;; CAREFUL-NEXT: (nop) ;; CAREFUL-NEXT: ) (func $refinable1 (param $ref (ref $A)) @@ -295,12 +299,12 @@ ) ) - ;; ALWAYS: (func $refinable2 (type $4) (param $ref (ref $A)) + ;; ALWAYS: (func $refinable2 (type $2) (param $ref (ref $A)) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $refinable2 (type $3) (param $0 (ref $A)) + ;; CAREFUL: (func $refinable2 (type $1) (param $0 (ref $A)) ;; CAREFUL-NEXT: (nop) ;; CAREFUL-NEXT: ) (func $refinable2 (param $ref (ref $A)) @@ -310,7 +314,7 @@ ) ) -;; ALWAYS: (func $refinable1_4 (type $5) (param $0 (ref $B)) +;; ALWAYS: (func $refinable1_4 (type $3) (param $0 (ref $B)) ;; ALWAYS-NEXT: (local $ref (ref $A)) ;; ALWAYS-NEXT: (local.set $ref ;; ALWAYS-NEXT: (local.get $0) @@ -320,7 +324,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS: (func $refinable1_5 (type $6) (param $0 (ref $C)) +;; ALWAYS: (func $refinable1_5 (type $7) (param $0 (ref $C)) ;; ALWAYS-NEXT: (local $ref (ref $A)) ;; ALWAYS-NEXT: (local.set $ref ;; ALWAYS-NEXT: (local.get $0) @@ -330,7 +334,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS: (func $refinable2_6 (type $5) (param $0 (ref $B)) +;; ALWAYS: (func $refinable2_6 (type $3) (param $0 (ref $B)) ;; ALWAYS-NEXT: (local $ref (ref $A)) ;; ALWAYS-NEXT: (local.set $ref ;; ALWAYS-NEXT: (local.get $0) @@ -351,72 +355,84 @@ ;; CAREFUL: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; ALWAYS: (type $2 (func (param (ref $B)))) + ;; ALWAYS: (type $2 (func (param (ref $A) (ref $B)))) - ;; ALWAYS: (type $3 (func)) + ;; ALWAYS: (type $3 (func (param (ref $B)))) - ;; ALWAYS: (type $4 (func (param (ref $A)))) + ;; ALWAYS: (type $4 (func (param (ref $B) (ref $B)))) - ;; ALWAYS: (import "a" "b" (func $import (type $2) (param (ref $B)))) - ;; CAREFUL: (type $2 (func (param (ref $B)))) + ;; ALWAYS: (import "a" "b" (func $import (type $3) (param (ref $B)))) + ;; CAREFUL: (type $2 (func (param (ref $A) (ref $B)))) - ;; CAREFUL: (type $3 (func)) + ;; CAREFUL: (type $3 (func (param (ref $B)))) - ;; CAREFUL: (type $4 (func (param (ref $A)))) + ;; CAREFUL: (type $4 (func (param (ref $B) (ref $B)))) - ;; CAREFUL: (import "a" "b" (func $import (type $2) (param (ref $B)))) + ;; CAREFUL: (import "a" "b" (func $import (type $3) (param (ref $B)))) (import "a" "b" (func $import (param (ref $B)))) ;; ALWAYS: (global $global (mut i32) (i32.const 1)) ;; CAREFUL: (global $global (mut i32) (i32.const 1)) (global $global (mut i32) (i32.const 1)) - ;; ALWAYS: (func $calls (type $3) + ;; ALWAYS: (func $calls (type $2) (param $A (ref $A)) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $refinable - ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: (local.get $A) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable - ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: (local.get $A) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable_3 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (call $refinable_3 - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $calls (type $3) + ;; CAREFUL: (func $calls (type $2) (param $A (ref $A)) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: (local.get $A) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable - ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: (local.get $A) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable_3 - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $refinable_3 - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls + (func $calls (param $A (ref $A)) (param $B (ref $B)) ;; The calls sending $B will switch to calling a refined version, as the ;; refined version is better, even in CAREFUL mode. (call $refinable - (struct.new $A) + (local.get $A) + (local.get $B) ) (call $refinable - (struct.new $A) + (local.get $A) + (local.get $B) ) (call $refinable - (struct.new $B) + (local.get $B) + (local.get $B) ) (call $refinable - (struct.new $B) + (local.get $B) + (local.get $B) ) ) - ;; ALWAYS: (func $refinable (type $4) (param $ref (ref $A)) + ;; ALWAYS: (func $refinable (type $2) (param $ref (ref $A)) (param $B (ref $B)) ;; ALWAYS-NEXT: (local $x (ref $A)) ;; ALWAYS-NEXT: (call $import ;; ALWAYS-NEXT: (ref.cast (ref $B) @@ -426,7 +442,7 @@ ;; ALWAYS-NEXT: (local.set $x ;; ALWAYS-NEXT: (select (result (ref $A)) ;; ALWAYS-NEXT: (local.get $ref) - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: (global.get $global) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -446,33 +462,32 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $refinable (type $4) (param $0 (ref $A)) - ;; CAREFUL-NEXT: (local $1 (ref $B)) + ;; CAREFUL: (func $refinable (type $2) (param $0 (ref $A)) (param $1 (ref $B)) ;; CAREFUL-NEXT: (local $2 (ref $B)) ;; CAREFUL-NEXT: (call $import - ;; CAREFUL-NEXT: (local.tee $1 + ;; CAREFUL-NEXT: (local.tee $2 ;; CAREFUL-NEXT: (ref.cast (ref $B) ;; CAREFUL-NEXT: (local.get $0) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $import - ;; CAREFUL-NEXT: (local.tee $2 + ;; CAREFUL-NEXT: (local.tee $1 ;; CAREFUL-NEXT: (select (result (ref $B)) + ;; CAREFUL-NEXT: (local.get $2) ;; CAREFUL-NEXT: (local.get $1) - ;; CAREFUL-NEXT: (struct.new_default $B) ;; CAREFUL-NEXT: (global.get $global) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $import - ;; CAREFUL-NEXT: (local.get $2) + ;; CAREFUL-NEXT: (local.get $1) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: (call $import - ;; CAREFUL-NEXT: (local.get $1) + ;; CAREFUL-NEXT: (local.get $2) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $refinable (param $ref (ref $A)) + (func $refinable (param $ref (ref $A)) (param $B (ref $B)) ;; Note that this large function will end up optimized in CAREFUL mode, as a ;; side effect of our keeping optimizations we run for comparison purposes. @@ -494,7 +509,7 @@ ;; Use a select here so optimizations don't just merge $x and $ref. (select (result (ref $A)) (local.get $ref) - (struct.new $B) + (local.get $B) (global.get $global) ) ) @@ -517,12 +532,16 @@ ) ) -;; ALWAYS: (func $refinable_3 (type $2) (param $0 (ref $B)) +;; ALWAYS: (func $refinable_3 (type $4) (param $0 (ref $B)) (param $1 (ref $B)) ;; ALWAYS-NEXT: (local $x (ref $A)) ;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local $B (ref $B)) ;; ALWAYS-NEXT: (local.set $ref ;; ALWAYS-NEXT: (local.get $0) ;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $B +;; ALWAYS-NEXT: (local.get $1) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (block ;; ALWAYS-NEXT: (call $import ;; ALWAYS-NEXT: (ref.cast (ref $B) @@ -532,7 +551,7 @@ ;; ALWAYS-NEXT: (local.set $x ;; ALWAYS-NEXT: (select (result (ref $A)) ;; ALWAYS-NEXT: (local.get $ref) -;; ALWAYS-NEXT: (struct.new_default $B) +;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: (global.get $global) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -554,8 +573,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; CAREFUL: (func $refinable_3 (type $2) (param $0 (ref $B)) -;; CAREFUL-NEXT: (local $1 (ref $B)) +;; CAREFUL: (func $refinable_3 (type $4) (param $0 (ref $B)) (param $1 (ref $B)) ;; CAREFUL-NEXT: (call $import ;; CAREFUL-NEXT: (local.get $0) ;; CAREFUL-NEXT: ) @@ -563,7 +581,7 @@ ;; CAREFUL-NEXT: (local.tee $1 ;; CAREFUL-NEXT: (select (result (ref $B)) ;; CAREFUL-NEXT: (local.get $0) -;; CAREFUL-NEXT: (struct.new_default $B) +;; CAREFUL-NEXT: (local.get $1) ;; CAREFUL-NEXT: (global.get $global) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) @@ -581,30 +599,30 @@ ;; ALWAYS: (type $A (sub (struct))) ;; CAREFUL: (type $A (sub (struct))) (type $A (sub (struct))) - ;; ALWAYS: (type $1 (func (param (ref $A)))) - ;; ALWAYS: (type $B (sub $A (struct))) - ;; CAREFUL: (type $1 (func (param (ref $A)))) - ;; CAREFUL: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; ALWAYS: (func $calls (type $1) (param $ref (ref $A)) + ;; ALWAYS: (type $2 (func (param (ref $B)))) + + ;; ALWAYS: (func $calls (type $2) (param $B (ref $B)) ;; ALWAYS-NEXT: (call $calls - ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (local.get $B) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) - ;; CAREFUL: (func $calls (type $1) (param $ref (ref $A)) + ;; CAREFUL: (type $2 (func (param (ref $B)))) + + ;; CAREFUL: (func $calls (type $2) (param $B (ref $B)) ;; CAREFUL-NEXT: (call $calls - ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (local.get $B) ;; CAREFUL-NEXT: ) ;; CAREFUL-NEXT: ) - (func $calls (param $ref (ref $A)) + (func $calls (param $B (ref $B)) ;; We should change nothing in this recursive call, even though we are ;; sending a more refined type, so we could try to monomorphize in theory. (call $calls - (struct.new $B) + (local.get $B) ) ) ) diff --git a/test/lit/passes/no-inline-monomorphize-inlining.wast b/test/lit/passes/no-inline-monomorphize-inlining.wast index b5dc2ed39..2479b46f4 100644 --- a/test/lit/passes/no-inline-monomorphize-inlining.wast +++ b/test/lit/passes/no-inline-monomorphize-inlining.wast @@ -19,87 +19,87 @@ ;; YESINLINE: (type $B (sub $A (struct))) (type $B (sub $A (struct))) - ;; NO_INLINE: (type $2 (func)) + ;; NO_INLINE: (type $2 (func (param (ref $A) (ref $B)))) ;; NO_INLINE: (type $3 (func (param (ref $A)))) ;; NO_INLINE: (type $4 (func (param (ref $B)))) - ;; NO_INLINE: (func $calls (type $2) + ;; NO_INLINE: (func $calls (type $2) (param $A (ref $A)) (param $B (ref $B)) ;; NO_INLINE-NEXT: (call $refinable_noinline - ;; NO_INLINE-NEXT: (struct.new_default $A) + ;; NO_INLINE-NEXT: (local.get $A) ;; NO_INLINE-NEXT: ) ;; NO_INLINE-NEXT: (call $refinable_noinline - ;; NO_INLINE-NEXT: (struct.new_default $A) + ;; NO_INLINE-NEXT: (local.get $A) ;; NO_INLINE-NEXT: ) ;; NO_INLINE-NEXT: (call $refinable_noinline_2 - ;; NO_INLINE-NEXT: (struct.new_default $B) + ;; NO_INLINE-NEXT: (local.get $B) ;; NO_INLINE-NEXT: ) ;; NO_INLINE-NEXT: (call $refinable_noinline_2 - ;; NO_INLINE-NEXT: (struct.new_default $B) + ;; NO_INLINE-NEXT: (local.get $B) ;; NO_INLINE-NEXT: ) ;; NO_INLINE-NEXT: ) - ;; YESINLINE: (type $2 (func)) + ;; YESINLINE: (type $2 (func (param (ref $A) (ref $B)))) - ;; YESINLINE: (func $calls (type $2) - ;; YESINLINE-NEXT: (local $0 (ref $A)) - ;; YESINLINE-NEXT: (local $1 (ref $A)) - ;; YESINLINE-NEXT: (local $2 (ref $B)) + ;; YESINLINE: (func $calls (type $2) (param $A (ref $A)) (param $B (ref $B)) + ;; YESINLINE-NEXT: (local $2 (ref $A)) ;; YESINLINE-NEXT: (local $3 (ref $A)) ;; YESINLINE-NEXT: (local $4 (ref $B)) ;; YESINLINE-NEXT: (local $5 (ref $A)) + ;; YESINLINE-NEXT: (local $6 (ref $B)) + ;; YESINLINE-NEXT: (local $7 (ref $A)) ;; YESINLINE-NEXT: (block ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline - ;; YESINLINE-NEXT: (local.set $0 - ;; YESINLINE-NEXT: (struct.new_default $A) + ;; YESINLINE-NEXT: (local.set $2 + ;; YESINLINE-NEXT: (local.get $A) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $0) + ;; YESINLINE-NEXT: (local.get $2) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (block ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline$1 - ;; YESINLINE-NEXT: (local.set $1 - ;; YESINLINE-NEXT: (struct.new_default $A) + ;; YESINLINE-NEXT: (local.set $3 + ;; YESINLINE-NEXT: (local.get $A) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $1) + ;; YESINLINE-NEXT: (local.get $3) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (block ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$2 - ;; YESINLINE-NEXT: (local.set $2 - ;; YESINLINE-NEXT: (struct.new_default $B) + ;; YESINLINE-NEXT: (local.set $4 + ;; YESINLINE-NEXT: (local.get $B) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (local.set $3 - ;; YESINLINE-NEXT: (local.get $2) + ;; YESINLINE-NEXT: (local.set $5 + ;; YESINLINE-NEXT: (local.get $4) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $3) + ;; YESINLINE-NEXT: (local.get $5) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (block ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$3 - ;; YESINLINE-NEXT: (local.set $4 - ;; YESINLINE-NEXT: (struct.new_default $B) + ;; YESINLINE-NEXT: (local.set $6 + ;; YESINLINE-NEXT: (local.get $B) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (block - ;; YESINLINE-NEXT: (local.set $5 - ;; YESINLINE-NEXT: (local.get $4) + ;; YESINLINE-NEXT: (local.set $7 + ;; YESINLINE-NEXT: (local.get $6) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $5) + ;; YESINLINE-NEXT: (local.get $7) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) - (func $calls + (func $calls (param $A (ref $A)) (param $B (ref $B)) ;; Two calls with $A, two with $B. The calls to $B will both go to the ;; same new monomorphized function which has a refined parameter of $B. ;; @@ -108,16 +108,16 @@ ;; inline the monomorphized ones). In YESINLINE mode we will inline all 4. ;; (call $refinable_noinline - (struct.new $A) + (local.get $A) ) (call $refinable_noinline - (struct.new $A) + (local.get $A) ) (call $refinable_noinline - (struct.new $B) + (local.get $B) ) (call $refinable_noinline - (struct.new $B) + (local.get $B) ) ) |