From 27097430cc1400c80039e0fa519d1c7f8874bd92 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 18 Nov 2025 21:32:58 -0500 Subject: [PATCH] Serialization: Lazily deserialize OpaqueTypeDecl's underlying substitutions We need to serialize the underlying type substitution map for an inlinable function. However, there is no reason to deserialize it eagerly, since doing so can lead to cycles. It is better for correctness and performance to only deserialize it when needed. Technically this fixes a regression from #84299, but the actual problem was there all along, it was just exposed by my change on a specific project. Fixes rdar://163301203. --- include/swift/AST/Decl.h | 28 +- include/swift/AST/LazyResolver.h | 10 + lib/AST/ASTContext.cpp | 8 +- lib/AST/Decl.cpp | 65 +++- lib/ClangImporter/ImporterImpl.h | 5 + lib/Sema/TypeCheckGeneric.cpp | 2 +- lib/Serialization/DeclTypeRecordNodes.def | 3 +- lib/Serialization/Deserialization.cpp | 304 ++++++++++-------- lib/Serialization/ModuleFile.h | 16 +- lib/Serialization/ModuleFormat.h | 36 ++- lib/Serialization/Serialization.cpp | 41 ++- .../opaque_circularity_swiftui_other.swift | 15 + .../opaque_circularity_swiftui.swift | 12 + 13 files changed, 362 insertions(+), 183 deletions(-) create mode 100644 test/Serialization/Inputs/opaque_circularity_swiftui_other.swift create mode 100644 test/Serialization/opaque_circularity_swiftui.swift diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9775f22a40492..ac2a1a21e7b41 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -761,6 +761,11 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated, public Swi HasAnyUnavailableDuringLoweringValues : 1 ); + SWIFT_INLINE_BITFIELD(OpaqueTypeDecl, GenericTypeDecl, 1, + /// Whether we have lazily-loaded underlying substitutions. + HasLazyUnderlyingSubstitutions : 1 + ); + SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+8, /// If the module is compiled as static library. StaticLibrary : 1, @@ -3646,7 +3651,8 @@ class OpaqueTypeDecl final : OpaqueTypeDecl(ValueDecl *NamingDecl, GenericParamList *GenericParams, DeclContext *DC, GenericSignature OpaqueInterfaceGenericSignature, - ArrayRef OpaqueReturnTypeReprs); + ArrayRef OpaqueReturnTypeReprs, + bool hasLazyUnderlyingSubstitutions); unsigned getNumOpaqueReturnTypeReprs() const { return NamingDeclAndHasOpaqueReturnTypeRepr.getInt() @@ -3658,13 +3664,21 @@ class OpaqueTypeDecl final : return getNumOpaqueReturnTypeReprs(); } + void loadLazyUnderlyingSubstitutions(); + public: - static OpaqueTypeDecl *get( + static OpaqueTypeDecl *create( ValueDecl *NamingDecl, GenericParamList *GenericParams, DeclContext *DC, GenericSignature OpaqueInterfaceGenericSignature, ArrayRef OpaqueReturnTypeReprs); + static OpaqueTypeDecl *createDeserialized( + GenericParamList *GenericParams, + DeclContext *DC, + GenericSignature OpaqueInterfaceGenericSignature, + LazyMemberLoader *lazyLoader, uint64_t underlyingSubsData); + ValueDecl *getNamingDecl() const { return NamingDeclAndHasOpaqueReturnTypeRepr.getPointer(); } @@ -3720,19 +3734,19 @@ class OpaqueTypeDecl final : bool typeCheckFunctionBodies=true) const; void setUniqueUnderlyingTypeSubstitutions(SubstitutionMap subs) { - assert(!UniqueUnderlyingType.has_value() && "resetting underlying type?!"); + ASSERT(!Bits.OpaqueTypeDecl.HasLazyUnderlyingSubstitutions); + ASSERT(!UniqueUnderlyingType.has_value() && "resetting underlying type?!"); UniqueUnderlyingType = subs; } bool hasConditionallyAvailableSubstitutions() const { + const_cast(this)->loadLazyUnderlyingSubstitutions(); + return ConditionallyAvailableTypes.has_value(); } ArrayRef - getConditionallyAvailableSubstitutions() const { - assert(ConditionallyAvailableTypes); - return ConditionallyAvailableTypes.value(); - } + getConditionallyAvailableSubstitutions() const; void setConditionallyAvailableSubstitutions( ArrayRef substitutions); diff --git a/include/swift/AST/LazyResolver.h b/include/swift/AST/LazyResolver.h index 1db9f2fd947b9..f86611c70d4d8 100644 --- a/include/swift/AST/LazyResolver.h +++ b/include/swift/AST/LazyResolver.h @@ -63,6 +63,12 @@ class LazyAssociatedTypeData : public LazyContextData { uint64_t defaultDefinitionTypeData = 0; }; +class LazyOpaqueTypeData : public LazyContextData { +public: + /// The context data used for loading the underlying type substitution map. + uint64_t underlyingSubsData = 0; +}; + /// Context data for protocols. class LazyProtocolData : public LazyIterableDeclContextData { public: @@ -138,6 +144,10 @@ class alignas(void*) LazyMemberLoader { // Returns the target parameter of the `@_specialize` attribute or null. virtual ValueDecl *loadTargetFunctionDecl(const AbstractSpecializeAttr *attr, uint64_t contextData) = 0; + + /// Loads the underlying type substitution map of an opaque result declaration. + virtual void + finishOpaqueTypeDecl(OpaqueTypeDecl *opaqueDecl, uint64_t contextData) = 0; }; /// A class that can lazily load conformances from a serialized format. diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 11ee2866f8bfb..2284c615e6f6d 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -3174,20 +3174,22 @@ ASTContext::getOrCreateLazyContextData(const Decl *decl, LazyMemberLoader *lazyLoader) { if (auto *data = getLazyContextData(decl)) { // Make sure we didn't provide an incompatible lazy loader. - assert(!lazyLoader || lazyLoader == data->loader); + ASSERT(!lazyLoader || lazyLoader == data->loader); return data; } LazyContextData *&entry = getImpl().LazyContexts[decl]; // Create new lazy context data with the given loader. - assert(lazyLoader && "Queried lazy data for non-lazy iterable context"); + ASSERT(lazyLoader && "Queried lazy data for non-lazy iterable context"); if (isa(decl)) { entry = Allocate(); } else if (isa(decl) || isa(decl)) { entry = Allocate(); + } else if (isa(decl)) { + entry = Allocate(); } else { - assert(isa(decl)); + ASSERT(isa(decl)); entry = Allocate(); } diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index ab4a297fd9fbb..61dcdcf8d92d0 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -10820,13 +10820,16 @@ bool AbstractFunctionDecl::isResilient(ModuleDecl *M, OpaqueTypeDecl::OpaqueTypeDecl(ValueDecl *NamingDecl, GenericParamList *GenericParams, DeclContext *DC, GenericSignature OpaqueInterfaceGenericSignature, - ArrayRef - OpaqueReturnTypeReprs) + ArrayRef OpaqueReturnTypeReprs, + bool hasLazyUnderlyingSubstitutions) : GenericTypeDecl(DeclKind::OpaqueType, DC, Identifier(), SourceLoc(), {}, GenericParams), NamingDeclAndHasOpaqueReturnTypeRepr( NamingDecl, !OpaqueReturnTypeReprs.empty()), OpaqueInterfaceGenericSignature(OpaqueInterfaceGenericSignature) { + Bits.OpaqueTypeDecl.HasLazyUnderlyingSubstitutions + = hasLazyUnderlyingSubstitutions; + // Always implicit. setImplicit(); @@ -10839,7 +10842,7 @@ OpaqueTypeDecl::OpaqueTypeDecl(ValueDecl *NamingDecl, OpaqueReturnTypeReprs.end(), getTrailingObjects()); } -OpaqueTypeDecl *OpaqueTypeDecl::get( +OpaqueTypeDecl *OpaqueTypeDecl::create( ValueDecl *NamingDecl, GenericParamList *GenericParams, DeclContext *DC, GenericSignature OpaqueInterfaceGenericSignature, @@ -10850,7 +10853,33 @@ OpaqueTypeDecl *OpaqueTypeDecl::get( auto mem = ctx.Allocate(size, alignof(OpaqueTypeDecl)); return new (mem) OpaqueTypeDecl( NamingDecl, GenericParams, DC, OpaqueInterfaceGenericSignature, - OpaqueReturnTypeReprs); + OpaqueReturnTypeReprs, /*hasLazyUnderlyingSubstitutions=*/false); +} + +OpaqueTypeDecl *OpaqueTypeDecl::createDeserialized( + GenericParamList *GenericParams, DeclContext *DC, + GenericSignature OpaqueInterfaceGenericSignature, + LazyMemberLoader *lazyLoader, uint64_t underlyingSubsData) { + bool hasLazyUnderlyingSubstitutions = (underlyingSubsData != 0); + + ASTContext &ctx = DC->getASTContext(); + auto size = totalSizeToAlloc(0); + auto mem = ctx.Allocate(size, alignof(OpaqueTypeDecl)); + + // NamingDecl is set later by deserialization + auto *decl = new (mem) OpaqueTypeDecl( + /*namingDecl=*/nullptr, GenericParams, DC, + OpaqueInterfaceGenericSignature, { }, + hasLazyUnderlyingSubstitutions); + + if (hasLazyUnderlyingSubstitutions) { + auto &ctx = DC->getASTContext(); + auto *data = static_cast( + ctx.getOrCreateLazyContextData(decl, lazyLoader)); + data->underlyingSubsData = underlyingSubsData; + } + + return decl; } bool OpaqueTypeDecl::isOpaqueReturnTypeOf(const Decl *ownerDecl) const { @@ -10906,9 +10935,26 @@ bool OpaqueTypeDecl::exportUnderlyingType() const { llvm_unreachable("The naming decl is expected to be either an AFD or ASD"); } +void OpaqueTypeDecl::loadLazyUnderlyingSubstitutions() { + if (!Bits.OpaqueTypeDecl.HasLazyUnderlyingSubstitutions) + return; + + Bits.OpaqueTypeDecl.HasLazyUnderlyingSubstitutions = 0; + + auto &ctx = getASTContext(); + auto *data = static_cast( + ctx.getLazyContextData(this)); + ASSERT(data != nullptr); + + data->loader->finishOpaqueTypeDecl( + this, data->underlyingSubsData); +} + std::optional OpaqueTypeDecl::getUniqueUnderlyingTypeSubstitutions( bool typeCheckFunctionBodies) const { + const_cast(this)->loadLazyUnderlyingSubstitutions(); + if (!typeCheckFunctionBodies) return UniqueUnderlyingType; @@ -10916,6 +10962,14 @@ OpaqueTypeDecl::getUniqueUnderlyingTypeSubstitutions( UniqueUnderlyingTypeSubstitutionsRequest{this}, {}); } +ArrayRef +OpaqueTypeDecl::getConditionallyAvailableSubstitutions() const { + const_cast(this)->loadLazyUnderlyingSubstitutions(); + + assert(ConditionallyAvailableTypes); + return ConditionallyAvailableTypes.value(); +} + std::optional OpaqueTypeDecl::getAnonymousOpaqueParamOrdinal(TypeRepr *repr) const { assert(NamingDeclAndHasOpaqueReturnTypeRepr.getInt() && @@ -10945,7 +10999,8 @@ Identifier OpaqueTypeDecl::getOpaqueReturnTypeIdentifier() const { void OpaqueTypeDecl::setConditionallyAvailableSubstitutions( ArrayRef substitutions) { - assert(!ConditionallyAvailableTypes && + ASSERT(!Bits.OpaqueTypeDecl.HasLazyUnderlyingSubstitutions); + ASSERT(!ConditionallyAvailableTypes && "resetting conditionally available substitutions?!"); ConditionallyAvailableTypes = getASTContext().AllocateCopy(substitutions); } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 1ce1ff5981b14..deb2e08723e94 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -1762,6 +1762,11 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation llvm_unreachable("unimplemented for ClangImporter"); } + void finishOpaqueTypeDecl(OpaqueTypeDecl *decl, + uint64_t contextData) override { + llvm_unreachable("unimplemented for ClangImporter"); + } + template DeclTy *createDeclWithClangNode(ClangNode ClangN, AccessLevel access, Targs &&... Args) { diff --git a/lib/Sema/TypeCheckGeneric.cpp b/lib/Sema/TypeCheckGeneric.cpp index 2a51bb2253380..92cfd9d19f80a 100644 --- a/lib/Sema/TypeCheckGeneric.cpp +++ b/lib/Sema/TypeCheckGeneric.cpp @@ -215,7 +215,7 @@ OpaqueResultTypeRequest::evaluate(Evaluator &evaluator, } // Create the OpaqueTypeDecl for the result type. - auto opaqueDecl = OpaqueTypeDecl::get( + auto opaqueDecl = OpaqueTypeDecl::create( originatingDecl, genericParams, parentDC, interfaceSignature, opaqueReprs); if (auto originatingSig = originatingDC->getGenericSignatureOfContext()) { diff --git a/lib/Serialization/DeclTypeRecordNodes.def b/lib/Serialization/DeclTypeRecordNodes.def index 0ee58b5d3e668..d1462d53cd735 100644 --- a/lib/Serialization/DeclTypeRecordNodes.def +++ b/lib/Serialization/DeclTypeRecordNodes.def @@ -215,10 +215,11 @@ OTHER(ERROR_FLAG, 155) OTHER(ABI_ONLY_COUNTERPART, 156) OTHER(DECL_NAME_REF, 157) +TRAILING_INFO(UNDERLYING_SUBSTITUTION) TRAILING_INFO(CONDITIONAL_SUBSTITUTION) TRAILING_INFO(CONDITIONAL_SUBSTITUTION_COND) -OTHER(LIFETIME_DEPENDENCE, 160) +OTHER(LIFETIME_DEPENDENCE, 161) TRAILING_INFO(INHERITED_PROTOCOLS) #ifndef DECL_ATTR diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index c92bbead37038..e5796a69ca66d 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -73,6 +73,8 @@ STATISTIC(NumNormalProtocolConformancesLoaded, "# of normal protocol conformances deserialized"); STATISTIC(NumNormalProtocolConformancesCompleted, "# of normal protocol conformances completed"); +STATISTIC(NumOpaqueTypeDeclsCompleted, + "# of opaque type declarations completed"); STATISTIC(NumNestedTypeShortcuts, "# of nested types resolved without full lookup"); @@ -4615,84 +4617,6 @@ class DeclDeserializer { return deserializeAnyFunc(scratch, blobData, /*isAccessor*/true); } - void deserializeConditionalSubstitutionAvailabilityQueries( - SmallVectorImpl &queries) { - using namespace decls_block; - - SmallVector scratch; - StringRef blobData; - - // FIXME: [availability] Support arbitrary domains (rdar://156513787). - auto domain = ctx.getTargetAvailabilityDomain(); - ASSERT(domain.isPlatform()); - - while (true) { - llvm::BitstreamEntry entry = - MF.fatalIfUnexpected(MF.DeclTypeCursor.advance(AF_DontPopBlockAtEnd)); - if (entry.Kind != llvm::BitstreamEntry::Record) - break; - - scratch.clear(); - - unsigned recordID = MF.fatalIfUnexpected( - MF.DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); - if (recordID != decls_block::CONDITIONAL_SUBSTITUTION_COND) - break; - - bool isUnavailability; - DEF_VER_TUPLE_PIECES(version); - - ConditionalSubstitutionConditionLayout::readRecord( - scratch, isUnavailability, LIST_VER_TUPLE_PIECES(version)); - - llvm::VersionTuple version; - DECODE_VER_TUPLE(version); - - queries.push_back(AvailabilityQuery::dynamic( - domain, AvailabilityRange(version), std::nullopt) - .asUnavailable(isUnavailability)); - } - } - - void deserializeConditionalSubstitutions( - SmallVectorImpl - &limitedAvailability) { - SmallVector scratch; - StringRef blobData; - - while (true) { - llvm::BitstreamEntry entry = - MF.fatalIfUnexpected(MF.DeclTypeCursor.advance(AF_DontPopBlockAtEnd)); - if (entry.Kind != llvm::BitstreamEntry::Record) - break; - - scratch.clear(); - unsigned recordID = MF.fatalIfUnexpected( - MF.DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); - if (recordID != decls_block::CONDITIONAL_SUBSTITUTION) - break; - - SubstitutionMapID substitutionMapRef; - - decls_block::ConditionalSubstitutionLayout::readRecord( - scratch, substitutionMapRef); - - SmallVector queries; - deserializeConditionalSubstitutionAvailabilityQueries(queries); - - if (queries.empty()) - return MF.diagnoseAndConsumeFatal(); - - auto subMapOrError = MF.getSubstitutionMapChecked(substitutionMapRef); - if (!subMapOrError) - return MF.diagnoseAndConsumeFatal(); - - limitedAvailability.push_back( - OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get( - ctx, queries, subMapOrError.get())); - } - } - Expected deserializeOpaqueType(ArrayRef scratch, StringRef blobData) { DeclID namingDeclID; @@ -4700,16 +4624,16 @@ class DeclDeserializer { GenericSignatureID interfaceSigID; TypeID interfaceTypeID; GenericSignatureID genericSigID; - SubstitutionMapID underlyingTypeSubsID; uint8_t rawAccessLevel; + bool hasUnderlyingType; bool exportUnderlyingType; decls_block::OpaqueTypeLayout::readRecord(scratch, contextID, namingDeclID, interfaceSigID, interfaceTypeID, genericSigID, - underlyingTypeSubsID, rawAccessLevel, + hasUnderlyingType, exportUnderlyingType); - + DeclContext *declContext; SET_OR_RETURN_ERROR(declContext, MF.getDeclContextChecked(contextID)); @@ -4724,15 +4648,36 @@ class DeclDeserializer { GenericParamList *genericParams; SET_OR_RETURN_ERROR(genericParams, MF.maybeReadGenericParams(declContext)); + // FIXME: Do we still need exportUnderlyingType? + uint64_t contextData = 0; + if (hasUnderlyingType && + (exportUnderlyingType || + MF.FileContext->getParentModule()->isMainModule())) { + contextData = MF.DeclTypeCursor.GetCurrentBitNo(); + } + // Create the decl. - auto opaqueDecl = OpaqueTypeDecl::get( - /*NamingDecl=*/ nullptr, genericParams, declContext, - interfaceSigOrErr.get(), /*OpaqueReturnTypeReprs*/ { }); + auto opaqueDecl = OpaqueTypeDecl::createDeserialized( + genericParams, declContext, interfaceSigOrErr.get(), + &MF, contextData); declOrOffset = opaqueDecl; auto namingDecl = cast(MF.getDecl(namingDeclID)); opaqueDecl->setNamingDecl(namingDecl); + if (contextData == 0) { + LLVM_DEBUG( + llvm::dbgs() << "Ignoring underlying information for opaque type of '"; + llvm::dbgs() << namingDecl->getName(); + llvm::dbgs() << "'\n"); + } else { + LLVM_DEBUG( + llvm::dbgs() << "Loading underlying information for opaque type of '"; + llvm::dbgs() << namingDecl->getName(); + llvm::dbgs() << "'\n"; + ); + } + auto interfaceType = MF.getType(interfaceTypeID); opaqueDecl->setInterfaceType(MetatypeType::get(interfaceType)); @@ -4747,55 +4692,6 @@ class DeclDeserializer { else opaqueDecl->setGenericSignature(GenericSignature()); - if (!MF.FileContext->getParentModule()->isMainModule() && - !exportUnderlyingType) { - // Do not try to read the underlying type information if the function - // is not inlinable in clients. This reflects the swiftinterface behavior - // in where clients are only aware of the underlying type when the body - // of the function is public. - LLVM_DEBUG( - llvm::dbgs() << "Ignoring underlying information for opaque type of '"; - llvm::dbgs() << namingDecl->getName(); - llvm::dbgs() << "'\n"; - ); - - } else if (underlyingTypeSubsID) { - LLVM_DEBUG( - llvm::dbgs() << "Loading underlying information for opaque type of '"; - llvm::dbgs() << namingDecl->getName(); - llvm::dbgs() << "'\n"; - ); - - auto subMapOrError = MF.getSubstitutionMapChecked(underlyingTypeSubsID); - if (!subMapOrError) { - // If the underlying type references internal details, ignore it. - auto unconsumedError = - MF.consumeExpectedError(subMapOrError.takeError()); - if (unconsumedError) - return std::move(unconsumedError); - } else { - // Check whether there are any conditionally available substitutions. - // If there are, it means that "unique" we just read is a universally - // available substitution. - SmallVector - limitedAvailability; - - deserializeConditionalSubstitutions(limitedAvailability); - - if (limitedAvailability.empty()) { - opaqueDecl->setUniqueUnderlyingTypeSubstitutions(subMapOrError.get()); - } else { - limitedAvailability.push_back( - OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get( - ctx, - {AvailabilityQuery::universallyConstant( - /*value=*/true)}, - subMapOrError.get())); - - opaqueDecl->setConditionallyAvailableSubstitutions(limitedAvailability); - } - } - } return opaqueDecl; } @@ -8902,6 +8798,148 @@ ModuleFile::loadAssociatedTypeDefault(const swift::AssociatedTypeDecl *ATD, return getType(contextData); } +void +ModuleFile::finishOpaqueTypeDecl(OpaqueTypeDecl *opaqueDecl, + uint64_t contextData) { + auto &ctx = getAssociatedModule()->getASTContext(); + auto *namingDecl = opaqueDecl->getNamingDecl(); + + using namespace decls_block; + PrettyStackTraceModuleFile traceModule(*this); + PrettyStackTraceDecl trace("finishing opaque result type ", namingDecl); + ++NumOpaqueTypeDeclsCompleted; + + // Find the underlying type substitutions record. + BCOffsetRAII restoreOffset(DeclTypeCursor); + if (diagnoseAndConsumeFatalIfNotSuccess( + DeclTypeCursor.JumpToBit(contextData))) + return; + llvm::BitstreamEntry entry = fatalIfUnexpected(DeclTypeCursor.advance()); + assert(entry.Kind == llvm::BitstreamEntry::Record && + "registered lazy loader incorrectly"); + + SubstitutionMapID underlyingTypeSubsID; + SmallVector scratch; + + unsigned kind = + fatalIfUnexpected(DeclTypeCursor.readRecord(entry.ID, scratch)); + if (kind != UNDERLYING_SUBSTITUTION) + fatal(llvm::make_error(kind, + "registered lazy loader incorrectly")); + + UnderlyingSubstitutionLayout::readRecord(scratch, underlyingTypeSubsID); + + auto subMapOrError = getSubstitutionMapChecked(underlyingTypeSubsID); + if (!subMapOrError) { + // If the underlying type references internal details, ignore it. + diagnoseAndConsumeError(subMapOrError.takeError()); + return; + } + + // Check whether there are any conditionally available substitutions. + // If there are, it means that "unique" we just read is a universally + // available substitution. + SmallVector + limitedAvailability; + + deserializeConditionalSubstitutions(limitedAvailability); + + if (limitedAvailability.empty()) { + opaqueDecl->setUniqueUnderlyingTypeSubstitutions(subMapOrError.get()); + } else { + limitedAvailability.push_back( + OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get( + ctx, + {AvailabilityQuery::universallyConstant( + /*value=*/true)}, + subMapOrError.get())); + + opaqueDecl->setConditionallyAvailableSubstitutions(limitedAvailability); + } +} + +void ModuleFile::deserializeConditionalSubstitutions( + SmallVectorImpl + &limitedAvailability) { + auto &ctx = getAssociatedModule()->getASTContext(); + + SmallVector scratch; + StringRef blobData; + + while (true) { + llvm::BitstreamEntry entry = + fatalIfUnexpected(DeclTypeCursor.advance(AF_DontPopBlockAtEnd)); + if (entry.Kind != llvm::BitstreamEntry::Record) + break; + + scratch.clear(); + unsigned recordID = fatalIfUnexpected( + DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); + if (recordID != decls_block::CONDITIONAL_SUBSTITUTION) + break; + + SubstitutionMapID substitutionMapRef; + + decls_block::ConditionalSubstitutionLayout::readRecord( + scratch, substitutionMapRef); + + SmallVector queries; + deserializeConditionalSubstitutionAvailabilityQueries(queries); + + if (queries.empty()) + return diagnoseAndConsumeFatal(); + + auto subMapOrError = getSubstitutionMapChecked(substitutionMapRef); + if (!subMapOrError) + return diagnoseAndConsumeFatal(); + + limitedAvailability.push_back( + OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get( + ctx, queries, subMapOrError.get())); + } +} + +void ModuleFile::deserializeConditionalSubstitutionAvailabilityQueries( + SmallVectorImpl &queries) { + using namespace decls_block; + + auto &ctx = getAssociatedModule()->getASTContext(); + + SmallVector scratch; + StringRef blobData; + + // FIXME: [availability] Support arbitrary domains (rdar://156513787). + auto domain = ctx.getTargetAvailabilityDomain(); + ASSERT(domain.isPlatform()); + + while (true) { + llvm::BitstreamEntry entry = + fatalIfUnexpected(DeclTypeCursor.advance(AF_DontPopBlockAtEnd)); + if (entry.Kind != llvm::BitstreamEntry::Record) + break; + + scratch.clear(); + + unsigned recordID = fatalIfUnexpected( + DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); + if (recordID != decls_block::CONDITIONAL_SUBSTITUTION_COND) + break; + + bool isUnavailability; + DEF_VER_TUPLE_PIECES(version); + + ConditionalSubstitutionConditionLayout::readRecord( + scratch, isUnavailability, LIST_VER_TUPLE_PIECES(version)); + + llvm::VersionTuple version; + DECODE_VER_TUPLE(version); + + queries.push_back(AvailabilityQuery::dynamic( + domain, AvailabilityRange(version), std::nullopt) + .asUnavailable(isUnavailability)); + } +} + ValueDecl *ModuleFile::loadDynamicallyReplacedFunctionDecl( const DynamicReplacementAttr *DRA, uint64_t contextData) { return cast(getDecl(contextData)); diff --git a/lib/Serialization/ModuleFile.h b/lib/Serialization/ModuleFile.h index 28bb4da5104fd..bb4e7edcf33bd 100644 --- a/lib/Serialization/ModuleFile.h +++ b/lib/Serialization/ModuleFile.h @@ -951,21 +951,31 @@ class ModuleFile virtual void finishNormalConformance(NormalProtocolConformance *conformance, uint64_t contextData) override; - void + virtual void loadRequirementSignature(const ProtocolDecl *proto, uint64_t contextData, SmallVectorImpl &requirements, SmallVectorImpl &typeAliases) override; - void + virtual void loadAssociatedTypes( const ProtocolDecl *proto, uint64_t contextData, SmallVectorImpl &assocTypes) override; - void + virtual void loadPrimaryAssociatedTypes( const ProtocolDecl *proto, uint64_t contextData, SmallVectorImpl &assocTypes) override; + virtual void + finishOpaqueTypeDecl(OpaqueTypeDecl *opaqueDecl, + uint64_t contextData) override; + + void deserializeConditionalSubstitutions( + SmallVectorImpl + &limitedAvailability); + void deserializeConditionalSubstitutionAvailabilityQueries( + SmallVectorImpl &queries); + std::optional getGroupNameById(unsigned Id) const; std::optional getSourceFileNameById(unsigned Id) const; std::optional getGroupNameForDecl(const Decl *D) const; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index fbfee7a5baaf7..1e8ac35904c87 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 974; // remove 'isCallerIsolated' bit from ParamDecl +const uint16_t SWIFTMODULE_VERSION_MINOR = 975; // Lazy OpaqueTypeDecl underlying type substitutions /// A standard hash seed used for all string hashes in a serialized module. /// @@ -1807,6 +1807,26 @@ namespace decls_block { // - inlinable body text, if any >; + using OpaqueTypeLayout = BCRecordLayout< + OPAQUE_TYPE_DECL, + DeclContextIDField, // decl context + DeclIDField, // naming decl + GenericSignatureIDField, // interface generic signature + TypeIDField, // interface type for opaque type + GenericSignatureIDField, // generic environment + AccessLevelField, // access level + BCFixed<1>, // has underlying substitutions? + BCFixed<1> // export underlying substitutions? + // trailed by generic parameters + // trailed by opaque type underlying substitutions (if has underlying substitutions) + // trailed by conditional substitutions (if has underlying substitutions) + >; + + using UnderlyingSubstitutionLayout = BCRecordLayout< + UNDERLYING_SUBSTITUTION, + SubstitutionMapIDField // the substitution map + >; + using ConditionalSubstitutionConditionLayout = BCRecordLayout< CONDITIONAL_SUBSTITUTION_COND, BCFixed<1>, // is unavailable? @@ -1820,20 +1840,6 @@ namespace decls_block { // unavailability indicator. >; - using OpaqueTypeLayout = BCRecordLayout< - OPAQUE_TYPE_DECL, - DeclContextIDField, // decl context - DeclIDField, // naming decl - GenericSignatureIDField, // interface generic signature - TypeIDField, // interface type for opaque type - GenericSignatureIDField, // generic environment - SubstitutionMapIDField, // optional substitution map for underlying type - AccessLevelField, // access level - BCFixed<1> // export underlying type details - // trailed by generic parameters - // trailed by conditional substitutions - >; - // TODO: remove the unnecessary FuncDecl components here using AccessorLayout = BCRecordLayout< ACCESSOR_DECL, diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index fb7a94dd56588..0e4a79478a692 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -4915,41 +4915,50 @@ class Serializer::DeclSerializer : public DeclVisitor { auto genericSigID = S.addGenericSignatureRef(opaqueDecl->getGenericSignature()); + uint8_t rawAccessLevel = + getRawStableAccessLevel(opaqueDecl->getFormalAccess()); + bool exportUnderlyingType = opaqueDecl->exportUnderlyingType(); + + bool hasUnderlyingType = false; SubstitutionMapID underlyingSubsID = 0; if (auto underlying = opaqueDecl->getUniqueUnderlyingTypeSubstitutions()) { underlyingSubsID = S.addSubstitutionMapRef(*underlying); + hasUnderlyingType = true; } else if (opaqueDecl->hasConditionallyAvailableSubstitutions()) { // Universally available type doesn't have any availability conditions - // so it could be serialized into "unique" slot to safe space. + // so it could be serialized into "unique" slot to save space. auto universal = opaqueDecl->getConditionallyAvailableSubstitutions().back(); underlyingSubsID = S.addSubstitutionMapRef(universal->getSubstitutions()); + hasUnderlyingType = true; } - uint8_t rawAccessLevel = - getRawStableAccessLevel(opaqueDecl->getFormalAccess()); - bool exportDetails = opaqueDecl->exportUnderlyingType(); - unsigned abbrCode = S.DeclTypeAbbrCodes[OpaqueTypeLayout::Code]; - OpaqueTypeLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode, + OpaqueTypeLayout::emitRecord(S.Out, S.ScratchRecord, + S.DeclTypeAbbrCodes[OpaqueTypeLayout::Code], contextID.getOpaqueValue(), namingDeclID, interfaceSigID, interfaceTypeID, genericSigID, - underlyingSubsID, rawAccessLevel, - exportDetails); + rawAccessLevel, hasUnderlyingType, + exportUnderlyingType); writeGenericParams(opaqueDecl->getGenericParams()); + if (!hasUnderlyingType) + return; + + UnderlyingSubstitutionLayout::emitRecord( + S.Out, S.ScratchRecord, + S.DeclTypeAbbrCodes[UnderlyingSubstitutionLayout::Code], + underlyingSubsID); + // Serialize all of the conditionally available substitutions expect the // last one - universal, it's serialized into "unique" slot. if (opaqueDecl->hasConditionallyAvailableSubstitutions()) { - unsigned abbrCode = - S.DeclTypeAbbrCodes[ConditionalSubstitutionLayout::Code]; for (const auto *subs : opaqueDecl->getConditionallyAvailableSubstitutions().drop_back()) { ConditionalSubstitutionLayout::emitRecord( - S.Out, S.ScratchRecord, abbrCode, + S.Out, S.ScratchRecord, + S.DeclTypeAbbrCodes[ConditionalSubstitutionLayout::Code], S.addSubstitutionMapRef(subs->getSubstitutions())); - unsigned condAbbrCode = - S.DeclTypeAbbrCodes[ConditionalSubstitutionConditionLayout::Code]; for (const auto &query : subs->getAvailabilityQueries()) { // FIXME: [availability] Support arbitrary domains (rdar://156513787). DEBUG_ASSERT(query.getDomain().isPlatform()); @@ -4961,8 +4970,9 @@ class Serializer::DeclSerializer : public DeclVisitor { std::optional( availableRange.getRawMinimumVersion())); ConditionalSubstitutionConditionLayout::emitRecord( - S.Out, S.ScratchRecord, condAbbrCode, query.isUnavailability(), - LIST_VER_TUPLE_PIECES(osVersion)); + S.Out, S.ScratchRecord, + S.DeclTypeAbbrCodes[ConditionalSubstitutionConditionLayout::Code], + query.isUnavailability(), LIST_VER_TUPLE_PIECES(osVersion)); } } } @@ -6559,6 +6569,7 @@ void Serializer::writeAllDeclsAndTypes() { registerDeclTypeAbbr(); registerDeclTypeAbbr(); + registerDeclTypeAbbr(); registerDeclTypeAbbr(); registerDeclTypeAbbr(); diff --git a/test/Serialization/Inputs/opaque_circularity_swiftui_other.swift b/test/Serialization/Inputs/opaque_circularity_swiftui_other.swift new file mode 100644 index 0000000000000..4cc9f9fa61566 --- /dev/null +++ b/test/Serialization/Inputs/opaque_circularity_swiftui_other.swift @@ -0,0 +1,15 @@ +import SwiftUI + +public struct OnOffColor { + public var on: any ShapeStyle { fatalError() } + + public func resolve() -> some ShapeStyle { + AnyShapeStyle(on) + } +} + +extension View { + @inlinable public func foregroundStyle(_ style: OnOffColor, isOn: Bool) -> some View { + foregroundStyle(style.resolve()) + } +} diff --git a/test/Serialization/opaque_circularity_swiftui.swift b/test/Serialization/opaque_circularity_swiftui.swift new file mode 100644 index 0000000000000..bba111139a14c --- /dev/null +++ b/test/Serialization/opaque_circularity_swiftui.swift @@ -0,0 +1,12 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module %S/Inputs/opaque_circularity_swiftui_other.swift -emit-module -emit-module-path %t/opaque_circularity_swiftui_other.swiftmodule -target %target-cpu-apple-macosx12 -swift-version 5 +// RUN: %target-swift-frontend -emit-silgen %s -I %t -target %target-cpu-apple-macosx12 -swift-version 5 + +// REQUIRES: OS=macosx + +import opaque_circularity_swiftui_other +import SwiftUI + +func foo(_ v: some View, _ tint: Color) { + _ = v.foregroundStyle(tint) +}