Skip to content

Commit

Permalink
Support lazy stat'ing of files referenced by module maps.
Browse files Browse the repository at this point in the history
This patch adds support for a `header` declaration in a module map to specify
certain `stat` information (currently, size and mtime) about that header file.
This has two purposes:

- It removes the need to eagerly `stat` every file referenced by a module map.
  Instead, we track a list of unresolved header files with each size / mtime
  (actually, for simplicity, we track submodules with such headers), and when
  attempting to look up a header file based on a `FileEntry`, we check if there
  are any unresolved header directives with that `FileEntry`'s size / mtime and
  perform deferred `stat`s if so.

- It permits a preprocessed module to be compiled without the original files
  being present on disk. The only reason we used to need those files was to get
  the `stat` information in order to do header -> module lookups when using the
  module. If we're provided with the `stat` information in the preprocessed
  module, we can avoid requiring the files to exist.

Unlike most `header` directives, if a `header` directive with `stat`
information has no corresponding on-disk file the enclosing module is *not*
marked unavailable (so that behavior is consistent regardless of whether we've
resolved a header directive, and so that preprocessed modules don't get marked
unavailable). We could actually do this for all `header` directives: the only
reason we mark the module unavailable if headers are missing is to give a
diagnostic slightly earlier (rather than waiting until we actually try to build
the module / load and validate its .pcm file).

Differential Revision: https://reviews.llvm.org/D33703

llvm-svn: 304515
  • Loading branch information
zygoloid committed Jun 2, 2017
1 parent ae80045 commit 040e126
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 152 deletions.
25 changes: 22 additions & 3 deletions clang/docs/Modules.rst
Expand Up @@ -469,9 +469,16 @@ A header declaration specifies that a particular header is associated with the e
.. parsed-literal::
*header-declaration*:
``private``:sub:`opt` ``textual``:sub:`opt` ``header`` *string-literal*
``umbrella`` ``header`` *string-literal*
``exclude`` ``header`` *string-literal*
``private``:sub:`opt` ``textual``:sub:`opt` ``header`` *string-literal* *header-attrs*:sub:`opt`
``umbrella`` ``header`` *string-literal* *header-attrs*:sub:`opt`
``exclude`` ``header`` *string-literal* *header-attrs*:sub:`opt`
*header-attrs*:
'{' *header-attr** '}'
*header-attr*:
``size`` *integer-literal*
``mtime`` *integer-literal*
A header declaration that does not contain ``exclude`` nor ``textual`` specifies a header that contributes to the enclosing module. Specifically, when the module is built, the named header will be parsed and its declarations will be (logically) placed into the enclosing submodule.

Expand Down Expand Up @@ -504,6 +511,18 @@ A header with the ``exclude`` specifier is excluded from the module. It will not
A given header shall not be referenced by more than one *header-declaration*.

Two *header-declaration*\s, or a *header-declaration* and a ``#include``, are
considered to refer to the same file if the paths resolve to the same file
and the specified *header-attr*\s (if any) match the attributes of that file,
even if the file is named differently (for instance, by a relative path or
via symlinks).

.. note::
The use of *header-attr*\s avoids the need for Clang to speculatively
``stat`` every header referenced by a module map. It is recommended that
*header-attr*\s only be used in machine-generated module maps, to avoid
mismatches between attribute values and the corresponding files.

Umbrella directory declaration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An umbrella directory declaration specifies that all of the headers in the specified directory should be included within the module.
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticLexKinds.td
Expand Up @@ -664,6 +664,12 @@ def warn_mmap_mismatched_top_level_private : Warning<
InGroup<PrivateModule>;
def note_mmap_rename_top_level_private_as_submodule : Note<
"make '%0' a submodule of '%1' to ensure it can be found by name">;
def err_mmap_duplicate_header_attribute : Error<
"header attribute '%0' specified multiple times">;
def err_mmap_invalid_header_attribute_value : Error<
"expected integer literal as value for header attribute '%0'">;
def err_mmap_expected_header_attribute : Error<
"expected a header attribute name ('size' or 'mtime')">;

def warn_auto_module_import : Warning<
"treating #%select{include|import|include_next|__include_macros}0 as an "
Expand Down
12 changes: 8 additions & 4 deletions clang/include/clang/Basic/DiagnosticSerializationKinds.td
Expand Up @@ -174,10 +174,6 @@ def note_module_odr_violation_mismatch_decl_diff : Note<"but in '%0' found "
"method %2 with %ordinal3 parameter of type %4%select{| decayed from %6}5|"
"method %2 with %ordinal3 parameter named %4}1">;

def warn_module_uses_date_time : Warning<
"%select{precompiled header|module}0 uses __DATE__ or __TIME__">,
InGroup<DiagGroup<"pch-date-time">>;

