mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
1220 lines
44 KiB
C++
1220 lines
44 KiB
C++
//===--- Devirtualize.cpp - Helper for devirtualizing apply ---------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-devirtualize-utility"
|
|
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
|
|
#include "swift/SILOptimizer/Utils/Devirtualize.h"
|
|
#include "swift/AST/Decl.h"
|
|
#include "swift/AST/GenericSignature.h"
|
|
#include "swift/AST/ProtocolConformance.h"
|
|
#include "swift/AST/SubstitutionMap.h"
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/SIL/OptimizationRemark.h"
|
|
#include "swift/SIL/SILDeclRef.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILModule.h"
|
|
#include "swift/SIL/SILType.h"
|
|
#include "swift/SIL/SILValue.h"
|
|
#include "swift/SIL/InstructionUtils.h"
|
|
#include "swift/SILOptimizer/Utils/Local.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/Support/Casting.h"
|
|
using namespace swift;
|
|
|
|
STATISTIC(NumClassDevirt, "Number of class_method applies devirtualized");
|
|
STATISTIC(NumWitnessDevirt, "Number of witness_method applies devirtualized");
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Class Method Optimization
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void swift::getAllSubclasses(ClassHierarchyAnalysis *CHA,
|
|
ClassDecl *CD,
|
|
CanType ClassType,
|
|
SILModule &M,
|
|
ClassHierarchyAnalysis::ClassList &Subs) {
|
|
// Collect the direct and indirect subclasses for the class.
|
|
// Sort these subclasses in the order they should be tested by the
|
|
// speculative devirtualization. Different strategies could be used,
|
|
// E.g. breadth-first, depth-first, etc.
|
|
// Currently, let's use the breadth-first strategy.
|
|
// The exact static type of the instance should be tested first.
|
|
auto &DirectSubs = CHA->getDirectSubClasses(CD);
|
|
auto &IndirectSubs = CHA->getIndirectSubClasses(CD);
|
|
|
|
Subs.append(DirectSubs.begin(), DirectSubs.end());
|
|
Subs.append(IndirectSubs.begin(), IndirectSubs.end());
|
|
|
|
// FIXME: This is wrong -- we could have a non-generic class nested
|
|
// inside a generic class
|
|
if (isa<BoundGenericClassType>(ClassType)) {
|
|
// Filter out any subclasses that do not inherit from this
|
|
// specific bound class.
|
|
auto RemovedIt = std::remove_if(Subs.begin(), Subs.end(),
|
|
[&ClassType](ClassDecl *Sub){
|
|
// FIXME: Add support for generic subclasses.
|
|
if (Sub->isGenericContext())
|
|
return false;
|
|
auto SubCanTy = Sub->getDeclaredInterfaceType()->getCanonicalType();
|
|
// Handle the usual case here: the class in question
|
|
// should be a real subclass of a bound generic class.
|
|
return !ClassType->isBindableToSuperclassOf(
|
|
SubCanTy);
|
|
});
|
|
Subs.erase(RemovedIt, Subs.end());
|
|
}
|
|
}
|
|
|
|
/// Returns true, if a method implementation corresponding to
|
|
/// the class_method applied to an instance of the class CD is
|
|
/// effectively final, i.e. it is statically known to be not overridden
|
|
/// by any subclasses of the class CD.
|
|
///
|
|
/// \p AI invocation instruction
|
|
/// \p ClassType type of the instance
|
|
/// \p CD static class of the instance whose method is being invoked
|
|
/// \p CHA class hierarchy analysis
|
|
static bool isEffectivelyFinalMethod(FullApplySite AI,
|
|
CanType ClassType,
|
|
ClassDecl *CD,
|
|
ClassHierarchyAnalysis *CHA) {
|
|
if (CD && CD->isFinal())
|
|
return true;
|
|
|
|
const DeclContext *DC = AI.getModule().getAssociatedContext();
|
|
|
|
// Without an associated context we cannot perform any
|
|
// access-based optimizations.
|
|
if (!DC)
|
|
return false;
|
|
|
|
auto *CMI = cast<MethodInst>(AI.getCallee());
|
|
|
|
if (!calleesAreStaticallyKnowable(AI.getModule(), CMI->getMember()))
|
|
return false;
|
|
|
|
auto *Method = CMI->getMember().getAbstractFunctionDecl();
|
|
assert(Method && "Expected abstract function decl!");
|
|
assert(!Method->isFinal() && "Unexpected indirect call to final method!");
|
|
|
|
// If this method is not overridden in the module,
|
|
// there is no other implementation.
|
|
if (!Method->isOverridden())
|
|
return true;
|
|
|
|
// Class declaration may be nullptr, e.g. for cases like:
|
|
// func foo<C:Base>(c: C) {}, where C is a class, but
|
|
// it does not have a class decl.
|
|
if (!CD)
|
|
return false;
|
|
|
|
if (!CHA)
|
|
return false;
|
|
|
|
// This is a private or a module internal class.
|
|
//
|
|
// We can analyze the class hierarchy rooted at it and
|
|
// eventually devirtualize a method call more efficiently.
|
|
|
|
ClassHierarchyAnalysis::ClassList Subs;
|
|
getAllSubclasses(CHA, CD, ClassType, AI.getModule(), Subs);
|
|
|
|
// This is the implementation of the method to be used
|
|
// if the exact class of the instance would be CD.
|
|
auto *ImplMethod = CD->findImplementingMethod(Method);
|
|
|
|
// First, analyze all direct subclasses.
|
|
for (auto S : Subs) {
|
|
// Check if the subclass overrides a method and provides
|
|
// a different implementation.
|
|
auto *ImplFD = S->findImplementingMethod(Method);
|
|
if (ImplFD != ImplMethod)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Check if a given class is final in terms of a current
|
|
/// compilation, i.e.:
|
|
/// - it is really final
|
|
/// - or it is private and has not sub-classes
|
|
/// - or it is an internal class without sub-classes and
|
|
/// it is a whole-module compilation.
|
|
static bool isKnownFinalClass(ClassDecl *CD, SILModule &M,
|
|
ClassHierarchyAnalysis *CHA) {
|
|
const DeclContext *DC = M.getAssociatedContext();
|
|
|
|
if (CD->isFinal())
|
|
return true;
|
|
|
|
// Without an associated context we cannot perform any
|
|
// access-based optimizations.
|
|
if (!DC)
|
|
return false;
|
|
|
|
// Only handle classes defined within the SILModule's associated context.
|
|
if (!CD->isChildContextOf(DC))
|
|
return false;
|
|
|
|
if (!CD->hasAccess())
|
|
return false;
|
|
|
|
// Only consider 'private' members, unless we are in whole-module compilation.
|
|
switch (CD->getEffectiveAccess()) {
|
|
case AccessLevel::Open:
|
|
return false;
|
|
case AccessLevel::Public:
|
|
case AccessLevel::Internal:
|
|
if (!M.isWholeModule())
|
|
return false;
|
|
break;
|
|
case AccessLevel::FilePrivate:
|
|
case AccessLevel::Private:
|
|
break;
|
|
}
|
|
|
|
// Take the ClassHierarchyAnalysis into account.
|
|
// If a given class has no subclasses and
|
|
// - private
|
|
// - or internal and it is a WMO compilation
|
|
// then this class can be considered final for the purpose
|
|
// of devirtualization.
|
|
if (CHA) {
|
|
if (!CHA->hasKnownDirectSubclasses(CD)) {
|
|
switch (CD->getEffectiveAccess()) {
|
|
case AccessLevel::Open:
|
|
return false;
|
|
case AccessLevel::Public:
|
|
case AccessLevel::Internal:
|
|
if (!M.isWholeModule())
|
|
return false;
|
|
break;
|
|
case AccessLevel::FilePrivate:
|
|
case AccessLevel::Private:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// Attempt to get the instance for S, whose static type is the same as
|
|
// its exact dynamic type, returning a null SILValue() if we cannot find it.
|
|
// The information that a static type is the same as the exact dynamic,
|
|
// can be derived e.g.:
|
|
// - from a constructor or
|
|
// - from a successful outcome of a checked_cast_br [exact] instruction.
|
|
SILValue swift::getInstanceWithExactDynamicType(SILValue S,
|
|
ClassHierarchyAnalysis *CHA) {
|
|
auto *F = S->getFunction();
|
|
auto &M = F->getModule();
|
|
|
|
while (S) {
|
|
S = stripCasts(S);
|
|
|
|
if (isa<AllocRefInst>(S) || isa<MetatypeInst>(S)) {
|
|
if (S->getType().getASTType()->hasDynamicSelfType())
|
|
return SILValue();
|
|
return S;
|
|
}
|
|
|
|
auto *Arg = dyn_cast<SILArgument>(S);
|
|
if (!Arg)
|
|
break;
|
|
|
|
auto *SinglePred = Arg->getParent()->getSinglePredecessorBlock();
|
|
if (!SinglePred) {
|
|
if (!isa<SILFunctionArgument>(Arg))
|
|
break;
|
|
auto *CD = Arg->getType().getClassOrBoundGenericClass();
|
|
// Check if this class is effectively final.
|
|
if (!CD || !isKnownFinalClass(CD, M, CHA))
|
|
break;
|
|
return Arg;
|
|
}
|
|
|
|
// Traverse the chain of predecessors.
|
|
if (isa<BranchInst>(SinglePred->getTerminator()) ||
|
|
isa<CondBranchInst>(SinglePred->getTerminator())) {
|
|
S = cast<SILPhiArgument>(Arg)->getIncomingPhiValue(SinglePred);
|
|
continue;
|
|
}
|
|
|
|
// If it is a BB argument received on a success branch
|
|
// of a checked_cast_br, then we know its exact type.
|
|
auto *CCBI = dyn_cast<CheckedCastBranchInst>(SinglePred->getTerminator());
|
|
if (!CCBI)
|
|
break;
|
|
if (!CCBI->isExact() || CCBI->getSuccessBB() != Arg->getParent())
|
|
break;
|
|
return S;
|
|
}
|
|
|
|
return SILValue();
|
|
}
|
|
|
|
/// Try to determine the exact dynamic type of an object.
|
|
/// returns the exact dynamic type of the object, or an empty type if the exact
|
|
/// type could not be determined.
|
|
SILType swift::getExactDynamicType(SILValue S,
|
|
ClassHierarchyAnalysis *CHA,
|
|
bool ForUnderlyingObject) {
|
|
auto *F = S->getFunction();
|
|
auto &M = F->getModule();
|
|
|
|
// Set of values to be checked for their exact types.
|
|
SmallVector<SILValue, 8> WorkList;
|
|
// The detected type of the underlying object.
|
|
SILType ResultType;
|
|
// Set of processed values.
|
|
llvm::SmallSet<SILValue, 8> Processed;
|
|
WorkList.push_back(S);
|
|
|
|
while (!WorkList.empty()) {
|
|
auto V = WorkList.pop_back_val();
|
|
if (!V)
|
|
return SILType();
|
|
if (Processed.count(V))
|
|
continue;
|
|
Processed.insert(V);
|
|
// For underlying object strip casts and projections.
|
|
// For the object itself, simply strip casts.
|
|
V = ForUnderlyingObject ? getUnderlyingObject(V) : stripCasts(V);
|
|
|
|
if (isa<AllocRefInst>(V) || isa<MetatypeInst>(V)) {
|
|
if (ResultType && ResultType != V->getType())
|
|
return SILType();
|
|
ResultType = V->getType();
|
|
continue;
|
|
}
|
|
|
|
if (isa<LiteralInst>(V)) {
|
|
if (ResultType && ResultType != V->getType())
|
|
return SILType();
|
|
ResultType = V->getType();
|
|
continue;
|
|
}
|
|
|
|
if (isa<StructInst>(V) || isa<TupleInst>(V) || isa<EnumInst>(V)) {
|
|
if (ResultType && ResultType != V->getType())
|
|
return SILType();
|
|
ResultType = V->getType();
|
|
continue;
|
|
}
|
|
|
|
if (ForUnderlyingObject) {
|
|
if (isa<AllocationInst>(V)) {
|
|
if (ResultType && ResultType != V->getType())
|
|
return SILType();
|
|
ResultType = V->getType();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto Arg = dyn_cast<SILArgument>(V);
|
|
if (!Arg) {
|
|
// We don't know what it is.
|
|
return SILType();
|
|
}
|
|
|
|
if (auto *FArg = dyn_cast<SILFunctionArgument>(Arg)) {
|
|
// Bail on metatypes for now.
|
|
if (FArg->getType().is<AnyMetatypeType>()) {
|
|
return SILType();
|
|
}
|
|
auto *CD = FArg->getType().getClassOrBoundGenericClass();
|
|
// If it is not class and it is a trivial type, then it
|
|
// should be the exact type.
|
|
if (!CD && FArg->getType().isTrivial(*F)) {
|
|
if (ResultType && ResultType != FArg->getType())
|
|
return SILType();
|
|
ResultType = FArg->getType();
|
|
continue;
|
|
}
|
|
|
|
if (!CD) {
|
|
// It is not a class or a trivial type, so we don't know what it is.
|
|
return SILType();
|
|
}
|
|
|
|
// Check if this class is effectively final.
|
|
if (!isKnownFinalClass(CD, M, CHA)) {
|
|
return SILType();
|
|
}
|
|
|
|
if (ResultType && ResultType != FArg->getType())
|
|
return SILType();
|
|
ResultType = FArg->getType();
|
|
continue;
|
|
}
|
|
|
|
auto *SinglePred = Arg->getParent()->getSinglePredecessorBlock();
|
|
if (SinglePred) {
|
|
// If it is a BB argument received on a success branch
|
|
// of a checked_cast_br, then we know its exact type.
|
|
auto *CCBI = dyn_cast<CheckedCastBranchInst>(SinglePred->getTerminator());
|
|
if (CCBI && CCBI->isExact() && CCBI->getSuccessBB() == Arg->getParent()) {
|
|
if (ResultType && ResultType != Arg->getType())
|
|
return SILType();
|
|
ResultType = Arg->getType();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// It is a BB argument, look through incoming values. If they all have the
|
|
// same exact type, then we consider it to be the type of the BB argument.
|
|
SmallVector<SILValue, 4> IncomingValues;
|
|
if (Arg->getSingleTerminatorOperands(IncomingValues)) {
|
|
for (auto InValue : IncomingValues) {
|
|
WorkList.push_back(InValue);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// The exact type is unknown.
|
|
return SILType();
|
|
}
|
|
|
|
return ResultType;
|
|
}
|
|
|
|
|
|
/// Try to determine the exact dynamic type of the underlying object.
|
|
/// returns the exact dynamic type of a value, or an empty type if the exact
|
|
/// type could not be determined.
|
|
SILType
|
|
swift::getExactDynamicTypeOfUnderlyingObject(SILValue S,
|
|
ClassHierarchyAnalysis *CHA) {
|
|
return getExactDynamicType(S, CHA, /* ForUnderlyingObject */ true);
|
|
}
|
|
|
|
// Start with the substitutions from the apply.
|
|
// Try to propagate them to find out the real substitutions required
|
|
// to invoke the method.
|
|
static SubstitutionMap
|
|
getSubstitutionsForCallee(SILModule &M,
|
|
CanSILFunctionType baseCalleeType,
|
|
CanType derivedSelfType,
|
|
FullApplySite AI) {
|
|
|
|
// If the base method is not polymorphic, no substitutions are required,
|
|
// even if we originally had substitutions for calling the derived method.
|
|
if (!baseCalleeType->isPolymorphic())
|
|
return SubstitutionMap();
|
|
|
|
// Add any generic substitutions for the base class.
|
|
Type baseSelfType = baseCalleeType->getSelfParameter().getType();
|
|
if (auto metatypeType = baseSelfType->getAs<MetatypeType>())
|
|
baseSelfType = metatypeType->getInstanceType();
|
|
|
|
auto *baseClassDecl = baseSelfType->getClassOrBoundGenericClass();
|
|
assert(baseClassDecl && "not a class method");
|
|
|
|
unsigned baseDepth = 0;
|
|
SubstitutionMap baseSubMap;
|
|
if (auto baseClassSig = baseClassDecl->getGenericSignatureOfContext()) {
|
|
baseDepth = baseClassSig->getGenericParams().back()->getDepth() + 1;
|
|
|
|
// Compute the type of the base class, starting from the
|
|
// derived class type and the type of the method's self
|
|
// parameter.
|
|
Type derivedClass = derivedSelfType;
|
|
if (auto metatypeType = derivedClass->getAs<MetatypeType>())
|
|
derivedClass = metatypeType->getInstanceType();
|
|
baseSubMap = derivedClass->getContextSubstitutionMap(
|
|
M.getSwiftModule(), baseClassDecl);
|
|
}
|
|
|
|
SubstitutionMap origSubMap = AI.getSubstitutionMap();
|
|
|
|
Type calleeSelfType = AI.getOrigCalleeType()->getSelfParameter().getType();
|
|
if (auto metatypeType = calleeSelfType->getAs<MetatypeType>())
|
|
calleeSelfType = metatypeType->getInstanceType();
|
|
auto *calleeClassDecl = calleeSelfType->getClassOrBoundGenericClass();
|
|
assert(calleeClassDecl && "self is not a class type");
|
|
|
|
// Add generic parameters from the method itself, ignoring any generic
|
|
// parameters from the derived class.
|
|
unsigned origDepth = 0;
|
|
if (auto calleeClassSig = calleeClassDecl->getGenericSignatureOfContext())
|
|
origDepth = calleeClassSig->getGenericParams().back()->getDepth() + 1;
|
|
|
|
auto baseCalleeSig = baseCalleeType->getGenericSignature();
|
|
|
|
return
|
|
SubstitutionMap::combineSubstitutionMaps(baseSubMap,
|
|
origSubMap,
|
|
CombineSubstitutionMaps::AtDepth,
|
|
baseDepth,
|
|
origDepth,
|
|
baseCalleeSig);
|
|
}
|
|
|
|
static ApplyInst *replaceApplyInst(SILBuilder &B, SILLocation Loc,
|
|
ApplyInst *OldAI,
|
|
SILValue NewFn,
|
|
SubstitutionMap NewSubs,
|
|
ArrayRef<SILValue> NewArgs,
|
|
ArrayRef<SILValue> NewArgBorrows) {
|
|
auto *NewAI = B.createApply(Loc, NewFn, NewSubs, NewArgs,
|
|
OldAI->isNonThrowing());
|
|
|
|
if (!NewArgBorrows.empty()) {
|
|
for (SILValue Arg : NewArgBorrows) {
|
|
B.createEndBorrow(Loc, Arg);
|
|
}
|
|
}
|
|
|
|
// Check if any casting is required for the return value.
|
|
SILValue ResultValue =
|
|
castValueToABICompatibleType(&B, Loc, NewAI, NewAI->getType(),
|
|
OldAI->getType());
|
|
|
|
OldAI->replaceAllUsesWith(ResultValue);
|
|
return NewAI;
|
|
}
|
|
|
|
static TryApplyInst *replaceTryApplyInst(SILBuilder &B, SILLocation Loc,
|
|
TryApplyInst *OldTAI, SILValue NewFn,
|
|
SubstitutionMap NewSubs,
|
|
ArrayRef<SILValue> NewArgs,
|
|
SILFunctionConventions Conv,
|
|
ArrayRef<SILValue> NewArgBorrows) {
|
|
SILBasicBlock *NormalBB = OldTAI->getNormalBB();
|
|
SILBasicBlock *ResultBB = nullptr;
|
|
|
|
SILType NewResultTy = Conv.getSILResultType();
|
|
|
|
// Does the result value need to be casted?
|
|
auto OldResultTy = NormalBB->getArgument(0)->getType();
|
|
bool ResultCastRequired = NewResultTy != OldResultTy;
|
|
|
|
// Create a new normal BB only if the result of the new apply differs
|
|
// in type from the argument of the original normal BB.
|
|
if (!ResultCastRequired) {
|
|
ResultBB = NormalBB;
|
|
} else {
|
|
ResultBB = B.getFunction().createBasicBlockBefore(NormalBB);
|
|
ResultBB->createPhiArgument(NewResultTy, ValueOwnershipKind::Owned);
|
|
}
|
|
|
|
// We can always just use the original error BB because we'll be
|
|
// deleting the edge to it from the old TAI.
|
|
SILBasicBlock *ErrorBB = OldTAI->getErrorBB();
|
|
|
|
// Insert a try_apply here.
|
|
// Note that this makes this block temporarily double-terminated!
|
|
// We won't fix that until deleteDevirtualizedApply.
|
|
auto NewTAI = B.createTryApply(Loc, NewFn, NewSubs, NewArgs,
|
|
ResultBB, ErrorBB);
|
|
|
|
if (!NewArgBorrows.empty()) {
|
|
B.setInsertionPoint(NormalBB->begin());
|
|
for (SILValue Arg : NewArgBorrows) {
|
|
B.createEndBorrow(Loc, Arg);
|
|
}
|
|
B.setInsertionPoint(ErrorBB->begin());
|
|
for (SILValue Arg : NewArgBorrows) {
|
|
B.createEndBorrow(Loc, Arg);
|
|
}
|
|
}
|
|
|
|
if (ResultCastRequired) {
|
|
B.setInsertionPoint(ResultBB);
|
|
|
|
SILValue ResultValue = ResultBB->getArgument(0);
|
|
ResultValue = castValueToABICompatibleType(&B, Loc, ResultValue,
|
|
NewResultTy, OldResultTy);
|
|
B.createBranch(Loc, NormalBB, { ResultValue });
|
|
}
|
|
|
|
B.setInsertionPoint(NormalBB->begin());
|
|
return NewTAI;
|
|
}
|
|
|
|
static BeginApplyInst *replaceBeginApplyInst(SILBuilder &B, SILLocation Loc,
|
|
BeginApplyInst *OldBAI,
|
|
SILValue NewFn,
|
|
SubstitutionMap NewSubs,
|
|
ArrayRef<SILValue> NewArgs,
|
|
ArrayRef<SILValue> NewArgBorrows) {
|
|
auto *NewBAI =
|
|
B.createBeginApply(Loc, NewFn, NewSubs, NewArgs, OldBAI->isNonThrowing());
|
|
|
|
// Forward the token.
|
|
OldBAI->getTokenResult()->replaceAllUsesWith(NewBAI->getTokenResult());
|
|
|
|
auto OldYields = OldBAI->getYieldedValues();
|
|
auto NewYields = NewBAI->getYieldedValues();
|
|
assert(OldYields.size() == NewYields.size());
|
|
|
|
for (auto i : indices(OldYields)) {
|
|
auto OldYield = OldYields[i];
|
|
auto NewYield = NewYields[i];
|
|
NewYield = castValueToABICompatibleType(&B, Loc, NewYield,
|
|
NewYield->getType(),
|
|
OldYield->getType());
|
|
OldYield->replaceAllUsesWith(NewYield);
|
|
}
|
|
|
|
if (NewArgBorrows.empty())
|
|
return NewBAI;
|
|
|
|
SILValue token = NewBAI->getTokenResult();
|
|
|
|
// The token will only be used by end_apply and abort_apply. Use that to
|
|
// insert the end_borrows we need.
|
|
for (auto *use : token->getUses()) {
|
|
SILBuilderWithScope builder(use->getUser(), B.getBuilderContext());
|
|
for (SILValue borrow : NewArgBorrows) {
|
|
builder.createEndBorrow(Loc, borrow);
|
|
}
|
|
}
|
|
|
|
return NewBAI;
|
|
}
|
|
|
|
static PartialApplyInst *replacePartialApplyInst(SILBuilder &B, SILLocation Loc,
|
|
PartialApplyInst *OldPAI,
|
|
SILValue NewFn,
|
|
SubstitutionMap NewSubs,
|
|
ArrayRef<SILValue> NewArgs) {
|
|
auto Convention =
|
|
OldPAI->getType().getAs<SILFunctionType>()->getCalleeConvention();
|
|
auto *NewPAI = B.createPartialApply(Loc, NewFn, NewSubs, NewArgs,
|
|
Convention);
|
|
|
|
// Check if any casting is required for the partially-applied function.
|
|
SILValue ResultValue = castValueToABICompatibleType(
|
|
&B, Loc, NewPAI, NewPAI->getType(), OldPAI->getType());
|
|
OldPAI->replaceAllUsesWith(ResultValue);
|
|
|
|
return NewPAI;
|
|
}
|
|
|
|
static ApplySite replaceApplySite(SILBuilder &B, SILLocation Loc,
|
|
ApplySite OldAS, SILValue NewFn,
|
|
SubstitutionMap NewSubs,
|
|
ArrayRef<SILValue> NewArgs,
|
|
SILFunctionConventions Conv,
|
|
ArrayRef<SILValue> NewArgBorrows) {
|
|
switch (OldAS.getKind()) {
|
|
case ApplySiteKind::ApplyInst: {
|
|
auto *OldAI = cast<ApplyInst>(OldAS);
|
|
return replaceApplyInst(B, Loc, OldAI, NewFn, NewSubs, NewArgs, NewArgBorrows);
|
|
}
|
|
case ApplySiteKind::TryApplyInst: {
|
|
auto *OldTAI = cast<TryApplyInst>(OldAS);
|
|
return replaceTryApplyInst(B, Loc, OldTAI, NewFn, NewSubs, NewArgs, Conv,
|
|
NewArgBorrows);
|
|
}
|
|
case ApplySiteKind::BeginApplyInst: {
|
|
auto *OldBAI = dyn_cast<BeginApplyInst>(OldAS);
|
|
return replaceBeginApplyInst(B, Loc, OldBAI, NewFn, NewSubs, NewArgs,
|
|
NewArgBorrows);
|
|
}
|
|
case ApplySiteKind::PartialApplyInst: {
|
|
assert(NewArgBorrows.empty());
|
|
auto *OldPAI = cast<PartialApplyInst>(OldAS);
|
|
return replacePartialApplyInst(B, Loc, OldPAI, NewFn, NewSubs, NewArgs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Delete an apply site that's been successfully devirtualized.
|
|
void swift::deleteDevirtualizedApply(ApplySite Old) {
|
|
auto *OldApply = Old.getInstruction();
|
|
recursivelyDeleteTriviallyDeadInstructions(OldApply, true);
|
|
}
|
|
|
|
SILFunction *swift::getTargetClassMethod(SILModule &M,
|
|
ClassDecl *CD,
|
|
MethodInst *MI) {
|
|
assert((isa<ClassMethodInst>(MI) || isa<SuperMethodInst>(MI)) &&
|
|
"Only class_method and super_method instructions are supported");
|
|
|
|
SILDeclRef Member = MI->getMember();
|
|
return M.lookUpFunctionInVTable(CD, Member);
|
|
}
|
|
|
|
CanType swift::getSelfInstanceType(CanType ClassOrMetatypeType) {
|
|
if (auto MetaType = dyn_cast<MetatypeType>(ClassOrMetatypeType))
|
|
ClassOrMetatypeType = MetaType.getInstanceType();
|
|
|
|
if (auto SelfType = dyn_cast<DynamicSelfType>(ClassOrMetatypeType))
|
|
ClassOrMetatypeType = SelfType.getSelfType();
|
|
|
|
return ClassOrMetatypeType;
|
|
}
|
|
|
|
/// Check if it is possible to devirtualize an Apply instruction
|
|
/// and a class member obtained using the class_method instruction into
|
|
/// a direct call to a specific member of a specific class.
|
|
///
|
|
/// \p AI is the apply to devirtualize.
|
|
/// \p CD is the class declaration we are devirtualizing for.
|
|
/// return true if it is possible to devirtualize, false - otherwise.
|
|
bool swift::canDevirtualizeClassMethod(FullApplySite AI,
|
|
ClassDecl *CD,
|
|
OptRemark::Emitter *ORE,
|
|
bool isEffectivelyFinalMethod) {
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize : "
|
|
<< *AI.getInstruction());
|
|
|
|
SILModule &Mod = AI.getModule();
|
|
|
|
auto *MI = cast<MethodInst>(AI.getCallee());
|
|
|
|
// Find the implementation of the member which should be invoked.
|
|
auto *F = getTargetClassMethod(Mod, CD, MI);
|
|
|
|
// If we do not find any such function, we have no function to devirtualize
|
|
// to... so bail.
|
|
if (!F) {
|
|
LLVM_DEBUG(llvm::dbgs() << " FAIL: Could not find matching VTable "
|
|
"or vtable method for this class.\n");
|
|
return false;
|
|
}
|
|
|
|
// We need to disable the “effectively final” opt if a function is inlinable
|
|
if (isEffectivelyFinalMethod && AI.getFunction()->isSerialized()) {
|
|
LLVM_DEBUG(llvm::dbgs() << " FAIL: Could not optimize function "
|
|
"because it is an effectively-final inlinable: "
|
|
<< AI.getFunction()->getName() << "\n");
|
|
return false;
|
|
}
|
|
|
|
// Mandatory inlining does class method devirtualization. I'm not sure if this
|
|
// is really needed, but some test rely on this.
|
|
// So even for Onone functions we have to do it if the SILStage is raw.
|
|
if (F->getModule().getStage() != SILStage::Raw && !F->shouldOptimize()) {
|
|
// Do not consider functions that should not be optimized.
|
|
LLVM_DEBUG(llvm::dbgs() << " FAIL: Could not optimize function "
|
|
<< " because it is marked no-opt: " << F->getName()
|
|
<< "\n");
|
|
return false;
|
|
}
|
|
|
|
if (AI.getFunction()->isSerialized()) {
|
|
// function_ref inside fragile function cannot reference a private or
|
|
// hidden symbol.
|
|
if (!F->hasValidLinkageForFragileRef())
|
|
return false;
|
|
}
|
|
|
|
// devirtualizeClassMethod below does not support this case. It currently
|
|
// assumes it can try_apply call the target.
|
|
if (!F->getLoweredFunctionType()->hasErrorResult() &&
|
|
isa<TryApplyInst>(AI.getInstruction())) {
|
|
LLVM_DEBUG(llvm::dbgs() << " FAIL: Trying to devirtualize a "
|
|
"try_apply but vtable entry has no error result.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Devirtualize an apply of a class method.
|
|
///
|
|
/// \p AI is the apply to devirtualize.
|
|
/// \p ClassOrMetatype is a class value or metatype value that is the
|
|
/// self argument of the apply we will devirtualize.
|
|
/// return the result value of the new ApplyInst if created one or null.
|
|
FullApplySite swift::devirtualizeClassMethod(FullApplySite AI,
|
|
SILValue ClassOrMetatype,
|
|
ClassDecl *CD,
|
|
OptRemark::Emitter *ORE) {
|
|
LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize : "
|
|
<< *AI.getInstruction());
|
|
|
|
SILModule &Mod = AI.getModule();
|
|
auto *MI = cast<MethodInst>(AI.getCallee());
|
|
|
|
auto *F = getTargetClassMethod(Mod, CD, MI);
|
|
|
|
CanSILFunctionType GenCalleeType = F->getLoweredFunctionType();
|
|
|
|
SubstitutionMap Subs =
|
|
getSubstitutionsForCallee(Mod, GenCalleeType,
|
|
ClassOrMetatype->getType().getASTType(),
|
|
AI);
|
|
CanSILFunctionType SubstCalleeType = GenCalleeType;
|
|
if (GenCalleeType->isPolymorphic())
|
|
SubstCalleeType = GenCalleeType->substGenericArgs(Mod, Subs);
|
|
SILFunctionConventions substConv(SubstCalleeType, Mod);
|
|
|
|
SILBuilderWithScope B(AI.getInstruction());
|
|
SILLocation Loc = AI.getLoc();
|
|
auto *FRI = B.createFunctionRefFor(Loc, F);
|
|
|
|
// Create the argument list for the new apply, casting when needed
|
|
// in order to handle covariant indirect return types and
|
|
// contravariant argument types.
|
|
SmallVector<SILValue, 8> NewArgs;
|
|
|
|
// If we have a value that is owned, but that we are going to use in as a
|
|
// guaranteed argument, we need to borrow/unborrow the argument. Otherwise, we
|
|
// will introduce new consuming uses. In contrast, if we have an owned value,
|
|
// we are ok due to the forwarding nature of upcasts.
|
|
SmallVector<SILValue, 8> NewArgBorrows;
|
|
|
|
auto IndirectResultArgIter = AI.getIndirectSILResults().begin();
|
|
for (auto ResultTy : substConv.getIndirectSILResultTypes()) {
|
|
NewArgs.push_back(
|
|
castValueToABICompatibleType(&B, Loc, *IndirectResultArgIter,
|
|
IndirectResultArgIter->getType(), ResultTy));
|
|
++IndirectResultArgIter;
|
|
}
|
|
|
|
auto ParamArgIter = AI.getArgumentsWithoutIndirectResults().begin();
|
|
// Skip the last parameter, which is `self`. Add it below.
|
|
for (auto param : substConv.getParameters()) {
|
|
auto paramType = substConv.getSILType(param);
|
|
SILValue arg = *ParamArgIter;
|
|
if (B.hasOwnership()
|
|
&& arg->getType().isObject()
|
|
&& arg.getOwnershipKind() == ValueOwnershipKind::Owned
|
|
&& param.isGuaranteed()) {
|
|
SILBuilderWithScope builder(AI.getInstruction(), B);
|
|
arg = builder.createBeginBorrow(Loc, arg);
|
|
NewArgBorrows.push_back(arg);
|
|
}
|
|
arg = castValueToABICompatibleType(&B, Loc, arg, ParamArgIter->getType(),
|
|
paramType);
|
|
NewArgs.push_back(arg);
|
|
++ParamArgIter;
|
|
}
|
|
ApplySite NewAS = replaceApplySite(B, Loc, AI, FRI, Subs, NewArgs, substConv,
|
|
NewArgBorrows);
|
|
FullApplySite NewAI = FullApplySite::isa(NewAS.getInstruction());
|
|
assert(NewAI);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << " SUCCESS: " << F->getName() << "\n");
|
|
if (ORE)
|
|
ORE->emit([&]() {
|
|
using namespace OptRemark;
|
|
return RemarkPassed("ClassMethodDevirtualized", *AI.getInstruction())
|
|
<< "Devirtualized call to class method " << NV("Method", F);
|
|
});
|
|
NumClassDevirt++;
|
|
|
|
return NewAI;
|
|
}
|
|
|
|
FullApplySite
|
|
swift::tryDevirtualizeClassMethod(FullApplySite AI, SILValue ClassInstance,
|
|
ClassDecl *CD,
|
|
OptRemark::Emitter *ORE,
|
|
bool isEffectivelyFinalMethod) {
|
|
if (!canDevirtualizeClassMethod(AI, CD, ORE, isEffectivelyFinalMethod))
|
|
return FullApplySite();
|
|
return devirtualizeClassMethod(AI, ClassInstance, CD, ORE);
|
|
}
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Witness Method Optimization
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Compute substitutions for making a direct call to a SIL function with
|
|
/// @convention(witness_method) convention.
|
|
///
|
|
/// Such functions have a substituted generic signature where the
|
|
/// abstract `Self` parameter from the original type of the protocol
|
|
/// requirement is replaced by a concrete type.
|
|
///
|
|
/// Thus, the original substitutions of the apply instruction that
|
|
/// are written in terms of the requirement's generic signature need
|
|
/// to be remapped to substitutions suitable for the witness signature.
|
|
///
|
|
/// Supported remappings are:
|
|
///
|
|
/// - (Concrete witness thunk) Original substitutions:
|
|
/// [Self := ConcreteType, R0 := X0, R1 := X1, ...]
|
|
/// - Requirement generic signature:
|
|
/// <Self : P, R0, R1, ...>
|
|
/// - Witness thunk generic signature:
|
|
/// <W0, W1, ...>
|
|
/// - Remapped substitutions:
|
|
/// [W0 := X0, W1 := X1, ...]
|
|
///
|
|
/// - (Class witness thunk) Original substitutions:
|
|
/// [Self := C<A0, A1>, T0 := X0, T1 := X1, ...]
|
|
/// - Requirement generic signature:
|
|
/// <Self : P, R0, R1, ...>
|
|
/// - Witness thunk generic signature:
|
|
/// <Self : C<B0, B1>, B0, B1, W0, W1, ...>
|
|
/// - Remapped substitutions:
|
|
/// [Self := C<B0, B1>, B0 := A0, B1 := A1, W0 := X0, W1 := X1]
|
|
///
|
|
/// - (Default witness thunk) Original substitutions:
|
|
/// [Self := ConcreteType, R0 := X0, R1 := X1, ...]
|
|
/// - Requirement generic signature:
|
|
/// <Self : P, R0, R1, ...>
|
|
/// - Witness thunk generic signature:
|
|
/// <Self : P, W0, W1, ...>
|
|
/// - Remapped substitutions:
|
|
/// [Self := ConcreteType, W0 := X0, W1 := X1, ...]
|
|
///
|
|
/// \param conformanceRef The (possibly-specialized) conformance
|
|
/// \param requirementSig The generic signature of the requirement
|
|
/// \param witnessThunkSig The generic signature of the witness method
|
|
/// \param origSubMap The substitutions from the call instruction
|
|
/// \param isSelfAbstract True if the Self type of the witness method is
|
|
/// still abstract (i.e., not a concrete type).
|
|
/// \param classWitness The ClassDecl if this is a class witness method
|
|
static SubstitutionMap
|
|
getWitnessMethodSubstitutions(
|
|
ModuleDecl *mod,
|
|
ProtocolConformanceRef conformanceRef,
|
|
GenericSignature *requirementSig,
|
|
GenericSignature *witnessThunkSig,
|
|
SubstitutionMap origSubMap,
|
|
bool isSelfAbstract,
|
|
ClassDecl *classWitness) {
|
|
|
|
if (witnessThunkSig == nullptr)
|
|
return SubstitutionMap();
|
|
|
|
if (isSelfAbstract && !classWitness)
|
|
return origSubMap;
|
|
|
|
assert(!conformanceRef.isAbstract());
|
|
auto conformance = conformanceRef.getConcrete();
|
|
|
|
// If `Self` maps to a bound generic type, this gives us the
|
|
// substitutions for the concrete type's generic parameters.
|
|
auto baseSubMap = conformance->getSubstitutions(mod);
|
|
|
|
unsigned baseDepth = 0;
|
|
auto *rootConformance = conformance->getRootNormalConformance();
|
|
if (auto *witnessSig = rootConformance->getGenericSignature())
|
|
baseDepth = witnessSig->getGenericParams().back()->getDepth() + 1;
|
|
|
|
// If the witness has a class-constrained 'Self' generic parameter,
|
|
// we have to build a new substitution map that shifts all generic
|
|
// parameters down by one.
|
|
if (classWitness != nullptr) {
|
|
auto *proto = conformance->getProtocol();
|
|
auto selfType = proto->getSelfInterfaceType();
|
|
|
|
auto selfSubMap = SubstitutionMap::getProtocolSubstitutions(
|
|
proto, selfType.subst(origSubMap), conformanceRef);
|
|
if (baseSubMap.empty()) {
|
|
assert(baseDepth == 0);
|
|
baseSubMap = selfSubMap;
|
|
} else {
|
|
baseSubMap = SubstitutionMap::combineSubstitutionMaps(
|
|
selfSubMap,
|
|
baseSubMap,
|
|
CombineSubstitutionMaps::AtDepth,
|
|
/*firstDepth=*/1,
|
|
/*secondDepth=*/0,
|
|
witnessThunkSig);
|
|
}
|
|
baseDepth += 1;
|
|
}
|
|
|
|
return SubstitutionMap::combineSubstitutionMaps(
|
|
baseSubMap,
|
|
origSubMap,
|
|
CombineSubstitutionMaps::AtDepth,
|
|
/*firstDepth=*/baseDepth,
|
|
/*secondDepth=*/1,
|
|
witnessThunkSig);
|
|
}
|
|
|
|
SubstitutionMap
|
|
swift::getWitnessMethodSubstitutions(SILModule &Module, ApplySite AI,
|
|
SILFunction *F,
|
|
ProtocolConformanceRef CRef) {
|
|
auto witnessFnTy = F->getLoweredFunctionType();
|
|
assert(witnessFnTy->getRepresentation() ==
|
|
SILFunctionTypeRepresentation::WitnessMethod);
|
|
|
|
auto requirementSig = AI.getOrigCalleeType()->getGenericSignature();
|
|
auto witnessThunkSig = witnessFnTy->getGenericSignature();
|
|
|
|
SubstitutionMap origSubs = AI.getSubstitutionMap();
|
|
|
|
auto *mod = Module.getSwiftModule();
|
|
bool isSelfAbstract =
|
|
witnessFnTy->getSelfInstanceType()->is<GenericTypeParamType>();
|
|
auto *classWitness = witnessFnTy->getWitnessMethodClass();
|
|
|
|
return ::getWitnessMethodSubstitutions(mod, CRef, requirementSig,
|
|
witnessThunkSig, origSubs,
|
|
isSelfAbstract, classWitness);
|
|
}
|
|
|
|
/// Generate a new apply of a function_ref to replace an apply of a
|
|
/// witness_method when we've determined the actual function we'll end
|
|
/// up calling.
|
|
static ApplySite
|
|
devirtualizeWitnessMethod(ApplySite AI, SILFunction *F,
|
|
ProtocolConformanceRef C, OptRemark::Emitter *ORE) {
|
|
// We know the witness thunk and the corresponding set of substitutions
|
|
// required to invoke the protocol method at this point.
|
|
auto &Module = AI.getModule();
|
|
|
|
// Collect all the required substitutions.
|
|
//
|
|
// The complete set of substitutions may be different, e.g. because the found
|
|
// witness thunk F may have been created by a specialization pass and have
|
|
// additional generic parameters.
|
|
auto SubMap = getWitnessMethodSubstitutions(Module, AI, F, C);
|
|
|
|
// Figure out the exact bound type of the function to be called by
|
|
// applying all substitutions.
|
|
auto CalleeCanType = F->getLoweredFunctionType();
|
|
auto SubstCalleeCanType = CalleeCanType->substGenericArgs(Module, SubMap);
|
|
|
|
// Collect arguments from the apply instruction.
|
|
SmallVector<SILValue, 4> Arguments;
|
|
SmallVector<SILValue, 4> BorrowedArgs;
|
|
|
|
// Iterate over the non self arguments and add them to the
|
|
// new argument list, upcasting when required.
|
|
SILBuilderWithScope B(AI.getInstruction());
|
|
SILFunctionConventions substConv(SubstCalleeCanType, Module);
|
|
unsigned substArgIdx = AI.getCalleeArgIndexOfFirstAppliedArg();
|
|
for (auto arg : AI.getArguments()) {
|
|
auto paramInfo = substConv.getSILArgumentConvention(substArgIdx);
|
|
auto paramType = substConv.getSILArgumentType(substArgIdx++);
|
|
if (arg->getType() != paramType) {
|
|
if (B.hasOwnership()
|
|
&& AI.getKind() != ApplySiteKind::PartialApplyInst
|
|
&& arg->getType().isObject()
|
|
&& arg.getOwnershipKind() == ValueOwnershipKind::Owned
|
|
&& paramInfo.isGuaranteedConvention()) {
|
|
SILBuilderWithScope builder(AI.getInstruction(), B);
|
|
arg = builder.createBeginBorrow(AI.getLoc(), arg);
|
|
BorrowedArgs.push_back(arg);
|
|
}
|
|
arg = castValueToABICompatibleType(&B, AI.getLoc(), arg,
|
|
arg->getType(), paramType);
|
|
}
|
|
Arguments.push_back(arg);
|
|
}
|
|
assert(substArgIdx == substConv.getNumSILArguments());
|
|
|
|
// Replace old apply instruction by a new apply instruction that invokes
|
|
// the witness thunk.
|
|
SILBuilderWithScope Builder(AI.getInstruction());
|
|
SILLocation Loc = AI.getLoc();
|
|
auto *FRI = Builder.createFunctionRefFor(Loc, F);
|
|
|
|
ApplySite SAI = replaceApplySite(Builder, Loc, AI, FRI, SubMap, Arguments,
|
|
substConv, BorrowedArgs);
|
|
|
|
if (ORE)
|
|
ORE->emit([&]() {
|
|
using namespace OptRemark;
|
|
return RemarkPassed("WitnessMethodDevirtualized", *AI.getInstruction())
|
|
<< "Devirtualized call to " << NV("Method", F);
|
|
});
|
|
NumWitnessDevirt++;
|
|
return SAI;
|
|
}
|
|
|
|
static bool canDevirtualizeWitnessMethod(ApplySite AI) {
|
|
SILFunction *F;
|
|
SILWitnessTable *WT;
|
|
|
|
auto *WMI = cast<WitnessMethodInst>(AI.getCallee());
|
|
|
|
std::tie(F, WT) =
|
|
AI.getModule().lookUpFunctionInWitnessTable(WMI->getConformance(),
|
|
WMI->getMember());
|
|
|
|
if (!F)
|
|
return false;
|
|
|
|
if (AI.getFunction()->isSerialized()) {
|
|
// function_ref inside fragile function cannot reference a private or
|
|
// hidden symbol.
|
|
if (!F->hasValidLinkageForFragileRef())
|
|
return false;
|
|
}
|
|
|
|
// devirtualizeWitnessMethod below does not support this case. It currently
|
|
// assumes it can try_apply call the target.
|
|
if (!F->getLoweredFunctionType()->hasErrorResult() &&
|
|
isa<TryApplyInst>(AI.getInstruction())) {
|
|
LLVM_DEBUG(llvm::dbgs() << " FAIL: Trying to devirtualize a "
|
|
"try_apply but wtable entry has no error result.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// In the cases where we can statically determine the function that
|
|
/// we'll call to, replace an apply of a witness_method with an apply
|
|
/// of a function_ref, returning the new apply.
|
|
ApplySite
|
|
swift::tryDevirtualizeWitnessMethod(ApplySite AI, OptRemark::Emitter *ORE) {
|
|
if (!canDevirtualizeWitnessMethod(AI))
|
|
return ApplySite();
|
|
|
|
SILFunction *F;
|
|
SILWitnessTable *WT;
|
|
|
|
auto *WMI = cast<WitnessMethodInst>(AI.getCallee());
|
|
|
|
std::tie(F, WT) =
|
|
AI.getModule().lookUpFunctionInWitnessTable(WMI->getConformance(),
|
|
WMI->getMember());
|
|
|
|
return devirtualizeWitnessMethod(AI, F, WMI->getConformance(), ORE);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Level Driver
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Attempt to devirtualize the given apply if possible, and return a
|
|
/// new instruction in that case, or nullptr otherwise.
|
|
ApplySite swift::tryDevirtualizeApply(ApplySite AI,
|
|
ClassHierarchyAnalysis *CHA,
|
|
OptRemark::Emitter *ORE) {
|
|
LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize: "
|
|
<< *AI.getInstruction());
|
|
|
|
// Devirtualize apply instructions that call witness_method instructions:
|
|
//
|
|
// %8 = witness_method $Optional<UInt16>, #LogicValue.boolValue!getter.1
|
|
// %9 = apply %8<Self = CodeUnit?>(%6#1) : ...
|
|
//
|
|
if (isa<WitnessMethodInst>(AI.getCallee()))
|
|
return tryDevirtualizeWitnessMethod(AI, ORE);
|
|
|
|
// TODO: check if we can also de-virtualize partial applies of class methods.
|
|
FullApplySite FAS = FullApplySite::isa(AI.getInstruction());
|
|
if (!FAS)
|
|
return ApplySite();
|
|
|
|
/// Optimize a class_method and alloc_ref pair into a direct function
|
|
/// reference:
|
|
///
|
|
/// \code
|
|
/// %XX = alloc_ref $Foo
|
|
/// %YY = class_method %XX : $Foo, #Foo.get!1 : $@convention(method)...
|
|
/// \endcode
|
|
///
|
|
/// or
|
|
///
|
|
/// %XX = metatype $...
|
|
/// %YY = class_method %XX : ...
|
|
///
|
|
/// into
|
|
///
|
|
/// %YY = function_ref @...
|
|
if (auto *CMI = dyn_cast<ClassMethodInst>(FAS.getCallee())) {
|
|
auto Instance = stripUpCasts(CMI->getOperand());
|
|
auto ClassType = getSelfInstanceType(Instance->getType().getASTType());
|
|
auto *CD = ClassType.getClassOrBoundGenericClass();
|
|
|
|
if (isEffectivelyFinalMethod(FAS, ClassType, CD, CHA))
|
|
return tryDevirtualizeClassMethod(FAS, Instance, CD, ORE,
|
|
true /*isEffectivelyFinalMethod*/);
|
|
|
|
// Try to check if the exact dynamic type of the instance is statically
|
|
// known.
|
|
if (auto Instance = getInstanceWithExactDynamicType(CMI->getOperand(), CHA))
|
|
return tryDevirtualizeClassMethod(FAS, Instance, CD, ORE);
|
|
|
|
if (auto ExactTy = getExactDynamicType(CMI->getOperand(), CHA)) {
|
|
if (ExactTy == CMI->getOperand()->getType())
|
|
return tryDevirtualizeClassMethod(FAS, CMI->getOperand(), CD, ORE);
|
|
}
|
|
}
|
|
|
|
if (isa<SuperMethodInst>(FAS.getCallee())) {
|
|
auto Instance = FAS.getArguments().back();
|
|
auto ClassType = getSelfInstanceType(Instance->getType().getASTType());
|
|
auto *CD = ClassType.getClassOrBoundGenericClass();
|
|
|
|
return tryDevirtualizeClassMethod(FAS, Instance, CD, ORE);
|
|
}
|
|
|
|
return ApplySite();
|
|
}
|
|
|
|
bool swift::canDevirtualizeApply(FullApplySite AI, ClassHierarchyAnalysis *CHA) {
|
|
LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize: "
|
|
<< *AI.getInstruction());
|
|
|
|
// Devirtualize apply instructions that call witness_method instructions:
|
|
//
|
|
// %8 = witness_method $Optional<UInt16>, #LogicValue.boolValue!getter.1
|
|
// %9 = apply %8<Self = CodeUnit?>(%6#1) : ...
|
|
//
|
|
if (isa<WitnessMethodInst>(AI.getCallee()))
|
|
return canDevirtualizeWitnessMethod(AI);
|
|
|
|
/// Optimize a class_method and alloc_ref pair into a direct function
|
|
/// reference:
|
|
///
|
|
/// \code
|
|
/// %XX = alloc_ref $Foo
|
|
/// %YY = class_method %XX : $Foo, #Foo.get!1 : $@convention(method)...
|
|
/// \endcode
|
|
///
|
|
/// or
|
|
///
|
|
/// %XX = metatype $...
|
|
/// %YY = class_method %XX : ...
|
|
///
|
|
/// into
|
|
///
|
|
/// %YY = function_ref @...
|
|
if (auto *CMI = dyn_cast<ClassMethodInst>(AI.getCallee())) {
|
|
auto Instance = stripUpCasts(CMI->getOperand());
|
|
auto ClassType = getSelfInstanceType(Instance->getType().getASTType());
|
|
auto *CD = ClassType.getClassOrBoundGenericClass();
|
|
|
|
if (isEffectivelyFinalMethod(AI, ClassType, CD, CHA))
|
|
return canDevirtualizeClassMethod(AI, CD,
|
|
nullptr /*ORE*/,
|
|
true /*isEffectivelyFinalMethod*/);
|
|
|
|
// Try to check if the exact dynamic type of the instance is statically
|
|
// known.
|
|
if (auto Instance = getInstanceWithExactDynamicType(CMI->getOperand(), CHA))
|
|
return canDevirtualizeClassMethod(AI, CD);
|
|
|
|
if (auto ExactTy = getExactDynamicType(CMI->getOperand(), CHA)) {
|
|
if (ExactTy == CMI->getOperand()->getType())
|
|
return canDevirtualizeClassMethod(AI, CD);
|
|
}
|
|
}
|
|
|
|
if (isa<SuperMethodInst>(AI.getCallee())) {
|
|
auto Instance = AI.getArguments().back();
|
|
auto ClassType = getSelfInstanceType(Instance->getType().getASTType());
|
|
auto *CD = ClassType.getClassOrBoundGenericClass();
|
|
|
|
return canDevirtualizeClassMethod(AI, CD);
|
|
}
|
|
|
|
return false;
|
|
} |