diff options
Diffstat (limited to 'src/passes')
-rw-r--r-- | src/passes/Asyncify.cpp | 6 | ||||
-rw-r--r-- | src/passes/DeadArgumentElimination.cpp | 54 | ||||
-rw-r--r-- | src/passes/DeadCodeElimination.cpp | 10 | ||||
-rw-r--r-- | src/passes/Directize.cpp | 3 | ||||
-rw-r--r-- | src/passes/I64ToI32Lowering.cpp | 14 | ||||
-rw-r--r-- | src/passes/Inlining.cpp | 87 |
6 files changed, 135 insertions, 39 deletions
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index cb80b90c7..98b4425b1 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -349,6 +349,9 @@ public: } struct Walker : PostWalker<Walker> { void visitCall(Call* curr) { + if (curr->isReturn) { + Fatal() << "tail calls not yet supported in aysncify"; + } auto* target = module->getFunction(curr->target); if (target->imported() && target->module == ASYNCIFY) { // Redirect the imports to the functions we'll add later. @@ -375,6 +378,9 @@ public: info->callsTo.insert(target); } void visitCallIndirect(CallIndirect* curr) { + if (curr->isReturn) { + Fatal() << "tail calls not yet supported in aysncify"; + } if (canIndirectChangeState) { info->canChangeState = true; } diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index 789332b93..9ef762f98 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -57,6 +57,16 @@ struct DAEFunctionInfo { // Map of all calls that are dropped, to their drops' locations (so that // if we can optimize out the drop, we can replace the drop there). std::unordered_map<Call*, Expression**> droppedCalls; + // Whether this function contains any tail calls (including indirect tail + // calls) and the set of functions this function tail calls. Tail-callers and + // tail-callees cannot have their dropped returns removed because of the + // constraint that tail-callees must have the same return type as + // tail-callers. Indirectly tail called functions are already not optimized + // because being in a table inhibits DAE. TODO: Allow the removal of dropped + // returns from tail-callers if their tail-callees can have their returns + // removed as well. + bool hasTailCalls = false; + std::unordered_set<Name> tailCallees; // Whether the function can be called from places that // affect what we can do. For now, any call we don't // see inhibits our optimizations, but TODO: an export @@ -117,6 +127,16 @@ struct DAEScanner if (!getModule()->getFunction(curr->target)->imported()) { info->calls[curr->target].push_back(curr); } + if (curr->isReturn) { + info->hasTailCalls = true; + info->tailCallees.insert(curr->target); + } + } + + void visitCallIndirect(CallIndirect* curr) { + if (curr->isReturn) { + info->hasTailCalls = true; + } } void visitDrop(Drop* curr) { @@ -239,6 +259,7 @@ struct DAE : public Pass { DAEScanner(&infoMap).run(runner, module); // Combine all the info. std::unordered_map<Name, std::vector<Call*>> allCalls; + std::unordered_set<Name> tailCallees; for (auto& pair : infoMap) { auto& info = pair.second; for (auto& pair : info.calls) { @@ -247,6 +268,9 @@ struct DAE : public Pass { auto& allCallsToName = allCalls[name]; allCallsToName.insert(allCallsToName.end(), calls.begin(), calls.end()); } + for (auto& callee : info.tailCallees) { + tailCallees.insert(callee); + } for (auto& pair : info.droppedCalls) { allDroppedCalls[pair.first] = pair.second; } @@ -314,14 +338,11 @@ struct DAE : public Pass { // Great, it's not used. Check if none of the calls has a param with // side effects, as that would prevent us removing them (flattening // should have been done earlier). - bool canRemove = true; - for (auto* call : calls) { - auto* operand = call->operands[i]; - if (EffectAnalyzer(runner->options, operand).hasSideEffects()) { - canRemove = false; - break; - } - } + bool canRemove = + std::none_of(calls.begin(), calls.end(), [&](Call* call) { + auto* operand = call->operands[i]; + return EffectAnalyzer(runner->options, operand).hasSideEffects(); + }); if (canRemove) { // Wonderful, nothing stands in our way! Do it. // TODO: parallelize this? @@ -348,18 +369,21 @@ struct DAE : public Pass { if (infoMap[name].hasUnseenCalls) { continue; } + if (infoMap[name].hasTailCalls) { + continue; + } + if (tailCallees.find(name) != tailCallees.end()) { + continue; + } auto iter = allCalls.find(name); if (iter == allCalls.end()) { continue; } auto& calls = iter->second; - bool allDropped = true; - for (auto* call : calls) { - if (!allDroppedCalls.count(call)) { - allDropped = false; - break; - } - } + bool allDropped = + std::all_of(calls.begin(), calls.end(), [&](Call* call) { + return allDroppedCalls.count(call); + }); if (!allDropped) { continue; } diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index 6e6f2c2b9..61d16afc0 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -365,7 +365,12 @@ struct DeadCodeElimination return curr; } - void visitCall(Call* curr) { handleCall(curr); } + void visitCall(Call* curr) { + handleCall(curr); + if (curr->isReturn) { + reachable = false; + } + } void visitCallIndirect(CallIndirect* curr) { if (handleCall(curr) != curr) { @@ -380,6 +385,9 @@ struct DeadCodeElimination block->finalize(curr->type); replaceCurrent(block); } + if (curr->isReturn) { + reachable = false; + } } // Append the reachable operands of the current node to a block, and replace diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index f6969cce5..663b8257b 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -65,7 +65,8 @@ struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> { } // Everything looks good! replaceCurrent( - Builder(*getModule()).makeCall(name, curr->operands, curr->type)); + Builder(*getModule()) + .makeCall(name, curr->operands, curr->type, curr->isReturn)); } } diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index e893b6296..5d7d5998c 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -262,9 +262,14 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { return call; } void visitCall(Call* curr) { + if (curr->isReturn && + getModule()->getFunction(curr->target)->result == i64) { + Fatal() + << "i64 to i32 lowering of return_call values not yet implemented"; + } auto* fixedCall = visitGenericCall<Call>( curr, [&](std::vector<Expression*>& args, Type ty) { - return builder->makeCall(curr->target, args, ty); + return builder->makeCall(curr->target, args, ty, curr->isReturn); }); // If this was to an import, we need to call the legal version. This assumes // that legalize-js-interface has been run before. @@ -275,10 +280,15 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { } void visitCallIndirect(CallIndirect* curr) { + if (curr->isReturn && + getModule()->getFunctionType(curr->fullType)->result == i64) { + Fatal() + << "i64 to i32 lowering of return_call values not yet implemented"; + } visitGenericCall<CallIndirect>( curr, [&](std::vector<Expression*>& args, Type ty) { return builder->makeCallIndirect( - curr->fullType, curr->target, args, ty); + curr->fullType, curr->target, args, ty, curr->isReturn); }); } diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index a625464cd..9ba01c4c1 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -146,7 +146,17 @@ struct Planner : public WalkerPass<PostWalker<Planner>> { // plan to inline if we know this is valid to inline, and if the call is // actually performed - if it is dead code, it's pointless to inline. // we also cannot inline ourselves. - if (state->worthInlining.count(curr->target) && curr->type != unreachable && + bool isUnreachable; + if (curr->isReturn) { + // Tail calls are only actually unreachable if an argument is + isUnreachable = + std::any_of(curr->operands.begin(), + curr->operands.end(), + [](Expression* op) { return op->type == unreachable; }); + } else { + isUnreachable = curr->type == unreachable; + } + if (state->worthInlining.count(curr->target) && !isUnreachable && curr->target != getFunction()->name) { // nest the call in a block. that way the location of the pointer to the // call will not change even if we inline multiple times into the same @@ -164,32 +174,69 @@ private: InliningState* state; }; +struct Updater : public PostWalker<Updater> { + Module* module; + std::map<Index, Index> localMapping; + Name returnName; + Builder* builder; + void visitReturn(Return* curr) { + replaceCurrent(builder->makeBreak(returnName, curr->value)); + } + // Return calls in inlined functions should only break out of the scope of + // the inlined code, not the entire function they are being inlined into. To + // achieve this, make the call a non-return call and add a break. This does + // not cause unbounded stack growth because inlining and return calling both + // avoid creating a new stack frame. + template<typename T> void handleReturnCall(T* curr, Type targetType) { + curr->isReturn = false; + curr->type = targetType; + if (isConcreteType(targetType)) { + replaceCurrent(builder->makeBreak(returnName, curr)); + } else { + replaceCurrent(builder->blockify(curr, builder->makeBreak(returnName))); + } + } + void visitCall(Call* curr) { + if (curr->isReturn) { + handleReturnCall(curr, module->getFunction(curr->target)->result); + } + } + void visitCallIndirect(CallIndirect* curr) { + if (curr->isReturn) { + handleReturnCall(curr, module->getFunctionType(curr->fullType)->result); + } + } + void visitLocalGet(LocalGet* curr) { + curr->index = localMapping[curr->index]; + } + void visitLocalSet(LocalSet* curr) { + curr->index = localMapping[curr->index]; + } +}; + // Core inlining logic. Modifies the outside function (adding locals as // needed), and returns the inlined code. static Expression* doInlining(Module* module, Function* into, InliningAction& action) { Function* from = action.contents; auto* call = (*action.callSite)->cast<Call>(); + // Works for return_call, too + Type retType = module->getFunction(call->target)->result; Builder builder(*module); - auto* block = Builder(*module).makeBlock(); + auto* block = builder.makeBlock(); block->name = Name(std::string("__inlined_func$") + from->name.str); - *action.callSite = block; - // Prepare to update the inlined code's locals and other things. - struct Updater : public PostWalker<Updater> { - std::map<Index, Index> localMapping; - Name returnName; - Builder* builder; - - void visitReturn(Return* curr) { - replaceCurrent(builder->makeBreak(returnName, curr->value)); + if (call->isReturn) { + if (isConcreteType(retType)) { + *action.callSite = builder.makeReturn(block); + } else { + *action.callSite = builder.makeSequence(block, builder.makeReturn()); } - void visitLocalGet(LocalGet* curr) { - curr->index = localMapping[curr->index]; - } - void visitLocalSet(LocalSet* curr) { - curr->index = localMapping[curr->index]; - } - } updater; + } else { + *action.callSite = block; + } + // Prepare to update the inlined code's locals and other things. + Updater updater; + updater.module = module; updater.returnName = block->name; updater.builder = &builder; // Set up a locals mapping @@ -215,12 +262,12 @@ doInlining(Module* module, Function* into, InliningAction& action) { } updater.walk(contents); block->list.push_back(contents); - block->type = call->type; + block->type = retType; // If the function returned a value, we just set the block containing the // inlined code to have that type. or, if the function was void and // contained void, that is fine too. a bad case is a void function in which // we have unreachable code, so we would be replacing a void call with an - // unreachable; we need to handle + // unreachable. if (contents->type == unreachable && block->type == none) { // Make the block reachable by adding a break to it block->list.push_back(builder.makeBreak(block->name)); |