diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index b5182bcca0f88..58be1f1e4f2f6 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -92,6 +92,15 @@ class SILBuilderContext { this->silConv = silConv; } + void setOpenedArchetypesTracker(SILOpenedArchetypesTracker *Tracker) { + OpenedArchetypesTracker = Tracker; + OpenedArchetypes.setOpenedArchetypesTracker(OpenedArchetypesTracker); + } + + SILOpenedArchetypesTracker *getOpenedArchetypesTracker() const { + return OpenedArchetypesTracker; + } + protected: /// Notify the context of each new instruction after it is inserted in the /// instruction stream. @@ -186,12 +195,11 @@ class SILBuilder { } void setOpenedArchetypesTracker(SILOpenedArchetypesTracker *Tracker) { - C.OpenedArchetypesTracker = Tracker; - C.OpenedArchetypes.setOpenedArchetypesTracker(C.OpenedArchetypesTracker); + C.setOpenedArchetypesTracker(Tracker); } SILOpenedArchetypesTracker *getOpenedArchetypesTracker() const { - return C.OpenedArchetypesTracker; + return C.getOpenedArchetypesTracker(); } SILOpenedArchetypesState &getOpenedArchetypes() { return C.OpenedArchetypes; } diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 78ae6d738a3e4..9b91efe79abd5 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -1930,8 +1930,6 @@ class ApplyInstBase assert(hasSelfArgument() && "Must have a self argument"); assert(getNumArguments() && "Should only be called when Callee has " "at least a self parameter."); - assert(hasSubstitutions() && "Should only be called when Callee has " - "substitutions."); ArrayRef ops = this->getArgumentOperands(); ArrayRef opsWithoutSelf = ArrayRef(&ops[0], ops.size()-1); diff --git a/include/swift/SILOptimizer/Utils/Existential.h b/include/swift/SILOptimizer/Utils/Existential.h index 51d584263d3fb..c84e85f4d6f5d 100644 --- a/include/swift/SILOptimizer/Utils/Existential.h +++ b/include/swift/SILOptimizer/Utils/Existential.h @@ -31,13 +31,76 @@ SILValue getAddressOfStackInit(AllocStackInst *ASI, SILInstruction *ASIUser, bool &isCopied); /// Find the init_existential, which could be used to determine a concrete -/// type of the \p Self. +/// type of the value used by \p openedUse. /// If the value is copied from another stack location, \p isCopied is set to /// true. -SILInstruction *findInitExistential(FullApplySite AI, SILValue Self, +/// +/// FIXME: replace all uses of this with ConcreteExistentialInfo. +SILInstruction *findInitExistential(Operand &openedUse, ArchetypeType *&OpenedArchetype, SILValue &OpenedArchetypeDef, bool &isCopied); + +/// Record conformance and concrete type info derived from an init_existential +/// value that is reopened before it's use. This is useful for finding the +/// concrete type of an apply's self argument. For example, an important pattern +/// for a class existential is: +/// +/// %e = init_existential_ref %c : $C : $C, $P & Q +/// %o = open_existential_ref %e : $P & Q to $@opened("PQ") P & Q +/// %r = apply %f<@opened("PQ") P & Q>(%o) +/// : $@convention(method) <τ_0_0 where τ_0_0 : P, τ_0_0 : Q> +/// (@guaranteed τ_0_0) -> @owned τ_0_0 +struct ConcreteExistentialInfo { + // The opened type passed as self. `$@opened("PQ")` above. + // This is also the replacement type of the method's Self type. + ArchetypeType *OpenedArchetype = nullptr; + // The definition of the OpenedArchetype. + SILValue OpenedArchetypeDef; + // True if the openedValue is copied from another stack location + bool isCopied; + // The init_existential instruction that produces the opened existential. + SILInstruction *InitExistential = nullptr; + // The existential type of the self argument before it is opened, + // produced by an init_existential. + CanType ExistentialType; + // The conformances required by the init_existential. `$C : $P & $Q` above. + ArrayRef ExistentialConformances; + // The concrete type of self from the init_existential. `$C` above. + CanType ConcreteType; + // When ConcreteType is itself an opened existential, record the type + // definition. May be nullptr for a valid AppliedConcreteType. + SingleValueInstruction *ConcreteTypeDef = nullptr; + // The Substitution map derived from the init_existential. + // This maps a single generic parameter to the replacement ConcreteType + // and includes the full list of existential conformances. + // signature:

