39 changes: 34 additions & 5 deletions clang/lib/Lex/Preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) {
CurLexerKind != CLK_CachingLexer) {
ModuleImportLoc = Identifier.getLocation();
NamedModuleImportPath.clear();
IsAtImport = true;
ModuleImportExpectsIdentifier = true;
CurLexerKind = CLK_LexAfterModuleImport;
}
Expand Down Expand Up @@ -940,6 +941,7 @@ void Preprocessor::Lex(Token &Result) {
case tok::semi:
TrackGMFState.handleSemi();
StdCXXImportSeqState.handleSemi();
ModuleDeclState.handleSemi();
break;
case tok::header_name:
case tok::annot_header_unit:
Expand All @@ -948,6 +950,13 @@ void Preprocessor::Lex(Token &Result) {
case tok::kw_export:
TrackGMFState.handleExport();
StdCXXImportSeqState.handleExport();
ModuleDeclState.handleExport();
break;
case tok::colon:
ModuleDeclState.handleColon();
break;
case tok::period:
ModuleDeclState.handlePeriod();
break;
case tok::identifier:
if (Result.getIdentifierInfo()->isModulesImport()) {
Expand All @@ -956,18 +965,25 @@ void Preprocessor::Lex(Token &Result) {
if (StdCXXImportSeqState.afterImportSeq()) {
ModuleImportLoc = Result.getLocation();
NamedModuleImportPath.clear();
IsAtImport = false;
ModuleImportExpectsIdentifier = true;
CurLexerKind = CLK_LexAfterModuleImport;
}
break;
} else if (Result.getIdentifierInfo() == getIdentifierInfo("module")) {
TrackGMFState.handleModule(StdCXXImportSeqState.afterTopLevelSeq());
ModuleDeclState.handleModule();
break;
} else {
ModuleDeclState.handleIdentifier(Result.getIdentifierInfo());
if (ModuleDeclState.isModuleCandidate())
break;
}
[[fallthrough]];
default:
TrackGMFState.handleMisc();
StdCXXImportSeqState.handleMisc();
ModuleDeclState.handleMisc();
break;
}
}
Expand Down Expand Up @@ -1151,6 +1167,15 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) {
if (NamedModuleImportPath.empty() && getLangOpts().CPlusPlusModules) {
if (LexHeaderName(Result))
return true;

if (Result.is(tok::colon) && ModuleDeclState.isNamedModule()) {
std::string Name = ModuleDeclState.getPrimaryName().str();
Name += ":";
NamedModuleImportPath.push_back(
{getIdentifierInfo(Name), Result.getLocation()});
CurLexerKind = CLK_LexAfterModuleImport;
return true;
}
} else {
Lex(Result);
}
Expand All @@ -1164,9 +1189,10 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) {
/*DisableMacroExpansion*/ true, /*IsReinject*/ false);
};

