Skip to content

Commit 95eca55

Browse files
committed
PartialApplySimplification: Transform nonescaping invocation functions that are also fully applied
1 parent 13e8ea5 commit 95eca55

File tree

2 files changed

+161
-42
lines changed

2 files changed

+161
-42
lines changed

lib/SILOptimizer/Transforms/PartialApplySimplification.cpp

Lines changed: 145 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ STATISTIC(NumInvocationFunctionsChanged,
4545
"Number of invocation functions rewritten");
4646
STATISTIC(NumUnsupportedChangesToInvocationFunctions,
4747
"Number of invocation functions that could be rewritten, but aren't yet");
48-
STATISTIC(NumPartialApplyCalleesWithNonPartialApplyUses,
49-
"Number of invocation functions with non-partial_apply uses");
48+
STATISTIC(NumPartialApplyCalleesWithNonApplyUses,
49+
"Number of invocation functions with non-apply uses");
50+
STATISTIC(NumPartialApplyCalleesWithEscapingAndApplyUses,
51+
"Number of invocation functions with both escaping and full apply uses");
5052
STATISTIC(NumPartialApplyCalleesPossiblyUsedExternally,
5153
"Number of invocation functions possibly used externally");
5254
STATISTIC(NumPartialApplyCalleesDeclarationOnly,
@@ -69,8 +71,11 @@ struct KnownCallee {
6971
llvm::SetVector<FunctionRefInst *> FunctionRefs;
7072
/// The set of partial application sites.
7173
llvm::SetVector<PartialApplyInst *> PartialApplications;
72-
/// If the callee has a non-partial-apply use, this points to an arbitrary one.
73-
SILInstruction *NonPartialApplyUse = nullptr;
74+
/// The set of full application sites.
75+
llvm::SetVector<FullApplySite> FullApplications;
76+
/// If the callee has a non-partial-apply, non-apply use, this points to an
77+
/// arbitrary one, for logging purposes.
78+
SILInstruction *NonApplyUse = nullptr;
7479
};
7580

7681
class PartialApplySimplificationPass : public SILModuleTransform {
@@ -115,15 +120,20 @@ class PartialApplySimplificationPass : public SILModuleTransform {
115120
/// out of the single applied argument
116121
/// - if the partial application is noescape:
117122
/// - the argument is word-sized or smaller
118-
/// - the argument is either trivial, or passed with a +0 convention
119-
/// (guaranteed, unowned, in_guaranteed)
123+
/// - the argument is either trivial, or passed with a net +0 convention
124+
/// (guaranteed, unowned, in_guaranteed, inout)
120125
/// - if the partial application is escapable:
121126
/// - the argument is either a single Swift-refcounted word, or trivial and
122127
/// sized strictly less than one word
123128
/// - the argument ownership convention matches the callee convention of the
124129
/// resulting function
125130
static bool isSimplePartialApply(PartialApplyInst *i) {
126131
auto calleeTy = i->getCallee()->getType().castTo<SILFunctionType>();
132+
if (calleeTy->isPolymorphic()) {
133+
// TODO: Check if the "self" parameter provides the generic environment
134+
return false;
135+
}
136+
127137
if (calleeTy->getRepresentation() != SILFunctionTypeRepresentation::Method) {
128138
return false;
129139
}
@@ -136,10 +146,24 @@ static bool isSimplePartialApply(PartialApplyInst *i) {
136146
auto argTy = i->getArguments()[0]->getType();
137147

138148
if (i->getFunctionType()->isNoEscape()) {
139-
if (argTy.isAddress()) {
149+
switch (calleeTy->getSelfParameter().getConvention()) {
150+
case ParameterConvention::Indirect_Inout:
151+
case ParameterConvention::Indirect_In_Constant:
152+
case ParameterConvention::Indirect_In_Guaranteed:
153+
case ParameterConvention::Indirect_InoutAliasable:
154+
// Indirect arguments are trivially word sized.
140155
return true;
156+
157+
case ParameterConvention::Direct_Guaranteed:
158+
case ParameterConvention::Direct_Unowned:
159+
// TODO: Handle word-sized direct arguments.
160+
return false;
161+
162+
// +1 arguments need a thunk to stage a copy for the callee to consume.
163+
case ParameterConvention::Direct_Owned:
164+
case ParameterConvention::Indirect_In:
165+
return false;
141166
}
142-
return false;
143167
} else {
144168
if (!argTy.isObject()) {
145169
return false;
@@ -175,8 +199,14 @@ void PartialApplySimplificationPass::scanFunction(SILFunction *f,
175199
continue;
176200
}
177201

202+
// Collect full apply sites for potential transformation as well.
203+
if (auto fa = FullApplySite::isa(frUse->getUser())) {
204+
knownCallee.FullApplications.insert(fa);
205+
continue;
206+
}
207+
178208
// Record if the function has uses that aren't partial applies.
179-
knownCallee.NonPartialApplyUse = frUse->getUser();
209+
knownCallee.NonApplyUse = frUse->getUser();
180210
}
181211
}
182212

@@ -207,10 +237,10 @@ void PartialApplySimplificationPass::processKnownCallee(SILFunction *callee,
207237

208238
// If the subject of the partial application has other uses that aren't
209239
// partial applications, then thunk it.
210-
if (pa.NonPartialApplyUse) {
211-
LLVM_DEBUG(llvm::dbgs() << "Callee has non-partial_apply uses; thunking\n";
212-
pa.NonPartialApplyUse->print(llvm::dbgs()));
213-
++NumPartialApplyCalleesWithNonPartialApplyUses;
240+
if (pa.NonApplyUse) {
241+
LLVM_DEBUG(llvm::dbgs() << "Callee has non-apply uses; thunking\n";
242+
pa.NonApplyUse->print(llvm::dbgs()));
243+
++NumPartialApplyCalleesWithNonApplyUses;
214244
goto create_forwarding_thunks;
215245
}
216246

@@ -265,6 +295,22 @@ void PartialApplySimplificationPass::processKnownCallee(SILFunction *callee,
265295
LLVM_DEBUG(llvm::dbgs() << "And they're already simple, don't need to do anything!\n");
266296
return;
267297
}
298+
299+
// TODO: Check if the partial_apply would become simple if we only change
300+
// the callee type to convention(method).
301+
302+
// If the partial applications form escaping closures, and there are also
303+
// full application sites, then we don't want to burden those full
304+
// application sites with having to allocate a box for the captured arguments.
305+
// Emit a thunk for the partial application sites.
306+
//
307+
// TODO: Evaluate if stack-allocating the escapable box is acceptable.
308+
if (!examplePA->isOnStack() && !pa.FullApplications.empty()) {
309+
LLVM_DEBUG(llvm::dbgs() << "Callee has mix of escaping partial_apply and full application sites; thunking:\n";
310+
pa.FullApplications.front().getInstruction()->print(llvm::dbgs()));
311+
++NumPartialApplyCalleesWithEscapingAndApplyUses;
312+
goto create_forwarding_thunks;
313+
}
268314

269315
// Rewrite the function type to take the captures in box form.
270316
auto origTy = callee->getLoweredFunctionType();
@@ -529,28 +575,44 @@ void PartialApplySimplificationPass::processKnownCallee(SILFunction *callee,
529575
}
530576

531577
// Rewrite partial applications to partially apply the new clone.
532-
for (auto pa : pa.PartialApplications) {
533-
auto caller = pa->getFunction();
578+
auto rewriteApplySite = [&](ApplySite site) {
579+
auto caller = site->getFunction();
534580
SILBuilder B(*caller);
535-
auto loc = pa->getLoc();
536-
B.setInsertionPoint(pa);
581+
auto loc = site->getLoc();
582+
B.setInsertionPoint(site.getInstruction());
537583

538584
auto newFunctionRef = B.createFunctionRef(loc, callee);
539585
SILValue contextBuffer, contextProj;
540586
auto contextStorageTy = SILType::getPrimitiveAddressType(contextTy)
541-
.subst(getModule()->Types, pa->getSubstitutionMap());
587+
.subst(getModule()->Types, site.getSubstitutionMap());
542588
if (isNoEscape) {
543589
auto contextAlloc = B.createAllocStack(loc, contextStorageTy);
544590
contextBuffer = contextProj = contextAlloc;
545591

546-
// We'll need to deallocate the context buffer after the end of the
547-
// original partial_apply's lifetime.
548-
auto deallocStackUses = pa->getUsersOfType<DeallocStackInst>();
549-
assert(deallocStackUses.begin() != deallocStackUses.end());
550-
for (auto use : deallocStackUses) {
551-
B.setInsertionPoint(use->getNextInstruction());
592+
// We'll need to deallocate the context buffer after we don't need it.
593+
// For a partial_apply, that's after the partial_apply itself is
594+
// deallocated.
595+
if (auto ppa = dyn_cast<PartialApplyInst>(site.getInstruction())) {
596+
auto deallocStackUses = ppa->getUsersOfType<DeallocStackInst>();
597+
assert(deallocStackUses.begin() != deallocStackUses.end());
598+
for (auto use : deallocStackUses) {
599+
B.setInsertionPoint(use->getNextInstruction());
600+
B.createDeallocStack(loc, contextBuffer);
601+
}
602+
// For a full application, we're done immediately after the call.
603+
// If the apply site is a terminator, dealloc in all the successor
604+
// blocks.
605+
} else if (auto term = dyn_cast<TermInst>(site.getInstruction())) {
606+
for (auto successor : term->getSuccessorBlocks()) {
607+
B.setInsertionPoint(successor->begin());
608+
B.createDeallocStack(loc, contextBuffer);
609+
}
610+
// If the apply site is a normal instruction, dealloc after it.
611+
} else {
612+
B.setInsertionPoint(site.getInstruction()->getNextInstruction());
552613
B.createDeallocStack(loc, contextBuffer);
553614
}
615+
// Continue emitting code to populate the context.
554616
B.setInsertionPoint(contextAlloc->getNextInstruction());
555617
} else {
556618
contextBuffer = B.createAllocBox(loc,
@@ -562,9 +624,15 @@ void PartialApplySimplificationPass::processKnownCallee(SILFunction *callee,
562624
}
563625

564626
// Transfer the formerly partially-applied arguments into the box.
565-
auto appliedArgs = pa->getArguments();
566-
for (unsigned i = 0; i < appliedArgs.size(); ++i) {
567-
auto arg = appliedArgs[i];
627+
SmallVector<SILValue, 4> newArgs;
628+
// Carry over non-partial-applied arguments, if any.
629+
auto appliedArgs = site.getArguments();
630+
auto paArgsOffset = appliedArgs.size() - boxFields.size();
631+
for (unsigned i = 0; i < paArgsOffset; ++i) {
632+
newArgs.push_back(appliedArgs[i]);
633+
}
634+
for (unsigned i = 0; i < boxFields.size(); ++i) {
635+
auto arg = appliedArgs[i + paArgsOffset];
568636
SILValue proj = contextProj;
569637
if (boxFields.size() > 1) {
570638
proj = B.createTupleElementAddr(loc, proj, i);
@@ -604,23 +672,58 @@ void PartialApplySimplificationPass::processKnownCallee(SILFunction *callee,
604672
}
605673
}
606674

607-
// Partially apply the new box to create the closure.
608-
auto paConvention = isNoEscape ? ParameterConvention::Direct_Guaranteed
609-
: contextParam.getConvention();
610-
auto paOnStack = isNoEscape ? PartialApplyInst::OnStack
611-
: PartialApplyInst::NotOnStack;
612-
auto newPA = B.createPartialApply(loc, newFunctionRef,
613-
pa->getSubstitutionMap(),
614-
contextBuffer,
615-
paConvention,
616-
paOnStack);
617-
assert(isSimplePartialApply(newPA)
618-
&& "partial apply wasn't simple after transformation?");
619-
pa->replaceAllUsesWith(newPA);
620-
pa->eraseFromParent();
675+
// Transform the application to use the context instead of the original
676+
// arguments.
677+
newArgs.push_back(contextBuffer);
678+
SILInstruction *newInst;
679+
switch (site.getKind()) {
680+
case ApplySiteKind::PartialApplyInst: {
681+
auto paConvention = isNoEscape ? ParameterConvention::Direct_Guaranteed
682+
: contextParam.getConvention();
683+
auto paOnStack = isNoEscape ? PartialApplyInst::OnStack
684+
: PartialApplyInst::NotOnStack;
685+
auto newPA = B.createPartialApply(loc, newFunctionRef,
686+
site.getSubstitutionMap(),
687+
newArgs,
688+
paConvention,
689+
paOnStack);
690+
assert(isSimplePartialApply(newPA)
691+
&& "partial apply wasn't simple after transformation?");
692+
newInst = newPA;
693+
break;
694+
}
695+
case ApplySiteKind::ApplyInst:
696+
newInst = B.createApply(loc, newFunctionRef,
697+
site.getSubstitutionMap(), newArgs);
698+
break;
699+
case ApplySiteKind::BeginApplyInst:
700+
newInst = B.createBeginApply(loc, newFunctionRef,
701+
site.getSubstitutionMap(), newArgs);
702+
break;
703+
case ApplySiteKind::TryApplyInst: {
704+
auto tai = cast<TryApplyInst>(site.getInstruction());
705+
newInst = B.createTryApply(loc, newFunctionRef,
706+
site.getSubstitutionMap(), newArgs,
707+
tai->getNormalBB(),
708+
tai->getErrorBB());
709+
break;
710+
}
711+
}
712+
site.getInstruction()->replaceAllUsesPairwiseWith(newInst);
713+
site.getInstruction()->eraseFromParent();
714+
};
715+
716+
for (auto paSite : pa.PartialApplications) {
717+
rewriteApplySite(paSite);
718+
}
719+
720+
// Rewrite full application sites to package up the partially applied
721+
// arguments as well.
722+
for (auto fa : pa.FullApplications) {
723+
rewriteApplySite(fa);
621724
}
622725

623-
// Once all the partial applications have been rewritten, then the original
726+
// Once all the applications have been rewritten, then the original
624727
// function refs with the old function type should all be unused. Delete
625728
// them, since they are no longer valid.
626729
for (auto fr : pa.FunctionRefs) {

test/SILOptimizer/partial_apply_simplification.sil

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,19 @@ entry(%x : $Int, %y : $Int, %z : $Int):
173173
dealloc_stack %c : $@noescape @callee_guaranteed (Int) -> Int
174174
return %r : $Int
175175
}
176+
177+
// CHECK-LABEL: sil @closure_nonescaping_full_apply
178+
// CHECK: bb0([[X:%.*]] : $Int, [[Y:%.*]] : $Int, [[Z:%.*]] : $Int):
179+
// CHECK: [[F:%.*]] = function_ref @closure_nonescaping
180+
// CHECK: [[CONTEXT:%.*]] = alloc_stack $(Int, Int)
181+
// CHECK: [[CONTEXT_Y:%.*]] = tuple_element_addr [[CONTEXT]] {{.*}}, 0
182+
// CHECK: store [[Y]] to [[CONTEXT_Y]]
183+
// CHECK: [[CONTEXT_Z:%.*]] = tuple_element_addr [[CONTEXT]] {{.*}}, 1
184+
// CHECK: store [[Z]] to [[CONTEXT_Z]]
185+
// CHECK: [[CLOSURE:%.*]] = apply [[F]]([[X]], [[CONTEXT]])
186+
sil @closure_nonescaping_full_apply : $@convention(thin) (Int, Int, Int) -> Int {
187+
entry(%x : $Int, %y : $Int, %z : $Int):
188+
%f = function_ref @closure_nonescaping : $@convention(thin) (Int, Int, Int) -> Int
189+
%r = apply %f(%x, %y, %z) : $@convention(thin) (Int, Int, Int) -> Int
190+
return %r : $Int
191+
}

0 commit comments

Comments
 (0)