Skip to content

Commit 54728ab

Browse files
authored
Merge pull request swiftlang#8670 from apple/jan_svoboda/including-mms-not-affecting-release
[clang][modules][deps] Shrink the set of affecting module maps
2 parents bca1c2b + 869f9b0 commit 54728ab

File tree

12 files changed

+256
-116
lines changed

12 files changed

+256
-116
lines changed

clang/include/clang/Driver/Options.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2579,6 +2579,11 @@ defm modules_skip_header_search_paths : BoolFOption<"modules-skip-header-search-
25792579
HeaderSearchOpts<"ModulesSkipHeaderSearchPaths">, DefaultFalse,
25802580
PosFlag<SetTrue, [], "Disable writing header search paths">,
25812581
NegFlag<SetFalse>, BothFlags<[CC1Option]>>;
2582+
def fno_modules_prune_non_affecting_module_map_files :
2583+
Flag<["-"], "fno-modules-prune-non-affecting-module-map-files">,
2584+
Group<f_Group>, Flags<[CC1Option]>,
2585+
MarshallingInfoNegativeFlag<HeaderSearchOpts<"ModulesPruneNonAffectingModuleMaps">>,
2586+
HelpText<"Do not prune non-affecting module map files when writing module files">;
25822587

25832588
def fincremental_extensions :
25842589
Flag<["-"], "fincremental-extensions">,

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ class TargetInfo;
5656
/// The preprocessor keeps track of this information for each
5757
/// file that is \#included.
5858
struct HeaderFileInfo {
59+
// TODO: Whether the file was included is not a property of the file itself.
60+
// It's a preprocessor state, move it there.
61+
/// True if this file has been included (or imported) **locally**.
62+
unsigned IsLocallyIncluded : 1;
63+
5964
// TODO: Whether the file was imported is not a property of the file itself.
6065
// It's a preprocessor state, move it there.
6166
/// True if this is a \#import'd file.
@@ -125,10 +130,10 @@ struct HeaderFileInfo {
125130
StringRef Framework;
126131

127132
HeaderFileInfo()
128-
: isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User),
129-
External(false), isModuleHeader(false), isTextualModuleHeader(false),
130-
isCompilingModuleHeader(false), Resolved(false),
131-
IndexHeaderMapHeader(false), IsValid(false) {}
133+
: IsLocallyIncluded(false), isImport(false), isPragmaOnce(false),
134+
DirInfo(SrcMgr::C_User), External(false), isModuleHeader(false),
135+
isTextualModuleHeader(false), isCompilingModuleHeader(false),
136+
Resolved(false), IndexHeaderMapHeader(false), IsValid(false) {}
132137

133138
/// Retrieve the controlling macro for this header file, if
134139
/// any.

clang/include/clang/Lex/HeaderSearchOptions.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ class HeaderSearchOptions {
228228
/// Primarily used to speed up deserialization during dependency scanning.
229229
unsigned ModulesSkipPragmaDiagnosticMappings : 1;
230230

231+
/// Whether to prune non-affecting module map files from PCM files.
232+
unsigned ModulesPruneNonAffectingModuleMaps : 1;
233+
231234
unsigned ModulesHashContent : 1;
232235

233236
/// Whether we should include all things that could impact the module in the
@@ -252,7 +255,8 @@ class HeaderSearchOptions {
252255
ModulesValidateDiagnosticOptions(true),
253256
ModulesSkipDiagnosticOptions(false),
254257
ModulesSkipHeaderSearchPaths(false),
255-
ModulesSkipPragmaDiagnosticMappings(false), ModulesHashContent(false),
258+
ModulesSkipPragmaDiagnosticMappings(false),
259+
ModulesPruneNonAffectingModuleMaps(true), ModulesHashContent(false),
256260
ModulesStrictContextHash(false), ModulesIncludeVFSUsage(false) {}
257261

258262
/// AddPath - Add the \p Path path to the specified \p Group list.

clang/include/clang/Serialization/ASTWriter.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ class StoredDeclsList;
7676
class SwitchCase;
7777
class Token;
7878

79+
namespace SrcMgr {
80+
class FileInfo;
81+
} // namespace SrcMgr
82+
7983
/// Writes an AST file containing the contents of a translation unit.
8084
///
8185
/// The ASTWriter class produces a bitstream containing the serialized
@@ -472,6 +476,11 @@ class ASTWriter : public ASTDeserializationListener,
472476
/// during \c SourceManager serialization.
473477
void computeNonAffectingInputFiles();
474478

479+
/// Some affecting files can be included from files that are not affecting.
480+
/// This function erases source locations pointing into such files.
481+
SourceLocation getAffectingIncludeLoc(const SourceManager &SourceMgr,
482+
const SrcMgr::FileInfo &File);
483+
475484
/// Returns an adjusted \c FileID, accounting for any non-affecting input
476485
/// files.
477486
FileID getAdjustedFileID(FileID FID) const;

clang/lib/Lex/HeaderSearch.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,7 @@ bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
15821582
}
15831583
}
15841584

1585+
FileInfo.IsLocallyIncluded = true;
15851586
IsFirstIncludeOfFile = PP.markIncluded(File);
15861587
return true;
15871588
}