bool ImportingHeader = Result.is(tok::header_name);
// Check for a header-name.
SmallVector<Token, 32> Suffix;
if (Result.is(tok::header_name)) {
if (ImportingHeader) {
// Enter the header-name token into the token stream; a Lex action cannot
// both return a token and cache tokens (doing so would corrupt the token
// cache if the call to Lex comes from CachingLex / PeekAhead).
Expand Down Expand Up @@ -1244,8 +1270,8 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) {
if (ModuleImportExpectsIdentifier && Result.getKind() == tok::identifier) {
// We expected to see an identifier here, and we did; continue handling
// identifiers.
NamedModuleImportPath.push_back(std::make_pair(Result.getIdentifierInfo(),
Result.getLocation()));
NamedModuleImportPath.push_back(
std::make_pair(Result.getIdentifierInfo(), Result.getLocation()));
ModuleImportExpectsIdentifier = false;
CurLexerKind = CLK_LexAfterModuleImport;
return true;
Expand Down Expand Up @@ -1285,7 +1311,8 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) {
std::string FlatModuleName;
if (getLangOpts().ModulesTS || getLangOpts().CPlusPlusModules) {
for (auto &Piece : NamedModuleImportPath) {
if (!FlatModuleName.empty())
// If the FlatModuleName ends with colon, it implies it is a partition.
if (!FlatModuleName.empty() && FlatModuleName.back() != ':')
FlatModuleName += ".";
FlatModuleName += Piece.first->getName();
}
Expand All @@ -1296,14 +1323,16 @@ bool Preprocessor::LexAfterModuleImport(Token &Result) {
}

Module *Imported = nullptr;
if (getLangOpts().Modules) {
// We don't/shouldn't load the standard c++20 modules when preprocessing.
if (getLangOpts().Modules && !isInImportingCXXNamedModules()) {
Imported = TheModuleLoader.loadModule(ModuleImportLoc,
NamedModuleImportPath,
Module::Hidden,
/*IsInclusionDirective=*/false);
if (Imported)
makeModuleVisible(Imported, SemiLoc);
}

if (Callbacks)
Callbacks->moduleImport(ModuleImportLoc, NamedModuleImportPath, Imported);

Expand Down
150 changes: 98 additions & 52 deletions clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,67 +40,69 @@ DependencyScanningTool::DependencyScanningTool(
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
: Worker(Service, std::move(FS)) {}

llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
const std::vector<std::string> &CommandLine, StringRef CWD,
std::optional<StringRef> ModuleName) {
/// Prints out all of the gathered dependencies into a string.
class MakeDependencyPrinterConsumer : public DependencyConsumer {
public:
void handleBuildCommand(Command) override {}
namespace {
/// Prints out all of the gathered dependencies into a string.
class MakeDependencyPrinterConsumer : public DependencyConsumer {
public:
void handleBuildCommand(Command) override {}

void
handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
}

void
handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
}
void handleFileDependency(StringRef File) override {
Dependencies.push_back(std::string(File));
}

void handleFileDependency(StringRef File) override {
Dependencies.push_back(std::string(File));
}
void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
// Same as `handleModuleDependency`.
}

void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
// Same as `handleModuleDependency`.
}
void handleModuleDependency(ModuleDeps MD) override {
// These are ignored for the make format as it can't support the full
// set of deps, and handleFileDependency handles enough for implicitly
// built modules to work.
}

void handleModuleDependency(ModuleDeps MD) override {
// These are ignored for the make format as it can't support the full
// set of deps, and handleFileDependency handles enough for implicitly
// built modules to work.
}
void handleContextHash(std::string Hash) override {}

void handleContextHash(std::string Hash) override {}
std::string lookupModuleOutput(const ModuleID &ID,
ModuleOutputKind Kind) override {
llvm::report_fatal_error("unexpected call to lookupModuleOutput");
}

std::string lookupModuleOutput(const ModuleID &ID,
ModuleOutputKind Kind) override {
llvm::report_fatal_error("unexpected call to lookupModuleOutput");
}
void printDependencies(std::string &S) {
assert(Opts && "Handled dependency output options.");

void printDependencies(std::string &S) {
assert(Opts && "Handled dependency output options.");

class DependencyPrinter : public DependencyFileGenerator {
public:
DependencyPrinter(DependencyOutputOptions &Opts,
ArrayRef<std::string> Dependencies)
: DependencyFileGenerator(Opts) {
for (const auto &Dep : Dependencies)
addDependency(Dep);
}

void printDependencies(std::string &S) {
llvm::raw_string_ostream OS(S);
outputDependencyFile(OS);
}
};

DependencyPrinter Generator(*Opts, Dependencies);
Generator.printDependencies(S);
}
class DependencyPrinter : public DependencyFileGenerator {
public:
DependencyPrinter(DependencyOutputOptions &Opts,
ArrayRef<std::string> Dependencies)
: DependencyFileGenerator(Opts) {
for (const auto &Dep : Dependencies)
addDependency(Dep);
}

private:
std::unique_ptr<DependencyOutputOptions> Opts;
std::vector<std::string> Dependencies;
};
void printDependencies(std::string &S) {
llvm::raw_string_ostream OS(S);
outputDependencyFile(OS);
}
};

DependencyPrinter Generator(*Opts, Dependencies);
Generator.printDependencies(S);
}

protected:
std::unique_ptr<DependencyOutputOptions> Opts;
std::vector<std::string> Dependencies;
};
} // anonymous namespace

llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
const std::vector<std::string> &CommandLine, StringRef CWD,
std::optional<StringRef> ModuleName) {
MakeDependencyPrinterConsumer Consumer;
auto Result =
Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName);
Expand All @@ -111,6 +113,50 @@ llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
return Output;
}

llvm::Expected<P1689Rule> DependencyScanningTool::getP1689ModuleDependencyFile(
const CompileCommand &Command, StringRef CWD,
std::string &MakeformatOutput, std::string &MakeformatOutputPath) {
class P1689ModuleDependencyPrinterConsumer
: public MakeDependencyPrinterConsumer {
public:
P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule,
const CompileCommand &Command)
: Filename(Command.Filename), Rule(Rule) {
Rule.PrimaryOutput = Command.Output;
}

void handleProvidedAndRequiredStdCXXModules(
std::optional<P1689ModuleInfo> Provided,
std::vector<P1689ModuleInfo> Requires) override {
Rule.Provides = Provided;
if (Rule.Provides)
Rule.Provides->SourcePath = Filename.str();
Rule.Requires = Requires;
}

StringRef getMakeFormatDependencyOutputPath() {
if (Opts->OutputFormat != DependencyOutputFormat::Make)
return {};
return Opts->OutputFile;
}

private:
StringRef Filename;
P1689Rule &Rule;
};

P1689Rule Rule;
P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command);
auto Result = Worker.computeDependencies(CWD, Command.CommandLine, Consumer);
if (Result)
return std::move(Result);

MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath();
if (!MakeformatOutputPath.empty())
Consumer.printDependencies(MakeformatOutput);
return Rule;
}

