mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
893 lines
36 KiB
C++
893 lines
36 KiB
C++
//===--- PartialApplySimplification.cpp - Lower partial applications ------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
///
|
|
/// Reduces all partial application functions into explicit closure
|
|
/// constructions.
|
|
///
|
|
/// \c partial_apply is a useful high-level representation for optimization
|
|
/// passes like inlining, but it abstracts over many details of how closures
|
|
/// are constructed. In order to make IRGen lowering simpler, and provide some
|
|
/// opportunity for other passes to optimize closure construction.
|
|
///
|
|
/// When a closure implementation function is private, and is only referenced by
|
|
/// partial applications all of the same shape, then we can replace the function
|
|
/// with one that takes a closure box instead of the partially applied
|
|
/// arguments. Otherwise, a partial application forwarder function is generated
|
|
/// as a shim between the closure entry point, which takes the box, and the
|
|
/// original function, which takes the loaded arguments.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-partial-apply-simplification"
|
|
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/SIL/SILCloner.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/TypeSubstCloner.h"
|
|
#include "swift/SILOptimizer/PassManager/Passes.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
|
|
#include "swift/SILOptimizer/Utils/SpecializationMangler.h"
|
|
|
|
STATISTIC(NumInvocationFunctionsChanged,
|
|
"Number of invocation functions rewritten");
|
|
STATISTIC(NumUnsupportedChangesToInvocationFunctions,
|
|
"Number of invocation functions that could be rewritten, but aren't yet");
|
|
STATISTIC(NumPartialApplyCalleesWithNonApplyUses,
|
|
"Number of invocation functions with non-apply uses");
|
|
STATISTIC(NumPartialApplyCalleesWithEscapingAndApplyUses,
|
|
"Number of invocation functions with both escaping and full apply uses");
|
|
STATISTIC(NumPartialApplyCalleesPossiblyUsedExternally,
|
|
"Number of invocation functions possibly used externally");
|
|
STATISTIC(NumPartialApplyCalleesDeclarationOnly,
|
|
"Number of invocation functions that are declaration-only");
|
|
STATISTIC(NumPartialApplyCalleesWithMismatchedPartialApplies,
|
|
"Number of invocation functions that have mismatched partial_apply sites");
|
|
STATISTIC(NumDynamicPartialApplicationForwarders,
|
|
"Number of dynamic partial application forwarder thunks generated");
|
|
|
|
using namespace swift;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Level Entrypoint
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct KnownCallee {
|
|
/// The set of function_refs to the callee.
|
|
llvm::SetVector<FunctionRefInst *> FunctionRefs;
|
|
/// The set of partial application sites.
|
|
llvm::SetVector<PartialApplyInst *> PartialApplications;
|
|
/// The set of full application sites.
|
|
llvm::SetVector<FullApplySite> FullApplications;
|
|
/// If the callee has a non-partial-apply, non-apply use, this points to an
|
|
/// arbitrary one, for logging purposes.
|
|
SILInstruction *NonApplyUse = nullptr;
|
|
};
|
|
|
|
class PartialApplySimplificationPass : public SILModuleTransform {
|
|
/// The entry point to the transformation.
|
|
void run() override {
|
|
// Scan all partial applications in the module so we know what to work with.
|
|
llvm::DenseMap<SILFunction *, KnownCallee> knownCallees;
|
|
llvm::SetVector<swift::PartialApplyInst *> dynamicCallees;
|
|
for (auto &f : *getModule()) {
|
|
scanFunction(&f, knownCallees, dynamicCallees);
|
|
}
|
|
|
|
for (auto &knownCallee : knownCallees) {
|
|
processKnownCallee(knownCallee.first, knownCallee.second);
|
|
}
|
|
|
|
for (auto *dynamicPA : dynamicCallees) {
|
|
processDynamicCallee(dynamicPA);
|
|
}
|
|
}
|
|
|
|
void scanFunction(SILFunction *f,
|
|
llvm::DenseMap<SILFunction *,
|
|
KnownCallee> &knownCallees,
|
|
llvm::SetVector<PartialApplyInst *> &dynamicCallees);
|
|
|
|
void processKnownCallee(SILFunction *callee,
|
|
const KnownCallee &pa);
|
|
|
|
void processDynamicCallee(PartialApplyInst *pa);
|
|
|
|
void generateForwardingThunksForKnownCallee();
|
|
void rewriteKnownCalleeConventionOnly(SILFunction *callee,
|
|
const KnownCallee &pa,
|
|
PartialApplyInst *examplePA,
|
|
CanSILFunctionType newCalleeTy);
|
|
void rewriteKnownCalleeWithExplicitContext(SILFunction *callee,
|
|
const KnownCallee &pa,
|
|
PartialApplyInst *examplePA);
|
|
};
|
|
|
|
}
|
|
|
|
/// True if the partial application is in a form that can be trivially
|
|
/// lowered.
|
|
///
|
|
/// This is true if:
|
|
/// - the callee has convention(method)
|
|
/// - one argument is applied
|
|
/// - the callee is either not generic, or can read its generic environment
|
|
/// out of the single applied argument
|
|
/// - if the partial application is noescape:
|
|
/// - the argument is word-sized or smaller
|
|
/// - the argument is either trivial, or passed with a net +0 convention
|
|
/// (guaranteed, unowned, in_guaranteed, inout)
|
|
/// - if the partial application is escapable:
|
|
/// - the argument is either a single Swift-refcounted word, or trivial and
|
|
/// sized strictly less than one word
|
|
/// - the argument ownership convention matches the callee convention of the
|
|
/// resulting function
|
|
static bool isSimplePartialApply(SILModule &M,
|
|
CanSILFunctionType calleeTy,
|
|
TypeExpansionContext context,
|
|
ParameterConvention calleeConvention,
|
|
unsigned numPartiallyAppliedArgs,
|
|
bool isOnStack) {
|
|
if (calleeTy->isPolymorphic()) {
|
|
// TODO: Check if the "self" parameter provides the generic environment
|
|
return false;
|
|
}
|
|
|
|
if (calleeTy->getRepresentation() != SILFunctionTypeRepresentation::Method) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: could discount empty captured values here
|
|
if (numPartiallyAppliedArgs != 1) {
|
|
return false;
|
|
}
|
|
|
|
auto contextParam = calleeTy->getSelfParameter();
|
|
|
|
auto argTy = contextParam.getArgumentType(M, calleeTy, context);
|
|
if (isOnStack) {
|
|
switch (contextParam.getConvention()) {
|
|
case ParameterConvention::Indirect_Inout:
|
|
case ParameterConvention::Indirect_In_Guaranteed:
|
|
case ParameterConvention::Indirect_InoutAliasable:
|
|
case ParameterConvention::Pack_Inout:
|
|
case ParameterConvention::Pack_Guaranteed:
|
|
case ParameterConvention::Pack_Owned:
|
|
// Indirect and pack arguments are trivially word sized.
|
|
return true;
|
|
|
|
case ParameterConvention::Direct_Guaranteed:
|
|
case ParameterConvention::Direct_Unowned:
|
|
return SILType::getPrimitiveObjectType(argTy)
|
|
.isPointerSizeAndAligned(M, context.getResilienceExpansion());
|
|
// TODO: If we're running as an IRGen pass, use IRGen's version of
|
|
// `isPointerSizeAndAligned` as a more accurate check.
|
|
|
|
// +1 arguments need a thunk to stage a copy for the callee to consume.
|
|
case ParameterConvention::Direct_Owned:
|
|
case ParameterConvention::Indirect_In_CXX:
|
|
case ParameterConvention::Indirect_In:
|
|
return false;
|
|
}
|
|
} else {
|
|
if (contextParam.isFormalIndirect()) {
|
|
return false;
|
|
}
|
|
|
|
// The context parameter's convention must match the callee convention of
|
|
// the resulting closure.
|
|
if (contextParam.getConvention() != calleeConvention) {
|
|
return false;
|
|
}
|
|
|
|
// The context type must consist of only a swift-refcounted object
|
|
// reference.
|
|
return SILType::getPrimitiveObjectType(argTy)
|
|
.isSingleSwiftRefcounted(M, context.getResilienceExpansion());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool isSimplePartialApply(PartialApplyInst *i) {
|
|
return isSimplePartialApply(i->getModule(),
|
|
i->getCallee()->getType().castTo<SILFunctionType>(),
|
|
i->getFunction()->getTypeExpansionContext(),
|
|
i->getFunctionType()->getCalleeConvention(),
|
|
i->getNumArguments(),
|
|
i->isOnStack());
|
|
}
|
|
|
|
void PartialApplySimplificationPass::scanFunction(SILFunction *f,
|
|
llvm::DenseMap<SILFunction *,
|
|
KnownCallee> &knownCallees,
|
|
llvm::SetVector<PartialApplyInst *> &dynamicCallees) {
|
|
// Consider all partial_apply instructions.
|
|
for (auto &block : *f) {
|
|
for (auto &inst : block) {
|
|
// Examine the uses of static function refs.
|
|
if (auto *fr = dyn_cast<FunctionRefInst>(&inst)) {
|
|
auto &knownCallee = knownCallees[fr->getReferencedFunction()];
|
|
knownCallee.FunctionRefs.insert(fr);
|
|
|
|
for (auto *frUse : fr->getUses()) {
|
|
// Collect partial applications for further transformation.
|
|
if (auto pa = dyn_cast<PartialApplyInst>(frUse->getUser())) {
|
|
knownCallee.PartialApplications.insert(pa);
|
|
continue;
|
|
}
|
|
|
|
// Collect full apply sites for potential transformation as well.
|
|
if (auto fa = FullApplySite::isa(frUse->getUser())) {
|
|
knownCallee.FullApplications.insert(fa);
|
|
continue;
|
|
}
|
|
|
|
// Record if the function has uses that aren't partial applies.
|
|
knownCallee.NonApplyUse = frUse->getUser();
|
|
}
|
|
}
|
|
|
|
if (auto *pa = dyn_cast<PartialApplyInst>(&inst)) {
|
|
// Static callees get handled when we see the function_ref.
|
|
if (isa<FunctionRefInst>(pa->getCallee())) {
|
|
continue;
|
|
}
|
|
|
|
// If the callee isn't static, then we'll need to create a dynamic
|
|
// forwarder thunk to simplify this partial application.
|
|
dynamicCallees.insert(pa);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PartialApplySimplificationPass::processKnownCallee(SILFunction *callee,
|
|
const KnownCallee &pa) {
|
|
// Skip functions with no partial application uses.
|
|
if (pa.PartialApplications.empty())
|
|
return;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "***** Processing known partial_apply callee "
|
|
<< callee->getName() << " *****\n");
|
|
|
|
// If the subject of the partial application has other uses that aren't
|
|
// partial applications, then thunk it.
|
|
if (pa.NonApplyUse) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Callee has non-apply uses; thunking\n";
|
|
pa.NonApplyUse->print(llvm::dbgs()));
|
|
++NumPartialApplyCalleesWithNonApplyUses;
|
|
return generateForwardingThunksForKnownCallee();
|
|
}
|
|
|
|
// If the subject of the partial application might have external references,
|
|
// or is itself an external reference, we can't change the existing function
|
|
// signature. We'll always use forwarding thunks in this case.
|
|
if (callee->isPossiblyUsedExternally()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Callee is possibly used externally; thunking\n");
|
|
++NumPartialApplyCalleesPossiblyUsedExternally;
|
|
return generateForwardingThunksForKnownCallee();
|
|
}
|
|
if (callee->empty()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Callee is a declaration only; thunking\n");
|
|
++NumPartialApplyCalleesDeclarationOnly;
|
|
return generateForwardingThunksForKnownCallee();
|
|
}
|
|
|
|
// Look at the set of all partial applications on this callee to figure
|
|
// out what to do.
|
|
// If all of the partial applications are identical (same number of arguments,
|
|
// same convention, same escapiness, etc.), then we'll alter the invocation
|
|
// function directly (or leave it alone, if the partial apply is simple
|
|
// enough already.)
|
|
|
|
// Take an arbitrary partial application as an example to compare the others.
|
|
auto examplePA = pa.PartialApplications.front();
|
|
for (auto i = pa.PartialApplications.begin() + 1,
|
|
e = pa.PartialApplications.end();
|
|
i != e;
|
|
++i) {
|
|
auto thisPA = *i;
|
|
if (examplePA->getNumArguments() != thisPA->getNumArguments()
|
|
|| examplePA->getFunctionType()->getCalleeConvention()
|
|
!= thisPA->getFunctionType()->getCalleeConvention()
|
|
|| !examplePA->getFunctionType()->getExtInfo()
|
|
.isEqualTo(thisPA->getFunctionType()->getExtInfo(), true)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Mismatched partial application arguments; thunking:\n";
|
|
thisPA->print(llvm::dbgs());
|
|
examplePA->print(llvm::dbgs()));
|
|
++NumPartialApplyCalleesWithMismatchedPartialApplies;
|
|
return generateForwardingThunksForKnownCallee();
|
|
}
|
|
}
|
|
|
|
// OK, all the partial applications look the same.
|
|
LLVM_DEBUG(llvm::dbgs() << "All partial applications look like this:\n";
|
|
examplePA->print(llvm::dbgs()));
|
|
|
|
// If they're simple already, then we don't need to do anything.
|
|
if (isSimplePartialApply(examplePA)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "And they're already simple, don't need to do anything!\n");
|
|
return;
|
|
}
|
|
|
|
// Would the partial application become simple with a mere convention change?
|
|
auto calleeTyAsMethod = callee->getLoweredFunctionType()
|
|
->getWithRepresentation(SILFunctionTypeRepresentation::Method);
|
|
if (isSimplePartialApply(callee->getModule(),
|
|
calleeTyAsMethod,
|
|
examplePA->getFunction()->getTypeExpansionContext(),
|
|
examplePA->getFunctionType()->getCalleeConvention(),
|
|
examplePA->getNumArguments(),
|
|
examplePA->isOnStack())) {
|
|
return rewriteKnownCalleeConventionOnly(callee, pa, examplePA,
|
|
calleeTyAsMethod);
|
|
}
|
|
// TODO: We could also look at whether a ownership convention change on the
|
|
// argument(s) might make it into a simple partial_apply.
|
|
|
|
// If the partial applications form escaping closures, and there are also
|
|
// full application sites, then we don't want to burden those full
|
|
// application sites with having to allocate a box for the captured arguments.
|
|
// Emit a thunk for the partial application sites.
|
|
//
|
|
// TODO: Evaluate if stack-allocating the escapable box is acceptable.
|
|
if (!examplePA->isOnStack() && !pa.FullApplications.empty()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Callee has mix of escaping partial_apply and full application sites; thunking:\n";
|
|
pa.FullApplications.front().getInstruction()->print(llvm::dbgs()));
|
|
++NumPartialApplyCalleesWithEscapingAndApplyUses;
|
|
return generateForwardingThunksForKnownCallee();
|
|
}
|
|
|
|
// Rewrite the function type to take the captures in box form.
|
|
rewriteKnownCalleeWithExplicitContext(callee, pa, examplePA);
|
|
|
|
}
|
|
|
|
void PartialApplySimplificationPass::processDynamicCallee(PartialApplyInst *pa){
|
|
// TODO
|
|
++NumDynamicPartialApplicationForwarders;
|
|
}
|
|
|
|
void PartialApplySimplificationPass::generateForwardingThunksForKnownCallee() {
|
|
LLVM_DEBUG(llvm::dbgs() << "TODO: create forwarding thunk here\n");
|
|
return;
|
|
}
|
|
|
|
void PartialApplySimplificationPass::
|
|
rewriteKnownCalleeConventionOnly(SILFunction *callee,
|
|
const KnownCallee &pa,
|
|
PartialApplyInst *examplePA,
|
|
CanSILFunctionType newCalleeTy) {
|
|
// Rewrite the type of the invocation function.
|
|
callee->rewriteLoweredTypeUnsafe(newCalleeTy);
|
|
|
|
// Rewrite the apply sites using the new function type.
|
|
auto rewriteApplySite = [&](ApplySite site) {
|
|
SILBuilder B(*site.getFunction());
|
|
B.setInsertionPoint(site.getInstruction());
|
|
auto loc = site.getLoc();
|
|
auto fr = B.createFunctionRef(loc, callee);
|
|
SILInstruction *newInst;
|
|
|
|
SmallVector<SILValue, 4> args;
|
|
args.append(site.getArguments().begin(),
|
|
site.getArguments().end());
|
|
|
|
switch (site.getKind()) {
|
|
case ApplySiteKind::PartialApplyInst: {
|
|
auto pa = cast<PartialApplyInst>(site.getInstruction());
|
|
newInst = B.createPartialApply(loc, fr, site.getSubstitutionMap(), args,
|
|
pa->getCalleeConvention(),
|
|
pa->getResultIsolation(),
|
|
pa->isOnStack());
|
|
break;
|
|
}
|
|
case ApplySiteKind::ApplyInst:
|
|
newInst = B.createApply(loc, fr, site.getSubstitutionMap(), args);
|
|
break;
|
|
case ApplySiteKind::BeginApplyInst:
|
|
newInst = B.createBeginApply(loc, fr, site.getSubstitutionMap(), args);
|
|
break;
|
|
case ApplySiteKind::TryApplyInst: {
|
|
auto tryApply = cast<TryApplyInst>(site.getInstruction());
|
|
newInst = B.createTryApply(loc, fr, site.getSubstitutionMap(), args,
|
|
tryApply->getNormalBB(),
|
|
tryApply->getErrorBB());
|
|
break;
|
|
}
|
|
}
|
|
site.getInstruction()->replaceAllUsesPairwiseWith(newInst);
|
|
site.getInstruction()->eraseFromParent();
|
|
};
|
|
|
|
for (auto paSite : pa.PartialApplications) {
|
|
rewriteApplySite(paSite);
|
|
}
|
|
for (auto faSite : pa.FullApplications) {
|
|
rewriteApplySite(faSite);
|
|
}
|
|
|
|
// Once all the applications have been rewritten, then the original
|
|
// function refs with the old function type should all be unused. Delete
|
|
// them, since they are no longer valid.
|
|
for (auto fr : pa.FunctionRefs) {
|
|
fr->eraseFromParent();
|
|
}
|
|
}
|
|
|
|
void PartialApplySimplificationPass::
|
|
rewriteKnownCalleeWithExplicitContext(SILFunction *callee,
|
|
const KnownCallee &pa,
|
|
PartialApplyInst *examplePA) {
|
|
auto &C = callee->getASTContext();
|
|
|
|
auto origTy = callee->getLoweredFunctionType();
|
|
auto paResultTy = cast<SILFunctionType>(examplePA->getType().getASTType());
|
|
// The box captures the generic context and the values of the arguments that
|
|
// were partially applied. The invocation function is modified to take
|
|
// a single partially-applied argument for the box, and unload the
|
|
// elements of the box inside the function.
|
|
SmallVector<SILField, 4> boxFields;
|
|
|
|
unsigned numUnapplied
|
|
= origTy->getParameters().size() - examplePA->getArguments().size();
|
|
auto partiallyAppliedParams = origTy->getParameters().slice(numUnapplied);
|
|
for (auto param : partiallyAppliedParams) {
|
|
switch (param.getConvention()) {
|
|
// Conventions where a copy of the argument is captured.
|
|
case ParameterConvention::Direct_Guaranteed:
|
|
case ParameterConvention::Direct_Owned:
|
|
case ParameterConvention::Direct_Unowned:
|
|
case ParameterConvention::Indirect_In:
|
|
case ParameterConvention::Indirect_In_Guaranteed:
|
|
case ParameterConvention::Indirect_In_CXX:
|
|
case ParameterConvention::Pack_Guaranteed:
|
|
case ParameterConvention::Pack_Owned:
|
|
boxFields.push_back(SILField(param.getInterfaceType(), /*mutable*/false));
|
|
break;
|
|
|
|
// Conventions where an address to the argument is captured.
|
|
case ParameterConvention::Indirect_Inout:
|
|
case ParameterConvention::Indirect_InoutAliasable:
|
|
case ParameterConvention::Pack_Inout:
|
|
// Put a RawPointer in the box, which we can turn back into an address
|
|
// in the function
|
|
boxFields.push_back(SILField(C.TheRawPointerType, /*mutable*/false));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The new signature carries over the unapplied arguments.
|
|
SmallVector<SILParameterInfo, 4> newParams;
|
|
for (unsigned i = 0; i < numUnapplied; ++i) {
|
|
newParams.push_back(origTy->getParameters()[i]);
|
|
}
|
|
|
|
// Instead of the applied arguments, we receive a box containing the
|
|
// values for those arguments. Work out what that box type is.
|
|
// TODO: We need a representation of boxes that
|
|
// capture the generic environment to represent partial applications in
|
|
// their full generality.
|
|
if (origTy->getInvocationGenericSignature()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "TODO: generic partial_apply not yet implemented\n");
|
|
++NumUnsupportedChangesToInvocationFunctions;
|
|
return;
|
|
}
|
|
|
|
// TODO: SILBoxType is only implemented for a single field right now, and we
|
|
// don't yet have a corresponding type for nonescaping captures, so
|
|
// represent the captures as a tuple for now.
|
|
CanType tupleTy;
|
|
|
|
if (boxFields.size() == 1) {
|
|
tupleTy = boxFields[0].getLoweredType();
|
|
} else {
|
|
llvm::SmallVector<TupleTypeElt, 4> tupleElts;
|
|
for (auto field : boxFields) {
|
|
tupleElts.push_back(TupleTypeElt(field.getLoweredType()));
|
|
}
|
|
tupleTy = TupleType::get(tupleElts, C)->getCanonicalType();
|
|
}
|
|
|
|
CanType contextTy;
|
|
SILParameterInfo contextParam;
|
|
|
|
bool isNoEscape = examplePA->getFunctionType()->isNoEscape();
|
|
if (isNoEscape) {
|
|
contextTy = tupleTy;
|
|
// Nonescaping closures borrow their context from the outer frame.
|
|
contextParam = SILParameterInfo(contextTy,
|
|
ParameterConvention::Indirect_In_Guaranteed);
|
|
} else {
|
|
SILField tupleField(tupleTy, /*mutable*/ false);
|
|
auto newBoxLayout = SILLayout::get(C,
|
|
origTy->getInvocationGenericSignature(),
|
|
tupleField,
|
|
/*capturesGenerics*/ false);
|
|
SubstitutionMap identitySubstitutionMap;
|
|
if (auto origSig = origTy->getInvocationGenericSignature()) {
|
|
identitySubstitutionMap = origSig->getIdentitySubstitutionMap();
|
|
}
|
|
contextTy = SILBoxType::get(C, newBoxLayout, identitySubstitutionMap);
|
|
contextParam = SILParameterInfo(contextTy,
|
|
paResultTy->getCalleeConvention());
|
|
}
|
|
|
|
newParams.push_back(contextParam);
|
|
|
|
auto newExtInfo = origTy->getExtInfo()
|
|
.withRepresentation(SILFunctionTypeRepresentation::Method);
|
|
|
|
auto newTy = SILFunctionType::get(origTy->getInvocationGenericSignature(),
|
|
newExtInfo, origTy->getCoroutineKind(),
|
|
origTy->getCalleeConvention(),
|
|
newParams,
|
|
origTy->getYields(),
|
|
origTy->getResults(),
|
|
origTy->getOptionalErrorResult(),
|
|
origTy->getPatternSubstitutions(),
|
|
origTy->getInvocationSubstitutions(),
|
|
C);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Changing invocation function signature to\n";
|
|
newTy->print(llvm::dbgs());
|
|
llvm::dbgs() << '\n');
|
|
|
|
// Change the invocation function to use the new type, and unbox the
|
|
// captures in its entry block.
|
|
callee->rewriteLoweredTypeUnsafe(newTy);
|
|
|
|
// Update the entry block.
|
|
{
|
|
SILBuilder B(*callee);
|
|
auto &entry = *callee->begin();
|
|
|
|
// Insert an argument for the context before the originally applied args.
|
|
auto contextArgTy = callee->mapTypeIntoContext(
|
|
SILType::getPrimitiveObjectType(contextTy));
|
|
if (isIndirectFormalParameter(contextParam.getConvention())) {
|
|
contextArgTy = contextArgTy.getAddressType();
|
|
}
|
|
|
|
ValueOwnershipKind contextOwnership(*callee, contextArgTy,
|
|
SILArgumentConvention(contextParam.getConvention()));
|
|
|
|
auto numUnappliedArgs = numUnapplied + origTy->getNumIndirectFormalResults();
|
|
|
|
auto contextArg = entry.insertFunctionArgument(numUnappliedArgs,
|
|
contextArgTy,
|
|
contextOwnership);
|
|
auto appliedBBArgs = entry.getArguments().slice(numUnappliedArgs + 1);
|
|
|
|
// Replace the original arguments applied by the partial_apply, by
|
|
// projections out of the box.
|
|
SmallVector<AllocStackInst *, 4> AddedStackAllocs;
|
|
B.setInsertionPoint(&entry, entry.begin());
|
|
auto loc = examplePA->getLoc();
|
|
for (unsigned i = 0; i < appliedBBArgs.size(); ++i) {
|
|
auto appliedArg = appliedBBArgs[i];
|
|
auto param = partiallyAppliedParams[i];
|
|
|
|
SILValue proj;
|
|
if (isNoEscape) {
|
|
proj = contextArg;
|
|
} else {
|
|
proj = B.createProjectBox(loc, contextArg, 0);
|
|
}
|
|
if (boxFields.size() > 1) {
|
|
proj = B.createTupleElementAddr(loc, proj, i);
|
|
}
|
|
// Load the value out of the context according to the current ownership
|
|
// mode of the function and the calling convention for the parameter.
|
|
SILValue projectedArg;
|
|
if (callee->hasOwnership()) {
|
|
switch (auto conv = param.getConvention()) {
|
|
case ParameterConvention::Direct_Unowned:
|
|
// Load an unowned image of the value from the box.
|
|
projectedArg = B.createLoadUnowned(loc, proj, IsNotTake);
|
|
break;
|
|
case ParameterConvention::Direct_Guaranteed:
|
|
// Load a borrow of the value from the box.
|
|
projectedArg = B.createLoadBorrow(loc, proj);
|
|
break;
|
|
case ParameterConvention::Direct_Owned:
|
|
// Load a copy of the value from the box.
|
|
projectedArg = B.createLoad(loc, proj, LoadOwnershipQualifier::Copy);
|
|
break;
|
|
case ParameterConvention::Indirect_In_CXX:
|
|
case ParameterConvention::Indirect_In: {
|
|
// Allocate space for a copy of the value that can be consumed by the
|
|
// function body. We'll need to deallocate the stack slot after the
|
|
// cloned body.
|
|
auto copySlot = B.createAllocStack(loc,
|
|
proj->getType().getAddressType());
|
|
AddedStackAllocs.push_back(copySlot);
|
|
B.createCopyAddr(loc, proj, copySlot, IsNotTake, IsInitialization);
|
|
projectedArg = copySlot;
|
|
break;
|
|
}
|
|
case ParameterConvention::Indirect_In_Guaranteed:
|
|
// We can borrow the value in-place in the box.
|
|
projectedArg = proj;
|
|
break;
|
|
case ParameterConvention::Indirect_Inout:
|
|
case ParameterConvention::Indirect_InoutAliasable: {
|
|
// The box capture is a RawPointer with the value of the capture
|
|
// address.
|
|
auto ptrVal = B.createLoad(loc, proj, LoadOwnershipQualifier::Trivial);
|
|
projectedArg = B.createPointerToAddress(loc, ptrVal,
|
|
appliedArg->getType(),
|
|
/*strict*/ conv == ParameterConvention::Indirect_Inout);
|
|
break;
|
|
}
|
|
case ParameterConvention::Pack_Guaranteed:
|
|
case ParameterConvention::Pack_Owned:
|
|
case ParameterConvention::Pack_Inout:
|
|
llvm_unreachable("unsupported!");
|
|
break;
|
|
}
|
|
} else {
|
|
switch (auto conv = param.getConvention()) {
|
|
case ParameterConvention::Direct_Unowned:
|
|
// Load an unowned image of the value from the box.
|
|
projectedArg = B.createLoad(loc, proj, LoadOwnershipQualifier::Unqualified);
|
|
break;
|
|
case ParameterConvention::Direct_Guaranteed:
|
|
// Load a borrow of the value from the box.
|
|
projectedArg = B.createLoad(loc, proj, LoadOwnershipQualifier::Unqualified);
|
|
break;
|
|
case ParameterConvention::Direct_Owned:
|
|
// Load a copy of the value from the box.
|
|
projectedArg = B.createLoad(loc, proj, LoadOwnershipQualifier::Unqualified);
|
|
B.createRetainValue(loc, projectedArg, Atomicity::Atomic);
|
|
break;
|
|
case ParameterConvention::Indirect_In_CXX:
|
|
case ParameterConvention::Indirect_In: {
|
|
// Allocate space for a copy of the value that can be consumed by the
|
|
// function body. We'll need to deallocate the stack slot after the
|
|
// cloned body.
|
|
auto copySlot = B.createAllocStack(loc,
|
|
proj->getType().getAddressType());
|
|
AddedStackAllocs.push_back(copySlot);
|
|
B.createCopyAddr(loc, proj, copySlot, IsNotTake, IsInitialization);
|
|
projectedArg = copySlot;
|
|
break;
|
|
}
|
|
case ParameterConvention::Indirect_In_Guaranteed:
|
|
// We can borrow the value in-place in the box.
|
|
projectedArg = proj;
|
|
break;
|
|
case ParameterConvention::Indirect_Inout:
|
|
case ParameterConvention::Indirect_InoutAliasable: {
|
|
// The box capture is a RawPointer with the value of the capture
|
|
// address.
|
|
auto ptrVal = B.createLoad(loc, proj, LoadOwnershipQualifier::Unqualified);
|
|
projectedArg = B.createPointerToAddress(loc, ptrVal,
|
|
appliedArg->getType(),
|
|
/*strict*/ conv == ParameterConvention::Indirect_Inout);
|
|
break;
|
|
}
|
|
case ParameterConvention::Pack_Guaranteed:
|
|
case ParameterConvention::Pack_Owned:
|
|
case ParameterConvention::Pack_Inout:
|
|
llvm_unreachable("unsupported!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Replace the original bb arg with the applied arg.
|
|
appliedArg->replaceAllUsesWith(projectedArg);
|
|
}
|
|
|
|
// If the box is callee-consumed, we can release it now.
|
|
if (contextParam.getConvention() == ParameterConvention::Direct_Owned) {
|
|
if (callee->hasOwnership()) {
|
|
B.createDestroyValue(loc, contextArg);
|
|
} else {
|
|
B.createStrongRelease(loc, contextArg, Atomicity::Atomic);
|
|
}
|
|
}
|
|
|
|
// Erase the original applied arguments.
|
|
for (unsigned i = 0; i < appliedBBArgs.size(); ++i) {
|
|
entry.eraseArgument(numUnappliedArgs + 1);
|
|
}
|
|
|
|
// If we needed to introduce any stack slots to consume copies of
|
|
// Indirect_In arguments, then balance them with deallocations on all
|
|
// function exits.
|
|
if (!AddedStackAllocs.empty()) {
|
|
llvm_unreachable("todo");
|
|
}
|
|
}
|
|
|
|
// Rewrite partial applications to partially apply the new clone.
|
|
auto rewriteApplySite = [&](ApplySite site) {
|
|
auto caller = site->getFunction();
|
|
SILBuilder B(*caller);
|
|
auto loc = site->getLoc();
|
|
B.setInsertionPoint(site.getInstruction());
|
|
|
|
auto newFunctionRef = B.createFunctionRef(loc, callee);
|
|
SILValue contextBuffer, contextProj;
|
|
auto contextStorageTy = SILType::getPrimitiveAddressType(contextTy)
|
|
.subst(getModule()->Types, site.getSubstitutionMap());
|
|
if (isNoEscape) {
|
|
auto contextAlloc = B.createAllocStack(loc, contextStorageTy);
|
|
contextBuffer = contextProj = contextAlloc;
|
|
|
|
// We'll need to deallocate the context buffer after we don't need it.
|
|
// For a partial_apply, that's after the partial_apply itself is
|
|
// deallocated.
|
|
if (auto ppa = dyn_cast<PartialApplyInst>(site.getInstruction())) {
|
|
auto deallocStackUses = ppa->getUsersOfType<DeallocStackInst>();
|
|
assert(deallocStackUses.begin() != deallocStackUses.end());
|
|
for (auto use : deallocStackUses) {
|
|
B.setInsertionPoint(use->getNextInstruction());
|
|
B.createDeallocStack(loc, contextBuffer);
|
|
}
|
|
// For a full application, we're done immediately after the call.
|
|
// If the apply site is a terminator, dealloc in all the successor
|
|
// blocks.
|
|
} else if (auto term = dyn_cast<TermInst>(site.getInstruction())) {
|
|
for (auto successor : term->getSuccessorBlocks()) {
|
|
B.setInsertionPoint(successor->begin());
|
|
B.createDeallocStack(loc, contextBuffer);
|
|
}
|
|
// If the apply site is a normal instruction, dealloc after it.
|
|
} else {
|
|
B.setInsertionPoint(site.getInstruction()->getNextInstruction());
|
|
B.createDeallocStack(loc, contextBuffer);
|
|
}
|
|
// Continue emitting code to populate the context.
|
|
B.setInsertionPoint(contextAlloc->getNextInstruction());
|
|
} else {
|
|
contextBuffer = B.createAllocBox(
|
|
loc, contextStorageTy.castTo<SILBoxType>(),
|
|
/*debug variable*/ std::nullopt, DoesNotHaveDynamicLifetime,
|
|
/*reflection*/ true);
|
|
contextProj = B.createProjectBox(loc, contextBuffer, 0);
|
|
}
|
|
|
|
// Transfer the formerly partially-applied arguments into the box.
|
|
SmallVector<SILValue, 4> newArgs;
|
|
// Carry over non-partial-applied arguments, if any.
|
|
auto appliedArgs = site.getArguments();
|
|
auto paArgsOffset = appliedArgs.size() - boxFields.size();
|
|
for (unsigned i = 0; i < paArgsOffset; ++i) {
|
|
newArgs.push_back(appliedArgs[i]);
|
|
}
|
|
for (unsigned i = 0; i < boxFields.size(); ++i) {
|
|
auto arg = appliedArgs[i + paArgsOffset];
|
|
SILValue proj = contextProj;
|
|
if (boxFields.size() > 1) {
|
|
proj = B.createTupleElementAddr(loc, proj, i);
|
|
}
|
|
auto param = partiallyAppliedParams[i];
|
|
|
|
switch (param.getConvention()) {
|
|
case ParameterConvention::Direct_Owned:
|
|
case ParameterConvention::Direct_Unowned:
|
|
case ParameterConvention::Direct_Guaranteed:
|
|
// Move the value into the box.
|
|
if (caller->hasOwnership()) {
|
|
B.createStore(loc, arg, proj, StoreOwnershipQualifier::Init);
|
|
} else {
|
|
B.createStore(loc, arg, proj, StoreOwnershipQualifier::Unqualified);
|
|
}
|
|
break;
|
|
|
|
case ParameterConvention::Indirect_In_Guaranteed:
|
|
case ParameterConvention::Indirect_In:
|
|
case ParameterConvention::Indirect_In_CXX:
|
|
// Move the value from its current memory location to the box.
|
|
B.createCopyAddr(loc, arg, proj, IsTake, IsInitialization);
|
|
break;
|
|
|
|
case ParameterConvention::Indirect_InoutAliasable:
|
|
case ParameterConvention::Indirect_Inout: {
|
|
// Pass a pointer to the argument into the box.
|
|
auto p = B.createAddressToPointer(loc, arg,
|
|
SILType::getRawPointerType(C),
|
|
/*needsStackProtection=*/ false);
|
|
if (caller->hasOwnership()) {
|
|
B.createStore(loc, p, proj, StoreOwnershipQualifier::Trivial);
|
|
} else {
|
|
B.createStore(loc, p, proj, StoreOwnershipQualifier::Unqualified);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ParameterConvention::Pack_Guaranteed:
|
|
case ParameterConvention::Pack_Owned:
|
|
case ParameterConvention::Pack_Inout:
|
|
llvm_unreachable("unsupported!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Transform the application to use the context instead of the original
|
|
// arguments.
|
|
newArgs.push_back(contextBuffer);
|
|
SILInstruction *newInst;
|
|
switch (site.getKind()) {
|
|
case ApplySiteKind::PartialApplyInst: {
|
|
auto oldPA = cast<PartialApplyInst>(site.getInstruction());
|
|
auto paIsolation = oldPA->getResultIsolation();
|
|
auto paConvention = isNoEscape ? ParameterConvention::Direct_Guaranteed
|
|
: contextParam.getConvention();
|
|
auto paOnStack = isNoEscape ? PartialApplyInst::OnStack
|
|
: PartialApplyInst::NotOnStack;
|
|
auto newPA = B.createPartialApply(loc, newFunctionRef,
|
|
site.getSubstitutionMap(),
|
|
newArgs,
|
|
paConvention,
|
|
paIsolation,
|
|
paOnStack);
|
|
assert(isSimplePartialApply(newPA)
|
|
&& "partial apply wasn't simple after transformation?");
|
|
newInst = newPA;
|
|
break;
|
|
}
|
|
case ApplySiteKind::ApplyInst:
|
|
newInst = B.createApply(loc, newFunctionRef,
|
|
site.getSubstitutionMap(), newArgs);
|
|
break;
|
|
case ApplySiteKind::BeginApplyInst:
|
|
newInst = B.createBeginApply(loc, newFunctionRef,
|
|
site.getSubstitutionMap(), newArgs);
|
|
break;
|
|
case ApplySiteKind::TryApplyInst: {
|
|
auto tai = cast<TryApplyInst>(site.getInstruction());
|
|
newInst = B.createTryApply(loc, newFunctionRef,
|
|
site.getSubstitutionMap(), newArgs,
|
|
tai->getNormalBB(),
|
|
tai->getErrorBB());
|
|
break;
|
|
}
|
|
}
|
|
site.getInstruction()->replaceAllUsesPairwiseWith(newInst);
|
|
site.getInstruction()->eraseFromParent();
|
|
};
|
|
|
|
for (auto paSite : pa.PartialApplications) {
|
|
rewriteApplySite(paSite);
|
|
}
|
|
|
|
// Rewrite full application sites to package up the partially applied
|
|
// arguments as well.
|
|
for (auto fa : pa.FullApplications) {
|
|
rewriteApplySite(fa);
|
|
}
|
|
|
|
// Once all the applications have been rewritten, then the original
|
|
// function refs with the old function type should all be unused. Delete
|
|
// them, since they are no longer valid.
|
|
for (auto fr : pa.FunctionRefs) {
|
|
fr->eraseFromParent();
|
|
}
|
|
|
|
++NumInvocationFunctionsChanged;
|
|
return;
|
|
}
|
|
|
|
SILTransform *swift::createPartialApplySimplification() {
|
|
return new PartialApplySimplificationPass();
|
|
}
|