//===-- Devirtualizer.cpp ------ Devirtualize virtual calls ---------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Devirtualizes virtual function calls into direct function calls. // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-devirtualizer" #include "swift/Basic/DemangleWrappers.h" #include "swift/Basic/Fallthrough.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/SILModule.h" #include "swift/SILAnalysis/ClassHierarchyAnalysis.h" #include "swift/SILPasses/Passes.h" #include "swift/SILPasses/Utils/Local.h" #include "swift/SILPasses/PassManager.h" #include "swift/SILPasses/Transforms.h" #include "swift/SILPasses/Utils/SILInliner.h" #include "swift/AST/ASTContext.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/Debug.h" #include "llvm/Support/CommandLine.h" using namespace swift; STATISTIC(NumInlineCaches, "Number of monomorphic inline caches inserted"); STATISTIC(NumDevirtualized, "Number of calls devirtualzied"); STATISTIC(NumAMI, "Number of witness_method devirtualzied"); // The number of subclasses to allow when placing polymorphic inline caches. static const int MaxNumPolymorphicInlineCaches = 6; //===----------------------------------------------------------------------===// // Class Method Optimization //===----------------------------------------------------------------------===// /// Is this an instruction kind which allows us to conclude definitively what /// the class decls of its results are. /// /// FIXME: We can expand this to use typed GEPs. static bool isClassDeclOracle(ValueKind Kind) { switch (Kind) { case ValueKind::AllocRefInst: case ValueKind::AllocRefDynamicInst: case ValueKind::MetatypeInst: return true; default: return false; } } /// \brief Recursively searches the ClassDecl for a class_method operand. /// Return the ClassDecl from the point of construction of \p S or null. static ClassDecl *getClassFromConstructor(SILValue S) { // First strip off casts. S = S.stripCasts(); // Then if S is not a class decl oracle, we can not ascertain what its results // "true" type is. if (!isClassDeclOracle(S->getKind())) return nullptr; // Look for a a static ClassTypes in AllocRefInst or MetatypeInst. if (AllocRefInst *ARI = dyn_cast(S)) return ARI->getType().getClassOrBoundGenericClass(); auto *MTI = dyn_cast(S); if (!MTI) return nullptr; CanType instTy = MTI->getType().castTo().getInstanceType(); return instTy.getClassOrBoundGenericClass(); } /// Return bound generic type for the unbound type Superclass, /// which is a superclass of a bound generic type BoundDerived /// (Base may be also the same as BoundDerived). static SILType bindSuperclass(Module *Module, CanType Superclass, SILType BoundDerived, ArrayRef& Subs) { SILType BoundSuperclass = BoundDerived; while (true) { auto CanBoundSuperclass = BoundSuperclass.getSwiftRValueType(); // Get declaration of the superclass. auto *Decl = CanBoundSuperclass.getNominalOrBoundGenericNominal(); // Obtain the unbound variant of the current superclass CanType UnboundSuperclass = Decl->getDeclaredType()->getCanonicalType(); // Check if we found a superclass we are looking for. if (UnboundSuperclass == Superclass) { auto BoundBaseType = dyn_cast(CanBoundSuperclass); if (BoundBaseType) // If it is a bound generic type, look up its substitutions Subs = BoundBaseType->getSubstitutions(Module, nullptr); else // If it is a non-bound type, there are no substitutions. Subs.empty(); return BoundSuperclass; } // Get the superclass of current one BoundSuperclass = BoundSuperclass.getSuperclass(nullptr); // Stop iteration if there is no superclass. if (!BoundSuperclass) return SILType(); } return SILType(); } /// \brief 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 Member is the class member to devirtualize. /// \p ClassInstance is the operand for the ClassMethodInst or an alternative /// reference (such as downcasted class reference). /// \p KnownClass (can be null) is a specific class type to devirtualize to. static bool devirtMethod(ApplyInst *AI, SILDeclRef Member, SILValue ClassInstance, ClassDecl *CD) { DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI); // First attempt to lookup the origin for our class method. The origin should // either be a metatype or an alloc_ref. DEBUG(llvm::dbgs() << " Origin: " << ClassInstance); assert(CD && "Invalid class type"); // Otherwise lookup from the module the least derived implementing method from // the module vtables. SILModule &Mod = AI->getModule(); // Find the implementation of the member which should be invoked. SILFunction *F = Mod.lookUpFunctionFromVTable(CD, Member); // If we do not find any such function, we have no function to devirtualize // to... so bail. if (!F) { DEBUG(llvm::dbgs() << " FAIL: Could not find matching VTable or " "vtable method for this class.\n"); return false; } // Ok, we found a function F that we can devirtualize our class method // to. We want to do everything on the substituted type in the case of // generics. Thus construct our subst callee type for F. SILModule &M = F->getModule(); CanSILFunctionType GenCalleeType = F->getLoweredFunctionType(); unsigned CalleeGenericParamsNum = 0; if (GenCalleeType->isPolymorphic()) CalleeGenericParamsNum = GenCalleeType->getGenericSignature() ->getGenericParams().size(); // Class F belongs to. CanType FSelfClass = GenCalleeType->getSelfParameter().getType(); SILType ClassInstanceType = ClassInstance.getType(); // *NOTE*: // Apply instruction substitutions are for the Member from a protocol or // class B, where this member was first defined, before it got overridden by // derived classes. // // The implementation F (the implementing method) which was found may have // a different set of generic parameters, e.g. because it is implemented by a // class D1 derived from B. // // ClassInstanceType may have a type different from both the type B // the Member belongs to and from the ClassInstanceType, e.g. if // ClassInstance is of a class D2, which is derived from D1, but does not // override the Member. // // As a result, substitutions provided by AI are for Member, whereas // substitutions in ClassInstanceType are for D2. And substitutions for D1 // are not available directly in a general case. Therefore, they have to // be computed. // // What we know for sure: // B is a superclass of D1 // D1 is a superclass of D2. // D1 can be the same as D2. D1 can be the same as B. // // So, substitutions from AI are for class B. // Substitutions for class D1 by means of bindSuperclass(), which starts // with a bound type ClassInstanceType and checks its superclasses until it // finds a bound superclass matching D1 and returns its substitutions. ArrayRef Substitutions; SILType FSelfSubstType; if (GenCalleeType->isPolymorphic()) { // Declaration of the class F belongs to. if (auto *FSelfTypeDecl = FSelfClass.getNominalOrBoundGenericNominal()) { // Get the unbound generic type F belongs to. CanType FSelfGenericType = FSelfTypeDecl->getDeclaredType()->getCanonicalType(); assert((isa(ClassInstanceType.getSwiftRValueType()) || isa(ClassInstanceType.getSwiftRValueType())) && "Self type should be either a bound generic type" "or a non-generic type"); assert((isa(FSelfGenericType) || isa(FSelfGenericType)) && "Method implementation self type should be generic"); // We know that ClassInstanceType is derived from FSelfGenericType. // We need to determine the proper substitutions for FGenericSILClass // based on the bound generic type of ClassInstanceType. FSelfSubstType = bindSuperclass(AI->getModule().getSwiftModule(), FSelfGenericType, ClassInstanceType, Substitutions); // Bail if it was not possible to determine the bound generic class. if (FSelfSubstType == SILType()) { return false; } if (!isa(ClassInstanceType.getSwiftRValueType()) && CalleeGenericParamsNum && Substitutions.empty()) // If ClassInstance is not a bound generic type, try to derive // substitutions from the apply instruction. Substitutions = AI->getSubstitutions(); } else { // It is not a type or bound type. // It could be that GenCalleeType is generic, but its arguments cannot // be derived from the type of self. In this case, we can try to // approach it from another end and take the AI substitutions. Substitutions = AI->getSubstitutions(); } } // If implementing method is not polymorphic, there is no need to // use any substitutions. if (CalleeGenericParamsNum == 0 && !Substitutions.empty()) Substitutions = {}; else if (CalleeGenericParamsNum != Substitutions.size()) // Bail if the number of generic parameters of the callee does not match // the number of substitutions, because we don't know how to handle this. return false; CanSILFunctionType SubstCalleeType = GenCalleeType->substGenericArgs(M, M.getSwiftModule(), Substitutions); // If F's this pointer has a different type from CMI's operand and the // "this" pointer type is a super class of the CMI's operand, insert an // upcast. auto paramTypes = SubstCalleeType->getParameterSILTypesWithoutIndirectResult(); // We should always have a this pointer. Assert on debug builds, return // nullptr on release builds. assert(!paramTypes.empty() && "Must have a this pointer when calling a class method inst."); if (paramTypes.empty()) return false; // Grab the self type from the function ref and the self type from the class // method inst. SILType FuncSelfTy = paramTypes[paramTypes.size() - 1]; SILType OriginTy = ClassInstance.getType(); SILBuilderWithScope<16> B(AI); // Then compare the two types and if they are unequal... if (FuncSelfTy != OriginTy) { if (ClassInstance.stripUpCasts().getType().getAs()) { auto &Module = AI->getModule(); (void) Module; assert(FuncSelfTy.getMetatypeInstanceType(Module). isSuperclassOf(OriginTy.getMetatypeInstanceType(Module)) && "Can not call a class method" " on a non-subclass of the class_methods class."); } else { assert(FuncSelfTy.isSuperclassOf(OriginTy) && "Can not call a class method" " on a non-subclass of the class_methods class."); } // Otherwise, upcast origin to the appropriate type. ClassInstance = B.createUpcast(AI->getLoc(), ClassInstance, FuncSelfTy); } // Success! Perform the devirtualization. FunctionRefInst *FRI = B.createFunctionRef(AI->getLoc(), F); // Construct a new arg list. First process all non-self operands, ref, addr // casting them to the appropriate types for F so that we allow for covariant // indirect return types and contravariant arguments. llvm::SmallVector NewArgs; auto Args = AI->getArguments(); auto allParamTypes = SubstCalleeType->getParameterSILTypes(); // For each old argument Op... for (unsigned i = 0, e = Args.size() - 1; i != e; ++i) { SILValue Op = Args[i]; SILType OpTy = Op.getType(); SILType FOpTy = allParamTypes[i]; // If the type matches the type for the given parameter in F, just add it to // our arg list and continue. if (OpTy == FOpTy) { NewArgs.push_back(Op); continue; } // Otherwise we have either a covariant return type or a contravariant // argument type. Cast it appropriately. assert((OpTy.isAddress() || OpTy.isHeapObjectReferenceType()) && "Only addresses and refs can have their types changed due to " "covariant return types or contravariant argument types."); // If OpTy is an address, perform an unchecked_addr_cast. if (OpTy.isAddress()) { NewArgs.push_back(B.createUncheckedAddrCast(AI->getLoc(), Op, FOpTy)); } else { // Otherwise perform a ref cast. NewArgs.push_back(B.createUncheckedRefCast(AI->getLoc(), Op, FOpTy)); } } // Add in self to the end. NewArgs.push_back(ClassInstance); // If we have a direct return type, make sure we use the subst callee return // type. If we have an indirect return type, AI's return type of the empty // tuple should be ok. SILType ReturnType = AI->getType(); if (!SubstCalleeType->hasIndirectResult()) { ReturnType = SubstCalleeType->getSILResult(); } SILType SubstCalleeSILType = SILType::getPrimitiveObjectType(SubstCalleeType); ApplyInst *NewAI = B.createApply(AI->getLoc(), FRI, SubstCalleeSILType, ReturnType, Substitutions, NewArgs, FRI->getReferencedFunction()->isTransparent()); // If our return type differs from AI's return type, then we know that we have // a covariant return type. Cast it before we RAUW. This can not happen if (ReturnType != AI->getType()) { assert((ReturnType.isAddress() || ReturnType.isHeapObjectReferenceType()) && "Only addresses and refs can have their types changed due to " "covariant return types or contravariant argument types."); SILValue CastedAI = NewAI; if (ReturnType.isAddress()) { CastedAI = B.createUncheckedAddrCast(AI->getLoc(), NewAI, AI->getType()); } else { CastedAI = B.createUncheckedRefCast(AI->getLoc(), NewAI, AI->getType()); } SILValue(AI).replaceAllUsesWith(CastedAI); } else { AI->replaceAllUsesWith(NewAI); } AI->eraseFromParent(); DEBUG(llvm::dbgs() << " SUCCESS: " << F->getName() << "\n"); NumDevirtualized++; return true; } //===----------------------------------------------------------------------===// // Witness Method Optimization //===----------------------------------------------------------------------===// static SILValue upcastArgument(SILValue Arg, SILType SuperTy, ApplyInst *AI) { SILType ArgTy = Arg.getType(); if (dyn_cast(Arg) || dyn_cast(Arg)) { // In case of metatypes passed as parameters, we need to upcast to a // metatype of a superclass. auto &Module = AI->getModule(); (void) Module; assert (SuperTy.getSwiftRValueType()->is() && "super type should be a metatype"); assert(SuperTy.getMetatypeInstanceType(Module) .isSuperclassOf(ArgTy.getMetatypeInstanceType(Module)) && "Should only create upcasts for sub class devirtualization."); } else { assert(SuperTy.isSuperclassOf(ArgTy) && "Should only create upcasts for sub class devirtualization."); (void)ArgTy; } Arg = SILBuilderWithScope<1>(AI).createUpcast(AI->getLoc(), Arg, SuperTy); return Arg; } /// Devirtualize the application of a witness_method. Replace this application /// by invocation of a witness thunk which was found by findFuncInWitnessTable. bool devirtulizeWitness(ApplyInst *AI, SILFunction *F, ArrayRef Subs) { // 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. SmallVector NewSubstList(Subs.begin(), Subs.end()); // Add the non-self-derived substitutions from the original application. for (auto &origSub : AI->getSubstitutionsWithoutSelfSubstitution()) { if (!origSub.getArchetype()->isSelfDerived()) NewSubstList.push_back(origSub); } // 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, Module.getSwiftModule(), NewSubstList); // Collect arguments from the apply instruction. auto Arguments = SmallVector(); auto ParamTypes = SubstCalleeCanType->getParameterSILTypes(); // Type of the current parameter being processed auto ParamType = ParamTypes.begin(); // Iterate over the non self arguments and add them to the // new argument list, upcasting when required. for (SILValue A : AI->getArguments()) { if (A.getType() != *ParamType) { // Upcast argument A = upcastArgument(A, *ParamType, AI); } Arguments.push_back(A); ++ParamType; } // Replace old apply instruction by a new apply instruction that invokes // the witness thunk. SILBuilderWithScope<2> Builder(AI); SILLocation Loc = AI->getLoc(); FunctionRefInst *FRI = Builder.createFunctionRef(Loc, F); auto SubstCalleeSILType = SILType::getPrimitiveObjectType(SubstCalleeCanType); auto ResultSILType = SubstCalleeCanType->getSILResult(); auto *SAI = Builder.createApply(Loc, FRI, SubstCalleeSILType, ResultSILType, NewSubstList, Arguments, FRI->getReferencedFunction()->isTransparent()); AI->replaceAllUsesWith(SAI); AI->eraseFromParent(); NumAMI++; return true; } /// Devirtualize apply instructions that call witness_method instructions: /// /// %8 = witness_method $Optional, #LogicValue.boolValue!getter.1 /// %9 = apply %8(%6#1) : ... /// static bool optimizeWitnessMethod(ApplyInst *AI, WitnessMethodInst *WMI) { // Use findFuncInWitnessTable to walk the inherited/specialized conformances // chain until it finds a NormalProtocolConformance. If such a conformance // is found, it would return the witness thunk as F, the corresponding // WitnessTable and a set of substitutions. // // Once we have this information, we essentially only need to replace the // current apply instruction by a new apply instruction that would invoke the // witness thunk and use proper substitutions. Creation of the new apply // instruction may also involve upcasting the arguments to the types required // by the witness thunk. // // Later on, the invocation of this witness thunk will be inlined by the // compiler, which would create an opportunity for a speculative // devirtualization of class_method invocations inside the body of this thunk. ProtocolConformance *C = WMI->getConformance(); if (!C) { DEBUG(llvm::dbgs() << " FAIL: Null conformance.\n"); return false; } // Lookup the witness method in the witness tables. SILFunction *F; ArrayRef Subs; SILWitnessTable *WT; std::tie(F, WT, Subs) = AI->getModule().findFuncInWitnessTable(C, WMI->getMember()); if (!F) { assert(!WT && "WitnessTable should always be null if F is."); DEBUG(llvm::dbgs() << " FAIL: Did not find a matching witness " "table or witness method.\n"); return false; } assert(WT && "WitnessTable should never be null if F is not."); return devirtulizeWitness(AI, F, Subs); } //===----------------------------------------------------------------------===// // Top Level Driver //===----------------------------------------------------------------------===// /// Return the final class decl based on access control information. static ClassDecl *getClassFromAccessControl(ClassMethodInst *CMI) { const DeclContext *associatedDC = CMI->getModule().getAssociatedContext(); if (!associatedDC) { // Without an associated context, we can't perform any access-based // optimizations. return nullptr; } SILDeclRef Member = CMI->getMember(); FuncDecl *FD = Member.getFuncDecl(); SILType ClassType = CMI->getOperand().stripUpCasts().getType(); ClassDecl *CD = ClassType.getClassOrBoundGenericClass(); // Only handle valid non-dynamic non-overridden members. if (!CD || !FD || FD->isInvalid() || FD->isDynamic() || FD->isOverridden()) return nullptr; // Only handle members defined within the SILModule's associated context. if (!FD->isChildContextOf(associatedDC)) return nullptr; if (!FD->hasAccessibility()) return nullptr; // Only consider 'private' members, unless we are in whole-module compilation. switch (FD->getAccessibility()) { case Accessibility::Public: return nullptr; case Accessibility::Internal: if (!CMI->getModule().isWholeModule()) return nullptr; break; case Accessibility::Private: break; } Type selfTypeInMember = FD->getDeclContext()->getDeclaredTypeInContext(); return selfTypeInMember->getClassOrBoundGenericClass(); } static bool optimizeApplyInst(ApplyInst *AI) { DEBUG(llvm::dbgs() << " Trying to optimize ApplyInst : " << *AI); // Devirtualize apply instructions that call witness_method instructions: // // %8 = witness_method $Optional, #LogicValue.boolValue!getter.1 // %9 = apply %8(%6#1) : ... // if (auto *AMI = dyn_cast(AI->getCallee())) return optimizeWitnessMethod(AI, AMI); /// 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 : $@cc(method) @thin ... /// \endcode /// /// or /// /// %XX = metatype $... /// %YY = class_method %XX : ... /// /// into /// /// %YY = function_ref @... if (auto *CMI = dyn_cast(AI->getCallee())) { // Check if the class member is known to be final. if (ClassDecl *C = getClassFromAccessControl(CMI)) return devirtMethod(AI, CMI->getMember(), CMI->getOperand(), C); // Try to search for the point of construction. if (ClassDecl *C = getClassFromConstructor(CMI->getOperand())) return devirtMethod(AI, CMI->getMember(), CMI->getOperand().stripUpCasts(), C); } return false; } namespace { class SILDevirtualizationPass : public SILModuleTransform { public: virtual ~SILDevirtualizationPass() {} /// The entry point to the transformation. virtual void run() { bool Changed = false; // Perform devirtualization locally and compute potential polymorphic // arguments for all existing functions. for (auto &F : *getModule()) { DEBUG(llvm::dbgs() << "*** Devirtualizing Function: " << demangle_wrappers::demangleSymbolAsString(F.getName()) << "\n"); for (auto &BB : F) { for (auto II = BB.begin(), IE = BB.end(); II != IE;) { ApplyInst *AI = dyn_cast(&*II); ++II; if (!AI) continue; Changed |= optimizeApplyInst(AI); } } DEBUG(llvm::dbgs() << "\n"); } if (Changed) { PM->scheduleAnotherIteration(); invalidateAnalysis(SILAnalysis::InvalidationKind::CallGraph); } } StringRef getName() override { return "Devirtualization"; } }; } // end anonymous namespace SILTransform *swift::createDevirtualizer() { return new SILDevirtualizationPass(); } // A utility function for cloning the apply instruction. static ApplyInst *CloneApply(ApplyInst *AI, SILBuilder &Builder) { // Clone the Apply. auto Args = AI->getArguments(); SmallVector Ret(Args.size()); for (unsigned i = 0, e = Args.size(); i != e; ++i) Ret[i] = Args[i]; auto NAI = Builder.createApply(AI->getLoc(), AI->getCallee(), AI->getSubstCalleeSILType(), AI->getType(), AI->getSubstitutions(), Ret, AI->isTransparent()); NAI->setDebugScope(AI->getDebugScope()); return NAI; } /// Insert monomorphic inline caches for a specific class type \p SubClassTy. static ApplyInst* insertMonomorphicInlineCaches(ApplyInst *AI, SILType SubClassTy) { ClassMethodInst *CMI = cast(AI->getCallee()); SILValue ClassInstance = CMI->getOperand(); ClassDecl *CD = SubClassTy.getClassOrBoundGenericClass(); // If method implementation for a SubClassTy is not known, // bail out early as we won't be able to devirtualize it. // This may happen e.g in case of a -primary-file // compilations, where information about methods implemented // in other files is unavailable. // Early exit guarantees that we do not create two // basic blocks which both perform virtual calls of a method. auto &Mod = AI->getModule(); auto *Method = Mod.lookUpFunctionFromVTable(CD, CMI->getMember()); if (!Method) return nullptr; bool IsValueMetatype = false; SILType RealSubClassTy = SubClassTy; if (isa(ClassInstance.stripUpCasts())) { // Convert this type to its metatype type. Lowering::TypeConverter TC(AI->getModule()); RealSubClassTy = TC.getLoweredLoadableType(CD->getType()); IsValueMetatype = true; } // Create a diamond shaped control flow and a checked_cast_branch // instruction that checks the exact type of the object. // This cast selects between two paths: one that calls the slow dynamic // dispatch and one that calls the specific method. SILBasicBlock::iterator It = AI; SILFunction *F = AI->getFunction(); SILBasicBlock *Entry = AI->getParent(); // Iden is the basic block containing the direct call. SILBasicBlock *Iden = F->createBasicBlock(); // Virt is the block containing the slow virtual call. SILBasicBlock *Virt = F->createBasicBlock(); Iden->createBBArg(RealSubClassTy); SILBasicBlock *Continue = Entry->splitBasicBlock(It); SILBuilderWithScope<> Builder(Entry, AI->getDebugScope()); // Create the checked_cast_branch instruction that checks at runtime if the // class instance is identical to the SILType. if (!IsValueMetatype) assert(SubClassTy.getClassOrBoundGenericClass() && "Dest type must be a class type"); It = Builder.createCheckedCastBranch(AI->getLoc(), /*exact*/ true, ClassInstance, RealSubClassTy, Iden, Virt); SILBuilder VirtBuilder(Virt); SILBuilder IdenBuilder(Iden); // This is the class reference downcasted into subclass SubClassTy. SILValue DownCastedClassInstance = Iden->getBBArg(0); // Try sinking the retain of the class instance into the diamond. This may // allow additional ARC optimizations on the fast path. if (It != Entry->begin()) { StrongRetainInst *SRI = dyn_cast(--It); // Try to skip another instruction, in case the class_method came first. if (!SRI && It != Entry->begin()) SRI = dyn_cast(--It); if (SRI && SRI->getOperand() == ClassInstance) { VirtBuilder.createStrongRetain(SRI->getLoc(), ClassInstance) ->setDebugScope(SRI->getDebugScope()); IdenBuilder.createStrongRetain(SRI->getLoc(), DownCastedClassInstance) ->setDebugScope(SRI->getDebugScope()); SRI->eraseFromParent(); } } // Copy the two apply instructions into the two blocks. ApplyInst *IdenAI = CloneApply(AI, IdenBuilder); ApplyInst *VirtAI = CloneApply(AI, VirtBuilder); // Create a PHInode for returning the return value from both apply // instructions. SILArgument *Arg = Continue->createBBArg(AI->getType()); IdenBuilder.createBranch(AI->getLoc(), Continue, ArrayRef(IdenAI)) ->setDebugScope(AI->getDebugScope()); VirtBuilder.createBranch(AI->getLoc(), Continue, ArrayRef(VirtAI)) ->setDebugScope(AI->getDebugScope()); // Remove the old Apply instruction. AI->replaceAllUsesWith(Arg); AI->eraseFromParent(); // Update the stats. NumInlineCaches++; // Devirtualize the apply instruction on the identical path. devirtMethod(IdenAI, CMI->getMember(), DownCastedClassInstance, CD); // Sink class_method instructions down to their single user. if (CMI->hasOneUse()) CMI->moveBefore(CMI->use_begin()->getUser()); return VirtAI; } /// \brief Returns true, if a method implementation to be called by the /// default case handler of a speculative devirtualization is statically /// known. This happens if it can be proven that generated /// checked_cast_br instructions cover all other possible cases. /// /// \p CHA class hierarchy analysis to be used /// \p AI invocation instruction /// \p CD static class of the instance whose method is being invoked /// \p Subs set of direct subclasses of this class static bool isDefaultCaseKnown(ClassHierarchyAnalysis *CHA, ApplyInst *AI, ClassDecl *CD, ClassHierarchyAnalysis::ClassList &Subs) { ClassMethodInst *CMI = cast(AI->getCallee()); auto *Method = CMI->getMember().getFuncDecl(); const DeclContext *DC = AI->getModule().getAssociatedContext(); // 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->hasAccessibility()) return false; // Only consider 'private' members, unless we are in whole-module compilation. switch (CD->getAccessibility()) { case Accessibility::Public: return false; case Accessibility::Internal: if (!AI->getModule().isWholeModule()) return false; break; case Accessibility::Private: break; } // 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. // First, analyze all direct subclasses. // We know that a dedicated checked_cast_br check is // generated for each direct subclass by insertInlineCaches. for (auto S : Subs) { // Check if the subclass overrides a method auto *FD = S->findOverridingDecl(Method); if (!FD) continue; if (CHA->hasKnownDirectSubclasses(S)) { // This subclass has its own subclasses and // they will use this implementation or provide // their own. In either case it is not covered by // checked_cast_br instructions generated by // insertInlineCaches. Therefore it increases // the number of remaining cases to be handled // by the default case handler. return false; } } // Then, analyze indirect subclasses. // Set of indirect subclasses for the class. auto &IndirectSubs = CHA->getIndirectSubClasses(CD); // Check if any indirect subclasses use an implementation // of the method different from the implementation in // the current class. If this is the case, then such // an indirect subclass would need a dedicated // checked_cast_br check to be devirtualized. But this is // not done by insertInlineCaches yet and therefore // such a subclass should be handled by the "default" // case handler, which essentially means that "default" // case cannot be devirtualized since it covers more // then one alternative. for (auto S : IndirectSubs) { auto *ImplFD = S->findImplementingMethod(Method); if (ImplFD != Method) { // Different implementation is used by a subclass. // Therefore, the default case is not known. return false; } } return true; } /// \brief Try to insert inline cahces for the call \p AI. This function /// returns true if a change was made. static bool insertInlineCaches(ApplyInst *AI, ClassHierarchyAnalysis *CHA) { ClassMethodInst *CMI = cast(AI->getCallee()); assert(CMI && "Invalid class method instruction"); SILValue ClassInstance = CMI->getOperand(); // The static type used by the class_method instruction // is the class which a given method belongs to. // Is either the class of the instance itself or one of its superclasses. // Therefore, strip all upcasts to get the real static type // of the instance. // Specifically, only the upcast to the static class which method belongs to // should be stripped. SILType InstanceType = ClassInstance.stripUpCasts().getType(); ClassDecl *CD = InstanceType.getClassOrBoundGenericClass(); if (auto *VMTI = dyn_cast(ClassInstance.stripUpCasts())) { CanType InstTy = VMTI->getType().castTo().getInstanceType(); CD = InstTy.getClassOrBoundGenericClass(); } // Check if it is legal to insert inline caches. if (!CD || CMI->isVolatile()) return false; if (ClassInstance.getType() != InstanceType) { // The implementation of a method to be invoked may actually // be defined by one of the superclasses. if (!ClassInstance.getType().isSuperclassOf(InstanceType)) return false; // ClassInstance and InstanceType should match for devirtMethod to work. ClassInstance = ClassInstance.stripUpCasts(); } if (!CHA->hasKnownDirectSubclasses(CD)) { DEBUG(llvm::dbgs() << "Inserting monomorphic inline caches for class " << CD->getName() << "\n"); return insertMonomorphicInlineCaches(AI, InstanceType); } // Collect the direct subclasses for the class. auto &Subs = CHA->getDirectSubClasses(CD); if (Subs.size() > MaxNumPolymorphicInlineCaches) { DEBUG(llvm::dbgs() << "Class " << CD->getName() << " has too many (" << Subs.size() << ") subclasses. Not inserting inline caches.\n"); return false; } DEBUG(llvm::dbgs() << "Class " << CD->getName() << " is a superclass. " "Inserting polymorphic inline caches.\n"); // Perform a speculative devirtualization of a method invocation. // It replaces an indirect class_method-based call by a code to perform // a direct call of the method implementation based on the dynamic class // of the instance. // // The code is generated according to the following principles: // // - For each direct subclass, a dedicated checked_cast_br instruction // is generated to check if a dynamic class of the instance is exactly // this subclass. // // - If this check succeeds, then it jumps to the code which performs a // direct call of a method implementation specific to this subclass. // // - If this check fails, then a different subclass is checked by means of // checked_cast_br in a similar way. // // - Finally, if the instance does not exactly match any of the direct // subclasses, the "default" case code is generated, which should handle // all remaining alternatives, i.e. it should be able to dispatch to any // possible remaining method implementations. Typically this is achieved by // using a class_method instruction, which performs an indirect invocation. // But if it can be proven that only one specific implementation of // a method will be always invoked by this code, then a class_method-based // call can be devirtualized and replaced by a more efficient direct // invocation of this specific method implementation. // // Remark: With the current implementation of a speculative devirtualization, // if devirtualization of the "default" case is possible, then it would // by construction directly invoke the implementation of the method // corresponding to the static type of the instance. This may change // in the future, if we start using PGO for ordering of checked_cast_br // checks. // TODO: The ordering of checks may benefit from using a PGO, because // the most probable alternatives could be checked first. // Number of subclasses which cannot be handled by checked_cast_br checks. int NotHandledSubsNum = 0; // True if any instructions were changed or generated. bool Changed = false; for (auto S : Subs) { DEBUG(llvm::dbgs() << "Inserting a cache for class " << CD->getName() << " and subclass " << S->getName() << "\n"); CanType CanClassType = S->getDeclaredType()->getCanonicalType(); SILType InstanceType = SILType::getPrimitiveObjectType(CanClassType); if (!InstanceType.getClassOrBoundGenericClass()) { // This subclass cannot be handled. This happens e.g. if it is // a generic class. NotHandledSubsNum++; continue; } AI = insertMonomorphicInlineCaches(AI, InstanceType); assert(AI && "Unable to insert inline caches!"); Changed = true; } // Check if there is only a single statically known implementation // of the method which can be called by the default case handler. if (NotHandledSubsNum || !isDefaultCaseKnown(CHA, AI, CD, Subs)) { // Devirtualization of remaining cases is not possible, // because more than one implementation of the method // needs to be handled here. Thus, an indirect call through // the class_method cannot be eliminated completely. // // But we can still try to devirtualize the static class of instance // if it is possible. return insertMonomorphicInlineCaches(AI, InstanceType); } // At this point it is known that there is only one remaining method // implementation which is not covered by checked_cast_br checks yet. // So, it is safe to replace a class_method invocation by // a direct call of this remaining implementation. devirtMethod(AI, CMI->getMember(), ClassInstance, CD); return true; } namespace { /// Generate inline caches of virtual calls by speculating that the requested /// class is at the bottom of the class hierarchy. class SILInlineCaches : public SILFunctionTransform { public: virtual ~SILInlineCaches() {} virtual void run() { ClassHierarchyAnalysis *CHA = PM->getAnalysis(); bool Changed = false; // Collect virtual calls that may be specialized. SmallVector ToSpecialize; for (auto &BB : *getFunction()) { for (auto II = BB.begin(), IE = BB.end(); II != IE; ++II) { ApplyInst *AI = dyn_cast(&*II); if (AI && isa(AI->getCallee())) ToSpecialize.push_back(AI); } } // Create the inline caches. for (auto AI : ToSpecialize) Changed |= insertInlineCaches(AI, CHA); if (Changed) { invalidateAnalysis(SILAnalysis::InvalidationKind::CallGraph); } } StringRef getName() override { return "Inline Caches"; } }; } // end anonymous namespace SILTransform *swift::createInlineCaches() { return new SILInlineCaches(); }