From 52dd46213d6c78575cfa543afe7db83fdbd4b36c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 18 Aug 2015 23:42:23 +0000 Subject: [PATCH] [modules] Fix HeaderFileInfo serialization to store all the known owning modules for a header, not just the current favourite. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@245390 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Lex/HeaderSearch.h | 41 ++++---------- include/clang/Lex/ModuleMap.h | 19 +++++++ include/clang/Serialization/ASTWriter.h | 9 +-- lib/Lex/HeaderSearch.cpp | 73 +++++++++++++----------- lib/Lex/ModuleMap.cpp | 45 ++++++++++----- lib/Serialization/ASTReader.cpp | 65 +++++++++++----------- lib/Serialization/ASTWriter.cpp | 74 +++++++++++++------------ 7 files changed, 180 insertions(+), 146 deletions(-) diff --git a/include/clang/Lex/HeaderSearch.h b/include/clang/Lex/HeaderSearch.h index 4c13380100..cb61c1436e 100644 --- a/include/clang/Lex/HeaderSearch.h +++ b/include/clang/Lex/HeaderSearch.h @@ -49,7 +49,8 @@ struct HeaderFileInfo { /// SrcMgr::CharacteristicKind. unsigned DirInfo : 2; - /// \brief Whether this header file info was supplied by an external source. + /// \brief Whether this header file info was supplied by an external source, + /// and has not changed since. unsigned External : 1; /// \brief Whether this header is part of a module. @@ -58,10 +59,6 @@ struct HeaderFileInfo { /// \brief Whether this header is part of the module that we are building. unsigned isCompilingModuleHeader : 1; - /// \brief Whether this header is part of the module that we are building. - /// This is an instance of ModuleMap::ModuleHeaderRole. - unsigned HeaderRole : 2; - /// \brief Whether this structure is considered to already have been /// "resolved", meaning that it was loaded from the external source. unsigned Resolved : 1; @@ -75,7 +72,7 @@ struct HeaderFileInfo { /// those framework headers. unsigned IndexHeaderMapHeader : 1; - /// \brief Whether this file had been looked up as a header. + /// \brief Whether this file has been looked up as a header. unsigned IsValid : 1; /// \brief The number of times the file has been included already. @@ -105,7 +102,6 @@ struct HeaderFileInfo { HeaderFileInfo() : isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User), External(false), isModuleHeader(false), isCompilingModuleHeader(false), - HeaderRole(ModuleMap::NormalHeader), Resolved(false), IndexHeaderMapHeader(false), IsValid(0), NumIncludes(0), ControllingMacroID(0), ControllingMacro(nullptr) {} @@ -120,16 +116,6 @@ struct HeaderFileInfo { return isImport || isPragmaOnce || NumIncludes || ControllingMacro || ControllingMacroID; } - - /// \brief Get the HeaderRole properly typed. - ModuleMap::ModuleHeaderRole getHeaderRole() const { - return static_cast(HeaderRole); - } - - /// \brief Set the HeaderRole properly typed. - void setHeaderRole(ModuleMap::ModuleHeaderRole Role) { - HeaderRole = Role; - } }; /// \brief An external source of header file information, which may supply @@ -189,7 +175,7 @@ class HeaderSearch { /// \brief All of the preprocessor-specific data about files that are /// included, indexed by the FileEntry's UID. - std::vector FileInfo; + mutable std::vector FileInfo; /// Keeps track of each lookup performed by LookupFile. struct LookupFileCacheInfo { @@ -575,20 +561,20 @@ private: /// of the given search directory. void loadSubdirectoryModuleMaps(DirectoryLookup &SearchDir); - /// \brief Return the HeaderFileInfo structure for the specified FileEntry. - const HeaderFileInfo &getFileInfo(const FileEntry *FE) const { - return const_cast(this)->getFileInfo(FE); - } - public: /// \brief Retrieve the module map. ModuleMap &getModuleMap() { return ModMap; } + /// \brief Retrieve the module map. + const ModuleMap &getModuleMap() const { return ModMap; } + unsigned header_file_size() const { return FileInfo.size(); } - /// \brief Get a \c HeaderFileInfo structure for the specified \c FileEntry, - /// if one exists. - bool tryGetFileInfo(const FileEntry *FE, HeaderFileInfo &Result) const; + /// \brief Return the HeaderFileInfo structure for the specified FileEntry. + HeaderFileInfo &getFileInfo(const FileEntry *FE); + + /// \brief Return the HeaderFileInfo structure for the specified FileEntry. + const HeaderFileInfo *getExistingFileInfo(const FileEntry *FE) const; // Used by external tools typedef std::vector::const_iterator search_dir_iterator; @@ -665,9 +651,6 @@ private: /// named directory. LoadModuleMapResult loadModuleMapFile(const DirectoryEntry *Dir, bool IsSystem, bool IsFramework); - - /// \brief Return the HeaderFileInfo structure for the specified FileEntry. - HeaderFileInfo &getFileInfo(const FileEntry *FE); }; } // end namespace clang diff --git a/include/clang/Lex/ModuleMap.h b/include/clang/Lex/ModuleMap.h index 2365a895a0..a4300553a3 100644 --- a/include/clang/Lex/ModuleMap.h +++ b/include/clang/Lex/ModuleMap.h @@ -112,6 +112,13 @@ public: KnownHeader() : Storage(nullptr, NormalHeader) { } KnownHeader(Module *M, ModuleHeaderRole Role) : Storage(M, Role) { } + friend bool operator==(const KnownHeader &A, const KnownHeader &B) { + return A.Storage == B.Storage; + } + friend bool operator!=(const KnownHeader &A, const KnownHeader &B) { + return A.Storage != B.Storage; + } + /// \brief Retrieve the module the header is stored in. Module *getModule() const { return Storage.getPointer(); } @@ -242,6 +249,10 @@ private: KnownHeader findHeaderInUmbrellaDirs(const FileEntry *File, SmallVectorImpl &IntermediateDirs); + /// \brief Given that \p File is not in the Headers map, look it up within + /// umbrella directories and find or create a module for it. + KnownHeader findOrCreateModuleForHeaderInUmbrellaDir(const FileEntry *File); + /// \brief A convenience method to determine if \p File is (possibly nested) /// in an umbrella directory. bool isHeaderInUmbrellaDirs(const FileEntry *File) { @@ -295,6 +306,14 @@ public: /// that no module owns this header file. KnownHeader findModuleForHeader(const FileEntry *File); + /// \brief Retrieve all the modules that contain the given header file. This + /// may not include umbrella modules, nor information from external sources, + /// if they have not yet been inferred / loaded. + /// + /// Typically, \ref findModuleForHeader should be used instead, as it picks + /// the preferred module for the header. + ArrayRef findAllModulesForHeader(const FileEntry *File) const; + /// \brief Reports errors if a module must not include a specific file. /// /// \param RequestingModule The module including a file. diff --git a/include/clang/Serialization/ASTWriter.h b/include/clang/Serialization/ASTWriter.h index 7cb91b4de1..d7c49462dc 100644 --- a/include/clang/Serialization/ASTWriter.h +++ b/include/clang/Serialization/ASTWriter.h @@ -493,7 +493,7 @@ private: /// \brief Retrieve or create a submodule ID for this module. unsigned getSubmoduleID(Module *Mod); - + /// \brief Write the given subexpression to the bitstream. void WriteSubStmt(Stmt *S, llvm::DenseMap &SubStmtEntries, @@ -767,9 +767,10 @@ public: /// source location. serialization::SubmoduleID inferSubmoduleIDFromLocation(SourceLocation Loc); - /// \brief Retrieve a submodule ID for this module. - /// Returns 0 If no ID has been associated with the module. - unsigned getExistingSubmoduleID(Module *Mod) const; + /// \brief Retrieve or create a submodule ID for this module, or return 0 if + /// the submodule is neither local (a submodle of the currently-written module) + /// nor from an imported module. + unsigned getLocalOrImportedSubmoduleID(Module *Mod); /// \brief Note that the identifier II occurs at the given offset /// within the identifier table. diff --git a/lib/Lex/HeaderSearch.cpp b/lib/Lex/HeaderSearch.cpp index eea47198d6..e1c8fa21a6 100644 --- a/lib/Lex/HeaderSearch.cpp +++ b/lib/Lex/HeaderSearch.cpp @@ -975,65 +975,72 @@ static void mergeHeaderFileInfo(HeaderFileInfo &HFI, if (OtherHFI.External) { HFI.DirInfo = OtherHFI.DirInfo; - HFI.External = OtherHFI.External; + HFI.External = OtherHFI.External && (!HFI.IsValid || HFI.External); HFI.IndexHeaderMapHeader = OtherHFI.IndexHeaderMapHeader; } if (HFI.Framework.empty()) HFI.Framework = OtherHFI.Framework; - - HFI.Resolved = true; } /// getFileInfo - Return the HeaderFileInfo structure for the specified /// FileEntry. HeaderFileInfo &HeaderSearch::getFileInfo(const FileEntry *FE) { if (FE->getUID() >= FileInfo.size()) - FileInfo.resize(FE->getUID()+1); - + FileInfo.resize(FE->getUID() + 1); + HeaderFileInfo &HFI = FileInfo[FE->getUID()]; - if (ExternalSource && !HFI.Resolved) + // FIXME: Use a generation count to check whether this is really up to date. + if (ExternalSource && !HFI.Resolved) { + HFI.Resolved = true; mergeHeaderFileInfo(HFI, ExternalSource->GetHeaderFileInfo(FE)); - HFI.IsValid = 1; + } + + HFI.IsValid = true; + // We have local information about this header file, so it's no longer + // strictly external. + HFI.External = false; return HFI; } -bool HeaderSearch::tryGetFileInfo(const FileEntry *FE, - HeaderFileInfo &Result) const { - if (FE->getUID() >= FileInfo.size()) - return false; - const HeaderFileInfo &HFI = FileInfo[FE->getUID()]; - if (HFI.IsValid) { - Result = HFI; - return true; +const HeaderFileInfo * +HeaderSearch::getExistingFileInfo(const FileEntry *FE) const { + // If we have an external source, ensure we have the latest information. + // FIXME: Use a generation count to check whether this is really up to date. + if (ExternalSource && + (FE->getUID() >= FileInfo.size() || !FileInfo[FE->getUID()].Resolved)) { + auto ExternalHFI = ExternalSource->GetHeaderFileInfo(FE); + if (ExternalHFI.External) { + if (FE->getUID() >= FileInfo.size()) + FileInfo.resize(FE->getUID() + 1); + mergeHeaderFileInfo(FileInfo[FE->getUID()], ExternalHFI); + } } - return false; + + if (FE->getUID() >= FileInfo.size()) + return nullptr; + + HeaderFileInfo &HFI = FileInfo[FE->getUID()]; + if (!HFI.IsValid) + return nullptr; + + return &HFI; } bool HeaderSearch::isFileMultipleIncludeGuarded(const FileEntry *File) { // Check if we've ever seen this file as a header. - if (File->getUID() >= FileInfo.size()) - return false; - - // Resolve header file info from the external source, if needed. - HeaderFileInfo &HFI = FileInfo[File->getUID()]; - if (ExternalSource && !HFI.Resolved) - mergeHeaderFileInfo(HFI, ExternalSource->GetHeaderFileInfo(File)); - - return HFI.isPragmaOnce || HFI.isImport || - HFI.ControllingMacro || HFI.ControllingMacroID; + if (auto *HFI = getExistingFileInfo(File)) + return HFI->isPragmaOnce || HFI->isImport || HFI->ControllingMacro || + HFI->ControllingMacroID; + return false; } void HeaderSearch::MarkFileModuleHeader(const FileEntry *FE, ModuleMap::ModuleHeaderRole Role, bool isCompilingModuleHeader) { - if (FE->getUID() >= FileInfo.size()) - FileInfo.resize(FE->getUID()+1); - - HeaderFileInfo &HFI = FileInfo[FE->getUID()]; - HFI.isModuleHeader = true; + auto &HFI = getFileInfo(FE); + HFI.isModuleHeader |= !(Role & ModuleMap::TextualHeader); HFI.isCompilingModuleHeader |= isCompilingModuleHeader; - HFI.setHeaderRole(Role); } bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP, @@ -1143,7 +1150,7 @@ HeaderSearch::findModuleForHeader(const FileEntry *File) const { if (ExternalSource) { // Make sure the external source has handled header info about this file, // which includes whether the file is part of a module. - (void)getFileInfo(File); + (void)getExistingFileInfo(File); } return ModMap.findModuleForHeader(File); } diff --git a/lib/Lex/ModuleMap.cpp b/lib/Lex/ModuleMap.cpp index 9554d3ba45..d619b52bef 100644 --- a/lib/Lex/ModuleMap.cpp +++ b/lib/Lex/ModuleMap.cpp @@ -359,6 +359,13 @@ ModuleMap::KnownHeader ModuleMap::findModuleForHeader(const FileEntry *File) { return MakeResult(Result); } + return MakeResult(findOrCreateModuleForHeaderInUmbrellaDir(File)); +} + +ModuleMap::KnownHeader +ModuleMap::findOrCreateModuleForHeaderInUmbrellaDir(const FileEntry *File) { + assert(!Headers.count(File) && "already have a module for this header"); + SmallVector SkippedDirs; KnownHeader H = findHeaderInUmbrellaDirs(File, SkippedDirs); if (H) { @@ -419,19 +426,22 @@ ModuleMap::KnownHeader ModuleMap::findModuleForHeader(const FileEntry *File) { UmbrellaDirs[SkippedDirs[I]] = Result; } - Headers[File].push_back(KnownHeader(Result, NormalHeader)); - - // If a header corresponds to an unavailable module, don't report - // that it maps to anything. - if (!Result->isAvailable()) - return KnownHeader(); - - return MakeResult(Headers[File].back()); + KnownHeader Header(Result, NormalHeader); + Headers[File].push_back(Header); + return Header; } return KnownHeader(); } +ArrayRef +ModuleMap::findAllModulesForHeader(const FileEntry *File) const { + auto It = Headers.find(File); + if (It == Headers.end()) + return None; + return It->second; +} + bool ModuleMap::isHeaderInUnavailableModule(const FileEntry *Header) const { return isHeaderUnavailableInModule(Header, nullptr); } @@ -787,14 +797,21 @@ static Module::HeaderKind headerRoleToKind(ModuleMap::ModuleHeaderRole Role) { void ModuleMap::addHeader(Module *Mod, Module::Header Header, ModuleHeaderRole Role) { - if (!(Role & TextualHeader)) { - bool isCompilingModuleHeader = Mod->getTopLevelModule() == CompilingModule; - HeaderInfo.MarkFileModuleHeader(Header.Entry, Role, - isCompilingModuleHeader); - } - Headers[Header.Entry].push_back(KnownHeader(Mod, Role)); + KnownHeader KH(Mod, Role); + + // Only add each header to the headers list once. + // FIXME: Should we diagnose if a header is listed twice in the + // same module definition? + auto &HeaderList = Headers[Header.Entry]; + for (auto H : HeaderList) + if (H == KH) + return; + HeaderList.push_back(KH); Mod->Headers[headerRoleToKind(Role)].push_back(std::move(Header)); + + bool isCompilingModuleHeader = Mod->getTopLevelModule() == CompilingModule; + HeaderInfo.MarkFileModuleHeader(Header.Entry, Role, isCompilingModuleHeader); } void ModuleMap::excludeHeader(Module *Mod, Module::Header Header) { diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index 6d41d155c5..fc9d29a908 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -1556,14 +1556,15 @@ HeaderFileInfoTrait::ReadData(internal_key_ref key, const unsigned char *d, using namespace llvm::support; HeaderFileInfo HFI; unsigned Flags = *d++; - HFI.HeaderRole = static_cast - ((Flags >> 6) & 0x03); - HFI.isImport = (Flags >> 5) & 0x01; - HFI.isPragmaOnce = (Flags >> 4) & 0x01; - HFI.DirInfo = (Flags >> 2) & 0x03; - HFI.Resolved = (Flags >> 1) & 0x01; + // FIXME: Refactor with mergeHeaderFileInfo in HeaderSearch.cpp. + HFI.isImport |= (Flags >> 4) & 0x01; + HFI.isPragmaOnce |= (Flags >> 3) & 0x01; + HFI.DirInfo = (Flags >> 1) & 0x03; HFI.IndexHeaderMapHeader = Flags & 0x01; - HFI.NumIncludes = endian::readNext(d); + // FIXME: Find a better way to handle this. Maybe just store a + // "has been included" flag? + HFI.NumIncludes = std::max(endian::readNext(d), + HFI.NumIncludes); HFI.ControllingMacroID = Reader.getGlobalIdentifierID( M, endian::readNext(d)); if (unsigned FrameworkOffset = @@ -1573,32 +1574,32 @@ HeaderFileInfoTrait::ReadData(internal_key_ref key, const unsigned char *d, StringRef FrameworkName(FrameworkStrings + FrameworkOffset - 1); HFI.Framework = HS->getUniqueFrameworkName(FrameworkName); } - - if (d != End) { + + assert((End - d) % 4 == 0 && + "Wrong data length in HeaderFileInfo deserialization"); + while (d != End) { uint32_t LocalSMID = endian::readNext(d); - if (LocalSMID) { - // This header is part of a module. Associate it with the module to enable - // implicit module import. - SubmoduleID GlobalSMID = Reader.getGlobalSubmoduleID(M, LocalSMID); - Module *Mod = Reader.getSubmodule(GlobalSMID); - HFI.isModuleHeader = true; - FileManager &FileMgr = Reader.getFileManager(); - ModuleMap &ModMap = - Reader.getPreprocessor().getHeaderSearchInfo().getModuleMap(); - // FIXME: This information should be propagated through the - // SUBMODULE_HEADER etc records rather than from here. - // FIXME: We don't ever mark excluded headers. - std::string Filename = key.Filename; - if (key.Imported) - Reader.ResolveImportedPath(M, Filename); - Module::Header H = { key.Filename, FileMgr.getFile(Filename) }; - ModMap.addHeader(Mod, H, HFI.getHeaderRole()); - } - } - - assert(End == d && "Wrong data length in HeaderFileInfo deserialization"); - (void)End; - + auto HeaderRole = static_cast(LocalSMID & 3); + LocalSMID >>= 2; + + // This header is part of a module. Associate it with the module to enable + // implicit module import. + SubmoduleID GlobalSMID = Reader.getGlobalSubmoduleID(M, LocalSMID); + Module *Mod = Reader.getSubmodule(GlobalSMID); + FileManager &FileMgr = Reader.getFileManager(); + ModuleMap &ModMap = + Reader.getPreprocessor().getHeaderSearchInfo().getModuleMap(); + + std::string Filename = key.Filename; + if (key.Imported) + Reader.ResolveImportedPath(M, Filename); + // FIXME: This is not always the right filename-as-written, but we're not + // going to use this information to rebuild the module, so it doesn't make + // a lot of difference. + Module::Header H = { key.Filename, FileMgr.getFile(Filename) }; + ModMap.addHeader(Mod, H, HeaderRole); + } + // This HeaderFileInfo was externally loaded. HFI.External = true; return HFI; diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp index 0aceb08293..b554f5f1cc 100644 --- a/lib/Serialization/ASTWriter.cpp +++ b/lib/Serialization/ASTWriter.cpp @@ -1637,13 +1637,14 @@ namespace { std::pair EmitKeyDataLength(raw_ostream& Out, key_type_ref key, data_type_ref Data) { using namespace llvm::support; - endian::Writer Writer(Out); + endian::Writer LE(Out); unsigned KeyLen = strlen(key.Filename) + 1 + 8 + 8; - Writer.write(KeyLen); + LE.write(KeyLen); unsigned DataLen = 1 + 2 + 4 + 4; - if (Data.isModuleHeader) - DataLen += 4; - Writer.write(DataLen); + for (auto ModInfo : HS.getModuleMap().findAllModulesForHeader(key.FE)) + if (Writer.getLocalOrImportedSubmoduleID(ModInfo.getModule())) + DataLen += 4; + LE.write(DataLen); return std::make_pair(KeyLen, DataLen); } @@ -1663,11 +1664,9 @@ namespace { endian::Writer LE(Out); uint64_t Start = Out.tell(); (void)Start; - unsigned char Flags = (Data.HeaderRole << 6) - | (Data.isImport << 5) - | (Data.isPragmaOnce << 4) - | (Data.DirInfo << 2) - | (Data.Resolved << 1) + unsigned char Flags = (Data.isImport << 4) + | (Data.isPragmaOnce << 3) + | (Data.DirInfo << 1) | Data.IndexHeaderMapHeader; LE.write(Flags); LE.write(Data.NumIncludes); @@ -1694,9 +1693,15 @@ namespace { } LE.write(Offset); - if (Data.isModuleHeader) { - Module *Mod = HS.findModuleForHeader(key.FE).getModule(); - LE.write(Writer.getExistingSubmoduleID(Mod)); + // FIXME: If the header is excluded, we should write out some + // record of that fact. + for (auto ModInfo : HS.getModuleMap().findAllModulesForHeader(key.FE)) { + if (uint32_t ModID = + Writer.getLocalOrImportedSubmoduleID(ModInfo.getModule())) { + uint32_t Value = (ModID << 2) | (unsigned)ModInfo.getRole(); + assert((Value >> 2) == ModID && "overflow in header module info"); + LE.write(Value); + } } assert(Out.tell() - Start == DataLen && "Wrong data length"); @@ -1726,12 +1731,15 @@ void ASTWriter::WriteHeaderSearch(const HeaderSearch &HS) { if (!File) continue; - // Use HeaderSearch's getFileInfo to make sure we get the HeaderFileInfo - // from the external source if it was not provided already. - HeaderFileInfo HFI; - if (!HS.tryGetFileInfo(File, HFI) || - (HFI.External && Chain) || - (HFI.isModuleHeader && !HFI.isCompilingModuleHeader)) + // Get the file info. This will load info from the external source if + // necessary. Skip emitting this file if we have no information on it + // as a header file (in which case HFI will be null) or if it hasn't + // changed since it was loaded. Also skip it if it's for a modular header + // from a different module; in that case, we rely on the module(s) + // containing the header to provide this information. + const HeaderFileInfo *HFI = HS.getExistingFileInfo(File); + if (!HFI || (HFI->External && Chain) || + (HFI->isModuleHeader && !HFI->isCompilingModuleHeader)) continue; // Massage the file path into an appropriate form. @@ -1745,7 +1753,7 @@ void ASTWriter::WriteHeaderSearch(const HeaderSearch &HS) { } HeaderFileInfoTrait::key_type key = { File, Filename }; - Generator.insert(key, HFI, GeneratorTrait); + Generator.insert(key, *HFI, GeneratorTrait); ++NumHeaderSearchEntries; } @@ -2283,27 +2291,28 @@ void ASTWriter::WritePreprocessorDetail(PreprocessingRecord &PPRec) { } } -unsigned ASTWriter::getSubmoduleID(Module *Mod) { +unsigned ASTWriter::getLocalOrImportedSubmoduleID(Module *Mod) { if (!Mod) return 0; llvm::DenseMap::iterator Known = SubmoduleIDs.find(Mod); if (Known != SubmoduleIDs.end()) return Known->second; - - return SubmoduleIDs[Mod] = NextSubmoduleID++; -} -unsigned ASTWriter::getExistingSubmoduleID(Module *Mod) const { - if (!Mod) + if (Mod->getTopLevelModule() != WritingModule) return 0; - llvm::DenseMap::const_iterator - Known = SubmoduleIDs.find(Mod); - if (Known != SubmoduleIDs.end()) - return Known->second; + return SubmoduleIDs[Mod] = NextSubmoduleID++; +} - return 0; +unsigned ASTWriter::getSubmoduleID(Module *Mod) { + // FIXME: This can easily happen, if we have a reference to a submodule that + // did not result in us loading a module file for that submodule. For + // instance, a cross-top-level-module 'conflict' declaration will hit this. + unsigned ID = getLocalOrImportedSubmoduleID(Mod); + assert((ID || !Mod) && + "asked for module ID for non-local, non-imported module"); + return ID; } /// \brief Compute the number of modules within the given tree (including the @@ -2542,9 +2551,6 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) { Stream.ExitBlock(); - // FIXME: This can easily happen, if we have a reference to a submodule that - // did not result in us loading a module file for that submodule. For - // instance, a cross-top-level-module 'conflict' declaration will hit this. assert((NextSubmoduleID - FirstSubmoduleID == getNumberOfModules(WritingModule)) && "Wrong # of submodules; found a reference to a non-local, " -- 2.40.0