/// \brief 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 bool isEffectivelyFinalMethod(FullApplySite AI, SILType ClassType, ClassDecl *CD, ClassHierarchyAnalysis *CHA) { if (CD && CD->isFinal()) return true; const DeclContext *DC = AI.getModule().getAssociatedContext(); // Without an associated context we cannot perform any // access-based optimizations. if (!DC) return false; auto *CMI = cast<MethodInst>(AI.getCallee()); if (!calleesAreStaticallyKnowable(AI.getModule(), CMI->getMember())) return false; auto *Method = CMI->getMember().getAbstractFunctionDecl(); assert(Method && "Expected abstract function decl!"); assert(!Method->isFinal() && "Unexpected indirect call to final method!"); // If this method is not overridden in the module, // there is no other implementation. if (!Method->isOverridden()) return true; // Class declaration may be nullptr, e.g. for cases like: // func foo<C:Base>(c: C) {}, where C is a class, but // it does not have a class decl. if (!CD) return false; if (!CHA) return false; // This is a private or a module internal class. // // We can analyze the class hierarchy rooted at it and // eventually devirtualize a method call more efficiently. ClassHierarchyAnalysis::ClassList Subs; getAllSubclasses(CHA, CD, ClassType, AI.getModule(), Subs); // This is the implementation of the method to be used // if the exact class of the instance would be CD. auto *ImplMethod = CD->findImplementingMethod(Method); // First, analyze all direct subclasses. for (auto S : Subs) { // Check if the subclass overrides a method and provides // a different implementation. auto *ImplFD = S->findImplementingMethod(Method); if (ImplFD != ImplMethod) return false; } return true; }
/// Attempt to devirtualize the given apply if possible, and return a /// new instruction in that case, or nullptr otherwise. DevirtualizationResult swift::tryDevirtualizeApply(FullApplySite AI, ClassHierarchyAnalysis *CHA) { DEBUG(llvm::dbgs() << " Trying to devirtualize: " << *AI.getInstruction()); // Devirtualize apply instructions that call witness_method instructions: // // %8 = witness_method $Optional<UInt16>, #LogicValue.boolValue!getter.1 // %9 = apply %8<Self = CodeUnit?>(%6#1) : ... // if (isa<WitnessMethodInst>(AI.getCallee())) return tryDevirtualizeWitnessMethod(AI); /// Optimize a class_method and alloc_ref pair into a direct function /// reference: /// /// \code /// %XX = alloc_ref $Foo /// %YY = class_method %XX : $Foo, #Foo.get!1 : $@convention(method)... /// \endcode /// /// or /// /// %XX = metatype $... /// %YY = class_method %XX : ... /// /// into /// /// %YY = function_ref @... if (auto *CMI = dyn_cast<ClassMethodInst>(AI.getCallee())) { auto &M = AI.getModule(); auto Instance = stripUpCasts(CMI->getOperand()); auto ClassType = Instance->getType(); if (ClassType.is<MetatypeType>()) ClassType = ClassType.getMetatypeInstanceType(M); auto *CD = ClassType.getClassOrBoundGenericClass(); if (isEffectivelyFinalMethod(AI, ClassType, CD, CHA)) return tryDevirtualizeClassMethod(AI, Instance); // Try to check if the exact dynamic type of the instance is statically // known. if (auto Instance = getInstanceWithExactDynamicType(CMI->getOperand(), CMI->getModule(), CHA)) return tryDevirtualizeClassMethod(AI, Instance); } if (isa<SuperMethodInst>(AI.getCallee())) { if (AI.hasSelfArgument()) { return tryDevirtualizeClassMethod(AI, AI.getSelfArgument()); } // It is an invocation of a class method. // Last operand is the metatype that should be used for dispatching. return tryDevirtualizeClassMethod(AI, AI.getArguments().back()); } return std::make_pair(nullptr, FullApplySite()); }
/// \brief 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 ClassOrMetatypeType is the class type or metatype type we are /// devirtualizing for. /// return true if it is possible to devirtualize, false - otherwise. bool swift::canDevirtualizeClassMethod(FullApplySite AI, SILType ClassOrMetatypeType, OptRemark::Emitter *ORE, bool isEffectivelyFinalMethod) { LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI.getInstruction()); SILModule &Mod = AI.getModule(); // First attempt to lookup the origin for our class method. The origin should // either be a metatype or an alloc_ref. LLVM_DEBUG(llvm::dbgs() << " Origin Type: " << ClassOrMetatypeType); auto *MI = cast<MethodInst>(AI.getCallee()); // Find the implementation of the member which should be invoked. auto *F = getTargetClassMethod(Mod, ClassOrMetatypeType, 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()->getResilienceExpansion() == ResilienceExpansion::Minimal) { 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; } return true; }
/// \brief 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 ClassOrMetatypeType is the class type or metatype type we are /// devirtualizing for. /// return true if it is possible to devirtualize, false - otherwise. bool swift::canDevirtualizeClassMethod(FullApplySite AI, SILType ClassOrMetatypeType) { DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI.getInstruction()); SILModule &Mod = AI.getModule(); // 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 Type: " << ClassOrMetatypeType); auto *MI = cast<MethodInst>(AI.getCallee()); // Find the implementation of the member which should be invoked. auto *F = getTargetClassMethod(Mod, ClassOrMetatypeType, MI); // 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; } if (!F->shouldOptimize()) { // Do not consider functions that should not be optimized. 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; } if (MI->isVolatile()) { // dynamic dispatch is semantically required, can't devirtualize return false; } return true; }
/// \brief 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. DevirtualizationResult swift::devirtualizeClassMethod(FullApplySite AI, SILValue ClassOrMetatype) { DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI.getInstruction()); SILModule &Mod = AI.getModule(); auto *CMI = cast<ClassMethodInst>(AI.getCallee()); auto ClassOrMetatypeType = ClassOrMetatype.getType(); auto *F = getTargetClassMethod(Mod, ClassOrMetatypeType, CMI->getMember()); CanSILFunctionType GenCalleeType = F->getLoweredFunctionType(); auto Subs = getSubstitutionsForCallee(Mod, GenCalleeType, ClassOrMetatypeType, AI); CanSILFunctionType SubstCalleeType = GenCalleeType; if (GenCalleeType->isPolymorphic()) SubstCalleeType = GenCalleeType->substGenericArgs(Mod, Mod.getSwiftModule(), Subs); SILBuilderWithScope B(AI.getInstruction()); FunctionRefInst *FRI = B.createFunctionRef(AI.getLoc(), F); // Create the argument list for the new apply, casting when needed // in order to handle covariant indirect return types and // contravariant argument types. llvm::SmallVector<SILValue, 8> NewArgs; auto Args = AI.getArguments(); auto ParamTypes = SubstCalleeType->getParameterSILTypes(); for (unsigned i = 0, e = Args.size() - 1; i != e; ++i) NewArgs.push_back(castValueToABICompatibleType(&B, AI.getLoc(), Args[i], Args[i].getType(), ParamTypes[i]).getValue()); // Add the self argument, upcasting if required because we're // calling a base class's method. auto SelfParamTy = SubstCalleeType->getSelfParameter().getSILType(); NewArgs.push_back(castValueToABICompatibleType(&B, AI.getLoc(), ClassOrMetatype, ClassOrMetatypeType, SelfParamTy).getValue()); // 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 ResultTy = AI.getType(); if (!SubstCalleeType->hasIndirectResult()) { ResultTy = SubstCalleeType->getSILResult(); } SILType SubstCalleeSILType = SILType::getPrimitiveObjectType(SubstCalleeType); FullApplySite NewAI; SILBasicBlock *ResultBB = nullptr; SILBasicBlock *NormalBB = nullptr; SILValue ResultValue; bool ResultCastRequired = false; SmallVector<Operand *, 4> OriginalResultUses; if (!isa<TryApplyInst>(AI)) { NewAI = B.createApply(AI.getLoc(), FRI, SubstCalleeSILType, ResultTy, Subs, NewArgs, cast<ApplyInst>(AI)->isNonThrowing()); ResultValue = SILValue(NewAI.getInstruction(), 0); } else { auto *TAI = cast<TryApplyInst>(AI); // Create new normal and error BBs only if: // - re-using a BB would create a critical edge // - or, the result of the new apply would be of different // type than the argument of the original normal BB. if (TAI->getNormalBB()->getSinglePredecessor()) ResultBB = TAI->getNormalBB(); else { ResultBB = B.getFunction().createBasicBlock(); ResultBB->createBBArg(ResultTy); } NormalBB = TAI->getNormalBB(); SILBasicBlock *ErrorBB = nullptr; if (TAI->getErrorBB()->getSinglePredecessor()) ErrorBB = TAI->getErrorBB(); else { ErrorBB = B.getFunction().createBasicBlock(); ErrorBB->createBBArg(TAI->getErrorBB()->getBBArg(0)->getType()); } NewAI = B.createTryApply(AI.getLoc(), FRI, SubstCalleeSILType, Subs, NewArgs, ResultBB, ErrorBB); if (ErrorBB != TAI->getErrorBB()) { B.setInsertionPoint(ErrorBB); B.createBranch(TAI->getLoc(), TAI->getErrorBB(), {ErrorBB->getBBArg(0)}); } // Does the result value need to be casted? ResultCastRequired = ResultTy != NormalBB->getBBArg(0)->getType(); if (ResultBB != NormalBB) B.setInsertionPoint(ResultBB); else if (ResultCastRequired) { B.setInsertionPoint(NormalBB->begin()); // Collect all uses, before casting. for (auto *Use : NormalBB->getBBArg(0)->getUses()) { OriginalResultUses.push_back(Use); } NormalBB->getBBArg(0)->replaceAllUsesWith(SILUndef::get(AI.getType(), Mod)); NormalBB->replaceBBArg(0, ResultTy, nullptr); } // The result value is passed as a parameter to the normal block. ResultValue = ResultBB->getBBArg(0); } // Check if any casting is required for the return value. ResultValue = castValueToABICompatibleType(&B, NewAI.getLoc(), ResultValue, ResultTy, AI.getType()).getValue(); DEBUG(llvm::dbgs() << " SUCCESS: " << F->getName() << "\n"); NumClassDevirt++; if (NormalBB) { if (NormalBB != ResultBB) { // If artificial normal BB was introduced, branch // to the original normal BB. B.createBranch(NewAI.getLoc(), NormalBB, { ResultValue }); } else if (ResultCastRequired) { // Update all original uses by the new value. for(auto *Use: OriginalResultUses) { Use->set(ResultValue); } } return std::make_pair(NewAI.getInstruction(), NewAI); } // We need to return a pair of values here: // - the first one is the actual result of the devirtualized call, possibly // casted into an appropriate type. This SILValue may be a BB arg, if it // was a cast between optional types. // - the second one is the new apply site. return std::make_pair(ResultValue.getDef(), NewAI); }
/// \brief 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 ClassOrMetatypeType is the class type or metatype type we are /// devirtualizing for. /// return true if it is possible to devirtualize, false - otherwise. bool swift::canDevirtualizeClassMethod(FullApplySite AI, SILType ClassOrMetatypeType) { DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI.getInstruction()); SILModule &Mod = AI.getModule(); // Bail if any generic types parameters of the class instance type are // unbound. // We cannot devirtualize unbound generic calls yet. if (isClassWithUnboundGenericParameters(ClassOrMetatypeType, Mod)) return false; // 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 Type: " << ClassOrMetatypeType); auto *CMI = cast<ClassMethodInst>(AI.getCallee()); // Find the implementation of the member which should be invoked. auto *F = getTargetClassMethod(Mod, ClassOrMetatypeType, CMI->getMember()); // 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; } if (AI.getFunction()->isFragile()) { // function_ref inside fragile function cannot reference a private or // hidden symbol. if (!(F->isFragile() || isValidLinkageForFragileRef(F->getLinkage()) || F->isExternalDeclaration())) return false; } CanSILFunctionType GenCalleeType = F->getLoweredFunctionType(); auto Subs = getSubstitutionsForCallee(Mod, GenCalleeType, ClassOrMetatypeType, AI); // For polymorphic functions, bail if the number of substitutions is // not the same as the number of expected generic parameters. if (GenCalleeType->isPolymorphic()) { auto GenericSig = GenCalleeType->getGenericSignature(); // Get the number of expected generic parameters, which // is a sum of the number of explicit generic parameters // and the number of their recursive member types exposed // through protocol requirements. auto DepTypes = GenericSig->getAllDependentTypes(); unsigned ExpectedGenParamsNum = 0; for (auto DT: DepTypes) { (void)DT; ExpectedGenParamsNum++; } if (ExpectedGenParamsNum != Subs.size()) return false; } // Check if the optimizer knows how to cast the return type. CanSILFunctionType SubstCalleeType = GenCalleeType; if (GenCalleeType->isPolymorphic()) SubstCalleeType = GenCalleeType->substGenericArgs(Mod, Mod.getSwiftModule(), Subs); // 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(); } if (!canCastValueToABICompatibleType(Mod, ReturnType, AI.getType())) return false; return true; }
/// \brief Try to speculate the call target for the call \p AI. This function /// returns true if a change was made. static bool tryToSpeculateTarget(FullApplySite AI, ClassHierarchyAnalysis *CHA) { ClassMethodInst *CMI = cast<ClassMethodInst>(AI.getCallee()); // We cannot devirtualize in cases where dynamic calls are // semantically required. if (CMI->isVolatile()) return false; // Strip any upcasts off of our 'self' value, potentially leaving us // with a value whose type is closer (in the class hierarchy) to the // actual dynamic type. auto SubTypeValue = CMI->getOperand().stripUpCasts(); SILType SubType = SubTypeValue.getType(); // Bail if any generic types parameters of the class instance type are // unbound. // We cannot devirtualize unbound generic calls yet. if (isNominalTypeWithUnboundGenericParameters(SubType, AI.getModule())) return false; auto &M = CMI->getModule(); auto ClassType = SubType; if (SubType.is<MetatypeType>()) ClassType = SubType.getMetatypeInstanceType(M); CheckedCastBranchInst *LastCCBI = nullptr; ClassDecl *CD = ClassType.getClassOrBoundGenericClass(); assert(CD && "Expected decl for class type!"); if (!CHA->hasKnownDirectSubclasses(CD)) { // If there is only one possible alternative for this method, // try to devirtualize it completely. ClassHierarchyAnalysis::ClassList Subs; if (isDefaultCaseKnown(CHA, AI, CD, Subs)) { auto NewInstPair = tryDevirtualizeClassMethod(AI, SubTypeValue); if (NewInstPair.first) replaceDeadApply(AI, NewInstPair.first); return NewInstPair.second.getInstruction() != nullptr; } DEBUG(llvm::dbgs() << "Inserting monomorphic speculative call for class " << CD->getName() << "\n"); return !!speculateMonomorphicTarget(AI, SubType, LastCCBI); } // True if any instructions were changed or generated. bool Changed = false; // 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); SmallVector<ClassDecl *, 8> Subs(DirectSubs); Subs.append(IndirectSubs.begin(), IndirectSubs.end()); if (isa<BoundGenericClassType>(ClassType.getSwiftRValueType())) { // Filter out any subclassses that do not inherit from this // specific bound class. auto RemovedIt = std::remove_if(Subs.begin(), Subs.end(), [&ClassType, &M](ClassDecl *Sub){ auto SubCanTy = Sub->getDeclaredType()->getCanonicalType(); // Unbound generic type can override a method from // a bound generic class, but this unbound generic // class is not considered to be a subclass of a // bound generic class in a general case. if (isa<UnboundGenericType>(SubCanTy)) return false; // Handle the usual case here: the class in question // should be a real subclass of a bound generic class. return !ClassType.isSuperclassOf( SILType::getPrimitiveObjectType(SubCanTy)); }); Subs.erase(RemovedIt, Subs.end()); } // Number of subclasses which cannot be handled by checked_cast_br checks. int NotHandledSubsNum = 0; if (Subs.size() > MaxNumSpeculativeTargets) { DEBUG(llvm::dbgs() << "Class " << CD->getName() << " has too many (" << Subs.size() << ") subclasses. Performing speculative " "devirtualization only for the first " << MaxNumSpeculativeTargets << " of them.\n"); NotHandledSubsNum += (Subs.size() - MaxNumSpeculativeTargets); Subs.erase(&Subs[MaxNumSpeculativeTargets], Subs.end()); } DEBUG(llvm::dbgs() << "Class " << CD->getName() << " is a superclass. " "Inserting polymorphic speculative call.\n"); // Try to devirtualize the static class of instance // if it is possible. auto FirstAI = speculateMonomorphicTarget(AI, SubType, LastCCBI); if (FirstAI) { Changed = true; AI = FirstAI; } // 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. for (auto S : Subs) { DEBUG(llvm::dbgs() << "Inserting a speculative call for class " << CD->getName() << " and subclass " << S->getName() << "\n"); CanType CanClassType = S->getDeclaredType()->getCanonicalType(); SILType ClassType = SILType::getPrimitiveObjectType(CanClassType); if (!ClassType.getClassOrBoundGenericClass()) { // This subclass cannot be handled. This happens e.g. if it is // a generic class. NotHandledSubsNum++; continue; } auto ClassOrMetatypeType = ClassType; if (auto EMT = SubType.getAs<AnyMetatypeType>()) { auto InstTy = ClassType.getSwiftRValueType(); auto *MetaTy = MetatypeType::get(InstTy, EMT->getRepresentation()); auto CanMetaTy = CanMetatypeType::CanTypeWrapper(MetaTy); ClassOrMetatypeType = SILType::getPrimitiveObjectType(CanMetaTy); } // Pass the metatype of the subclass. auto NewAI = speculateMonomorphicTarget(AI, ClassOrMetatypeType, LastCCBI); if (!NewAI) { NotHandledSubsNum++; continue; } AI = NewAI; 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. // return Changed; } // 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. if (LastCCBI && SubTypeValue == LastCCBI->getOperand()) { // Remove last checked_cast_br, because it will always succeed. SILBuilderWithScope B(LastCCBI); auto CastedValue = B.createUncheckedBitCast(LastCCBI->getLoc(), LastCCBI->getOperand(), LastCCBI->getCastType()); B.createBranch(LastCCBI->getLoc(), LastCCBI->getSuccessBB(), {CastedValue}); LastCCBI->eraseFromParent(); return true; } auto NewInstPair = tryDevirtualizeClassMethod(AI, SubTypeValue); assert(NewInstPair.first && "Expected to be able to devirtualize apply!"); replaceDeadApply(AI, NewInstPair.first); return true; }
/// \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, FullApplySite AI, ClassDecl *CD, ClassHierarchyAnalysis::ClassList &Subs) { ClassMethodInst *CMI = cast<ClassMethodInst>(AI.getCallee()); auto *Method = CMI->getMember().getFuncDecl(); const DeclContext *DC = AI.getModule().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->hasAccessibility()) return false; // Only consider 'private' members, unless we are in whole-module compilation. switch (CD->getEffectiveAccess()) { 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 tryToSpeculateTarget. 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 // tryToSpeculateTarget. 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 tryToSpeculateTarget 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 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, OptRemark::Emitter *ORE) { LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI.getInstruction()); SILModule &Mod = AI.getModule(); auto *MI = cast<MethodInst>(AI.getCallee()); auto ClassOrMetatypeType = ClassOrMetatype->getType(); auto *F = getTargetClassMethod(Mod, ClassOrMetatypeType, MI); CanSILFunctionType GenCalleeType = F->getLoweredFunctionType(); SubstitutionMap Subs = getSubstitutionsForCallee(Mod, GenCalleeType, ClassOrMetatypeType.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(); FunctionRefInst *FRI = B.createFunctionRef(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. llvm::SmallVector<SILValue, 8> NewArgs; 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().drop_back()) { auto paramType = substConv.getSILType(param); NewArgs.push_back( castValueToABICompatibleType(&B, Loc, *ParamArgIter, ParamArgIter->getType(), paramType)); ++ParamArgIter; } // Add the self argument, upcasting if required because we're // calling a base class's method. auto SelfParamTy = substConv.getSILType(SubstCalleeType->getSelfParameter()); NewArgs.push_back(castValueToABICompatibleType(&B, Loc, ClassOrMetatype, ClassOrMetatypeType, SelfParamTy)); ApplySite NewAS = replaceApplySite(B, Loc, AI, FRI, Subs, NewArgs, substConv); 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; }
/// Attempt to devirtualize the given apply if possible, and return a /// new instruction in that case, or nullptr otherwise. ApplySite swift::tryDevirtualizeApply(ApplySite AI, ClassHierarchyAnalysis *CHA, OptRemark::Emitter *ORE) { LLVM_DEBUG(llvm::dbgs() << " Trying to devirtualize: " << *AI.getInstruction()); // Devirtualize apply instructions that call witness_method instructions: // // %8 = witness_method $Optional<UInt16>, #LogicValue.boolValue!getter.1 // %9 = apply %8<Self = CodeUnit?>(%6#1) : ... // if (isa<WitnessMethodInst>(AI.getCallee())) return tryDevirtualizeWitnessMethod(AI, ORE); // TODO: check if we can also de-virtualize partial applies of class methods. FullApplySite FAS = FullApplySite::isa(AI.getInstruction()); if (!FAS) return ApplySite(); /// Optimize a class_method and alloc_ref pair into a direct function /// reference: /// /// \code /// %XX = alloc_ref $Foo /// %YY = class_method %XX : $Foo, #Foo.get!1 : $@convention(method)... /// \endcode /// /// or /// /// %XX = metatype $... /// %YY = class_method %XX : ... /// /// into /// /// %YY = function_ref @... if (auto *CMI = dyn_cast<ClassMethodInst>(FAS.getCallee())) { auto &M = FAS.getModule(); auto Instance = stripUpCasts(CMI->getOperand()); auto ClassType = Instance->getType(); if (ClassType.is<MetatypeType>()) ClassType = ClassType.getMetatypeInstanceType(M); auto *CD = ClassType.getClassOrBoundGenericClass(); if (isEffectivelyFinalMethod(FAS, ClassType, CD, CHA)) return tryDevirtualizeClassMethod(FAS, Instance, ORE, true /*isEffectivelyFinalMethod*/); // Try to check if the exact dynamic type of the instance is statically // known. if (auto Instance = getInstanceWithExactDynamicType(CMI->getOperand(), CMI->getModule(), CHA)) return tryDevirtualizeClassMethod(FAS, Instance, ORE); if (auto ExactTy = getExactDynamicType(CMI->getOperand(), CMI->getModule(), CHA)) { if (ExactTy == CMI->getOperand()->getType()) return tryDevirtualizeClassMethod(FAS, CMI->getOperand(), ORE); } } if (isa<SuperMethodInst>(FAS.getCallee())) { if (FAS.hasSelfArgument()) { return tryDevirtualizeClassMethod(FAS, FAS.getSelfArgument(), ORE); } // It is an invocation of a class method. // Last operand is the metatype that should be used for dispatching. return tryDevirtualizeClassMethod(FAS, FAS.getArguments().back(), ORE); } return ApplySite(); }
/// \brief 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 ClassOrMetatypeType is the class type or metatype type we are /// devirtualizing for. /// return true if it is possible to devirtualize, false - otherwise. bool swift::canDevirtualizeClassMethod(FullApplySite AI, SILType ClassOrMetatypeType) { DEBUG(llvm::dbgs() << " Trying to devirtualize : " << *AI.getInstruction()); SILModule &Mod = AI.getModule(); // 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 Type: " << ClassOrMetatypeType); auto *MI = cast<MethodInst>(AI.getCallee()); // Find the implementation of the member which should be invoked. auto *F = getTargetClassMethod(Mod, ClassOrMetatypeType, MI); // 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; } if (!F->shouldOptimize()) { // Do not consider functions that should not be optimized. DEBUG(llvm::dbgs() << " FAIL: Could not optimize function " << " because it is marked no-opt: " << F->getName() << "\n"); return false; } if (AI.getFunction()->isFragile()) { // function_ref inside fragile function cannot reference a private or // hidden symbol. if (!F->hasValidLinkageForFragileRef()) return false; } // Type of the actual function to be called. CanSILFunctionType GenCalleeType = F->getLoweredFunctionType(); // Type of the actual function to be called with substitutions applied. CanSILFunctionType SubstCalleeType = GenCalleeType; // For polymorphic functions, bail if the number of substitutions is // not the same as the number of expected generic parameters. if (GenCalleeType->isPolymorphic()) { // First, find proper list of substitutions for the concrete // method to be called. SmallVector<Substitution, 4> Subs; getSubstitutionsForCallee(Mod, GenCalleeType, ClassOrMetatypeType.getSwiftRValueType(), AI, Subs); SubstCalleeType = GenCalleeType->substGenericArgs(Mod, Subs); } // Check if the optimizer knows how to cast the return type. SILType ReturnType = SubstCalleeType->getSILResult(); if (!canCastValueToABICompatibleType(Mod, ReturnType, AI.getType())) return false; return true; }