clang/lib/Serialization/ASTWriter.cpp

Lines changed: 103 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -163,81 +163,106 @@ namespace {
163163

164164
std::optional<std::set<const FileEntry *>>
165165
GetAffectingModuleMaps(const Preprocessor &PP, Module *RootModule) {
166-
// Without implicit module map search, there's no good reason to know about
167-
// any module maps that are not affecting.
168-
if (!PP.getHeaderSearchInfo().getHeaderSearchOpts().ImplicitModuleMaps)
166+
if (!PP.getHeaderSearchInfo()
167+
.getHeaderSearchOpts()
168+
.ModulesPruneNonAffectingModuleMaps)
169169
return std::nullopt;
170170

171-
SmallVector<const Module *> ModulesToProcess{RootModule};
172-
173171
const HeaderSearch &HS = PP.getHeaderSearchInfo();
174-
175-
SmallVector<const FileEntry *, 16> FilesByUID;
176-
HS.getFileMgr().GetUniqueIDMapping(FilesByUID);
177-
178-
if (FilesByUID.size() > HS.header_file_size())
179-
FilesByUID.resize(HS.header_file_size());
180-
181-
for (unsigned UID = 0, LastUID = FilesByUID.size(); UID != LastUID; ++UID) {
182-
const FileEntry *File = FilesByUID[UID];
183-
if (!File)
184-
continue;
185-
186-
const HeaderFileInfo *HFI = HS.getExistingLocalFileInfo(File);
187-
if (!HFI || (HFI->isModuleHeader && !HFI->isCompilingModuleHeader))
188-
continue;
189-
190-
for (const auto &KH : HS.findResolvedModulesForHeader(File)) {
191-
if (!KH.getModule())
192-
continue;
193-
ModulesToProcess.push_back(KH.getModule());
194-
}
195-
}
196-
197172
const ModuleMap &MM = HS.getModuleMap();
198-
SourceManager &SourceMgr = PP.getSourceManager();
199173

200174
std::set<const FileEntry *> ModuleMaps;
201-
auto CollectIncludingModuleMaps = [&](FileID FID, FileEntryRef F) {
202-
if (!ModuleMaps.insert(F).second)
175+
std::set<const Module *> ProcessedModules;
176+
auto CollectModuleMapsForHierarchy = [&](const Module *M) {
177+
M = M->getTopLevelModule();
178+
179+
if (!ProcessedModules.insert(M).second)
203180
return;
204-
SourceLocation Loc = SourceMgr.getIncludeLoc(FID);
205-
// The include location of inferred module maps can point into the header
206-
// file that triggered the inferring. Cut off the walk if that's the case.
207-
while (Loc.isValid() && isModuleMap(SourceMgr.getFileCharacteristic(Loc))) {
208-
FID = SourceMgr.getFileID(Loc);
209-
F = *SourceMgr.getFileEntryRefForID(FID);
210-
if (!ModuleMaps.insert(F).second)
211-
break;
212-
Loc = SourceMgr.getIncludeLoc(FID);
213-
}
214-
};
215181

216-
std::set<const Module *> ProcessedModules;
217-
auto CollectIncludingMapsFromAncestors = [&](const Module *M) {
218-
for (const Module *Mod = M; Mod; Mod = Mod->Parent) {
219-
if (!ProcessedModules.insert(Mod).second)
220-
break;
182+
std::queue<const Module *> Q;
183+
Q.push(M);
184+
while (!Q.empty()) {
185+
const Module *Mod = Q.front();
186+
Q.pop();
187+
221188
// The containing module map is affecting, because it's being pointed
222189
// into by Module::DefinitionLoc.
223-
if (FileID FID = MM.getContainingModuleMapFileID(Mod); FID.isValid())
224-
CollectIncludingModuleMaps(FID, *SourceMgr.getFileEntryRefForID(FID));
225-
// For inferred modules, the module map that allowed inferring is not in
226-
// the include chain of the virtual containing module map file. It did
227-
// affect the compilation, though.
228-
if (FileID FID = MM.getModuleMapFileIDForUniquing(Mod); FID.isValid())
229-
CollectIncludingModuleMaps(FID, *SourceMgr.getFileEntryRefForID(FID));
190+
if (auto FE = MM.getContainingModuleMapFile(Mod))
191+
ModuleMaps.insert(*FE);
192+
// For inferred modules, the module map that allowed inferring is not
193+
// related to the virtual containing module map file. It did affect the
194+
// compilation, though.
195+
if (auto FE = MM.getModuleMapFileForUniquing(Mod))
196+
ModuleMaps.insert(*FE);
197+
198+
for (auto *SubM : Mod->submodules())
199+
Q.push(SubM);
230200
}
231201
};
232202

233-
for (const Module *CurrentModule : ModulesToProcess) {
234-
CollectIncludingMapsFromAncestors(CurrentModule);
203+
// Handle all the affecting modules referenced from the root module.
204+
205+
CollectModuleMapsForHierarchy(RootModule);
206+
207+
std::queue<const Module *> Q;
208+
Q.push(RootModule);
209+
while (!Q.empty()) {
210+
const Module *CurrentModule = Q.front();
211+
Q.pop();
212+
235213
for (const Module *ImportedModule : CurrentModule->Imports)
236-
CollectIncludingMapsFromAncestors(ImportedModule);
214+
CollectModuleMapsForHierarchy(ImportedModule);
237215
for (const Module *UndeclaredModule : CurrentModule->UndeclaredUses)
238-
CollectIncludingMapsFromAncestors(UndeclaredModule);
216+
CollectModuleMapsForHierarchy(UndeclaredModule);
217+
218+
for (auto *M : CurrentModule->submodules())
219+
Q.push(M);
239220
}
240221

222+
// Handle textually-included headers that belong to other modules.
223+
224+
SmallVector<const FileEntry *, 16> FilesByUID;
225+
HS.getFileMgr().GetUniqueIDMapping(FilesByUID);
226+
227+
if (FilesByUID.size() > HS.header_file_size())
228+
FilesByUID.resize(HS.header_file_size());
229+
230+
for (unsigned UID = 0, LastUID = FilesByUID.size(); UID != LastUID; ++UID) {
231+
const FileEntry *File = FilesByUID[UID];
232+
if (!File)
233+
continue;
234+
235+
const HeaderFileInfo *HFI = HS.getExistingLocalFileInfo(File);
236+
if (!HFI)
237+
continue; // We have no information on this being a header file.
238+
if (!HFI->isCompilingModuleHeader && HFI->isModuleHeader)
239+
continue; // Modular header, handled in the above module-based loop.
240+
if (!HFI->isCompilingModuleHeader && !HFI->IsLocallyIncluded)
241+
continue; // Non-modular header not included locally is not affecting.
242+
243+
for (const auto &KH : HS.findResolvedModulesForHeader(File))
244+
if (const Module *M = KH.getModule())
245+
CollectModuleMapsForHierarchy(M);
246+
}
247+
248+
// FIXME: This algorithm is not correct for module map hierarchies where
249+
// module map file defining a (sub)module of a top-level module X includes
250+
// a module map file that defines a (sub)module of another top-level module Y.
251+
// Whenever X is affecting and Y is not, "replaying" this PCM file will fail
252+
// when parsing module map files for X due to not knowing about the `extern`
253+
// module map for Y.
254+
//
255+
// We don't have a good way to fix it here. We could mark all children of
256+
// affecting module map files as being affecting as well, but that's
257+
// expensive. SourceManager does not model the edge from parent to child
258+
// SLocEntries, so instead, we would need to iterate over leaf module map
259+
// files, walk up their include hierarchy and check whether we arrive at an
260+
// affecting module map.
261+
//
262+
// Instead of complicating and slowing down this function, we should probably
263+
// just ban module map hierarchies where module map defining a (sub)module X
264+
// includes a module map defining a module that's not a submodule of X.
265+
241266
return ModuleMaps;
242267
}
243268

@@ -1637,6 +1662,18 @@ struct InputFileEntry {
16371662

16381663
} // namespace
16391664

1665+
SourceLocation ASTWriter::getAffectingIncludeLoc(const SourceManager &SourceMgr,
1666+
const SrcMgr::FileInfo &File) {
1667+
SourceLocation IncludeLoc = File.getIncludeLoc();
1668+
if (IncludeLoc.isValid()) {
1669+
FileID IncludeFID = SourceMgr.getFileID(IncludeLoc);
1670+
assert(IncludeFID.isValid() && "IncludeLoc in invalid file");
1671+
if (!IsSLocAffecting[IncludeFID.ID])
1672+
IncludeLoc = SourceLocation();
1673+
}
1674+
return IncludeLoc;
1675+
}
1676+
16401677
void ASTWriter::WriteInputFiles(SourceManager &SourceMgr,
16411678
HeaderSearchOptions &HSOpts) {
16421679
using namespace llvm;
@@ -1690,7 +1727,7 @@ void ASTWriter::WriteInputFiles(SourceManager &SourceMgr,
16901727
Entry.IsSystemFile = isSystem(File.getFileCharacteristic());
16911728
Entry.IsTransient = Cache->IsTransient;
16921729
Entry.BufferOverridden = Cache->BufferOverridden;
1693-
Entry.IsTopLevel = File.getIncludeLoc().isInvalid();
1730+
Entry.IsTopLevel = getAffectingIncludeLoc(SourceMgr, File).isInvalid();
16941731
Entry.IsModuleMap = isModuleMap(File.getFileCharacteristic());
16951732

16961733
auto ContentHash = hash_code(-1);
@@ -2061,14 +2098,13 @@ void ASTWriter::WriteHeaderSearch(const HeaderSearch &HS) {
20612098
if (!File)
20622099
continue;
20632100

2064-
// Get the file info. Skip emitting this file if we have no information on
2065-
// it as a header file (in which case HFI will be null) or if it hasn't
2066-
// changed since it was loaded. Also skip it if it's for a modular header
2067-
// from a different module; in that case, we rely on the module(s)
2068-
// containing the header to provide this information.
20692101
const HeaderFileInfo *HFI = HS.getExistingLocalFileInfo(File);
2070-
if (!HFI || (HFI->isModuleHeader && !HFI->isCompilingModuleHeader))
2071-
continue;
2102+
if (!HFI)
2103+
continue; // We have no information on this being a header file.
2104+
if (!HFI->isCompilingModuleHeader && HFI->isModuleHeader)
2105+
continue; // Header file info is tracked by the owning module file.
2106+
if (!HFI->isCompilingModuleHeader && !PP->alreadyIncluded(File))
2107+
continue; // Non-modular header not included is not needed.
20722108

20732109
// Massage the file path into an appropriate form.
20742110
StringRef Filename = File->getName();
@@ -2217,7 +2253,7 @@ void ASTWriter::WriteSourceManagerBlock(SourceManager &SourceMgr,
22172253
SLocEntryOffsets.push_back(Offset);
22182254
// Starting offset of this entry within this module, so skip the dummy.
22192255
Record.push_back(getAdjustedOffset(SLoc->getOffset()) - 2);
2220-
AddSourceLocation(File.getIncludeLoc(), Record);
2256+
AddSourceLocation(getAffectingIncludeLoc(SourceMgr, File), Record);
22212257
Record.push_back(File.getFileCharacteristic()); // FIXME: stable encoding
22222258
Record.push_back(File.hasLineDirectives());
22232259

clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ makeCommonInvocationForModuleBuild(CompilerInvocation CI) {
185185
CI.resetNonModularOptions();
186186
CI.clearImplicitModuleBuildOptions();
187187

188+
// The scanner takes care to avoid passing non-affecting module maps to the
189+
// explicit compiles. No need to do extra work just to find out there are no
190+
// module map files to prune.
191+
CI.getHeaderSearchOpts().ModulesPruneNonAffectingModuleMaps = false;
192+
188193
// Remove options incompatible with explicit module build or are likely to
189194
// differ between identical modules discovered from different translation
190195
// units.

clang/test/ClangScanDeps/modules-extern-unrelated.m

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// This test checks that only module map files defining affecting modules are
2+
// affecting.
3+
14
// RUN: rm -rf %t
25
// RUN: split-file %s %t
36

@@ -22,15 +25,8 @@
2225
//--- second/second.h
2326
#include "first_other.h"
2427

25-
//--- cdb.json.template
26-
[{
27-
"directory": "DIR",
28-
"file": "DIR/tu.m",
29-
"command": "clang -fmodules -fmodules-cache-path=DIR/cache -I DIR/zeroth -I DIR/first -I DIR/second -c DIR/tu.m -o DIR/tu.o"
30-
}]
31-
32-
// RUN: sed -e "s|DIR|%/t|g" -e "s|INPUTS|%/S/Inputs|g" %t/cdb.json.template > %t/cdb.json
33-
// RUN: clang-scan-deps -compilation-database %t/cdb.json -format experimental-full > %t/result.json
28+
// RUN: clang-scan-deps -format experimental-full -o %t/result.json \
29+
// RUN: -- %clang -fmodules -fmodules-cache-path=%t/cache -I %t/zeroth -I %t/first -I %t/second -c %t/tu.m -o %t/tu.o
3430
// RUN: cat %t/result.json | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t
3531

3632
// CHECK: {
@@ -67,11 +63,11 @@
6763
// CHECK-NEXT: ],
6864
// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/second/second.modulemap",
6965
// CHECK-NEXT: "command-line": [
66+
// CHECK-NOT: "-fmodule-map-file=[[PREFIX]]/second/module.modulemap"
7067
// CHECK: ],
7168
// CHECK-NEXT: "context-hash": "{{.*}}",
7269
// CHECK-NEXT: "file-deps": [
7370
// CHECK-NEXT: "[[PREFIX]]/first/module.modulemap",
74-
// CHECK-NEXT: "[[PREFIX]]/second/module.modulemap",
7571
// CHECK-NEXT: "[[PREFIX]]/second/second.h",
7672
// CHECK-NEXT: "[[PREFIX]]/second/second.modulemap"
7773
// CHECK-NEXT: ],
@@ -90,11 +86,11 @@
9086
// CHECK-NEXT: ],
9187
// CHECK-NEXT: "clang-modulemap-file": "[[PREFIX]]/zeroth/module.modulemap",
9288
// CHECK-NEXT: "command-line": [
89+
// CHECK-NOT: "-fmodule-map-file=[[PREFIX]]/second/module.modulemap"
9390
// CHECK: ],
9491
// CHECK-NEXT: "context-hash": "{{.*}}",
9592
// CHECK-NEXT: "file-deps": [
9693
// CHECK-NEXT: "[[PREFIX]]/first/module.modulemap",
97-
// CHECK-NEXT: "[[PREFIX]]/second/module.modulemap",
9894
// CHECK-NEXT: "[[PREFIX]]/second/second.modulemap",
9995
// CHECK-NEXT: "[[PREFIX]]/zeroth/module.modulemap",
10096
// CHECK-NEXT: "[[PREFIX]]/zeroth/zeroth.h"
@@ -115,7 +111,7 @@
115111
// CHECK-NEXT: ],
116112
// CHECK-NEXT: "command-line": [
117113
// CHECK: ],
118-
// CHECK-NEXT: "executable": "clang",
114+
// CHECK-NEXT: "executable": "{{.*}}",
119115
// CHECK-NEXT: "file-deps": [
120116
// CHECK-NEXT: "[[PREFIX]]/tu.m"
121117
// CHECK-NEXT: ],

0 commit comments

Comments
 (0)