//===--- 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(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(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: 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(S) || isa(S)) { if (S->getType().getASTType()->hasDynamicSelfType()) return SILValue(); return S; } auto *Arg = dyn_cast(S); if (!Arg) break; auto *SinglePred = Arg->getParent()->getSinglePredecessorBlock(); if (!SinglePred) { if (!isa(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(SinglePred->getTerminator()) || isa(SinglePred->getTerminator())) { S = cast(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(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 WorkList; // The detected type of the underlying object. SILType ResultType; // Set of processed values. llvm::SmallSet 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(V) || isa(V)) { if (ResultType && ResultType != V->getType()) return SILType(); ResultType = V->getType(); continue; } if (isa(V)) { if (ResultType && ResultType != V->getType()) return SILType(); ResultType = V->getType(); continue; } if (isa(V) || isa(V) || isa(V)) { if (ResultType && ResultType != V->getType()) return SILType(); ResultType = V->getType(); continue; } if (ForUnderlyingObject) { if (isa(V)) { if (ResultType && ResultType != V->getType()) return SILType(); ResultType = V->getType(); continue; } } auto Arg = dyn_cast(V); if (!Arg) { // We don't know what it is. return SILType(); } if (auto *FArg = dyn_cast(Arg)) { // Bail on metatypes for now. if (FArg->getType().is()) { 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(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 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()) 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()) derivedClass = metatypeType->getInstanceType(); baseSubMap = derivedClass->getContextSubstitutionMap( M.getSwiftModule(), baseClassDecl); } SubstitutionMap origSubMap = AI.getSubstitutionMap(); Type calleeSelfType = AI.getOrigCalleeType()->getSelfParameter().getType(); if (auto metatypeType = calleeSelfType->getAs()) 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 NewArgs, ArrayRef 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 NewArgs, SILFunctionConventions Conv, ArrayRef 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 NewArgs, ArrayRef 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 NewArgs) { auto Convention = OldPAI->getType().getAs()->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 NewArgs, SILFunctionConventions Conv, ArrayRef NewArgBorrows) { switch (OldAS.getKind()) { case ApplySiteKind::ApplyInst: { auto *OldAI = cast(OldAS); return replaceApplyInst(B, Loc, OldAI, NewFn, NewSubs, NewArgs, NewArgBorrows); } case ApplySiteKind::TryApplyInst: { auto *OldTAI = cast(OldAS); return replaceTryApplyInst(B, Loc, OldTAI, NewFn, NewSubs, NewArgs, Conv, NewArgBorrows); } case ApplySiteKind::BeginApplyInst: { auto *OldBAI = dyn_cast(OldAS); return replaceBeginApplyInst(B, Loc, OldBAI, NewFn, NewSubs, NewArgs, NewArgBorrows); } case ApplySiteKind::PartialApplyInst: { assert(NewArgBorrows.empty()); auto *OldPAI = cast(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(MI) || isa(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(ClassOrMetatypeType)) ClassOrMetatypeType = MetaType.getInstanceType(); if (auto SelfType = dyn_cast(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(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(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(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 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 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: /// /// - Witness thunk generic signature: /// /// - Remapped substitutions: /// [W0 := X0, W1 := X1, ...] /// /// - (Class witness thunk) Original substitutions: /// [Self := C, T0 := X0, T1 := X1, ...] /// - Requirement generic signature: /// /// - Witness thunk generic signature: /// , B0, B1, W0, W1, ...> /// - Remapped substitutions: /// [Self := C, B0 := A0, B1 := A1, W0 := X0, W1 := X1] /// /// - (Default witness thunk) Original substitutions: /// [Self := ConcreteType, R0 := X0, R1 := X1, ...] /// - Requirement generic signature: /// /// - Witness thunk generic signature: /// /// - 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(); 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 Arguments; SmallVector 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(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(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(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, #LogicValue.boolValue!getter.1 // %9 = apply %8(%6#1) : ... // if (isa(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(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(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, #LogicValue.boolValue!getter.1 // %9 = apply %8(%6#1) : ... // if (isa(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(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(AI.getCallee())) { auto Instance = AI.getArguments().back(); auto ClassType = getSelfInstanceType(Instance->getType().getASTType()); auto *CD = ClassType.getClassOrBoundGenericClass(); return canDevirtualizeClassMethod(AI, CD); } return false; }