def warn_duplicate_module_file_extension : Warning<
"duplicate module file extension block name '%0'">,
InGroup<ModuleFileExtension>;
Expand All @@ -186,7 +182,15 @@ def warn_module_system_bit_conflict : Warning<
"module file '%0' was validated as a system module and is now being imported "
"as a non-system module; any difference in diagnostic options will be ignored">,
InGroup<ModuleConflict>;
} // let CategoryName

let CategoryName = "AST Serialization Issue" in {
def warn_module_uses_date_time : Warning<
"%select{precompiled header|module}0 uses __DATE__ or __TIME__">,
InGroup<DiagGroup<"pch-date-time">>;
def err_module_no_size_mtime_for_header : Error<
"cannot emit module %0: %select{size|mtime}1 must be explicitly specified "
"for missing header file \"%2\"">;
} // let CategoryName
} // let Component

10 changes: 9 additions & 1 deletion clang/include/clang/Basic/Module.h
Expand Up @@ -154,11 +154,19 @@ class Module {
/// \brief Stored information about a header directive that was found in the
/// module map file but has not been resolved to a file.
struct UnresolvedHeaderDirective {
HeaderKind Kind = HK_Normal;
SourceLocation FileNameLoc;
std::string FileName;
bool IsUmbrella;
bool IsUmbrella = false;
bool HasBuiltinHeader = false;
Optional<off_t> Size;
Optional<time_t> ModTime;
};

/// Headers that are mentioned in the module map file but that we have not
/// yet attempted to resolve to a file on the file system.
SmallVector<UnresolvedHeaderDirective, 1> UnresolvedHeaders;

/// \brief Headers that are mentioned in the module map file but could not be
/// found on the file system.
SmallVector<UnresolvedHeaderDirective, 1> MissingHeaders;
Expand Down
45 changes: 37 additions & 8 deletions clang/include/clang/Lex/ModuleMap.h
Expand Up @@ -26,6 +26,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/ADT/Twine.h"
#include <algorithm>
#include <memory>
Expand Down Expand Up @@ -116,6 +117,11 @@ class ModuleMap {
// Adjust ModuleMap::addHeader.
};

/// Convert a header kind to a role. Requires Kind to not be HK_Excluded.
static ModuleHeaderRole headerKindToRole(Module::HeaderKind Kind);
/// Convert a header role to a kind.
static Module::HeaderKind headerRoleToKind(ModuleHeaderRole Role);

/// \brief A header that is known to reside within a given module,
/// whether it was included or excluded.
class KnownHeader {
Expand Down Expand Up @@ -165,7 +171,13 @@ class ModuleMap {
/// \brief Mapping from each header to the module that owns the contents of
/// that header.
HeadersMap Headers;


/// Map from file sizes to modules with lazy header directives of that size.
mutable llvm::DenseMap<off_t, llvm::TinyPtrVector<Module*>> LazyHeadersBySize;
/// Map from mtimes to modules with lazy header directives with those mtimes.
mutable llvm::DenseMap<time_t, llvm::TinyPtrVector<Module*>>
LazyHeadersByModTime;

/// \brief Mapping from directories with umbrella headers to the module
/// that is generated from the umbrella header.
///
Expand Down Expand Up @@ -257,22 +269,30 @@ class ModuleMap {
/// resolved.
Module *resolveModuleId(const ModuleId &Id, Module *Mod, bool Complain) const;

/// Resolve the given header directive to an actual header file.
/// Add an unresolved header to a module.
void addUnresolvedHeader(Module *Mod,
Module::UnresolvedHeaderDirective Header);

/// Look up the given header directive to find an actual header file.
///
/// \param M The module in which we're resolving the header directive.
/// \param Header The header directive to resolve.
/// \param RelativePathName Filled in with the relative path name from the
/// module to the resolved header.
/// \return The resolved file, if any.
const FileEntry *resolveHeader(Module *M,
Module::UnresolvedHeaderDirective Header,
SmallVectorImpl<char> &RelativePathName);
const FileEntry *findHeader(Module *M,
const Module::UnresolvedHeaderDirective &Header,
SmallVectorImpl<char> &RelativePathName);

/// Resolve the given header directive.
void resolveHeader(Module *M,
const Module::UnresolvedHeaderDirective &Header);

/// Attempt to resolve the specified header directive as naming a builtin
/// header.
const FileEntry *
resolveAsBuiltinHeader(Module *M, Module::UnresolvedHeaderDirective Header,
SmallVectorImpl<char> &BuiltinPathName);
/// \return \c true if a corresponding builtin header was found.
bool resolveAsBuiltinHeader(Module *M,
const Module::UnresolvedHeaderDirective &Header);

/// \brief Looks up the modules that \p File corresponds to.
///
Expand Down Expand Up @@ -368,6 +388,15 @@ class ModuleMap {
/// the preferred module for the header.
ArrayRef<KnownHeader> findAllModulesForHeader(const FileEntry *File) const;

/// Resolve all lazy header directives for the specified file.
///
/// This ensures that the HeaderFileInfo on HeaderSearch is up to date. This
/// is effectively internal, but is exposed so HeaderSearch can call it.
void resolveHeaderDirectives(const FileEntry *File) const;

/// Resolve all lazy header directives for the specified module.
void resolveHeaderDirectives(Module *Mod) const;

/// \brief Reports errors if a module must not include a specific file.
///
/// \param RequestingModule The module including a file.
Expand Down
21 changes: 20 additions & 1 deletion clang/lib/Basic/Module.cpp
Expand Up @@ -394,11 +394,30 @@ void Module::print(raw_ostream &OS, unsigned Indent) const {
{"exclude ", HK_Excluded}};

for (auto &K : Kinds) {
assert(&K == &Kinds[K.Kind] && "kinds in wrong order");
for (auto &H : Headers[K.Kind]) {
OS.indent(Indent + 2);
OS << K.Prefix << "header \"";
OS.write_escaped(H.NameAsWritten);
OS << "\"\n";
OS << "\" { size " << H.Entry->getSize()
<< " mtime " << H.Entry->getModificationTime() << " }\n";
}
}
for (auto *Unresolved : {&UnresolvedHeaders, &MissingHeaders}) {
for (auto &U : *Unresolved) {
OS.indent(Indent + 2);
OS << Kinds[U.Kind].Prefix << "header \"";
OS.write_escaped(U.FileName);
OS << "\"";
if (U.Size || U.ModTime) {
OS << " {";
if (U.Size)
OS << " size " << *U.Size;
if (U.ModTime)
OS << " mtime " << *U.ModTime;
OS << " }";
}
OS << "\n";
}
}

Expand Down
26 changes: 20 additions & 6 deletions clang/lib/Frontend/FrontendAction.cpp
Expand Up @@ -289,14 +289,28 @@ static void addHeaderInclude(StringRef HeaderName,
///
/// \param Includes Will be augmented with the set of \#includes or \#imports
/// needed to load all of the named headers.
static std::error_code
collectModuleHeaderIncludes(const LangOptions &LangOpts, FileManager &FileMgr,
ModuleMap &ModMap, clang::Module *Module,
SmallVectorImpl<char> &Includes) {
static std::error_code collectModuleHeaderIncludes(
const LangOptions &LangOpts, FileManager &FileMgr, DiagnosticsEngine &Diag,
ModuleMap &ModMap, clang::Module *Module, SmallVectorImpl<char> &Includes) {
// Don't collect any headers for unavailable modules.
if (!Module->isAvailable())
return std::error_code();

// Resolve all lazy header directives to header files.
ModMap.resolveHeaderDirectives(Module);

// If any headers are missing, we can't build this module. In most cases,
// diagnostics for this should have already been produced; we only get here
// if explicit stat information was provided.
// FIXME: If the name resolves to a file with different stat information,
// produce a better diagnostic.
if (!Module->MissingHeaders.empty()) {
auto &MissingHeader = Module->MissingHeaders.front();
Diag.Report(MissingHeader.FileNameLoc, diag::err_module_header_missing)
<< MissingHeader.IsUmbrella << MissingHeader.FileName;
return std::error_code();
}

// Add includes for each of these headers.
for (auto HK : {Module::HK_Normal, Module::HK_Private}) {
for (Module::Header &H : Module->Headers[HK]) {
Expand Down Expand Up @@ -367,7 +381,7 @@ collectModuleHeaderIncludes(const LangOptions &LangOpts, FileManager &FileMgr,
SubEnd = Module->submodule_end();
Sub != SubEnd; ++Sub)
if (std::error_code Err = collectModuleHeaderIncludes(
LangOpts, FileMgr, ModMap, *Sub, Includes))
LangOpts, FileMgr, Diag, ModMap, *Sub, Includes))
return Err;

return std::error_code();
Expand Down Expand Up @@ -494,7 +508,7 @@ getInputBufferForModule(CompilerInstance &CI, Module *M) {
addHeaderInclude(UmbrellaHeader.NameAsWritten, HeaderContents,
CI.getLangOpts(), M->IsExternC);
Err = collectModuleHeaderIncludes(
CI.getLangOpts(), FileMgr,
CI.getLangOpts(), FileMgr, CI.getDiagnostics(),
CI.getPreprocessor().getHeaderSearchInfo().getModuleMap(), M,
HeaderContents);

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Lex/HeaderSearch.cpp
Expand Up @@ -1114,6 +1114,8 @@ bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
auto TryEnterImported = [&](void) -> bool {
if (!ModulesEnabled)
return false;
// Ensure FileInfo bits are up to date.
ModMap.resolveHeaderDirectives(File);
// Modules with builtins are special; multiple modules use builtins as
// modular headers, example:
//
Expand Down

0 comments on commit 040e126

Please sign in to comment.