/// Checks whether any of the arguments to the apply are closures and diagnoses /// if any of the @inout_aliasable captures passed to those closures have /// in-progress accesses that would conflict with any access the summary /// says the closure would perform. // /// TODO: We currently fail to statically diagnose non-escaping closures pased /// via @block_storage convention. To enforce this case, we should statically /// recognize when the apply takes a block argument that has been initialized to /// a non-escaping closure. static void checkForViolationsInNoEscapeClosures( const StorageMap &Accesses, FullApplySite FAS, AccessSummaryAnalysis *ASA, llvm::SmallVectorImpl<ConflictingAccess> &ConflictingAccesses) { SILFunction *Callee = FAS.getCalleeFunction(); if (Callee && !Callee->empty()) { // Check for violation with directly called closure checkForViolationWithCall(Accesses, Callee, 0, FAS.getArguments(), ASA, ConflictingAccesses); } // Check for violation with closures passed as arguments for (SILValue Argument : FAS.getArguments()) { auto *PAI = lookThroughForPartialApply(Argument); if (!PAI) continue; SILFunction *Closure = PAI->getCalleeFunction(); if (!Closure || Closure->empty()) continue; // Check the closure's captures, which are a suffix of the closure's // parameters. unsigned StartIndex = Closure->getArguments().size() - PAI->getNumCallArguments(); checkForViolationWithCall(Accesses, Closure, StartIndex, PAI->getArguments(), ASA, ConflictingAccesses); } }
/// 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()); }
// A utility function for cloning the apply instruction. static FullApplySite CloneApply(FullApplySite AI, SILBuilder &Builder) { // Clone the Apply. Builder.setCurrentDebugScope(AI.getDebugScope()); Builder.addOpenedArchetypeOperands(AI.getInstruction()); auto Args = AI.getArguments(); SmallVector<SILValue, 8> Ret(Args.size()); for (unsigned i = 0, e = Args.size(); i != e; ++i) Ret[i] = Args[i]; FullApplySite NAI; switch (AI.getInstruction()->getKind()) { case SILInstructionKind::ApplyInst: NAI = Builder.createApply(AI.getLoc(), AI.getCallee(), AI.getSubstitutions(), Ret, cast<ApplyInst>(AI)->isNonThrowing()); break; case SILInstructionKind::TryApplyInst: { auto *TryApplyI = cast<TryApplyInst>(AI.getInstruction()); NAI = Builder.createTryApply(AI.getLoc(), AI.getCallee(), AI.getSubstitutions(), Ret, TryApplyI->getNormalBB(), TryApplyI->getErrorBB()); } break; default: llvm_unreachable("Trying to clone an unsupported apply instruction"); } NAI.getInstruction(); return NAI; }
/// \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); }
/// Update the callsite to pass in the correct arguments. static void rewriteApplyInst(const CallSiteDescriptor &CSDesc, SILFunction *NewF) { FullApplySite AI = CSDesc.getApplyInst(); SingleValueInstruction *Closure = CSDesc.getClosure(); SILBuilderWithScope Builder(Closure); FunctionRefInst *FRI = Builder.createFunctionRef(AI.getLoc(), NewF); // Create the args for the new apply by removing the closure argument... llvm::SmallVector<SILValue, 8> NewArgs; unsigned Index = 0; for (auto Arg : AI.getArguments()) { if (Index != CSDesc.getClosureIndex()) NewArgs.push_back(Arg); Index++; } // ... and appending the captured arguments. We also insert retains here at // the location of the original closure. This is needed to balance the // implicit release of all captured arguments that occurs when the partial // apply is destroyed. SILModule &M = NewF->getModule(); auto ClosureCalleeConv = CSDesc.getClosureCallee()->getConventions(); unsigned ClosureArgIdx = ClosureCalleeConv.getNumSILArguments() - CSDesc.getNumArguments(); for (auto Arg : CSDesc.getArguments()) { SILType ArgTy = Arg->getType(); // If our argument is of trivial type, continue... if (ArgTy.isTrivial(M)) { NewArgs.push_back(Arg); ++ClosureArgIdx; continue; } auto ArgConvention = ClosureCalleeConv.getSILArgumentConvention(ClosureArgIdx); // Non-inout indirect arguments are not supported yet. assert(ArgTy.isObject() || !isNonInoutIndirectSILArgument(Arg, ArgConvention)); // If argument is not an object and it is an inout parameter, // continue... if (!ArgTy.isObject() && !isNonInoutIndirectSILArgument(Arg, ArgConvention)) { NewArgs.push_back(Arg); ++ClosureArgIdx; continue; } // TODO: When we support address types, this code path will need to be // updated. // We need to balance the consumed argument of the new partial_apply in the // specialized callee by a retain. If both the original partial_apply and // the apply of the callee are in the same basic block we can assume they // are executed the same number of times. Therefore it is sufficient to just // retain the argument at the site of the original partial_apply. // // %closure = partial_apply (%arg) // = apply %callee(%closure) // => // retain %arg // %closure = partial_apply (%arg) // apply %specialized_callee(..., %arg) // // However, if they are not in the same basic block the callee might be // executed more frequently than the closure (for example, if the closure is // created in a loop preheader and the callee taking the closure is executed // in the loop). In such a case we must keep the argument live across the // call site of the callee and emit a matching retain for every invocation // of the callee. // // %closure = partial_apply (%arg) // // while () { // = %callee(%closure) // } // => // retain %arg // %closure = partial_apply (%arg) // // while () { // retain %arg // apply %specialized_callee(.., %arg) // } // release %arg // if (AI.getParent() != Closure->getParent()) { // Emit the retain and release that keeps the argument life across the // callee using the closure. CSDesc.extendArgumentLifetime(Arg, ArgConvention); // Emit the retain that matches the captured argument by the // partial_apply // in the callee that is consumed by the partial_apply. Builder.setInsertionPoint(AI.getInstruction()); Builder.createRetainValue(Closure->getLoc(), Arg, Builder.getDefaultAtomicity()); } else { Builder.createRetainValue(Closure->getLoc(), Arg, Builder.getDefaultAtomicity()); } NewArgs.push_back(Arg); ++ClosureArgIdx; } Builder.setInsertionPoint(AI.getInstruction()); FullApplySite NewAI; switch (AI.getKind()) { case FullApplySiteKind::TryApplyInst: { auto *TAI = cast<TryApplyInst>(AI); NewAI = Builder.createTryApply(AI.getLoc(), FRI, SubstitutionMap(), NewArgs, TAI->getNormalBB(), TAI->getErrorBB()); // If we passed in the original closure as @owned, then insert a release // right after NewAI. This is to balance the +1 from being an @owned // argument to AI. if (!CSDesc.isClosureConsumed() || !CSDesc.closureHasRefSemanticContext()) { break; } Builder.setInsertionPoint(TAI->getNormalBB()->begin()); Builder.createReleaseValue(Closure->getLoc(), Closure, Builder.getDefaultAtomicity()); Builder.setInsertionPoint(TAI->getErrorBB()->begin()); Builder.createReleaseValue(Closure->getLoc(), Closure, Builder.getDefaultAtomicity()); Builder.setInsertionPoint(AI.getInstruction()); break; } case FullApplySiteKind::ApplyInst: { auto oldApply = cast<ApplyInst>(AI); auto newApply = Builder.createApply(oldApply->getLoc(), FRI, SubstitutionMap(), NewArgs, oldApply->isNonThrowing()); // If we passed in the original closure as @owned, then insert a release // right after NewAI. This is to balance the +1 from being an @owned // argument to AI. if (CSDesc.isClosureConsumed() && CSDesc.closureHasRefSemanticContext()) Builder.createReleaseValue(Closure->getLoc(), Closure, Builder.getDefaultAtomicity()); // Replace all uses of the old apply with the new apply. oldApply->replaceAllUsesWith(newApply); break; } case FullApplySiteKind::BeginApplyInst: llvm_unreachable("Unhandled case"); } // Erase the old apply. AI.getInstruction()->eraseFromParent(); // TODO: Maybe include invalidation code for CallSiteDescriptor after we erase // AI from parent? }
/// Process an apply instruction which uses a partial_apply /// as its callee. /// Returns true on success. bool PartialApplyCombiner::processSingleApply(FullApplySite AI) { Builder.setInsertionPoint(AI.getInstruction()); Builder.setCurrentDebugScope(AI.getDebugScope()); // Prepare the args. SmallVector<SILValue, 8> Args; // First the ApplyInst args. for (auto Op : AI.getArguments()) Args.push_back(Op); SILInstruction *InsertionPoint = &*Builder.getInsertionPoint(); // Next, the partial apply args. // Pre-process partial_apply arguments only once, lazily. if (isFirstTime) { isFirstTime = false; if (!allocateTemporaries()) return false; } // Now, copy over the partial apply args. for (auto Op : PAI->getArguments()) { auto Arg = Op; // If there is new temporary for this argument, use it instead. if (isa<AllocStackInst>(Arg)) { if (ArgToTmp.count(Arg)) { Op = ArgToTmp.lookup(Arg); } } Args.push_back(Op); } Builder.setInsertionPoint(InsertionPoint); Builder.setCurrentDebugScope(AI.getDebugScope()); // The thunk that implements the partial apply calls the closure function // that expects all arguments to be consumed by the function. However, the // captured arguments are not arguments of *this* apply, so they are not // pre-incremented. When we combine the partial_apply and this apply into // a new apply we need to retain all of the closure non-address type // arguments. auto ParamInfo = PAI->getSubstCalleeType()->getParameters(); auto PartialApplyArgs = PAI->getArguments(); // Set of arguments that need to be released after each invocation. SmallVector<SILValue, 8> ToBeReleasedArgs; for (unsigned i = 0, e = PartialApplyArgs.size(); i < e; ++i) { SILValue Arg = PartialApplyArgs[i]; if (!Arg->getType().isAddress()) { // Retain the argument as the callee may consume it. Builder.emitRetainValueOperation(PAI->getLoc(), Arg); // For non consumed parameters (e.g. guaranteed), we also need to // insert releases after each apply instruction that we create. if (!ParamInfo[ParamInfo.size() - PartialApplyArgs.size() + i]. isConsumed()) ToBeReleasedArgs.push_back(Arg); } } auto *F = FRI->getReferencedFunction(); SILType FnType = F->getLoweredType(); SILType ResultTy = F->getLoweredFunctionType()->getSILResult(); ArrayRef<Substitution> Subs = PAI->getSubstitutions(); if (!Subs.empty()) { FnType = FnType.substGenericArgs(PAI->getModule(), Subs); ResultTy = FnType.getAs<SILFunctionType>()->getSILResult(); } FullApplySite NAI; if (auto *TAI = dyn_cast<TryApplyInst>(AI)) NAI = Builder.createTryApply(AI.getLoc(), FRI, FnType, Subs, Args, TAI->getNormalBB(), TAI->getErrorBB()); else NAI = Builder.createApply(AI.getLoc(), FRI, FnType, ResultTy, Subs, Args, cast<ApplyInst>(AI)->isNonThrowing()); // We also need to release the partial_apply instruction itself because it // is consumed by the apply_instruction. if (auto *TAI = dyn_cast<TryApplyInst>(AI)) { Builder.setInsertionPoint(TAI->getNormalBB()->begin()); for (auto Arg : ToBeReleasedArgs) { Builder.emitReleaseValueOperation(PAI->getLoc(), Arg); } Builder.createStrongRelease(AI.getLoc(), PAI, Atomicity::Atomic); Builder.setInsertionPoint(TAI->getErrorBB()->begin()); // Release the non-consumed parameters. for (auto Arg : ToBeReleasedArgs) { Builder.emitReleaseValueOperation(PAI->getLoc(), Arg); } Builder.createStrongRelease(AI.getLoc(), PAI, Atomicity::Atomic); Builder.setInsertionPoint(AI.getInstruction()); } else { // Release the non-consumed parameters. for (auto Arg : ToBeReleasedArgs) { Builder.emitReleaseValueOperation(PAI->getLoc(), Arg); } Builder.createStrongRelease(AI.getLoc(), PAI, Atomicity::Atomic); } SilCombiner->replaceInstUsesWith(*AI.getInstruction(), NAI.getInstruction()); SilCombiner->eraseInstFromFunction(*AI.getInstruction()); return true; }
/// Update the callsite to pass in the correct arguments. static void rewriteApplyInst(const CallSiteDescriptor &CSDesc, SILFunction *NewF) { FullApplySite AI = CSDesc.getApplyInst(); SILInstruction *Closure = CSDesc.getClosure(); SILBuilderWithScope Builder(Closure); FunctionRefInst *FRI = Builder.createFunctionRef(AI.getLoc(), NewF); // Create the args for the new apply by removing the closure argument... llvm::SmallVector<SILValue, 8> NewArgs; unsigned Index = 0; for (auto Arg : AI.getArguments()) { if (Index != CSDesc.getClosureIndex()) NewArgs.push_back(Arg); Index++; } // ... and appending the captured arguments. We also insert retains here at // the location of the original closure. This is needed to balance the // implicit release of all captured arguments that occurs when the partial // apply is destroyed. SILModule &M = NewF->getModule(); for (auto Arg : CSDesc.getArguments()) { NewArgs.push_back(Arg); SILType ArgTy = Arg->getType(); // If our argument is of trivial type, continue... if (ArgTy.isTrivial(M)) continue; // TODO: When we support address types, this code path will need to be // updated. // We need to balance the consumed argument of the new partial_apply in the // specialized callee by a retain. If both the original partial_apply and // the apply of the callee are in the same basic block we can assume they // are executed the same number of times. Therefore it is sufficient to just // retain the argument at the site of the original partial_apply. // // %closure = partial_apply (%arg) // = apply %callee(%closure) // => // retain %arg // %closure = partial_apply (%arg) // apply %specialized_callee(..., %arg) // // However, if they are not in the same basic block the callee might be // executed more frequently than the closure (for example, if the closure is // created in a loop preheader and the callee taking the closure is executed // in the loop). In such a case we must keep the argument live across the // call site of the callee and emit a matching retain for every invocation // of the callee. // // %closure = partial_apply (%arg) // // while () { // = %callee(%closure) // } // => // retain %arg // %closure = partial_apply (%arg) // // while () { // retain %arg // apply %specialized_callee(.., %arg) // } // release %arg // if (AI.getParent() != Closure->getParent()) { // Emit the retain and release that keeps the argument life across the // callee using the closure. CSDesc.extendArgumentLifetime(Arg); // Emit the retain that matches the captured argument by the partial_apply // in the callee that is consumed by the partial_apply. Builder.setInsertionPoint(AI.getInstruction()); Builder.createRetainValue(Closure->getLoc(), Arg, Atomicity::Atomic); } else { Builder.createRetainValue(Closure->getLoc(), Arg, Atomicity::Atomic); } } SILType LoweredType = NewF->getLoweredType(); SILType ResultType = LoweredType.castTo<SILFunctionType>()->getSILResult(); Builder.setInsertionPoint(AI.getInstruction()); FullApplySite NewAI; if (auto *TAI = dyn_cast<TryApplyInst>(AI)) { NewAI = Builder.createTryApply(AI.getLoc(), FRI, LoweredType, ArrayRef<Substitution>(), NewArgs, TAI->getNormalBB(), TAI->getErrorBB()); // If we passed in the original closure as @owned, then insert a release // right after NewAI. This is to balance the +1 from being an @owned // argument to AI. if (CSDesc.isClosureConsumed() && CSDesc.closureHasRefSemanticContext()) { Builder.setInsertionPoint(TAI->getNormalBB()->begin()); Builder.createReleaseValue(Closure->getLoc(), Closure, Atomicity::Atomic); Builder.setInsertionPoint(TAI->getErrorBB()->begin()); Builder.createReleaseValue(Closure->getLoc(), Closure, Atomicity::Atomic); Builder.setInsertionPoint(AI.getInstruction()); } } else { NewAI = Builder.createApply(AI.getLoc(), FRI, LoweredType, ResultType, ArrayRef<Substitution>(), NewArgs, cast<ApplyInst>(AI)->isNonThrowing()); // If we passed in the original closure as @owned, then insert a release // right after NewAI. This is to balance the +1 from being an @owned // argument to AI. if (CSDesc.isClosureConsumed() && CSDesc.closureHasRefSemanticContext()) Builder.createReleaseValue(Closure->getLoc(), Closure, Atomicity::Atomic); } // Replace all uses of the old apply with the new apply. if (isa<ApplyInst>(AI)) AI.getInstruction()->replaceAllUsesWith(NewAI.getInstruction()); // Erase the old apply. AI.getInstruction()->eraseFromParent(); // TODO: Maybe include invalidation code for CallSiteDescriptor after we erase // AI from parent? }
/// Insert monomorphic inline caches for a specific class or metatype /// type \p SubClassTy. static FullApplySite speculateMonomorphicTarget(FullApplySite AI, SILType SubType, CheckedCastBranchInst *&CCBI) { CCBI = nullptr; // Bail if this class_method cannot be devirtualized. if (!canDevirtualizeClassMethod(AI, SubType)) return FullApplySite(); // 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. auto It = AI.getInstruction()->getIterator(); 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(SubType); SILBasicBlock *Continue = Entry->splitBasicBlock(It); SILBuilderWithScope Builder(Entry, AI.getInstruction()); // Create the checked_cast_branch instruction that checks at runtime if the // class instance is identical to the SILType. ClassMethodInst *CMI = cast<ClassMethodInst>(AI.getCallee()); CCBI = Builder.createCheckedCastBranch(AI.getLoc(), /*exact*/ true, CMI->getOperand(), SubType, Iden, Virt); It = CCBI->getIterator(); SILBuilderWithScope VirtBuilder(Virt, AI.getInstruction()); SILBuilderWithScope IdenBuilder(Iden, AI.getInstruction()); // This is the class reference downcasted into subclass SubType. SILValue DownCastedClassInstance = Iden->getBBArg(0); // Copy the two apply instructions into the two blocks. FullApplySite IdenAI = CloneApply(AI, IdenBuilder); FullApplySite VirtAI = CloneApply(AI, VirtBuilder); // See if Continue has a release on self as the instruction right after the // apply. If it exists, move it into position in the diamond. if (auto *Release = dyn_cast<StrongReleaseInst>(std::next(Continue->begin()))) { if (Release->getOperand() == CMI->getOperand()) { VirtBuilder.createStrongRelease(Release->getLoc(), CMI->getOperand()); IdenBuilder.createStrongRelease(Release->getLoc(), DownCastedClassInstance); Release->eraseFromParent(); } } // Create a PHInode for returning the return value from both apply // instructions. SILArgument *Arg = Continue->createBBArg(AI.getType()); if (!isa<TryApplyInst>(AI)) { IdenBuilder.createBranch(AI.getLoc(), Continue, ArrayRef<SILValue>(IdenAI.getInstruction())); VirtBuilder.createBranch(AI.getLoc(), Continue, ArrayRef<SILValue>(VirtAI.getInstruction())); } // Remove the old Apply instruction. if (!isa<TryApplyInst>(AI)) AI.getInstruction()->replaceAllUsesWith(Arg); auto *OriginalBB = AI.getParent(); AI.getInstruction()->eraseFromParent(); if (OriginalBB->empty()) OriginalBB->removeFromParent(); // Update the stats. NumTargetsPredicted++; // Devirtualize the apply instruction on the identical path. auto NewInstPair = devirtualizeClassMethod(IdenAI, DownCastedClassInstance); assert(NewInstPair.first && "Expected to be able to devirtualize apply!"); replaceDeadApply(IdenAI, NewInstPair.first); // Split critical edges resulting from VirtAI. if (auto *TAI = dyn_cast<TryApplyInst>(VirtAI)) { auto *ErrorBB = TAI->getFunction()->createBasicBlock(); ErrorBB->createBBArg(TAI->getErrorBB()->getBBArg(0)->getType()); Builder.setInsertionPoint(ErrorBB); Builder.createBranch(TAI->getLoc(), TAI->getErrorBB(), {ErrorBB->getBBArg(0)}); auto *NormalBB = TAI->getFunction()->createBasicBlock(); NormalBB->createBBArg(TAI->getNormalBB()->getBBArg(0)->getType()); Builder.setInsertionPoint(NormalBB); Builder.createBranch(TAI->getLoc(), TAI->getNormalBB(), {NormalBB->getBBArg(0) }); Builder.setInsertionPoint(VirtAI.getInstruction()); SmallVector<SILValue, 4> Args; for (auto Arg : VirtAI.getArguments()) { Args.push_back(Arg); } FullApplySite NewVirtAI = Builder.createTryApply(VirtAI.getLoc(), VirtAI.getCallee(), VirtAI.getSubstCalleeSILType(), VirtAI.getSubstitutions(), Args, NormalBB, ErrorBB); VirtAI.getInstruction()->eraseFromParent(); VirtAI = NewVirtAI; } return VirtAI; }
/// 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(); }