-
Notifications
You must be signed in to change notification settings - Fork 10.5k
SILInliner: Initial support for begin_apply #17026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,13 +18,181 @@ | |
using namespace swift; | ||
|
||
bool SILInliner::canInlineFunction(FullApplySite AI) { | ||
// For now, we cannot inline begin_apply at all. | ||
if (isa<BeginApplyInst>(AI)) | ||
return false; | ||
|
||
return AI.getFunction() != &Original; | ||
} | ||
|
||
/// Utility class for rewiring control-flow of inlined begin_apply functions. | ||
class BeginApplySite { | ||
SmallVector<SILBasicBlock *, 4> ExitingBlocks; | ||
SmallVector<AllocStackInst*, 8> YieldedIndirectValues; | ||
SILLocation Loc; | ||
SILBuilder &Builder; | ||
BeginApplyInst *BeginApply; | ||
SILFunction *F; | ||
EndApplyInst *EndApply = nullptr; | ||
SILBasicBlock *EndApplyBB = nullptr; | ||
SILBasicBlock *EndApplyBBMerge = nullptr; | ||
AbortApplyInst *AbortApply = nullptr; | ||
SILBasicBlock *AbortApplyBB = nullptr; | ||
SILBasicBlock *AbortApplyBBMerge = nullptr; | ||
SILArgument *IntToken = nullptr; | ||
|
||
unsigned YieldNum = 0; | ||
SmallVector<SILBasicBlock*, 8> YieldResumes; | ||
SmallVector<SILBasicBlock*, 8> YieldUnwinds; | ||
|
||
void | ||
getYieldCaseBBs(SmallVectorImpl<std::pair<SILValue, SILBasicBlock *>> &Result, | ||
SmallVectorImpl<SILBasicBlock *> &Dests) { | ||
unsigned Token = 0; | ||
for (auto *Blk : Dests) { | ||
Result.push_back(std::make_pair( | ||
SILValue(Builder.createIntegerLiteral( | ||
Loc, | ||
SILType::getBuiltinIntegerType( | ||
32, Builder.getFunction().getModule().getASTContext()), | ||
Token++)), | ||
Blk)); | ||
} | ||
} | ||
|
||
public: | ||
BeginApplySite(BeginApplyInst *BeginApply, SILLocation Loc, | ||
SILBuilder &Builder) | ||
: Loc(Loc), Builder(Builder), BeginApply(BeginApply), | ||
F(BeginApply->getFunction()) {} | ||
|
||
static Optional<BeginApplySite> isa(FullApplySite AI, SILLocation Loc, | ||
SILBuilder &Builder) { | ||
auto *BeginApply = dyn_cast<BeginApplyInst>(AI); | ||
if (!BeginApply) | ||
return None; | ||
return BeginApplySite(BeginApply, Loc, Builder); | ||
} | ||
|
||
void collectCallerExitingBlocks() { | ||
F->findExitingBlocks(ExitingBlocks); | ||
} | ||
|
||
void processApply(SILBasicBlock *ReturnToBB) { | ||
// Handle direct and indirect results. | ||
for (auto YieldedValue : BeginApply->getYieldedValues()) { | ||
// Insert an alloc_stack for indirect results. | ||
if (YieldedValue->getType().isAddress()) { | ||
Builder.setInsertionPoint(F->getEntryBlock()->begin()); | ||
auto Addr = Builder.createAllocStack( | ||
Loc, YieldedValue->getType().getObjectType()); | ||
YieldedValue->replaceAllUsesWith(Addr); | ||
YieldedIndirectValues.push_back(Addr); | ||
for (auto *Exit : ExitingBlocks) { | ||
Builder.setInsertionPoint(Exit->getTerminator()); | ||
Builder.createDeallocStack(Loc, Addr); | ||
} | ||
continue; | ||
} | ||
// Insert a phi for direct results. | ||
auto *RetArg = ReturnToBB->createPHIArgument(YieldedValue->getType(), | ||
ValueOwnershipKind::Owned); | ||
// Replace all uses of the ApplyInst with the new argument. | ||
YieldedValue->replaceAllUsesWith(RetArg); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you thinking that you'll There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I overlooked that. You are correct that I have to disallow inlining if the values live across the yield are not dominating all yields with the current approach (or I think I could rewrite the code such that's they to dominate for address values and add phis and undefs for direct values). I tried not to replicate caller code. |
||
} | ||
|
||
// Add a trailing phi argument for the token integer (tells us which yield | ||
// we came from). | ||
IntToken = ReturnToBB->createPHIArgument( | ||
SILType::getBuiltinIntegerType(32, F->getModule().getASTContext()), | ||
ValueOwnershipKind::Owned); | ||
|
||
// Get the end_apply, abort_apply instructions. | ||
auto Token = BeginApply->getTokenResult(); | ||
for (auto *TokenUse : Token->getUses()) { | ||
EndApply = dyn_cast<EndApplyInst>(TokenUse->getUser()); | ||
if (EndApply) | ||
continue; | ||
AbortApply = cast<AbortApplyInst>(TokenUse->getUser()); | ||
} | ||
|
||
// Split the basic block before the end/abort_apply. We will insert code | ||
// to jump to the resume/unwind blocks depending on the integer token | ||
// later. And the inlined resume/unwind return blocks will jump back to | ||
// the merge blocks. | ||
EndApplyBB = EndApply->getParent(); | ||
EndApplyBBMerge = EndApplyBB->split(SILBasicBlock::iterator(EndApply)); | ||
if (AbortApply) { | ||
AbortApplyBB = AbortApply->getParent(); | ||
AbortApplyBBMerge = | ||
AbortApplyBB->split(SILBasicBlock::iterator(AbortApply)); | ||
} | ||
} | ||
|
||
void processTerminator( | ||
TermInst *Terminator, SILBasicBlock *ReturnToBB, | ||
llvm::function_ref<SILBasicBlock *(SILBasicBlock *)> remapBlock, | ||
llvm::function_ref<SILValue(SILValue)> remapValue, | ||
llvm::function_ref<void(TermInst *)> mapTerminator) { | ||
// A yield branches to the begin_apply return block passing the yielded | ||
// results as branch arguments. Collect the yields target block for | ||
// resuming later. Pass an integer token to the begin_apply return block | ||
// to mark the yield we came from. | ||
if (auto *Yield = dyn_cast<YieldInst>(Terminator)) { | ||
YieldResumes.push_back(remapBlock(Yield->getResumeBB())); | ||
YieldUnwinds.push_back(remapBlock(Yield->getUnwindBB())); | ||
auto ContextToken = Builder.createIntegerLiteral( | ||
Loc, | ||
SILType::getBuiltinIntegerType(32, F->getModule().getASTContext()), | ||
YieldNum++); | ||
|
||
SmallVector<SILValue, 8> BrResults; | ||
unsigned IndirectIdx = 0; | ||
for (auto CalleeYieldedVal : Yield->getYieldedValues()) { | ||
auto YieldedVal = remapValue(CalleeYieldedVal); | ||
if (YieldedVal->getType().isAddress()) { | ||
auto YieldedDestAddr = YieldedIndirectValues[IndirectIdx++]; | ||
Builder.createCopyAddr(Loc, YieldedVal, YieldedDestAddr, IsTake, | ||
IsInitialization); | ||
} else | ||
BrResults.push_back(YieldedVal); | ||
} | ||
BrResults.push_back(SILValue(ContextToken)); | ||
Builder.createBranch(Loc, ReturnToBB, BrResults); | ||
return; | ||
} | ||
|
||
// Return and unwind terminators branch to the end_apply/abort_apply merge | ||
// block respectively. | ||
if (auto *RI = dyn_cast<ReturnInst>(Terminator)) { | ||
Builder.createBranch(Loc, EndApplyBBMerge); | ||
return; | ||
} | ||
if (auto *Unwind = dyn_cast<UnwindInst>(Terminator)) { | ||
Builder.createBranch(Loc, AbortApplyBBMerge); | ||
return; | ||
} | ||
|
||
// Otherwise, we just map the branch instruction. | ||
assert(!::isa<ThrowInst>(Terminator) && | ||
"Unexpected throw instruction in yield_once function"); | ||
mapTerminator(Terminator); | ||
} | ||
|
||
void dispatchToResumeUnwindBlocks() { | ||
// Resume edge. | ||
Builder.setInsertionPoint(EndApplyBB); | ||
SmallVector<std::pair<SILValue, SILBasicBlock *>, 8> CaseBBs; | ||
getYieldCaseBBs(CaseBBs, YieldResumes); | ||
Builder.createSwitchValue(Loc, IntToken, nullptr, CaseBBs); | ||
EndApply->eraseFromParent(); | ||
// Unwind edge. | ||
if (AbortApplyBB) { | ||
Builder.setInsertionPoint(AbortApplyBB); | ||
SmallVector<std::pair<SILValue, SILBasicBlock *>, 8> CaseBBs; | ||
getYieldCaseBBs(CaseBBs, YieldUnwinds); | ||
Builder.createSwitchValue(Loc, IntToken, nullptr, CaseBBs); | ||
AbortApply->eraseFromParent(); | ||
} | ||
} | ||
}; | ||
|
||
/// \brief Inlines the callee of a given ApplyInst (which must be the value of a | ||
/// FunctionRefInst referencing a function with a known body), into the caller | ||
/// containing the ApplyInst, which must be the same function as provided to the | ||
|
@@ -114,6 +282,12 @@ void SILInliner::inlineFunction(FullApplySite AI, ArrayRef<SILValue> Args) { | |
ValueMap.insert(std::make_pair(calleeArg, callArg)); | ||
} | ||
|
||
// Find the existing blocks. We will need them for inlining the co-routine | ||
// call. | ||
auto BeginApply = BeginApplySite::isa(AI, Loc.getValue(), getBuilder()); | ||
if (BeginApply) | ||
BeginApply->collectCallerExitingBlocks(); | ||
|
||
// Recursively visit callee's BB in depth-first preorder, starting with the | ||
// entry block, cloning all instructions other than terminators. | ||
visitSILBasicBlock(CalleeEntryBB); | ||
|
@@ -131,6 +305,7 @@ void SILInliner::inlineFunction(FullApplySite AI, ArrayRef<SILValue> Args) { | |
|
||
// If we're inlining into a try_apply, we already have a return-to BB. | ||
SILBasicBlock *ReturnToBB; | ||
|
||
if (auto tryAI = dyn_cast<TryApplyInst>(AI)) { | ||
ReturnToBB = tryAI->getNormalBB(); | ||
|
||
|
@@ -149,17 +324,35 @@ void SILInliner::inlineFunction(FullApplySite AI, ArrayRef<SILValue> Args) { | |
SILFunction::iterator(ReturnToBB)); | ||
|
||
// Create an argument on the return-to BB representing the returned value. | ||
auto apply = cast<ApplyInst>(AI.getInstruction()); | ||
auto *RetArg = ReturnToBB->createPHIArgument(apply->getType(), | ||
ValueOwnershipKind::Owned); | ||
// Replace all uses of the ApplyInst with the new argument. | ||
apply->replaceAllUsesWith(RetArg); | ||
if (auto apply = dyn_cast<ApplyInst>(AI.getInstruction())) { | ||
auto *RetArg = ReturnToBB->createPHIArgument(apply->getType(), | ||
ValueOwnershipKind::Owned); | ||
// Replace all uses of the ApplyInst with the new argument. | ||
apply->replaceAllUsesWith(RetArg); | ||
} else { | ||
// Handle begin_apply. | ||
BeginApply->processApply(ReturnToBB); | ||
} | ||
} | ||
|
||
// Now iterate over the callee BBs and fix up the terminators. | ||
for (auto BI = BBMap.begin(), BE = BBMap.end(); BI != BE; ++BI) { | ||
getBuilder().setInsertionPoint(BI->second); | ||
|
||
// Coroutine terminators need special handling. | ||
if (BeginApply) { | ||
BeginApply->processTerminator( | ||
BI->first->getTerminator(), ReturnToBB, | ||
[=](SILBasicBlock *Block) -> SILBasicBlock * { | ||
return this->remapBasicBlock(Block); | ||
}, | ||
[=](SILValue Val) -> SILValue { | ||
return this->remapValue(Val); | ||
}, | ||
[=](TermInst *Term) { this->visit(Term); }); | ||
continue; | ||
} | ||
|
||
// Modify return terminators to branch to the return-to BB, rather than | ||
// trying to clone the ReturnInst. | ||
if (auto *RI = dyn_cast<ReturnInst>(BI->first->getTerminator())) { | ||
|
@@ -190,6 +383,10 @@ void SILInliner::inlineFunction(FullApplySite AI, ArrayRef<SILValue> Args) { | |
// but remaps basic blocks and values. | ||
visit(BI->first->getTerminator()); | ||
} | ||
|
||
// Insert dispatch code at end/abort_apply to the resume/unwind target blocks. | ||
if (BeginApply) | ||
BeginApply->dispatchToResumeUnwindBlocks(); | ||
} | ||
|
||
SILValue SILInliner::borrowFunctionArgument(SILValue callArg, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just a placeholder? Because this shouldn't be necessary — the indirect yield comes from a value that should be live after inlining — and it's not semantically valid to introduce a copy if e.g. the yielded value is
inout
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote this under the assumption we yield indirect values only
@in
.I agree. I could have just used the inlined alloc_stack instead.
I am not sure how inout yields work. What is being yielded on the callee side? I would assume the following: We create a temporary location in the callee that is then replaced by the caller's location if we inline. Is the following a correct use of an inout yield?
Caller:
Callee:
Couldn't this just as well be expressed as a parameter that we pass inout to the begin_apply?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right the interesting case is this:
Caller:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should think of the
yield
like a function call. The coroutine can produce any address it wants as theyield
value, and the caller just receives an opaque pointer — just like a function can pass any address it wants as aninout
argument, and the callee just receives an opaque pointer.The convention on the yielded address is what the caller is expected to have done before it resumes the coroutine; it becomes invalid after the coroutine resumes. Among other things, this means that if the caller wants to save a value that was yielded
@in
to it, it's required to move the value into caller-owned memory. In the inliner, you aren't reordering code in the caller, so you can just assume that kind of lifetime issue is handled correctly; it's the code-motion and copy-propagation optimizations that will have to worry about it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, thanks.
Fixed both issues in #18477