, replacement: $C : conformances: [$P, $Q] + SubstitutionMap ExistentialSubs; + // The value of concrete type used to initialize the existential. `%c` above. + SILValue ConcreteValue; + + // Search for a recognized pattern in which the given value is an opened + // existential that was previously initialized to a concrete type. + // Constructs a valid ConcreteExistentialInfo object if successfull. + ConcreteExistentialInfo(Operand &openedUse); + + ConcreteExistentialInfo(ConcreteExistentialInfo &) = delete; + + bool isValid() const { + return OpenedArchetype && OpenedArchetypeDef && InitExistential + && ConcreteType && !ExistentialSubs.empty() && ConcreteValue; + } + + // Do a conformance lookup on ConcreteType with the given requirement, P. If P + // is satisfiable based on the existential's conformance, return the new + // conformance on P. Otherwise return None. + Optional + lookupExistentialConformance(ProtocolDecl *P) const { + CanType selfTy = P->getSelfInterfaceType()->getCanonicalType(); + return ExistentialSubs.lookupConformance(selfTy, P); + } +}; + } // end namespace swift #endif diff --git a/lib/SILOptimizer/SILCombiner/SILCombiner.h b/lib/SILOptimizer/SILCombiner/SILCombiner.h index fcfd0d86bfd31..b2cae236af470 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombiner.h +++ b/lib/SILOptimizer/SILCombiner/SILCombiner.h @@ -26,6 +26,7 @@ #include "swift/SIL/SILValue.h" #include "swift/SIL/SILVisitor.h" #include "swift/SILOptimizer/Utils/CastOptimizer.h" +#include "swift/SILOptimizer/Utils/Existential.h" #include "swift/SILOptimizer/Utils/Local.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" @@ -279,24 +280,16 @@ class SILCombiner : StringRef FInverseName, StringRef FName); private: - SILInstruction * createApplyWithConcreteType(FullApplySite AI, - SILValue NewSelf, - SILValue Self, - CanType ConcreteType, - SILValue ConcreteTypeDef, - ProtocolConformanceRef Conformance, - ArchetypeType *OpenedArchetype); - FullApplySite rewriteApplyCallee(FullApplySite apply, SILValue callee); - SILInstruction * - propagateConcreteTypeOfInitExistential(FullApplySite AI, - ProtocolDecl *Protocol, - llvm::function_ref Propagate); + SILInstruction *createApplyWithConcreteType(FullApplySite Apply, + const ConcreteExistentialInfo &CEI, + SILBuilderContext &BuilderCtx); - SILInstruction *propagateConcreteTypeOfInitExistential(FullApplySite AI, - WitnessMethodInst *WMI); - SILInstruction *propagateConcreteTypeOfInitExistential(FullApplySite AI); + SILInstruction * + propagateConcreteTypeOfInitExistential(FullApplySite Apply, + WitnessMethodInst *WMI); + SILInstruction *propagateConcreteTypeOfInitExistential(FullApplySite Apply); /// Perform one SILCombine iteration. bool doOneIteration(SILFunction &F, unsigned Iteration); diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp index 4181afbf63211..8299b0b02ae87 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp @@ -13,6 +13,7 @@ #define DEBUG_TYPE "sil-combine" #include "SILCombiner.h" #include "swift/AST/GenericSignature.h" +#include "swift/AST/Module.h" #include "swift/AST/SubstitutionMap.h" #include "swift/Basic/Range.h" #include "swift/SIL/DebugUtils.h" @@ -601,189 +602,31 @@ SILCombiner::optimizeConcatenationOfStringLiterals(ApplyInst *AI) { return tryToConcatenateStrings(AI, Builder); } -/// Create a new apply instructions that uses the concrete type instead -/// of the existential type. -SILInstruction * -SILCombiner::createApplyWithConcreteType(FullApplySite AI, - SILValue NewSelf, - SILValue Self, - CanType ConcreteType, - SILValue ConcreteTypeDef, - ProtocolConformanceRef Conformance, - ArchetypeType *OpenedArchetype) { - // Create a set of arguments. - SmallVector Args; - for (auto Arg : AI.getArgumentsWithoutSelf()) { - Args.push_back(Arg); - } - Args.push_back(NewSelf); - - auto FnTy = AI.getCallee()->getType().castTo(); - SILType SubstCalleeType = AI.getSubstCalleeSILType(); - SILType NewSubstCalleeType; - - // Form a new set of substitutions where Self is - // replaced by a concrete type. - SubstitutionMap Substitutions; - if (FnTy->isPolymorphic()) { - auto FnSubsMap = AI.getSubstitutionMap(); - Substitutions = FnSubsMap.subst( - [&](SubstitutableType *type) -> Type { - if (type == OpenedArchetype) - return ConcreteType; - return type; - }, - [&](CanType origTy, Type substTy, - ProtocolDecl *proto) -> Optional { - if (substTy->isEqual(ConcreteType)) { - assert(proto == Conformance.getRequirement()); - return Conformance; - } - return ProtocolConformanceRef(proto); - }); - - // Handle polymorphic functions by properly substituting - // their parameter types. - CanSILFunctionType SFT = FnTy->substGenericArgs( - AI.getModule(), - Substitutions); - NewSubstCalleeType = SILType::getPrimitiveObjectType(SFT); - } else { - NewSubstCalleeType = - SubstCalleeType.subst(AI.getModule(), - [&](SubstitutableType *type) -> Type { - if (type == OpenedArchetype) - return ConcreteType; - return type; - }, - MakeAbstractConformanceForGenericType()); - } - - FullApplySite NewAI; - Builder.setCurrentDebugScope(AI.getDebugScope()); - Builder.addOpenedArchetypeOperands(AI.getInstruction()); - - if (auto *TAI = dyn_cast(AI)) - NewAI = Builder.createTryApply(AI.getLoc(), AI.getCallee(), Substitutions, - Args, TAI->getNormalBB(), TAI->getErrorBB()); - else - NewAI = Builder.createApply(AI.getLoc(), AI.getCallee(), Substitutions, - Args, cast(AI)->isNonThrowing()); - - if (auto apply = dyn_cast(NewAI)) - replaceInstUsesWith(*cast(AI.getInstruction()), apply); - eraseInstFromFunction(*AI.getInstruction()); - - return NewAI.getInstruction(); -} - -namespace { -/// Record conformance and concrete type info derived from init_existential. -struct ConformanceAndConcreteType { - Optional Conformance; - // Concrete type of self from the found init_existential. - CanType ConcreteType; - // For opened existentials, record the type definition. - SingleValueInstruction *ConcreteTypeDef = nullptr; - // The value of concrete type used to initialize the existential. - SILValue NewSelf; - // The value that owns the lifetime of NewSelf. - // init_existential_addr's source address. - // init_existential_ref's defined value. - SILValue NewSelfOwner; - ArrayRef Conformances; - - ConformanceAndConcreteType(ASTContext &Ctx, FullApplySite AI, - SILInstruction *InitExistential, - ProtocolDecl *Protocol); - - bool isValid() const { - return Conformance.hasValue() && ConcreteType && NewSelf; - } - - ProtocolConformanceRef getConformance() const { - return Conformance.getValue(); - } -}; -} // namespace - -/// Derive a concrete type of self and conformance from the init_existential -/// instruction. -/// If successful, initializes a valid ConformanceAndConcreteType. -ConformanceAndConcreteType::ConformanceAndConcreteType( - ASTContext &Ctx, FullApplySite AI, SILInstruction *InitExistential, - ProtocolDecl *Protocol) { - - // The existential type result of the found init_existential. - CanType ExistentialType; - - // FIXME: Factor this out. All we really need here is the ExistentialSig - // below, which should be stored directly in the SILInstruction. - if (auto IE = dyn_cast(InitExistential)) { - Conformances = IE->getConformances(); - ConcreteType = IE->getFormalConcreteType(); - NewSelf = IE; - ExistentialType = IE->getOperand()->getType().getASTType(); - } else if (auto IER = dyn_cast(InitExistential)) { - Conformances = IER->getConformances(); - ConcreteType = IER->getFormalConcreteType(); - NewSelf = IER->getOperand(); - ExistentialType = IER->getType().getASTType(); - } else if (auto IEM = dyn_cast(InitExistential)){ - Conformances = IEM->getConformances(); - NewSelf = IEM->getOperand(); - ConcreteType = NewSelf->getType().getASTType(); - ExistentialType = IEM->getType().getASTType(); - while (auto InstanceType = dyn_cast(ExistentialType)) { - ExistentialType = InstanceType.getInstanceType(); - ConcreteType = cast(ConcreteType).getInstanceType(); - } - } else { - assert(!isValid()); - return; - } +/// Given an Apply and an argument value produced by InitExistentialAddrInst, +/// return true if the argument can be replaced by a copy of its value. +/// +/// FIXME: remove this helper when we can assume SIL opaque values. +static bool canReplaceCopiedSelf(FullApplySite Apply, + SILInstruction *InitExistential, + DominanceAnalysis *DA) { + // If the witness method mutates self, we cannot replace self with + // the source of a copy. Otherwise the call would modify another value than + // the original self. + if (Apply.getOrigCalleeType()->getSelfParameter().isIndirectMutating()) + return false; - // Construct a substitution map from the existential type's generic - // parameter to the concrete type. - auto ExistentialSig = Ctx.getExistentialSignature(ExistentialType, - AI.getModule().getSwiftModule()); - - auto SubMap = SubstitutionMap::get(ExistentialSig, { ConcreteType }, - Conformances); - - // If the requirement is in a base protocol that is refined by the - // conforming protocol, fish out the exact conformance for the base - // protocol using the substitution map. - Conformance = SubMap.lookupConformance( - CanType(ExistentialSig->getGenericParams()[0]), Protocol); - - // If the concrete type is another existential, we're "forwarding" an - // opened existential type, so we must keep track of the original - // defining instruction. - if (ConcreteType->isOpenedExistential()) { - if (InitExistential->getTypeDependentOperands().empty()) { - auto op = InitExistential->getOperand(0); - assert(op->getType().hasOpenedExistential() && - "init_existential is supposed to have a typedef operand"); - ConcreteTypeDef = cast(op); - } else { - ConcreteTypeDef = cast( - InitExistential->getTypeDependentOperands()[0].get()); - } - } - assert(isValid()); -} + auto *DT = DA->get(Apply.getFunction()); + auto *AI = Apply.getInstruction(); + // Only init_existential_addr may be copied. + SILValue existentialAddr = + cast(InitExistential)->getOperand(); -// Return true if the given value is guaranteed to be initialized across the -// given call site. -// -// It's possible for an address to be initialized/deinitialized/reinitialized. -// Rather than keeping track of liveness, we very conservatively check that all -// deinitialization occures after the call. -// -// FIXME: Rather than whitelisting, use a common AllocStackAnalyzer. -static bool isAddressInitializedAtCall(SILValue addr, SILInstruction *AI, - DominanceInfo *DT) { + // Return true only if the given value is guaranteed to be initialized across + // the given call site. + // + // It's possible for an address to be initialized/deinitialized/reinitialized. + // Rather than keeping track of liveness, we very conservatively check that + // all deinitialization occures after the call. auto isDestroy = [](Operand *use) { switch (use->getUser()->getKind()) { default: @@ -797,7 +640,7 @@ static bool isAddressInitializedAtCall(SILValue addr, SILInstruction *AI, } } }; - for (auto use : addr->getUses()) { + for (auto use : existentialAddr->getUses()) { SILInstruction *user = use->getUser(); if (isDestroy(use)) { if (!DT->properlyDominates(AI, user)) @@ -812,97 +655,112 @@ static bool isAddressInitializedAtCall(SILValue addr, SILInstruction *AI, return true; } -/// Scoped registration of opened archetypes. -class RAIIOpenedArchetypesTracker { - SILBuilder &B; - // The original tracker may be null. - SILOpenedArchetypesTracker *OldOpenedArchetypesTracker; - SILOpenedArchetypesTracker OpenedArchetypesTracker; - -public: - RAIIOpenedArchetypesTracker(SILBuilder &B) - : B(B), OldOpenedArchetypesTracker(B.getOpenedArchetypesTracker()), - OpenedArchetypesTracker(&B.getFunction()) { - B.setOpenedArchetypesTracker(&OpenedArchetypesTracker); - } - - SILOpenedArchetypesTracker &getTracker() { - return OpenedArchetypesTracker; - } - - ~RAIIOpenedArchetypesTracker() { - B.setOpenedArchetypesTracker(OldOpenedArchetypesTracker); - } -}; - -/// Propagate information about a concrete type from init_existential_addr -/// or init_existential_ref into witness_method conformances and into -/// apply instructions. -/// This helps the devirtualizer to replace witness_method by +/// Rewrite the given method apply instruction in terms of the provided conrete +/// type information. +/// +/// If the rewrite is successful, the original apply will be removed and the new +/// apply is returned. Otherwise, the original apply will not be removed and +/// nullptr is returned. +/// +/// Creates a new apply instruction that uses the concrete type instead of the +/// existential type. Type substitution will be performed from all occurrences +/// of CEI.OpenedArchetype to the replacement type CEI.ConcreteType within the +/// applied function type. The single self argument of the apply will be +/// rewritten. This helps the devirtualizer to replace witness_method by /// class_method instructions and then devirtualize. -SILInstruction *SILCombiner::propagateConcreteTypeOfInitExistential( - FullApplySite Apply, ProtocolDecl *Protocol, - llvm::function_ref Propagate) { - - ASTContext &Ctx = Builder.getASTContext(); - - // Get the self argument. - assert(Apply.hasSelfArgument() && "Self argument should be present"); - SILValue Self = Apply.getSelfArgument(); - - // Try to find the init_existential, which could be used to - // determine a concrete type of the self. - ArchetypeType *OpenedArchetype = nullptr; - SILValue OpenedArchetypeDef; - bool isCopied = false; - SILInstruction *InitExistential = findInitExistential( - Apply, Self, OpenedArchetype, OpenedArchetypeDef, isCopied); - if (!InitExistential) +/// +/// Note that the substituted type, CEI.OpenedArchetype, is the same type as the +/// self argument for nonstatic methods, but for static methods self is the +/// metatype instead. For witness methods, CEI.OpenedArchetype is usually the +/// same as WMI->getLookupType() but differs in the unusual situation in which +/// the witness method is looked up using a different opened archetype. +/// +/// FIXME: Protocol methods (witness or default) that return Self will be given +/// a new return type. This implementation fails to update the type signature of +/// SSA uses in those cases. Currently we bail out on methods that return Self. +SILInstruction * +SILCombiner::createApplyWithConcreteType(FullApplySite Apply, + const ConcreteExistentialInfo &CEI, + SILBuilderContext &BuilderCtx) { + assert(Apply.getOrigCalleeType()->isPolymorphic()); + + // Don't specialize apply instructions that return the callee's Self type, + // because this optimization does not know how to substitute types in the + // users of this apply. In the function type substitution below, all + // references to OpenedArchetype will be substituted. So walk to type to find + // all possible references, such as returning Optional. + if (Apply.getType().getASTType().findIf( + [&CEI](Type t) -> bool { return t->isEqual(CEI.OpenedArchetype); })) { return nullptr; - - // Try to derive the concrete type of self and a related conformance from - // the found init_existential. - ConformanceAndConcreteType CCT(Ctx, Apply, InitExistential, Protocol); - if (!CCT.isValid()) + } + // Bail out if any non-self arguments or indirect result that refer to the + // OpenedArchetype. The following optimization substitutes all occurrences of + // OpenedArchetype in the function signature, but will only rewrite the self + // operand. + // + // Note that the language does not allow Self to occur in contravariant + // position. However, SIL does allow this and it can happen as a result of + // upstream transformations. Since this is bail-out logic, it must handle all + // verifiable SIL. + for (auto Arg : Apply.getArgumentsWithoutSelf()) { + if (Arg->getType().getASTType().findIf([&CEI](Type t) -> bool { + return t->isEqual(CEI.OpenedArchetype); + })) { + return nullptr; + } + } + // The apply can only be rewritten in terms of the concrete value if it is + // legal to pass that value as the self argument. + if (CEI.isCopied && !canReplaceCopiedSelf(Apply, CEI.InitExistential, DA)) return nullptr; - RAIIOpenedArchetypesTracker tempTracker(Builder); - if (CCT.ConcreteType->isOpenedExistential()) { - // Temporarily record this opened existential def. Don't permanently record - // in the Builder's tracker because this opened existential's original - // dominating def may not have been recorded yet. - // FIXME: Redesign the tracker. This is not robust. - tempTracker.getTracker().addOpenedArchetypeDef( - cast(CCT.ConcreteType), CCT.ConcreteTypeDef); + // Create a set of arguments. + SmallVector NewArgs; + for (auto Arg : Apply.getArgumentsWithoutSelf()) { + NewArgs.push_back(Arg); } + NewArgs.push_back(CEI.ConcreteValue); - // Propagate the concrete type into the callee-operand if required. - Propagate(CCT.ConcreteType, CCT.getConformance()); + assert(Apply.getOrigCalleeType()->isPolymorphic()); - auto canReplaceCopiedSelf = [&]() { - // If the witness method is mutating self, we cannot replace self with - // the source of a copy. Otherwise the call would modify another value than - // the original self. - if (Apply.getOrigCalleeType()->getSelfParameter().isIndirectMutating()) - return false; + // Form a new set of substitutions where Self is + // replaced by a concrete type. + SubstitutionMap OrigCallSubs = Apply.getSubstitutionMap(); + SubstitutionMap NewCallSubs = OrigCallSubs.subst( + [&](SubstitutableType *type) -> Type { + if (type == CEI.OpenedArchetype) + return CEI.ConcreteType; + return type; + }, + [&](CanType origTy, Type substTy, + ProtocolDecl *proto) -> Optional { + if (origTy->isEqual(CEI.OpenedArchetype)) { + assert(substTy->isEqual(CEI.ConcreteType)); + // Do a conformance lookup on this witness requirement using the + // existential's conformances. The witness requirement may be a base + // type of the existential's requirements. + return CEI.lookupExistentialConformance(proto).getValue(); + } + return ProtocolConformanceRef(proto); + }); + + SILBuilderWithScope ApplyBuilder(Apply.getInstruction(), BuilderCtx); + FullApplySite NewApply; + if (auto *TAI = dyn_cast(Apply)) + NewApply = ApplyBuilder.createTryApply( + Apply.getLoc(), Apply.getCallee(), NewCallSubs, NewArgs, + TAI->getNormalBB(), TAI->getErrorBB()); + else + NewApply = ApplyBuilder.createApply( + Apply.getLoc(), Apply.getCallee(), NewCallSubs, NewArgs, + cast(Apply)->isNonThrowing()); - auto *DT = DA->get(Apply.getFunction()); - auto *AI = Apply.getInstruction(); - // Only init_existential_addr may be copied. - SILValue existentialAddr = - cast(InitExistential)->getOperand(); - return isAddressInitializedAtCall(existentialAddr, AI, DT); - }; - if (isCopied && !canReplaceCopiedSelf()) - return nullptr; + if (auto NewAI = dyn_cast(NewApply)) + replaceInstUsesWith(*cast(Apply.getInstruction()), NewAI); - // Create a new apply instruction that uses the concrete type instead - // of the existential type. - auto *NewAI = createApplyWithConcreteType( - Apply, CCT.NewSelf, Self, CCT.ConcreteType, CCT.ConcreteTypeDef, - CCT.getConformance(), OpenedArchetype); + eraseInstFromFunction(*Apply.getInstruction()); - return NewAI; + return NewApply.getInstruction(); } /// Rewrite a witness method's lookup type from an archetype to a concrete type. @@ -915,116 +773,108 @@ SILInstruction *SILCombiner::propagateConcreteTypeOfInitExistential( /// /// ==> apply %witness(%existential) SILInstruction * -SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI, +SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply, WitnessMethodInst *WMI) { // Check if it is legal to perform the propagation. if (WMI->getConformance().isConcrete()) return nullptr; - // Don't specialize Apply instructions that return the Self type. - // Notice that it is sufficient to compare the return type to the - // substituted type because types that depend on the Self type are - // not allowed (for example [Self] is not allowed). - if (AI.getType().getASTType() == WMI->getLookupType()) - return nullptr; - - // We need to handle the Self return type. - // In we find arguments that are not the 'self' argument and if - // they are of the Self type then we abort the optimization. - for (auto Arg : AI.getArgumentsWithoutSelf()) { - if (Arg->getType().getASTType() == WMI->getLookupType()) - return nullptr; - } - // The lookup type is not an opened existential type, - // thus it cannot be made more concrete. + // If the lookup type is not an opened existential type, + // it cannot be made more concrete. if (!WMI->getLookupType()->isOpenedExistential()) return nullptr; - // Obtain the protocol which should be used by the conformance. - auto *PD = WMI->getLookupProtocol(); + // Try to derive the concrete type of self and the related conformance by + // searching for a preceding init_existential. + const ConcreteExistentialInfo CEI(Apply.getSelfArgumentOperand()); + if (!CEI.isValid()) + return nullptr; + // Get the conformance of the init_existential type, which is passed as the + // self argument, on the witness' protocol. + ProtocolConformanceRef SelfConformance = + *CEI.lookupExistentialConformance(WMI->getLookupProtocol()); + + SILBuilderContext BuilderCtx(Builder.getModule(), Builder.getTrackingList()); + SILOpenedArchetypesTracker OpenedArchetypesTracker(&Builder.getFunction()); + BuilderCtx.setOpenedArchetypesTracker(&OpenedArchetypesTracker); + if (CEI.ConcreteType->isOpenedExistential()) { + // Temporarily record this opened existential def in this local + // BuilderContext before rewriting the witness method. + OpenedArchetypesTracker.addOpenedArchetypeDef( + cast(CEI.ConcreteType), CEI.ConcreteTypeDef); + } // Propagate the concrete type into a callee-operand, which is a - // witness_method instruction. - auto PropagateIntoOperand = [this, &WMI, &AI]( - CanType ConcreteType, ProtocolConformanceRef Conformance) { - if (ConcreteType == WMI->getLookupType() && - Conformance == WMI->getConformance()) { - // If we create a new instruction that’s the same as the old one we’ll - // cause an infinite loop: - // NewWMI will be added to the Builder’s tracker list. - // SILCombine, in turn, uses the tracker list to populate the worklist - // As such, if we don’t remove the witness method later on in the pass, we - // are stuck: - // We will re-create the same instruction and re-populate the worklist - // with it - return; - } + // witness_method instruction. It's ok to rewrite the witness method in terms + // of a concrete type without rewriting the apply itself. In fact, doing so + // may allow the Devirtualizer pass to finish the job. + // + // If we create a new instruction that’s the same as the old one we’ll + // cause an infinite loop: + // NewWMI will be added to the Builder’s tracker list. + // SILCombine, in turn, uses the tracker list to populate the worklist + // As such, if we don’t remove the witness method later on in the pass, we + // are stuck: + // We will re-create the same instruction and re-populate the worklist + // with it. + if (CEI.ConcreteType != WMI->getLookupType() + || SelfConformance != WMI->getConformance()) { + SILBuilderWithScope WMIBuilder(WMI, BuilderCtx); // Keep around the dependence on the open instruction unless we've // actually eliminated the use. - auto *NewWMI = Builder.createWitnessMethod(WMI->getLoc(), - ConcreteType, - Conformance, WMI->getMember(), - WMI->getType()); - // Replace only uses of the witness_method in the apply that is going to - // be changed. - MutableArrayRef Operands = AI.getInstruction()->getAllOperands(); + auto *NewWMI = WMIBuilder.createWitnessMethod( + WMI->getLoc(), CEI.ConcreteType, SelfConformance, WMI->getMember(), + WMI->getType()); + NewWMI->dump(); + // Replace only uses of the witness_method in the apply that was analyzed by + // ConcreteExistentialInfo. + MutableArrayRef Operands = + Apply.getInstruction()->getAllOperands(); for (auto &Op : Operands) { if (Op.get() == WMI) Op.set(NewWMI); } if (WMI->use_empty()) eraseInstFromFunction(*WMI); - }; - - // Try to perform the propagation. - return propagateConcreteTypeOfInitExistential(AI, PD, PropagateIntoOperand); + } + // Try to rewrite the apply. + return createApplyWithConcreteType(Apply, CEI, BuilderCtx); } - +/// Rewrite a protocol extension lookup type from an archetype to a concrete +/// type. +/// Example: +/// %ref = alloc_ref $C +/// %existential = init_existential_ref %ref : $C : $C, $P +/// %opened = open_existential_ref %existential : $P to $@opened +/// %f = function_ref @defaultMethod +/// apply %f<@opened P>(%opened) +/// +/// ==> apply %f(%ref) SILInstruction * -SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI) { - // Check if it is legal to perform the propagation. - if (!AI.hasSubstitutions()) - return nullptr; - auto *Callee = AI.getReferencedFunction(); - if (!Callee || !Callee->getDeclContext()) +SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply) { + // This optimization requires a generic self argument. + if (!Apply.hasSelfArgument() || !Apply.hasSubstitutions()) return nullptr; - // Bail, if there is no self argument. - SILValue Self; - if (auto *Apply = dyn_cast(AI)) { - if (Apply->hasSelfArgument()) - Self = Apply->getSelfArgument(); - } else if (auto *Apply = dyn_cast(AI)) { - if (Apply->hasSelfArgument()) - Self = Apply->getSelfArgument(); - } - if (!Self) + // Try to derive the concrete type of self and a related conformance from + // the found init_existential. + const ConcreteExistentialInfo CEI(Apply.getSelfArgumentOperand()); + if (!CEI.isValid()) return nullptr; - // We need to handle the Self return type. - // In we find arguments that are not the 'self' argument and if - // they are of the Self type then we abort the optimization. - for (auto Arg : AI.getArgumentsWithoutSelf()) { - if (Arg->getType().getASTType() == - AI.getArguments().back()->getType().getASTType()) - return nullptr; + SILBuilderContext BuilderCtx(Builder.getModule(), Builder.getTrackingList()); + SILOpenedArchetypesTracker OpenedArchetypesTracker(&Builder.getFunction()); + BuilderCtx.setOpenedArchetypesTracker(&OpenedArchetypesTracker); + if (CEI.ConcreteType->isOpenedExistential()) { + // Temporarily record this opened existential def in this local + // BuilderContext before rewriting the witness method. + OpenedArchetypesTracker.addOpenedArchetypeDef( + cast(CEI.ConcreteType), CEI.ConcreteTypeDef); } - - // Obtain the protocol which should be used by the conformance. - auto *AFD = dyn_cast(Callee->getDeclContext()); - if (!AFD) - return nullptr; - auto *PD = AFD->getDeclContext()->getAsProtocolOrProtocolExtensionContext(); - - - // No need to propagate anything into the callee operand. - auto PropagateIntoOperand = [] (CanType ConcreteType, - ProtocolConformanceRef Conformance) {}; - - // Try to perform the propagation. - return propagateConcreteTypeOfInitExistential(AI, PD, PropagateIntoOperand); + // Perform the transformation by rewriting the apply. + return createApplyWithConcreteType(Apply, CEI, BuilderCtx); } /// \brief Check that all users of the apply are retain/release ignoring one diff --git a/lib/SILOptimizer/Utils/Existential.cpp b/lib/SILOptimizer/Utils/Existential.cpp index abc840a8bf319..2c35f8574b0ef 100644 --- a/lib/SILOptimizer/Utils/Existential.cpp +++ b/lib/SILOptimizer/Utils/Existential.cpp @@ -141,16 +141,17 @@ SILValue swift::getAddressOfStackInit(AllocStackInst *ASI, /// type of the \p Self. /// If the value is copied from another stack location, \p isCopied is set to /// true. -SILInstruction *swift::findInitExistential(FullApplySite AI, SILValue Self, +SILInstruction *swift::findInitExistential(Operand &openedUse, ArchetypeType *&OpenedArchetype, SILValue &OpenedArchetypeDef, bool &isCopied) { + SILValue Self = openedUse.get(); + SILInstruction *User = openedUse.getUser(); isCopied = false; if (auto *Instance = dyn_cast(Self)) { // In case the Self operand is an alloc_stack where a copy_addr copies the // result of an open_existential_addr to this stack location. - if (SILValue Src = - getAddressOfStackInit(Instance, AI.getInstruction(), isCopied)) + if (SILValue Src = getAddressOfStackInit(Instance, User, isCopied)) Self = Src; } @@ -195,3 +196,65 @@ SILInstruction *swift::findInitExistential(FullApplySite AI, SILValue Self, } return nullptr; } + +/// Derive a concrete type of self and conformance from the init_existential +/// instruction. +/// If successful, initializes a valid ConformanceAndConcreteType. +ConcreteExistentialInfo::ConcreteExistentialInfo(Operand &openedUse) { + // Try to find the init_existential, which could be used to + // determine a concrete type of the self. + // Returns: InitExistential, OpenedArchetype, OpenedArchetypeDef, isCopied. + InitExistential = findInitExistential(openedUse, OpenedArchetype, + OpenedArchetypeDef, isCopied); + if (!InitExistential) + return; + + if (auto IE = dyn_cast(InitExistential)) { + ExistentialType = IE->getOperand()->getType().getASTType(); + ExistentialConformances = IE->getConformances(); + ConcreteType = IE->getFormalConcreteType(); + ConcreteValue = IE; + } else if (auto IER = dyn_cast(InitExistential)) { + ExistentialType = IER->getType().getASTType(); + ExistentialConformances = IER->getConformances(); + ConcreteType = IER->getFormalConcreteType(); + ConcreteValue = IER->getOperand(); + } else if (auto IEM = + dyn_cast(InitExistential)) { + ExistentialType = IEM->getType().getASTType(); + ExistentialConformances = IEM->getConformances(); + ConcreteValue = IEM->getOperand(); + ConcreteType = ConcreteValue->getType().getASTType(); + while (auto InstanceType = + dyn_cast(ExistentialType)) { + ExistentialType = InstanceType.getInstanceType(); + ConcreteType = cast(ConcreteType).getInstanceType(); + } + } else { + assert(!isValid()); + return; + } + // Construct a single-generic-parameter substitution map directly to the + // ConcreteType with this existential's full list of conformances. + SILModule &M = InitExistential->getModule(); + CanGenericSignature ExistentialSig = + M.getASTContext().getExistentialSignature(ExistentialType, + M.getSwiftModule()); + ExistentialSubs = SubstitutionMap::get(ExistentialSig, {ConcreteType}, + ExistentialConformances); + // If the concrete type is another existential, we're "forwarding" an + // opened existential type, so we must keep track of the original + // defining instruction. + if (ConcreteType->isOpenedExistential()) { + if (InitExistential->getTypeDependentOperands().empty()) { + auto op = InitExistential->getOperand(0); + assert(op->getType().hasOpenedExistential() + && "init_existential is supposed to have a typedef operand"); + ConcreteTypeDef = cast(op); + } else { + ConcreteTypeDef = cast( + InitExistential->getTypeDependentOperands()[0].get()); + } + } + assert(isValid()); +} diff --git a/test/SILOptimizer/sil_combine_concrete_existential.sil b/test/SILOptimizer/sil_combine_concrete_existential.sil new file mode 100644 index 0000000000000..38f73f754b387 --- /dev/null +++ b/test/SILOptimizer/sil_combine_concrete_existential.sil @@ -0,0 +1,372 @@ +// RUN: %target-sil-opt -enable-objc-interop -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all %s -sil-combine | %FileCheck %s + +// These tests exercise the same SILCombine optimization as +// existential_type_propagation.sil, but cover additional corner +// cases. These are pure unit tests. They do not run the devirtualizer +// or inliner. + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +//===----------------------------------------------------------------------===// +// testReturnSelf: Call to a protocol extension method with +// an existential self that can be type-propagated. +// SILCombine should bailout since it does not propagate +// type substitutions on the return value. +// +// [SR-7773]: +// SILCombiner::propagateConcreteTypeOfInitExistential fails to full propagate +// type substitutions. +//===----------------------------------------------------------------------===// +public protocol P : AnyObject { +} + +extension P { + public func returnSelf() -> Self +} + +final class C : P { + init() + deinit +} + +public func testReturnSelf() -> P + +// P.returnSelf() +sil @$S21extension_return_self1PPAAE0B4SelfxyF : $@convention(method) (@guaranteed Self) -> @owned Self + +// C.__allocating_init() +sil @$S21extension_return_self1CCACycfC : $@convention(method) (@thick C.Type) -> @owned C + +// public func testReturnSelf() -> P { +// let p: P = C() +// return p.returnSelf().returnSelf() +// } +// Neither apply responds to type propagation. +// +// CHECK-LABEL: sil @$S21extension_return_self14testReturnSelfAA1P_pyF : $@convention(thin) () -> @owned P { +// CHECK: [[E1:%.*]] = init_existential_ref %{{.*}} : $C : $C, $P +// CHECK: [[O1:%.*]] = open_existential_ref [[E1]] : $P to $@opened("{{.*}}") P +// CHECK: [[F1:%.*]] = function_ref @$S21extension_return_self1PPAAE0B4SelfxyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK: [[C1:%.*]] = apply [[F1]]<@opened("{{.*}}") P>([[O1]]) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK: [[E2:%.*]] = init_existential_ref [[C1]] : $@opened("{{.*}}") P : $@opened("{{.*}}") P, $P +// CHECK: [[O2:%.*]] = open_existential_ref [[E2]] : $P to $@opened("{{.*}}") P +// CHECK: [[F2:%.*]] = function_ref @$S21extension_return_self1PPAAE0B4SelfxyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK: apply [[F2]]<@opened("{{.*}}") P>([[O2]]) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK-LABEL: } // end sil function '$S21extension_return_self14testReturnSelfAA1P_pyF' +sil @$S21extension_return_self14testReturnSelfAA1P_pyF : $@convention(thin) () -> @owned P { +bb0: + %0 = metatype $@thick C.Type + // function_ref C.__allocating_init() + %1 = function_ref @$S21extension_return_self1CCACycfC : $@convention(method) (@thick C.Type) -> @owned C + %2 = apply %1(%0) : $@convention(method) (@thick C.Type) -> @owned C + %3 = init_existential_ref %2 : $C : $C, $P + %5 = open_existential_ref %3 : $P to $@opened("1217498E-72AC-11E8-9816-ACDE48001122") P + // function_ref P.returnSelf() + %6 = function_ref @$S21extension_return_self1PPAAE0B4SelfxyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 + %7 = apply %6<@opened("1217498E-72AC-11E8-9816-ACDE48001122") P>(%5) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 + %8 = init_existential_ref %7 : $@opened("1217498E-72AC-11E8-9816-ACDE48001122") P : $@opened("1217498E-72AC-11E8-9816-ACDE48001122") P, $P + %9 = open_existential_ref %8 : $P to $@opened("12174BD2-72AC-11E8-9816-ACDE48001122") P + // function_ref P.returnSelf() + %10 = function_ref @$S21extension_return_self1PPAAE0B4SelfxyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 + %11 = apply %10<@opened("12174BD2-72AC-11E8-9816-ACDE48001122") P>(%9) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 + %12 = init_existential_ref %11 : $@opened("12174BD2-72AC-11E8-9816-ACDE48001122") P : $@opened("12174BD2-72AC-11E8-9816-ACDE48001122") P, $P + strong_release %9 : $@opened("12174BD2-72AC-11E8-9816-ACDE48001122") P + strong_release %3 : $P + return %12 : $P +} + +//===----------------------------------------------------------------------===// +// testWitnessReturnOptionalSelf: Call to a witness method with an existential +// self that can be type-propagated. SILCombine should bailout since it does +// not propagate type substitutions on the return value, and it must walk the +// Optional type to find Self in the return type. +//===----------------------------------------------------------------------===// +public protocol PP : AnyObject { + func returnOptionalSelf() -> Self? +} + +final class CC : PP { + init() + final func returnOptionalSelf() -> Self? + deinit +} + +public func testWitnessReturnOptionalSelf() -> PP? + +// CC.__allocating_init() +sil @$S28witness_return_optional_self2CCCACycfC : $@convention(method) (@thick CC.Type) -> @owned CC + +// public func testWitnessReturnOptionalSelf() -> PP? { +// let p: PP = CC() +// return p.returnOptionalSelf()?.returnOptionalSelf() +// } +// +// Although SILCombine will not replace the self operand, it will still +// rewrite the witness_method. +// +// The first witness_method is rewritten for the concrete lookup type 'CC'. +// +// The second witness_method is rewritten for the first opened existential type. +// Neither apply is rewritten. +// +// CHECK-LABEL: sil @$S28witness_return_optional_self29testWitnessReturnOptionalSelfAA2PP_pSgyF : $@convention(thin) () -> @owned Optional { +// CHECK: [[E1:%.*]] = init_existential_ref %{{.*}} : $CC : $CC, $PP +// CHECK: [[O1:%.*]] = open_existential_ref [[E1]] : $PP to $@opened("{{.*}}") PP +// CHECK: [[W1:%.*]] = witness_method $CC, #PP.returnOptionalSelf!1 : (Self) -> () -> @dynamic_self Self? : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> +// CHECK: apply [[W1]]<@opened("{{.*}}") PP>([[O1]]) : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> +// CHECK: [[E2:%.*]] = init_existential_ref %{{.*}} : $@opened("{{.*}}") PP : $@opened("{{.*}}") PP, $PP +// CHECK: [[O2:%.*]] = open_existential_ref [[E2]] : $PP to $@opened("{{.*}}") PP +// CHECK: [[W2:%.*]] = witness_method $@opened("{{.*}}") PP, #PP.returnOptionalSelf!1 : (Self) -> () -> @dynamic_self Self?, [[O1]] : $@opened("{{.*}}") PP : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> +// CHECK: apply [[W2]]<@opened("{{.*}}") PP>([[O2]]) : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> +// CHECK-LABEL: } // end sil function '$S28witness_return_optional_self29testWitnessReturnOptionalSelfAA2PP_pSgyF' +sil @$S28witness_return_optional_self29testWitnessReturnOptionalSelfAA2PP_pSgyF : $@convention(thin) () -> @owned Optional { +bb0: + %0 = metatype $@thick CC.Type + // function_ref CC.__allocating_init() + %1 = function_ref @$S28witness_return_optional_self2CCCACycfC : $@convention(method) (@thick CC.Type) -> @owned CC + %2 = apply %1(%0) : $@convention(method) (@thick CC.Type) -> @owned CC + %3 = init_existential_ref %2 : $CC : $CC, $PP + %5 = open_existential_ref %3 : $PP to $@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP + %6 = witness_method $@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP, #PP.returnOptionalSelf!1 : (Self) -> () -> @dynamic_self Self?, %5 : $@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> + %7 = apply %6<@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP>(%5) : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> + %8 = unchecked_enum_data %7 : $Optional<@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP>, #Optional.some!enumelt.1 + %11 = init_existential_ref %8 : $@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP : $@opened("00000000-72AD-11E8-88DF-ACDE48001122") PP, $PP + %12 = enum $Optional, #Optional.some!enumelt.1, %11 : $PP + %13 = unchecked_enum_data %12 : $Optional, #Optional.some!enumelt.1 + %18 = open_existential_ref %13 : $PP to $@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP + %19 = witness_method $@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP, #PP.returnOptionalSelf!1 : (Self) -> () -> @dynamic_self Self?, %18 : $@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> + %20 = apply %19<@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP>(%18) : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> + %21 = unchecked_enum_data %20 : $Optional<@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP>, #Optional.some!enumelt.1 + %22 = init_existential_ref %21 : $@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP : $@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP, $PP + %23 = enum $Optional, #Optional.some!enumelt.1, %22 : $PP + strong_release %18 : $@opened("FFFFFFFF-72AD-11E8-88DF-ACDE48001122") PP + strong_release %3 : $PP + return %23 : $Optional +} + +//===----------------------------------------------------------------------===// +// testWitnessReturnOptionalIndirectSelf: Call to a witness method with an +// existential self that can be type-propagated. SILCombine should bailout +// since it does not propagate type substitutions on non-self arguments. It must +// walk the Optional type to find Self in the non-self argument. +//===----------------------------------------------------------------------===// +protocol PPP { + func returnsOptionalIndirect() -> Self? +} + +struct S : PPP { + func returnsOptionalIndirect() -> S? + init() +} + +public func testWitnessReturnOptionalIndirectSelf() + +// S.init() +sil @$S37witness_return_optional_indirect_self1SVACycfC : $@convention(method) (@thin S.Type) -> S + +// testWitnessReturnOptionalIndirectSelf() +// public func testWitnessReturnOptionalIndirectSelf() { +// let p: PPP = S() +// p.returnsOptionalIndirect()?.returnsOptionalIndirect() +// } +// +// Although SILCombine will not replace the self operand, it will still +// rewrite the witness_method. The devirtualizer could then handle the first call. +// +// CHECK-LABEL: sil @$S37witness_return_optional_indirect_self37testWitnessReturnOptionalIndirectSelfyyF : $@convention(thin) () -> () { +// CHECK: [[O1:%.*]] = open_existential_addr immutable_access %0 : $*PPP to $*@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP +// CHECK: [[R1:%.*]] = alloc_stack $Optional<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP> +// CHECK: [[W1:%.*]] = witness_method $S, #PPP.returnsOptionalIndirect!1 : (Self) -> () -> @dynamic_self Self? : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> +// CHECK: apply [[W1]]<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP>([[R1]], [[O1]]) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> +// CHECK: inject_enum_addr [[OE1:%.*]] : $*Optional, #Optional.some!enumelt.1 +// CHECK: [[E1:%.*]] = unchecked_take_enum_data_addr [[OE1]] : $*Optional, #Optional.some!enumelt.1 +// CHECK: [[O2:%.*]] = open_existential_addr immutable_access [[E1]] : $*PPP to $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP +// CHECK: [[R2:%.*]] = alloc_stack $Optional<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP> +// CHECK: [[W2:%.*]] = witness_method $@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP, #PPP.returnsOptionalIndirect!1 : (Self) -> () -> @dynamic_self Self?, %19 : $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> +// CHECK: apply [[W2]]<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP>([[R2]], [[O2]]) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> +// CHECK-LABEL: } // end sil function '$S37witness_return_optional_indirect_self37testWitnessReturnOptionalIndirectSelfyyF' +sil @$S37witness_return_optional_indirect_self37testWitnessReturnOptionalIndirectSelfyyF : $@convention(thin) () -> () { +bb0: + %0 = alloc_stack $PPP, let, name "p" + %1 = init_existential_addr %0 : $*PPP, $S + %2 = metatype $@thin S.Type + // function_ref S.init() + %3 = function_ref @$S37witness_return_optional_indirect_self1SVACycfC : $@convention(method) (@thin S.Type) -> S + %4 = apply %3(%2) : $@convention(method) (@thin S.Type) -> S + store %4 to %1 : $*S + %6 = alloc_stack $Optional + %7 = alloc_stack $Optional + %8 = open_existential_addr immutable_access %0 : $*PPP to $*@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP + %9 = init_enum_data_addr %7 : $*Optional, #Optional.some!enumelt.1 + %10 = init_existential_addr %9 : $*PPP, $@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP + %11 = alloc_stack $Optional<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP> + %12 = witness_method $@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP, #PPP.returnsOptionalIndirect!1 : (Self) -> () -> @dynamic_self Self?, %8 : $*@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> + %13 = apply %12<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP>(%11, %8) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> + %14 = unchecked_take_enum_data_addr %11 : $*Optional<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP>, #Optional.some!enumelt.1 + copy_addr [take] %14 to [initialization] %10 : $*@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP + inject_enum_addr %7 : $*Optional, #Optional.some!enumelt.1 + dealloc_stack %11 : $*Optional<@opened("83DE9694-7315-11E8-955C-ACDE48001122") PPP> + %28 = unchecked_take_enum_data_addr %7 : $*Optional, #Optional.some!enumelt.1 + %29 = open_existential_addr immutable_access %28 : $*PPP to $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP + %30 = init_enum_data_addr %6 : $*Optional, #Optional.some!enumelt.1 + %31 = init_existential_addr %30 : $*PPP, $@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP + %32 = alloc_stack $Optional<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP> + %33 = witness_method $@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP, #PPP.returnsOptionalIndirect!1 : (Self) -> () -> @dynamic_self Self?, %29 : $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> + %34 = apply %33<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP>(%32, %29) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> + %41 = unchecked_take_enum_data_addr %32 : $*Optional<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP>, #Optional.some!enumelt.1 + copy_addr [take] %41 to [initialization] %31 : $*@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP + inject_enum_addr %6 : $*Optional, #Optional.some!enumelt.1 + dealloc_stack %32 : $*Optional<@opened("83DE97CA-7315-11E8-955C-ACDE48001122") PPP> + destroy_addr %28 : $*PPP + dealloc_stack %7 : $*Optional + destroy_addr %6 : $*Optional + dealloc_stack %6 : $*Optional + destroy_addr %0 : $*PPP + dealloc_stack %0 : $*PPP + %52 = tuple () + return %52 : $() +} + + +// ===----------------------------------------------------------------------===// +// testOptionalSelfArg: Call a protocol extension method with an +// existential self that can be type-propagated. SILCombine should +// bailout since it does not know how to rewrite non-self operands. +// SILCombine would previously generate incorrect SIL because it did +// not properly walk all types in the function signature to find +// dependencies on the Self type. +// ===----------------------------------------------------------------------===// +protocol PPPP {} + +extension PPPP { + func takeOptionalSelf(_ s: Self?) +} + +class CCCC : PPPP { + init() + deinit +} + +sil [noinline] @takeOptionalSelf : $@convention(method) (@in_guaranteed Optional, @in_guaranteed Self) -> () + +// CHECK-LABEL: sil @testOptionalSelfArg : $@convention(thin) (@guaranteed CCCC) -> () { +// CHECK: init_existential_addr [[A:%.*]] : $*PPPP, $CCCC // user: %4 +// CHECK: [[OE:%.*]] = open_existential_addr immutable_access [[A]] : $*PPPP to $*@opened("{{.*}}") PPPP // users: %11, %11, %8, %6 +// CHECK: [[F:%.*]] = function_ref @takeOptionalSelf : $@convention(method) <τ_0_0 where τ_0_0 : PPPP> (@in_guaranteed Optional<τ_0_0>, @in_guaranteed τ_0_0) -> () // user: %11 +// CHECK: apply [[F]]<@opened("{{.*}}") PPPP>(%{{.*}}, [[OE]]) : $@convention(method) <τ_0_0 where τ_0_0 : PPPP> (@in_guaranteed Optional<τ_0_0>, @in_guaranteed τ_0_0) -> () // type-defs: %5 +// CHECK-LABEL: } // end sil function 'testOptionalSelfArg' +sil @testOptionalSelfArg : $@convention(thin) (@guaranteed CCCC) -> () { +bb0(%0 : $CCCC): + strong_retain %0 : $CCCC + + %pa = alloc_stack $PPPP, let, name "p" + %ea = init_existential_addr %pa : $*PPPP, $CCCC + store %0 to %ea : $*CCCC + %oe = open_existential_addr immutable_access %pa : $*PPPP to $*@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP + + %optional = alloc_stack $Optional<@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP> + %someadr = init_enum_data_addr %optional : $*Optional<@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP>, #Optional.some!enumelt.1 + copy_addr %oe to [initialization] %someadr : $*@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP + inject_enum_addr %optional : $*Optional<@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP>, #Optional.some!enumelt.1 + + %f = function_ref @takeOptionalSelf : $@convention(method) <τ_0_0 where τ_0_0 : PPPP> (@in_guaranteed Optional<τ_0_0>, @in_guaranteed τ_0_0) -> () + %call = apply %f<@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP>(%optional, %oe) : $@convention(method) <τ_0_0 where τ_0_0 : PPPP> (@in_guaranteed Optional<τ_0_0>, @in_guaranteed τ_0_0) -> () + + destroy_addr %optional : $*Optional<@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP> + dealloc_stack %optional : $*Optional<@opened("A6DDDAF6-70BD-11E8-ADF1-ACDE48001122") PPPP> + + destroy_addr %pa : $*PPPP + dealloc_stack %pa : $*PPPP + %10 = tuple () + return %10 : $() +} + +//===----------------------------------------------------------------------===// +// testExtensionProtocolComposition: Call to a witness method with an +// existential self that can be type-propagated. Handle an existential with +// multiple conformances. +// +// This previously crashed in SILCombiner::propagateConcreteTypeOfInitExistential +// with assertion failed: (proto == Conformance.getRequirement()). +// ===----------------------------------------------------------------------===// +public protocol Q {} + +extension P where Self : Q { + public func witnessComposition() {} +} + +public class C_PQ: P & Q {} + +// P<>.witnessComposition() +sil @$S32sil_combine_concrete_existential1PPA2A1QRzrlE18witnessCompositionyyF : $@convention(method) <τ_0_0 where τ_0_0 : P, τ_0_0 : Q> (@guaranteed τ_0_0) -> () + +// testExtensionProtocolComposition(c:) +// public func testExtensionProtocolComposition(c: C_PQ) { +// let pp: P & Q = c +// pp.witnessComposition() +// } +// +// SILCombine substitutes the applies opened existention parameter with a concrete type +// CHECK-LABEL: sil @$S32sil_combine_concrete_existential32testExtensionProtocolComposition1cyAA4C_PQC_tF : $@convention(thin) (@guaranteed C_PQ) -> () { +// CHECK-NOT: init_existential_ref +// CHECK-NOT: open_existential_ref +// function_ref P<>.witnessComposition() +// CHECK: [[F:%.*]] = function_ref @$S32sil_combine_concrete_existential1PPA2A1QRzrlE18witnessCompositionyyF : $@convention(method) <τ_0_0 where τ_0_0 : P, τ_0_0 : Q> (@guaranteed τ_0_0) -> () +// CHECK: apply [[F]](%0) : $@convention(method) <τ_0_0 where τ_0_0 : P, τ_0_0 : Q> (@guaranteed τ_0_0) -> () +// CHECK-LABEL: } // end sil function '$S32sil_combine_concrete_existential32testExtensionProtocolComposition1cyAA4C_PQC_tF' +sil @$S32sil_combine_concrete_existential32testExtensionProtocolComposition1cyAA4C_PQC_tF : $@convention(thin) (@guaranteed C_PQ) -> () { +bb0(%0 : $C_PQ): + strong_retain %0 : $C_PQ + %3 = init_existential_ref %0 : $C_PQ : $C_PQ, $P & Q + %5 = open_existential_ref %3 : $P & Q to $@opened("044D530E-7327-11E8-A998-ACDE48001122") P & Q + // function_ref P<>.witnessComposition() + %6 = function_ref @$S32sil_combine_concrete_existential1PPA2A1QRzrlE18witnessCompositionyyF : $@convention(method) <τ_0_0 where τ_0_0 : P, τ_0_0 : Q> (@guaranteed τ_0_0) -> () + %7 = apply %6<@opened("044D530E-7327-11E8-A998-ACDE48001122") P & Q>(%5) : $@convention(method) <τ_0_0 where τ_0_0 : P, τ_0_0 : Q> (@guaranteed τ_0_0) -> () + strong_release %3 : $P & Q + %9 = tuple () + return %9 : $() +} + +// ===----------------------------------------------------------------------===// +// testDefaultStaticMethod: Test concrete type propagation into a direct +// apply of a default witness method. +// ===----------------------------------------------------------------------===// +public protocol PDefaultStatic: class { + static func witnessDefaultStatic() +} + +extension PDefaultStatic { + @inline(never) + public static func witnessDefaultStatic() {} +} + +public class CDefaultStatic : PDefaultStatic {} + +public func callDefaultStatic() { + return CDefaultStatic.witnessDefaultStatic() +} + +// static P<>.witnessDefaultStatic(_:) +sil @witnessDefaultStatic : $@convention(method) (@thick Self.Type) -> () + +// CHECK-LABEL: sil @testDefaultStaticMethod : $@convention(thin) () -> () { +// CHECK: %0 = metatype $@thick CDefaultStatic.Type +// CHECK-NOT: init_existential_metatype +// CHECK-NOT: open_existential_metatype +// CHECK: [[F:%.*]] = function_ref @witnessDefaultStatic : $@convention(method) <τ_0_0 where τ_0_0 : PDefaultStatic> (@thick τ_0_0.Type) -> () +// CHECK: apply [[F]](%0) : $@convention(method) <τ_0_0 where τ_0_0 : PDefaultStatic> (@thick τ_0_0.Type) -> () +// CHECK-LABEL: } // end sil function 'testDefaultStaticMethod' +sil @testDefaultStaticMethod : $@convention(thin) () -> () { +bb0: + %mt = metatype $@thick CDefaultStatic.Type + %em = init_existential_metatype %mt : $@thick CDefaultStatic.Type, $@thick PDefaultStatic.Type + %om = open_existential_metatype %em : $@thick PDefaultStatic.Type to $@thick (@opened("22222222-72AC-11E8-9816-ACDE48001122") PDefaultStatic).Type + %f = function_ref @witnessDefaultStatic : $@convention(method) (@thick Self.Type) -> () + %call = apply %f<@opened("22222222-72AC-11E8-9816-ACDE48001122") PDefaultStatic>(%om) : $@convention(method) (@thick Self.Type) -> () + %v = tuple () + return %v : $() +} diff --git a/test/SILOptimizer/sil_combine_concrete_existential.swift b/test/SILOptimizer/sil_combine_concrete_existential.swift new file mode 100644 index 0000000000000..7824574135ec2 --- /dev/null +++ b/test/SILOptimizer/sil_combine_concrete_existential.swift @@ -0,0 +1,126 @@ +// RUN: %target-swift-frontend -O -emit-sil -sil-verify-all -Xllvm -sil-disable-pass=function-signature-opts %s | %FileCheck %s + +//===----------------------------------------------------------------------===// +// testReturnSelf: Call to a protocol extension method with +// an existential self that can be type-propagated. +// sil-combine should bailout since it does not propagate +// type substitutions on the return value. +// +// [SR-7773]: +// SILCombiner::propagateConcreteTypeOfInitExistential fails to full propagate +// type substitutions. +//===----------------------------------------------------------------------===// +public protocol P: class {} + +extension P { + public func returnSelf() -> Self { + return self + } +} + +final class C: P {} +// CHECK-LABEL: sil @$S32sil_combine_concrete_existential14testReturnSelfAA1P_pyF : $@convention(thin) () -> @owned P { +// CHECK: [[E1:%.*]] = init_existential_ref %0 : $C : $C, $P +// CHECK: [[O1:%.*]] = open_existential_ref [[E1]] : $P to $@opened("{{.*}}") P +// CHECK: [[F1:%.*]] = function_ref @$S32sil_combine_concrete_existential1PPAAE10returnSelfxyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK: [[C1:%.*]] = apply [[F1]]<@opened("{{.*}}") P>([[O1]]) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK: [[E2:%.*]] = init_existential_ref [[C1]] : $@opened("{{.*}}") P : $@opened("{{.*}}") P, $P +// CHECK: [[O2:%.*]] = open_existential_ref [[E2]] : $P to $@opened("{{.*}}") P +// CHECK: apply [[F1]]<@opened("{{.*}}") P>([[O2]]) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0 +// CHECK-LABEL: } // end sil function '$S32sil_combine_concrete_existential14testReturnSelfAA1P_pyF' +public func testReturnSelf() -> P { + let p: P = C() + return p.returnSelf().returnSelf() +} + +//===----------------------------------------------------------------------===// +// testWitnessReturnOptionalSelf: Call to a witness method with an existential +// self that can be type-propagated. sil-combine should bailout since it does +// not propagate type substitutions on the return value, and it must walk the +// Optional type to find Self in the return type. +// +// Although sil-combine will not replace the self operand, it will still +// rewrite the witness_method. The devirtualizer then handles the first call. +//===----------------------------------------------------------------------===// +public protocol PP: class { + func returnOptionalSelf() -> Self? +} + +final class CC: PP { + init() {} + func returnOptionalSelf() -> Self? { + return self + } +} + +// The first apply has been devirtualized and inlined. The second remains unspecialized. +// CHECK-LABEL: sil @$S32sil_combine_concrete_existential29testWitnessReturnOptionalSelfAA2PP_pSgyF : $@convention(thin) () -> @owned Optional { +// CHECK: [[E1:%.*]] = init_existential_ref %0 : $CC : $CC, $PP +// CHECK: [[O1:%.*]] = open_existential_ref [[E1]] : $PP to $@opened("{{.*}}") PP +// CHECK: [[E2:%.*]] = init_existential_ref %{{.*}} : $@opened("{{.*}}") PP : $@opened("{{.*}}") PP, $PP +// CHECK: [[O2:%.*]] = open_existential_ref [[E2]] : $PP to $@opened("{{.*}}") PP +// CHECK: [[W:%.*]] = witness_method $@opened("{{.*}}") PP, #PP.returnOptionalSelf!1 : (Self) -> () -> @dynamic_self Self?, [[O1]] : $@opened("{{.*}}") PP : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> +// CHECK: apply [[W]]<@opened("{{.*}}") PP>([[O2]]) : $@convention(witness_method: PP) <τ_0_0 where τ_0_0 : PP> (@guaranteed τ_0_0) -> @owned Optional<τ_0_0> +// CHECK-LABEL: } // end sil function '$S32sil_combine_concrete_existential29testWitnessReturnOptionalSelfAA2PP_pSgyF' +public func testWitnessReturnOptionalSelf() -> PP? { + let p: PP = CC() + return p.returnOptionalSelf()?.returnOptionalSelf() +} + +//===----------------------------------------------------------------------===// +// testWitnessReturnOptionalIndirectSelf: Call to a witness method with an +// existential self that can be type-propagated. sil-combine should bailout +// since it does not propagate type substitutions on non-self arguments. It must +// walk the Optional type to find Self in the non-self argument. +// +// Although sil-combine will not replace the self operand, it will still +// rewrite the witness_method. The devirtualizer then handles the first call. +//===----------------------------------------------------------------------===// +protocol PPP { + func returnsOptionalIndirect() -> Self? +} + +struct S: PPP { + func returnsOptionalIndirect() -> S? { + return self + } +} + +// The first apply has been devirtualized and inlined. The second remains unspecialized. +// CHECK-LABEL: sil @$S32sil_combine_concrete_existential37testWitnessReturnOptionalIndirectSelfyyF : $@convention(thin) () -> () { +// CHECK: switch_enum_addr %{{.*}} : $*Optional<@opened("{{.*}}") PPP>, case #Optional.some!enumelt.1: bb{{.*}}, case #Optional.none!enumelt: bb{{.*}} +// CHECK: [[O:%.*]] = open_existential_addr immutable_access %{{.*}} : $*PPP to $*@opened("{{.*}}") PPP +// CHECK: [[W:%.*]] = witness_method $@opened("{{.*}}") PPP, #PPP.returnsOptionalIndirect!1 : (Self) -> () -> @dynamic_self Self?, [[O]] : $*@opened("{{.*}}") PPP : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> +// CHECK: apply [[W]]<@opened("{{.*}}") PPP>(%{{.*}}, [[O]]) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0> +// CHECK-LABEL: } // end sil function '$S32sil_combine_concrete_existential37testWitnessReturnOptionalIndirectSelfyyF' +public func testWitnessReturnOptionalIndirectSelf() { + let p: PPP = S() + p.returnsOptionalIndirect()?.returnsOptionalIndirect() +} + +//===----------------------------------------------------------------------===// +// testExtensionProtocolComposition: Call to a witness method with an +// existential self that can be type-propagated. Handle an existential with +// multiple conformances. +// +// Previously crashed with in SILCombiner::propagateConcreteTypeOfInitExistential +// with assertion failed: (proto == Conformance.getRequirement()). +// ===----------------------------------------------------------------------===// +public protocol Q {} + +extension P where Self : Q { + public func witnessComposition() {} +} + +public class C_PQ: P & Q {} + +// testExtensionProtocolComposition(c:) +// CHECK-LABEL: sil @$S32sil_combine_concrete_existential32testExtensionProtocolComposition1cyAA4C_PQC_tF : $@convention(thin) (@guaranteed C_PQ) -> () { +// CHECK-NOT: init_existential_ref +// CHECK-NOT: function_ref +// CHECK-NOT: apply +// CHECK: } // end sil function '$S32sil_combine_concrete_existential32testExtensionProtocolComposition1cyAA4C_PQC_tF' +public func testExtensionProtocolComposition(c: C_PQ) { + let pp: P & Q = c + pp.witnessComposition() +}