llvm::Expected<FullDependenciesResult>
DependencyScanningTool::getFullDependencies(
const std::vector<std::string> &CommandLine, StringRef CWD,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,12 @@ class DependencyScanningAction : public tooling::ToolAction {
std::make_shared<DependencyConsumerForwarder>(
std::move(Opts), WorkingDirectory, Consumer));
break;
case ScanningOutputFormat::P1689:
case ScanningOutputFormat::Full:
MDC = std::make_shared<ModuleDepCollector>(
std::move(Opts), ScanInstance, Consumer, OriginalInvocation,
OptimizeArgs, EagerLoadModules);
OptimizeArgs, EagerLoadModules,
Format == ScanningOutputFormat::P1689);
ScanInstance.addDependencyCollector(MDC);
break;
}
Expand Down
33 changes: 31 additions & 2 deletions clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,14 @@ void ModuleDepCollectorPP::InclusionDirective(
void ModuleDepCollectorPP::moduleImport(SourceLocation ImportLoc,
ModuleIdPath Path,
const Module *Imported) {
if (MDC.ScanInstance.getPreprocessor().isInImportingCXXNamedModules()) {
P1689ModuleInfo RequiredModule;
RequiredModule.ModuleName = Path[0].first->getName().str();
RequiredModule.Type = P1689ModuleInfo::ModuleType::NamedCXXModule;
MDC.RequiredStdCXXModules.push_back(RequiredModule);
return;
}

handleImport(Imported);
}

Expand All @@ -361,6 +369,21 @@ void ModuleDepCollectorPP::EndOfMainFile() {
.getFileEntryForID(MainFileID)
->getName());

auto &PP = MDC.ScanInstance.getPreprocessor();
if (PP.isInNamedModule()) {
P1689ModuleInfo ProvidedModule;
ProvidedModule.ModuleName = PP.getNamedModuleName();
ProvidedModule.Type = P1689ModuleInfo::ModuleType::NamedCXXModule;
ProvidedModule.IsStdCXXModuleInterface = PP.isInNamedInterfaceUnit();
// Don't put implementation (non partition) unit as Provide.
// Put the module as required instead. Since the implementation
// unit will import the primary module implicitly.
if (PP.isInImplementationUnit())
MDC.RequiredStdCXXModules.push_back(ProvidedModule);
else
MDC.ProvidedStdCXXModule = ProvidedModule;
}

if (!MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty())
MDC.addFileDep(MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude);

Expand All @@ -374,6 +397,10 @@ void ModuleDepCollectorPP::EndOfMainFile() {

MDC.Consumer.handleDependencyOutputOpts(*MDC.Opts);

if (MDC.IsStdModuleP1689Format)
MDC.Consumer.handleProvidedAndRequiredStdCXXModules(
MDC.ProvidedStdCXXModule, MDC.RequiredStdCXXModules);

for (auto &&I : MDC.ModularDeps)
MDC.Consumer.handleModuleDependency(*I.second);

Expand Down Expand Up @@ -548,10 +575,12 @@ void ModuleDepCollectorPP::addAffectingClangModule(
ModuleDepCollector::ModuleDepCollector(
std::unique_ptr<DependencyOutputOptions> Opts,
CompilerInstance &ScanInstance, DependencyConsumer &C,
CompilerInvocation OriginalCI, bool OptimizeArgs, bool EagerLoadModules)
CompilerInvocation OriginalCI, bool OptimizeArgs, bool EagerLoadModules,
bool IsStdModuleP1689Format)
: ScanInstance(ScanInstance), Consumer(C), Opts(std::move(Opts)),
OriginalInvocation(std::move(OriginalCI)), OptimizeArgs(OptimizeArgs),
EagerLoadModules(EagerLoadModules) {}
EagerLoadModules(EagerLoadModules),
IsStdModuleP1689Format(IsStdModuleP1689Format) {}

void ModuleDepCollector::attachToPreprocessor(Preprocessor &PP) {
PP.addPPCallbacks(std::make_unique<ModuleDepCollectorPP>(*this));
Expand Down
304 changes: 304 additions & 0 deletions clang/test/ClangScanDeps/P1689.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// It is annoying to handle different slash direction
// in the filesystem of Windows and Linux.
// So we disable the test on Windows here.
// REQUIRES: !system-windows
//
// RUN: rm -fr %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: sed "s|DIR|%/t|g" %t/P1689.json.in > %t/P1689.json
// RUN: clang-scan-deps -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/Checks.cpp -DPREFIX=%/t
// RUN: clang-scan-deps --mode=preprocess-dependency-directives -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/Checks.cpp -DPREFIX=%/t
//
// Check the separated dependency format. This is required by CMake for the case
// that we have non-exist files in a fresh build and potentially out-of-date after that.
// So the build system need to wrtie a compilation database just for scanning purposes,
// which is not so good. So here is the per file mode for P1689.
// RUN: clang-scan-deps -format=p1689 \
// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/M.cppm -o %t/M.o \
// RUN: | FileCheck %t/M.cppm -DPREFIX=%/t
// RUN: clang-scan-deps -format=p1689 \
// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/Impl.cpp -o %t/Impl.o \
// RUN: | FileCheck %t/Impl.cpp -DPREFIX=%/t
// RUN: clang-scan-deps -format=p1689 \
// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/impl_part.cppm -o %t/impl_part.o \
// RUN: | FileCheck %t/impl_part.cppm -DPREFIX=%/t
// RUN: clang-scan-deps -format=p1689 \
// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/interface_part.cppm -o %t/interface_part.o \
// RUN: | FileCheck %t/interface_part.cppm -DPREFIX=%/t
// RUN: clang-scan-deps -format=p1689 \
// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/User.cpp -o %t/User.o \
// RUN: | FileCheck %t/User.cpp -DPREFIX=%/t
//
// Check we can generate the make-style dependencies as expected.
// RUN: clang-scan-deps -format=p1689 \
// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/impl_part.cppm -o %t/impl_part.o \
// RUN: -MT %t/impl_part.o.ddi -MD -MF %t/impl_part.dep
// RUN: cat %t/impl_part.dep | FileCheck %t/impl_part.cppm -DPREFIX=%/t --check-prefix=CHECK-MAKE
//
// Check that we can generate multiple make-style dependency information with compilation database.
// RUN: cat %t/P1689.dep | FileCheck %t/Checks.cpp -DPREFIX=%/t --check-prefix=CHECK-MAKE

//--- P1689.json.in
[
{
"directory": "DIR",
"command": "clang++ -std=c++20 DIR/M.cppm -c -o DIR/M.o -MT DIR/M.o.ddi -MD -MF DIR/P1689.dep",
"file": "DIR/M.cppm",
"output": "DIR/M.o"
},
{
"directory": "DIR",
"command": "clang++ -std=c++20 DIR/Impl.cpp -c -o DIR/Impl.o -MT DIR/Impl.o.ddi -MD -MF DIR/P1689.dep",
"file": "DIR/Impl.cpp",
"output": "DIR/Impl.o"
},
{
"directory": "DIR",
"command": "clang++ -std=c++20 DIR/impl_part.cppm -c -o DIR/impl_part.o -MT DIR/impl_part.o.ddi -MD -MF DIR/P1689.dep",
"file": "DIR/impl_part.cppm",
"output": "DIR/impl_part.o"
},
{
"directory": "DIR",
"command": "clang++ -std=c++20 DIR/interface_part.cppm -c -o DIR/interface_part.o -MT DIR/interface_part.o.ddi -MD -MF DIR/P1689.dep",
"file": "DIR/interface_part.cppm",
"output": "DIR/interface_part.o"
},
{
"directory": "DIR",
"command": "clang++ -std=c++20 DIR/User.cpp -c -o DIR/User.o -MT DIR/User.o.ddi -MD -MF DIR/P1689.dep",
"file": "DIR/User.cpp",
"output": "DIR/User.o"
}
]

//--- M.cppm
export module M;
export import :interface_part;
import :impl_part;
export void Hello();

// CHECK: {
// CHECK-NEXT: "revision": 0,
// CHECK-NEXT: "rules": [
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/M.o",
// CHECK-NEXT: "provides": [
// CHECK-NEXT: {
// CHECK-NEXT: "is-interface": true,
// CHECK-NEXT: "logical-name": "M",
// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M:interface_part"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M:impl_part"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "version": 1
// CHECK-NEXT: }

//--- Impl.cpp
module;
#include "header.mock"
module M;
void Hello() {
std::cout << "Hello ";
}

// CHECK: {
// CHECK-NEXT: "revision": 0,
// CHECK-NEXT: "rules": [
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/Impl.o",
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "version": 1
// CHECK-NEXT: }

//--- impl_part.cppm
module;
#include "header.mock"
module M:impl_part;
import :interface_part;

std::string W = "World.";
void World() {
std::cout << W << std::endl;
}

// CHECK: {
// CHECK-NEXT: "revision": 0,
// CHECK-NEXT: "rules": [
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/impl_part.o",
// CHECK-NEXT: "provides": [
// CHECK-NEXT: {
// CHECK-NEXT: "is-interface": false,
// CHECK-NEXT: "logical-name": "M:impl_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/impl_part.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M:interface_part"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "version": 1
// CHECK-NEXT: }

// CHECK-MAKE: [[PREFIX]]/impl_part.o.ddi:
// CHECK-MAKE: [[PREFIX]]/impl_part.cppm
// CHECK-MAKE: [[PREFIX]]/header.mock

//--- interface_part.cppm
export module M:interface_part;
export void World();

// CHECK: {
// CHECK-NEXT: "revision": 0,
// CHECK-NEXT: "rules": [
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/interface_part.o",
// CHECK-NEXT: "provides": [
// CHECK-NEXT: {
// CHECK-NEXT: "is-interface": true,
// CHECK-NEXT: "logical-name": "M:interface_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "version": 1
// CHECK-NEXT: }

//--- User.cpp
import M;
import third_party_module;
int main() {
Hello();
World();
return 0;
}

// CHECK: {
// CHECK-NEXT: "revision": 0,
// CHECK-NEXT: "rules": [
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/User.o",
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "third_party_module"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "version": 1
// CHECK-NEXT: }

//--- Checks.cpp
// CHECK: {
// CHECK-NEXT: "revision": 0,
// CHECK-NEXT: "rules": [
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/Impl.o",
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M",
// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/M.o",
// CHECK-NEXT: "provides": [
// CHECK-NEXT: {
// CHECK-NEXT: "is-interface": true,
// CHECK-NEXT: "logical-name": "M",
// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M:interface_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M:impl_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/impl_part.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/User.o",
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M",
// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "third_party_module"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/impl_part.o",
// CHECK-NEXT: "provides": [
// CHECK-NEXT: {
// CHECK-NEXT: "is-interface": false,
// CHECK-NEXT: "logical-name": "M:impl_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/impl_part.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "requires": [
// CHECK-NEXT: {
// CHECK-NEXT: "logical-name": "M:interface_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "primary-output": "[[PREFIX]]/interface_part.o",
// CHECK-NEXT: "provides": [
// CHECK-NEXT: {
// CHECK-NEXT: "is-interface": true,
// CHECK-NEXT: "logical-name": "M:interface_part",
// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "version": 1
// CHECK-NEXT: }

// CHECK-MAKE-DAG: [[PREFIX]]/impl_part.o.ddi: \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/impl_part.cppm \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/header.mock
// CHECK-MAKE-DAG: [[PREFIX]]/interface_part.o.ddi: \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/interface_part.cppm
// CHECK-MAKE-DAG: [[PREFIX]]/M.o.ddi: \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/M.cppm
// CHECK-MAKE-DAG: [[PREFIX]]/User.o.ddi: \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/User.cpp
// CHECK-MAKE-DAG: [[PREFIX]]/Impl.o.ddi: \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/Impl.cpp \
// CHECK-MAKE-DAG-NEXT: [[PREFIX]]/header.mock

//--- header.mock
2 changes: 2 additions & 0 deletions clang/tools/clang-scan-deps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
set(LLVM_LINK_COMPONENTS
Core
Option
Support
TargetParser
)

add_clang_tool(clang-scan-deps
Expand Down
260 changes: 245 additions & 15 deletions clang/tools/clang-scan-deps/ClangScanDeps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CommonOptionsParser.h"
Expand All @@ -17,6 +18,7 @@
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Program.h"
Expand Down Expand Up @@ -131,12 +133,15 @@ static llvm::cl::opt<ScanningMode> ScanMode(

static llvm::cl::opt<ScanningOutputFormat> Format(
"format", llvm::cl::desc("The output format for the dependencies"),
llvm::cl::values(clEnumValN(ScanningOutputFormat::Make, "make",
"Makefile compatible dep file"),
clEnumValN(ScanningOutputFormat::Full, "experimental-full",
"Full dependency graph suitable"
" for explicitly building modules. This format "
"is experimental and will change.")),
llvm::cl::values(
clEnumValN(ScanningOutputFormat::Make, "make",
"Makefile compatible dep file"),
clEnumValN(ScanningOutputFormat::P1689, "p1689",
"Generate standard c++ modules dependency P1689 format"),
clEnumValN(ScanningOutputFormat::Full, "experimental-full",
"Full dependency graph suitable"
" for explicitly building modules. This format "
"is experimental and will change.")),
llvm::cl::init(ScanningOutputFormat::Make),
llvm::cl::cat(DependencyScannerCategory));

Expand Down Expand Up @@ -165,9 +170,14 @@ llvm::cl::opt<unsigned>

llvm::cl::opt<std::string>
CompilationDB("compilation-database",
llvm::cl::desc("Compilation database"), llvm::cl::Required,
llvm::cl::desc("Compilation database"), llvm::cl::Optional,
llvm::cl::cat(DependencyScannerCategory));

llvm::cl::opt<std::string> P1689TargettedCommand(
llvm::cl::Positional, llvm::cl::ZeroOrMore,
llvm::cl::desc("The command line flags for the target of which "
"the dependencies are to be computed."));

llvm::cl::opt<std::string> ModuleName(
"module-name", llvm::cl::Optional,
llvm::cl::desc("the module of which the dependencies are to be computed"),
Expand Down Expand Up @@ -402,6 +412,92 @@ static bool handleFullDependencyToolResult(
return false;
}

class P1689Deps {
public:
void printDependencies(raw_ostream &OS) {
addSourcePathsToRequires();
// Sort the modules by name to get a deterministic order.
llvm::sort(Rules, [](const P1689Rule &A, const P1689Rule &B) {
return A.PrimaryOutput < B.PrimaryOutput;
});

using namespace llvm::json;
Array OutputRules;
for (const P1689Rule &R : Rules) {
Object O{{"primary-output", R.PrimaryOutput}};

if (R.Provides) {
Array Provides;
Object Provided{{"logical-name", R.Provides->ModuleName},
{"source-path", R.Provides->SourcePath},
{"is-interface", R.Provides->IsStdCXXModuleInterface}};
Provides.push_back(std::move(Provided));
O.insert({"provides", std::move(Provides)});
}

Array Requires;
for (const P1689ModuleInfo &Info : R.Requires) {
Object RequiredInfo{{"logical-name", Info.ModuleName}};
if (!Info.SourcePath.empty())
RequiredInfo.insert({"source-path", Info.SourcePath});
Requires.push_back(std::move(RequiredInfo));
}

if (!Requires.empty())
O.insert({"requires", std::move(Requires)});

OutputRules.push_back(std::move(O));
}

Object Output{
{"version", 1}, {"revision", 0}, {"rules", std::move(OutputRules)}};

OS << llvm::formatv("{0:2}\n", Value(std::move(Output)));
}

void addRules(P1689Rule &Rule) {
std::unique_lock<std::mutex> LockGuard(Lock);
Rules.push_back(Rule);
}

private:
void addSourcePathsToRequires() {
llvm::DenseMap<StringRef, StringRef> ModuleSourceMapper;
for (const P1689Rule &R : Rules)
if (R.Provides && !R.Provides->SourcePath.empty())
ModuleSourceMapper[R.Provides->ModuleName] = R.Provides->SourcePath;

for (P1689Rule &R : Rules) {
for (P1689ModuleInfo &Info : R.Requires) {
auto Iter = ModuleSourceMapper.find(Info.ModuleName);
if (Iter != ModuleSourceMapper.end())
Info.SourcePath = Iter->second;
}
}
}

std::mutex Lock;
std::vector<P1689Rule> Rules;
};

static bool
handleP1689DependencyToolResult(const std::string &Input,
llvm::Expected<P1689Rule> &MaybeRule,
P1689Deps &PD, SharedStream &Errs) {
if (!MaybeRule) {
llvm::handleAllErrors(
MaybeRule.takeError(), [&Input, &Errs](llvm::StringError &Err) {
Errs.applyLocked([&](raw_ostream &OS) {
OS << "Error while scanning dependencies for " << Input << ":\n";
OS << Err.getMessage();
});
});
return true;
}
PD.addRules(*MaybeRule);
return false;
}

/// Construct a path for the explicitly built PCM.
static std::string constructPCMPath(ModuleID MID, StringRef OutputDir) {
SmallString<256> ExplicitPCMPath(OutputDir);
Expand Down Expand Up @@ -438,19 +534,109 @@ static std::string getModuleCachePath(ArrayRef<std::string> Args) {
return std::string(Path);
}

int main(int argc, const char **argv) {
// getCompilationDataBase - If -compilation-database is set, load the
// compilation database from the specified file. Otherwise if the we're
// generating P1689 format, trying to generate the compilation database
// form specified command line after the positional parameter "--".
static std::unique_ptr<tooling::CompilationDatabase>
getCompilationDataBase(int argc, const char **argv, std::string &ErrorMessage) {
llvm::InitLLVM X(argc, argv);
llvm::cl::HideUnrelatedOptions(DependencyScannerCategory);
if (!llvm::cl::ParseCommandLineOptions(argc, argv))
return 1;
return nullptr;

if (!CompilationDB.empty())
return tooling::JSONCompilationDatabase::loadFromFile(
CompilationDB, ErrorMessage,
tooling::JSONCommandLineSyntax::AutoDetect);

if (Format != ScanningOutputFormat::P1689) {
llvm::errs() << "the --compilation-database option: must be specified at "
"least once!";
return nullptr;
}

// Trying to get the input file, the output file and the command line options
// from the positional parameter "--".
const char **DoubleDash = std::find(argv, argv + argc, StringRef("--"));
if (DoubleDash == argv + argc) {
llvm::errs() << "The command line arguments is required after '--' in "
"P1689 per file mode.";
return nullptr;
}
std::vector<const char *> CommandLine(DoubleDash + 1, argv + argc);

llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions);
driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(),
*Diags);
std::unique_ptr<driver::Compilation> C(
TheDriver.BuildCompilation(CommandLine));
if (!C)
return nullptr;

auto Cmd = C->getJobs().begin();
auto CI = std::make_unique<CompilerInvocation>();
CompilerInvocation::CreateFromArgs(*CI, Cmd->getArguments(), *Diags,
CommandLine[0]);
if (!CI)
return nullptr;

FrontendOptions &FEOpts = CI->getFrontendOpts();
if (FEOpts.Inputs.size() != 1) {
llvm::errs() << "Only one input file is allowed in P1689 per file mode.";
return nullptr;
}

// There might be multiple jobs for a compilation. Extract the specified
// output filename from the last job.
auto LastCmd = C->getJobs().end();
LastCmd--;
if (LastCmd->getOutputFilenames().size() != 1) {
llvm::errs() << "The command line should provide exactly one output file "
"in P1689 per file mode.\n";
}
StringRef OutputFile = LastCmd->getOutputFilenames().front();

class InplaceCompilationDatabase : public tooling::CompilationDatabase {
public:
InplaceCompilationDatabase(StringRef InputFile, StringRef OutputFile,
ArrayRef<const char *> CommandLine)
: Command(".", InputFile, {}, OutputFile) {
for (auto *C : CommandLine)
Command.CommandLine.push_back(C);
}

std::vector<tooling::CompileCommand>
getCompileCommands(StringRef FilePath) const override {
if (FilePath != Command.Filename)
return {};
return {Command};
}

std::vector<std::string> getAllFiles() const override {
return {Command.Filename};
}

std::vector<tooling::CompileCommand>
getAllCompileCommands() const override {
return {Command};
}

private:
tooling::CompileCommand Command;
};

return std::make_unique<InplaceCompilationDatabase>(
FEOpts.Inputs[0].getFile(), OutputFile, CommandLine);
}

int main(int argc, const char **argv) {
std::string ErrorMessage;
std::unique_ptr<tooling::JSONCompilationDatabase> Compilations =
tooling::JSONCompilationDatabase::loadFromFile(
CompilationDB, ErrorMessage,
tooling::JSONCommandLineSyntax::AutoDetect);
std::unique_ptr<tooling::CompilationDatabase> Compilations =
getCompilationDataBase(argc, argv, ErrorMessage);
if (!Compilations) {
llvm::errs() << "error: " << ErrorMessage << "\n";
llvm::errs() << ErrorMessage << "\n";
return 1;
}

Expand Down Expand Up @@ -537,6 +723,7 @@ int main(int argc, const char **argv) {

std::atomic<bool> HadErrors(false);
FullDeps FD;
P1689Deps PD;
std::mutex Lock;
size_t Index = 0;

Expand All @@ -545,7 +732,7 @@ int main(int argc, const char **argv) {
<< " files using " << Pool.getThreadCount() << " workers\n";
}
for (unsigned I = 0; I < Pool.getThreadCount(); ++I) {
Pool.async([I, &Lock, &Index, &Inputs, &HadErrors, &FD, &WorkerTools,
Pool.async([I, &Lock, &Index, &Inputs, &HadErrors, &FD, &PD, &WorkerTools,
&DependencyOS, &Errs]() {
llvm::StringSet<> AlreadySeenModules;
while (true) {
Expand Down Expand Up @@ -581,6 +768,47 @@ int main(int argc, const char **argv) {
if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS,
Errs))
HadErrors = true;
} else if (Format == ScanningOutputFormat::P1689) {
// It is useful to generate the make-format dependency output during
// the scanning for P1689. Otherwise the users need to scan again for
// it. We will generate the make-format dependency output if we find
// `-MF` in the command lines.
std::string MakeformatOutputPath;
std::string MakeformatOutput;

auto MaybeRule = WorkerTools[I]->getP1689ModuleDependencyFile(
*Input, CWD, MakeformatOutput, MakeformatOutputPath);
HadErrors =
handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs);

if (!MakeformatOutputPath.empty() && !MakeformatOutput.empty() &&
!HadErrors) {
static std::mutex Lock;
// With compilation database, we may open different files
// concurrently or we may write the same file concurrently. So we
// use a map here to allow multiple compile commands to write to the
// same file. Also we need a lock here to avoid data race.
static llvm::StringMap<llvm::raw_fd_ostream> OSs;
std::unique_lock<std::mutex> LockGuard(Lock);

auto OSIter = OSs.find(MakeformatOutputPath);
if (OSIter == OSs.end()) {
std::error_code EC;
OSIter = OSs.try_emplace(MakeformatOutputPath,
MakeformatOutputPath, EC)
.first;
if (EC)
llvm::errs()
<< "Failed to open P1689 make format output file \""
<< MakeformatOutputPath << "\" for " << EC.message()
<< "\n";
}

SharedStream MakeformatOS(OSIter->second);
llvm::Expected<std::string> MaybeOutput(MakeformatOutput);
HadErrors = handleMakeDependencyToolResult(Filename, MaybeOutput,
MakeformatOS, Errs);
}
} else if (DeprecatedDriverCommand) {
auto MaybeFullDeps =
WorkerTools[I]->getFullDependenciesLegacyDriverCommand(
Expand All @@ -604,6 +832,8 @@ int main(int argc, const char **argv) {

if (Format == ScanningOutputFormat::Full)
FD.printFullOutput(llvm::outs());
else if (Format == ScanningOutputFormat::P1689)
PD.printDependencies(llvm::outs());

return HadErrors;
}
2 changes: 2 additions & 0 deletions clang/unittests/Lex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_clang_unittest(LexTests
HeaderMapTest.cpp
HeaderSearchTest.cpp
LexerTest.cpp
ModuleDeclStateTest.cpp
PPCallbacksTest.cpp
PPConditionalDirectiveRecordTest.cpp
PPDependencyDirectivesTest.cpp
Expand All @@ -17,6 +18,7 @@ clang_target_link_libraries(LexTests
PRIVATE
clangAST
clangBasic
clangFrontend
clangLex
clangParse
clangSema
Expand Down
348 changes: 348 additions & 0 deletions clang/unittests/Lex/ModuleDeclStateTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
//===- unittests/Lex/ModuleDeclStateTest.cpp - PPCallbacks tests ------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===--------------------------------------------------------------===//

#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/HeaderSearchOptions.h"
#include "clang/Lex/ModuleLoader.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <initializer_list>

using namespace clang;

namespace {

class CheckNamedModuleImportingCB : public PPCallbacks {
Preprocessor &PP;
std::vector<bool> IsImportingNamedModulesAssertions;
std::size_t NextCheckingIndex;

public:
CheckNamedModuleImportingCB(Preprocessor &PP,
std::initializer_list<bool> lists)
: PP(PP), IsImportingNamedModulesAssertions(lists), NextCheckingIndex(0) {
}

void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path,
const Module *Imported) override {
ASSERT_TRUE(NextCheckingIndex < IsImportingNamedModulesAssertions.size());
EXPECT_EQ(PP.isInImportingCXXNamedModules(),
IsImportingNamedModulesAssertions[NextCheckingIndex]);
NextCheckingIndex++;

ASSERT_EQ(Imported, nullptr);
}

// Currently, only the named module will be handled by `moduleImport`
// callback.
std::size_t importNamedModuleNum() { return NextCheckingIndex; }
};
class ModuleDeclStateTest : public ::testing::Test {
protected:
ModuleDeclStateTest()
: FileMgr(FileMgrOpts), DiagID(new DiagnosticIDs()),
Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions), Invocation() {
TargetOpts->Triple = "x86_64-unknown-linux-gnu";
Target = TargetInfo::CreateTargetInfo(Diags, TargetOpts);
}

LangOptions &getLangOpts(ArrayRef<const char *> CommandLineArgs) {
CompilerInvocation::CreateFromArgs(Invocation, CommandLineArgs, Diags);
return *Invocation.getLangOpts();
}

std::unique_ptr<Preprocessor>
getPreprocessor(const char *source, ArrayRef<const char *> CommandLineArgs) {
std::unique_ptr<llvm::MemoryBuffer> Buf =
llvm::MemoryBuffer::getMemBuffer(source);
SourceMgr.setMainFileID(SourceMgr.createFileID(std::move(Buf)));

LangOptions &LangOpts = getLangOpts(CommandLineArgs);
HeaderInfo.emplace(std::make_shared<HeaderSearchOptions>(), SourceMgr,
Diags, LangOpts, Target.get());

return std::make_unique<Preprocessor>(
std::make_shared<PreprocessorOptions>(), Diags, LangOpts, SourceMgr,
*HeaderInfo, ModLoader,
/*IILookup =*/nullptr,
/*OwnsHeaderSearch =*/false);
}

void preprocess(Preprocessor &PP, std::unique_ptr<PPCallbacks> C) {
PP.Initialize(*Target);
PP.addPPCallbacks(std::move(C));
PP.EnterMainSourceFile();

while (1) {
Token tok;
PP.Lex(tok);
if (tok.is(tok::eof))
break;
}
}

FileSystemOptions FileMgrOpts;
FileManager FileMgr;
IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
DiagnosticsEngine Diags;
SourceManager SourceMgr;
std::shared_ptr<TargetOptions> TargetOpts;
IntrusiveRefCntPtr<TargetInfo> Target;
CompilerInvocation Invocation;
TrivialModuleLoader ModLoader;
std::optional<HeaderSearch> HeaderInfo;
};

TEST_F(ModuleDeclStateTest, NamedModuleInterface) {
const char *source = R"(
export module foo;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo");
}

TEST_F(ModuleDeclStateTest, NamedModuleImplementation) {
const char *source = R"(
module foo;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_TRUE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo");
}

TEST_F(ModuleDeclStateTest, ModuleImplementationPartition) {
const char *source = R"(
module foo:part;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}

TEST_F(ModuleDeclStateTest, ModuleInterfacePartition) {
const char *source = R"(
export module foo:part;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}

TEST_F(ModuleDeclStateTest, ModuleNameWithDot) {
const char *source = R"(
export module foo.dot:part.dot;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)0);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo.dot:part.dot");
}

TEST_F(ModuleDeclStateTest, NotModule) {
const char *source = R"(
// export module foo:part;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)0);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}

TEST_F(ModuleDeclStateTest, ModuleWithGMF) {
const char *source = R"(
module;
#include "bar.h"
#include <zoo.h>
import "bar";
import <zoo>;
export module foo:part;
import "HU";
import M;
import :another;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {true, true};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)2);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}

TEST_F(ModuleDeclStateTest, ModuleWithGMFWithClangNamedModule) {
const char *source = R"(
module;
#include "bar.h"
#include <zoo.h>
import "bar";
import <zoo>;
export module foo:part;
import "HU";
import M;
import :another;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {true, true};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)2);
EXPECT_TRUE(PP->isInNamedModule());
EXPECT_TRUE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
EXPECT_EQ(PP->getNamedModuleName(), "foo:part");
}

TEST_F(ModuleDeclStateTest, ImportsInNormalTU) {
const char *source = R"(
#include "bar.h"
#include <zoo.h>
import "bar";
import <zoo>;
import "HU";
import M;
// We can't import a partition in non-module TU.
import :another;
)";
std::unique_ptr<Preprocessor> PP = getPreprocessor(source, "-std=c++20");

std::initializer_list<bool> ImportKinds = {true};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)1);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}

TEST_F(ModuleDeclStateTest, ImportAClangNamedModule) {
const char *source = R"(
@import anything;
)";
std::unique_ptr<Preprocessor> PP =
getPreprocessor(source, {"-fmodules", "-fimplicit-module-maps", "-x",
"objective-c++", "-std=c++20"});

std::initializer_list<bool> ImportKinds = {false};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)1);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}

TEST_F(ModuleDeclStateTest, ImportWixedForm) {
const char *source = R"(
import "HU";
@import anything;
import M;
@import another;
import M2;
)";
std::unique_ptr<Preprocessor> PP =
getPreprocessor(source, {"-fmodules", "-fimplicit-module-maps", "-x",
"objective-c++", "-std=c++20"});

std::initializer_list<bool> ImportKinds = {false, true, false, true};
preprocess(*PP,
std::make_unique<CheckNamedModuleImportingCB>(*PP, ImportKinds));

auto *Callback =
static_cast<CheckNamedModuleImportingCB *>(PP->getPPCallbacks());
EXPECT_EQ(Callback->importNamedModuleNum(), (size_t)4);
EXPECT_FALSE(PP->isInNamedModule());
EXPECT_FALSE(PP->isInNamedInterfaceUnit());
EXPECT_FALSE(PP->isInImplementationUnit());
}

} // namespace
2 changes: 1 addition & 1 deletion libcxx/test/support/assert_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ std::string test_concat_message([[maybe_unused]] Args&&... args) {
[[noreturn]] void test_log_error(const char* condition, const char* file, int line, std::string&& message) {
const char* msg = condition ? "Assertion failure: " : "Unconditional failure:";
std::fprintf(stderr, "%s%s %s %d\n%s", msg, condition, file, line, message.c_str());
exit(EXIT_FAILURE);
std::abort();
}

inline void test_fail(const char* file, int line, std::string&& message) {
Expand Down
5 changes: 4 additions & 1 deletion openmp/runtime/src/z_Linux_asm.S
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,10 @@ __tid = 8
// for when we call pkfn below
push {r3-r11,lr}
// Load p_argv and &exit_frame
ldrd r4, r5, [sp, #10*4]
ldr r4, [sp, #10*4]
# if OMPT_SUPPORT
ldr r5, [sp, #11*4]
# endif

# if KMP_OS_DARWIN || (defined(__thumb__) && !KMP_OS_WINDOWS)
# define FP r7
Expand Down