| //===--- 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/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/InstructionUtils.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/SILOptimizer/Analysis/ClassHierarchyAnalysis.h" |
| #include "swift/SILOptimizer/Utils/InstOptUtils.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 &module, |
| 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 applySite 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 applySite, CanType classType, |
| ClassDecl *cd, |
| ClassHierarchyAnalysis *cha) { |
| if (cd && cd->isFinal()) |
| return true; |
| |
| const DeclContext *dc = applySite.getModule().getAssociatedContext(); |
| |
| // Without an associated context we cannot perform any |
| // access-based optimizations. |
| if (!dc) |
| return false; |
| |
| auto *cmi = cast<MethodInst>(applySite.getCallee()); |
| |
| if (!calleesAreStaticallyKnowable(applySite.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, applySite.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 &module, |
| ClassHierarchyAnalysis *cha) { |
| const DeclContext *dc = module.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 (!module.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 (!module.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 instance, |
| ClassHierarchyAnalysis *cha) { |
| auto *f = instance->getFunction(); |
| auto &module = f->getModule(); |
| |
| while (instance) { |
| instance = stripCasts(instance); |
| |
| if (isa<AllocRefInst>(instance) || isa<MetatypeInst>(instance)) { |
| if (instance->getType().getASTType()->hasDynamicSelfType()) |
| return SILValue(); |
| return instance; |
| } |
| |
| auto *arg = dyn_cast<SILArgument>(instance); |
| 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, module, cha)) |
| break; |
| return arg; |
| } |
| |
| // Traverse the chain of predecessors. |
| if (isa<BranchInst>(singlePred->getTerminator()) |
| || isa<CondBranchInst>(singlePred->getTerminator())) { |
| instance = 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 instance; |
| } |
| |
| 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 instance, |
| ClassHierarchyAnalysis *cha, |
| bool forUnderlyingObject) { |
| auto *f = instance->getFunction(); |
| auto &module = 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(instance); |
| |
| 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, module, 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 instance, |
| ClassHierarchyAnalysis *cha) { |
| return getExactDynamicType(instance, 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 &module, CanSILFunctionType baseCalleeType, |
| CanType derivedSelfType, FullApplySite applySite) { |
| |
| // 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( |
| module.getSwiftModule(), baseClassDecl); |
| } |
| |
| SubstitutionMap origSubMap = applySite.getSubstitutionMap(); |
| |
| Type calleeSelfType = |
| applySite.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 &builder, SILLocation loc, |
| ApplyInst *oldAI, SILValue newFn, |
| SubstitutionMap newSubs, |
| ArrayRef<SILValue> newArgs, |
| ArrayRef<SILValue> newArgBorrows) { |
| auto *newAI = |
| builder.createApply(loc, newFn, newSubs, newArgs, oldAI->isNonThrowing()); |
| |
| if (!newArgBorrows.empty()) { |
| for (SILValue arg : newArgBorrows) { |
| builder.createEndBorrow(loc, arg); |
| } |
| } |
| |
| // Check if any casting is required for the return value. |
| SILValue resultValue = castValueToABICompatibleType( |
| &builder, loc, newAI, newAI->getType(), oldAI->getType()); |
| |
| oldAI->replaceAllUsesWith(resultValue); |
| return newAI; |
| } |
| |
| static TryApplyInst *replaceTryApplyInst(SILBuilder &builder, 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 = builder.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 = |
| builder.createTryApply(loc, newFn, newSubs, newArgs, resultBB, errorBB); |
| |
| if (!newArgBorrows.empty()) { |
| builder.setInsertionPoint(normalBB->begin()); |
| for (SILValue arg : newArgBorrows) { |
| builder.createEndBorrow(loc, arg); |
| } |
| builder.setInsertionPoint(errorBB->begin()); |
| for (SILValue arg : newArgBorrows) { |
| builder.createEndBorrow(loc, arg); |
| } |
| } |
| |
| if (resultCastRequired) { |
| builder.setInsertionPoint(resultBB); |
| |
| SILValue resultValue = resultBB->getArgument(0); |
| resultValue = castValueToABICompatibleType(&builder, loc, resultValue, |
| newResultTy, oldResultTy); |
| builder.createBranch(loc, normalBB, {resultValue}); |
| } |
| |
| builder.setInsertionPoint(normalBB->begin()); |
| return newTAI; |
| } |
| |
| static BeginApplyInst * |
| replaceBeginApplyInst(SILBuilder &builder, SILLocation loc, |
| BeginApplyInst *oldBAI, SILValue newFn, |
| SubstitutionMap newSubs, ArrayRef<SILValue> newArgs, |
| ArrayRef<SILValue> newArgBorrows) { |
| auto *newBAI = builder.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( |
| &builder, 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 borrowBuilder(use->getUser(), |
| builder.getBuilderContext()); |
| for (SILValue borrow : newArgBorrows) { |
| borrowBuilder.createEndBorrow(loc, borrow); |
| } |
| } |
| |
| return newBAI; |
| } |
| |
| static PartialApplyInst * |
| replacePartialApplyInst(SILBuilder &builder, SILLocation loc, |
| PartialApplyInst *oldPAI, SILValue newFn, |
| SubstitutionMap newSubs, ArrayRef<SILValue> newArgs) { |
| auto convention = |
| oldPAI->getType().getAs<SILFunctionType>()->getCalleeConvention(); |
| auto *newPAI = |
| builder.createPartialApply(loc, newFn, newSubs, newArgs, convention); |
| |
| // Check if any casting is required for the partially-applied function. |
| SILValue resultValue = castValueToABICompatibleType( |
| &builder, loc, newPAI, newPAI->getType(), oldPAI->getType()); |
| oldPAI->replaceAllUsesWith(resultValue); |
| |
| return newPAI; |
| } |
| |
| static ApplySite replaceApplySite(SILBuilder &builder, 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(builder, loc, oldAI, newFn, newSubs, newArgs, |
| newArgBorrows); |
| } |
| case ApplySiteKind::TryApplyInst: { |
| auto *oldTAI = cast<TryApplyInst>(oldAS); |
| return replaceTryApplyInst(builder, loc, oldTAI, newFn, newSubs, newArgs, |
| conv, newArgBorrows); |
| } |
| case ApplySiteKind::BeginApplyInst: { |
| auto *oldBAI = dyn_cast<BeginApplyInst>(oldAS); |
| return replaceBeginApplyInst(builder, loc, oldBAI, newFn, newSubs, newArgs, |
| newArgBorrows); |
| } |
| case ApplySiteKind::PartialApplyInst: { |
| assert(newArgBorrows.empty()); |
| auto *oldPAI = cast<PartialApplyInst>(oldAS); |
| return replacePartialApplyInst(builder, loc, oldPAI, newFn, newSubs, |
| newArgs); |
| } |
| } |
| llvm_unreachable("covered switch"); |
| } |
| |
| /// 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 &module, 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 module.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 applySite 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 applySite, ClassDecl *cd, |
| OptRemark::Emitter *ore, |
| bool isEffectivelyFinalMethod) { |
| |
| LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize : " |
| << *applySite.getInstruction()); |
| |
| SILModule &module = applySite.getModule(); |
| |
| auto *mi = cast<MethodInst>(applySite.getCallee()); |
| |
| // Find the implementation of the member which should be invoked. |
| auto *f = getTargetClassMethod(module, 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 && applySite.getFunction()->isSerialized()) { |
| LLVM_DEBUG(llvm::dbgs() << " FAIL: Could not optimize function " |
| "because it is an effectively-final inlinable: " |
| << applySite.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 (applySite.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>(applySite.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 applySite 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 applySite, |
| SILValue classOrMetatype, |
| ClassDecl *cd, |
| OptRemark::Emitter *ore) { |
| LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize : " |
| << *applySite.getInstruction()); |
| |
| SILModule &module = applySite.getModule(); |
| auto *mi = cast<MethodInst>(applySite.getCallee()); |
| |
| auto *f = getTargetClassMethod(module, cd, mi); |
| |
| CanSILFunctionType genCalleeType = f->getLoweredFunctionType(); |
| |
| SubstitutionMap subs = getSubstitutionsForCallee( |
| module, genCalleeType, classOrMetatype->getType().getASTType(), |
| applySite); |
| CanSILFunctionType substCalleeType = genCalleeType; |
| if (genCalleeType->isPolymorphic()) |
| substCalleeType = genCalleeType->substGenericArgs(module, subs); |
| SILFunctionConventions substConv(substCalleeType, module); |
| |
| SILBuilderWithScope builder(applySite.getInstruction()); |
| SILLocation loc = applySite.getLoc(); |
| auto *fri = builder.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 = applySite.getIndirectSILResults().begin(); |
| for (auto resultTy : substConv.getIndirectSILResultTypes()) { |
| newArgs.push_back(castValueToABICompatibleType( |
| &builder, loc, *indirectResultArgIter, indirectResultArgIter->getType(), |
| resultTy)); |
| ++indirectResultArgIter; |
| } |
| |
| auto paramArgIter = applySite.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 (builder.hasOwnership() && arg->getType().isObject() |
| && arg.getOwnershipKind() == ValueOwnershipKind::Owned |
| && param.isGuaranteed()) { |
| SILBuilderWithScope borrowBuilder(applySite.getInstruction(), builder); |
| arg = borrowBuilder.createBeginBorrow(loc, arg); |
| newArgBorrows.push_back(arg); |
| } |
| arg = castValueToABICompatibleType(&builder, loc, arg, |
| paramArgIter->getType(), paramType); |
| newArgs.push_back(arg); |
| ++paramArgIter; |
| } |
| ApplySite newAS = replaceApplySite(builder, loc, applySite, 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", |
| *applySite.getInstruction()) |
| << "Devirtualized call to class method " << NV("Method", f); |
| }); |
| NumClassDevirt++; |
| |
| return newAI; |
| } |
| |
| FullApplySite swift::tryDevirtualizeClassMethod(FullApplySite applySite, |
| SILValue classInstance, |
| ClassDecl *cd, |
| OptRemark::Emitter *ore, |
| bool isEffectivelyFinalMethod) { |
| if (!canDevirtualizeClassMethod(applySite, cd, ore, isEffectivelyFinalMethod)) |
| return FullApplySite(); |
| return devirtualizeClassMethod(applySite, 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.isNull()) |
| 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->getRootConformance(); |
| 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 applySite, |
| SILFunction *f, |
| ProtocolConformanceRef cRef) { |
| auto witnessFnTy = f->getLoweredFunctionType(); |
| assert(witnessFnTy->getRepresentation() == |
| SILFunctionTypeRepresentation::WitnessMethod); |
| |
| auto requirementSig = applySite.getOrigCalleeType()->getGenericSignature(); |
| auto witnessThunkSig = witnessFnTy->getGenericSignature(); |
| |
| SubstitutionMap origSubs = applySite.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 applySite, SILFunction *f, |
| ProtocolConformanceRef cRef, |
| 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 = applySite.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, applySite, f, cRef); |
| |
| // 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 argBuilder(applySite.getInstruction()); |
| SILFunctionConventions substConv(substCalleeCanType, module); |
| unsigned substArgIdx = applySite.getCalleeArgIndexOfFirstAppliedArg(); |
| for (auto arg : applySite.getArguments()) { |
| auto paramInfo = substConv.getSILArgumentConvention(substArgIdx); |
| auto paramType = substConv.getSILArgumentType(substArgIdx++); |
| if (arg->getType() != paramType) { |
| if (argBuilder.hasOwnership() |
| && applySite.getKind() != ApplySiteKind::PartialApplyInst |
| && arg->getType().isObject() |
| && arg.getOwnershipKind() == ValueOwnershipKind::Owned |
| && paramInfo.isGuaranteedConvention()) { |
| SILBuilderWithScope borrowBuilder(applySite.getInstruction(), |
| argBuilder); |
| arg = borrowBuilder.createBeginBorrow(applySite.getLoc(), arg); |
| borrowedArgs.push_back(arg); |
| } |
| arg = castValueToABICompatibleType(&argBuilder, applySite.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 applyBuilder(applySite.getInstruction()); |
| SILLocation loc = applySite.getLoc(); |
| auto *fri = applyBuilder.createFunctionRefFor(loc, f); |
| |
| ApplySite newApplySite = |
| replaceApplySite(applyBuilder, loc, applySite, fri, subMap, arguments, |
| substConv, borrowedArgs); |
| |
| if (ore) |
| ore->emit([&]() { |
| using namespace OptRemark; |
| return RemarkPassed("WitnessMethodDevirtualized", |
| *applySite.getInstruction()) |
| << "Devirtualized call to " << NV("Method", f); |
| }); |
| NumWitnessDevirt++; |
| return newApplySite; |
| } |
| |
| static bool canDevirtualizeWitnessMethod(ApplySite applySite) { |
| SILFunction *f; |
| SILWitnessTable *wt; |
| |
| auto *wmi = cast<WitnessMethodInst>(applySite.getCallee()); |
| |
| std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable( |
| wmi->getConformance(), wmi->getMember()); |
| |
| if (!f) |
| return false; |
| |
| if (applySite.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>(applySite.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 applySite, |
| OptRemark::Emitter *ore) { |
| if (!canDevirtualizeWitnessMethod(applySite)) |
| return ApplySite(); |
| |
| SILFunction *f; |
| SILWitnessTable *wt; |
| |
| auto *wmi = cast<WitnessMethodInst>(applySite.getCallee()); |
| |
| std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable( |
| wmi->getConformance(), wmi->getMember()); |
| |
| return devirtualizeWitnessMethod(applySite, 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 applySite, |
| ClassHierarchyAnalysis *cha, |
| OptRemark::Emitter *ore) { |
| LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize: " |
| << *applySite.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>(applySite.getCallee())) |
| return tryDevirtualizeWitnessMethod(applySite, ore); |
| |
| // TODO: check if we can also de-virtualize partial applies of class methods. |
| FullApplySite fas = FullApplySite::isa(applySite.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 applySite, |
| ClassHierarchyAnalysis *cha) { |
| LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize: " |
| << *applySite.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>(applySite.getCallee())) |
| return canDevirtualizeWitnessMethod(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>(applySite.getCallee())) { |
| auto instance = stripUpCasts(cmi->getOperand()); |
| auto classType = getSelfInstanceType(instance->getType().getASTType()); |
| auto *cd = classType.getClassOrBoundGenericClass(); |
| |
| if (isEffectivelyFinalMethod(applySite, classType, cd, cha)) |
| return canDevirtualizeClassMethod(applySite, 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(applySite, cd); |
| |
| if (auto exactTy = getExactDynamicType(cmi->getOperand(), cha)) { |
| if (exactTy == cmi->getOperand()->getType()) |
| return canDevirtualizeClassMethod(applySite, cd); |
| } |
| } |
| |
| if (isa<SuperMethodInst>(applySite.getCallee())) { |
| auto instance = applySite.getArguments().back(); |
| auto classType = getSelfInstanceType(instance->getType().getASTType()); |
| auto *cd = classType.getClassOrBoundGenericClass(); |
| |
| return canDevirtualizeClassMethod(applySite, cd); |
| } |
| |
| return false; |
| } |