diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -11,6 +11,7 @@ #include "clang/AST/Decl.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LangStandard.h" +#include "clang/Basic/Module.h" #include "clang/Basic/TargetInfo.h" #include "clang/Frontend/ASTConsumers.h" #include "clang/Frontend/CompilerInstance.h" @@ -24,6 +25,7 @@ #include "clang/Sema/TemplateInstCallback.h" #include "clang/Serialization/ASTReader.h" #include "clang/Serialization/ASTWriter.h" +#include "clang/Serialization/ModuleFile.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" @@ -806,7 +808,25 @@ return true; } +static StringRef ModuleKindName(Module::ModuleKind MK) { + switch (MK) { + case Module::ModuleMapModule: + return "Module Map Module"; + case Module::ModuleInterfaceUnit: + return "Interface Unit"; + case Module::ModulePartitionInterface: + return "Partition Interface"; + case Module::ModulePartitionImplementation: + return "Partition Implementation"; + case Module::GlobalModuleFragment: + return "Global Module Fragment"; + case Module::PrivateModuleFragment: + return "Private Module Fragment"; + } +} + void DumpModuleInfoAction::ExecuteAction() { + assert(isCurrentFileAST() && "dumping non-AST?"); // Set up the output file. std::unique_ptr OutFile; StringRef OutputFileName = getCompilerInstance().getFrontendOpts().OutputFile; @@ -827,8 +847,87 @@ Preprocessor &PP = getCompilerInstance().getPreprocessor(); DumpModuleInfoListener Listener(Out); - HeaderSearchOptions &HSOpts = - PP.getHeaderSearchInfo().getHeaderSearchOpts(); + HeaderSearchOptions &HSOpts = PP.getHeaderSearchInfo().getHeaderSearchOpts(); + + // The FrontendAction::BeginSourceFile () method loads the AST so that much + // of the information is already available and modules should have been + // loaded. + + const LangOptions &LO = getCurrentASTUnit().getLangOpts(); + if (LO.CPlusPlusModules && !LO.CurrentModule.empty()) { + + ASTReader *R = getCurrentASTUnit().getASTReader().get(); + unsigned SubModuleCount = R->getTotalNumSubmodules(); + serialization::ModuleFile &MF = R->getModuleManager().getPrimaryModule(); + Out << " ====== C++20 Module structure ======\n"; + + if (MF.ModuleName != LO.CurrentModule) + Out << " Mismatched module names : " << MF.ModuleName << " and " + << LO.CurrentModule << "\n"; + + struct SubModInfo { + unsigned Idx; + Module *Mod; + Module::ModuleKind Kind; + std::string &Name; + bool Seen; + }; + std::map SubModMap; + auto PrintSubMapEntry = [&](std::string Name, Module::ModuleKind Kind) { + Out << " " << ModuleKindName(Kind) << " '" << Name << "'"; + auto I = SubModMap.find(Name); + if (I == SubModMap.end()) + Out << " was not found in the sub modules!\n"; + else { + I->second.Seen = true; + Out << " is at index #" << I->second.Idx << "\n"; + } + }; + Module *Primary = nullptr; + for (unsigned Idx = 0; Idx <= SubModuleCount; ++Idx) { + Module *M = R->getModule(Idx); + if (!M) + continue; + if (M->Name == LO.CurrentModule) { + Primary = M; + Out << " " << ModuleKindName(M->Kind) << " '" << LO.CurrentModule + << "' is the Primary Module at index #" << Idx << "\n"; + SubModMap.insert({M->Name, {Idx, M, M->Kind, M->Name, true}}); + } else + SubModMap.insert({M->Name, {Idx, M, M->Kind, M->Name, false}}); + } + if (Primary) { + if (!Primary->submodules().empty()) + Out << " Sub Modules:\n"; + for (auto MI : Primary->submodules()) { + PrintSubMapEntry(MI->Name, MI->Kind); + } + if (!Primary->Imports.empty()) + Out << " Imports:\n"; + for (auto IMP : Primary->Imports) { + PrintSubMapEntry(IMP->Name, IMP->Kind); + } + if (!Primary->Exports.empty()) + Out << " Exports:\n"; + for (unsigned MN = 0, N = Primary->Exports.size(); MN != N; ++MN) { + if (Module *M = Primary->Exports[MN].getPointer()) { + PrintSubMapEntry(M->Name, M->Kind); + } + } + } + // Now let's print out any modules we did not see as part of the Primary. + for (auto SM : SubModMap) { + if (!SM.second.Seen && SM.second.Mod) { + Out << " " << ModuleKindName(SM.second.Kind) << " '" << SM.first + << "' at index #" << SM.second.Idx + << " has no direct reference in the Primary\n"; + } + } + Out << " ====== ======\n"; + } + + // The reminder of the output is produced from the listener as the AST + // FileCcontrolBlock is (re-)parsed. ASTReader::readASTFileControlBlock( getCurrentFile(), FileMgr, getCompilerInstance().getPCHContainerReader(), /*FindModuleFileExtensions=*/true, Listener, diff --git a/clang/test/Modules/cxx20-module-file-info.cpp b/clang/test/Modules/cxx20-module-file-info.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/cxx20-module-file-info.cpp @@ -0,0 +1,64 @@ +// Test output from -module-file-info about C++20 modules. + +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/mod-info-tu1.cpp \ +// RUN: -o %t/A.pcm + +// RUN: %clang_cc1 -std=c++20 -module-file-info %t/A.pcm | FileCheck \ +// RUN: --check-prefix=CHECK-A %s + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/mod-info-tu2.cpp \ +// RUN: -o %t/B.pcm + +// RUN: %clang_cc1 -std=c++20 -module-file-info %t/B.pcm | FileCheck \ +// RUN: --check-prefix=CHECK-B %s + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/mod-info-tu3.cpp \ +// RUN: -fmodule-file=%t/A.pcm -fmodule-file=%t/B.pcm -o %t/Foo.pcm + +// RUN: %clang_cc1 -std=c++20 -module-file-info %t/Foo.pcm | FileCheck \ +// RUN: --check-prefix=CHECK-FOO %s + +// expected-no-diagnostics + +//--- mod-info-tu1.cpp +export module A; + +void a(); + +// CHECK-A: ====== C++20 +// CHECK-A-NEXT: Interface Unit 'A' is the Primary Module at index #1 + +//--- mod-info-tu2.cpp +export module B; + +void b(); + +// CHECK-B: ====== C++20 +// CHECK-B-NEXT: Interface Unit 'B' is the Primary Module at index #1 + +//--- mod-info-tu3.cpp +module; + +export module Foo; + +import A; +export import B; + +namespace hello { +export void say(const char *); +} + +void foo() {} + +// CHECK-FOO: ====== C++20 +// CHECK-FOO-NEXT: Interface Unit 'Foo' is the Primary Module at index #3 +// CHECK-FOO-NEXT: Sub Modules: +// CHECK-FOO-NEXT: Global Module Fragment '' is at index #4 +// CHECK-FOO-NEXT: Imports: +// CHECK-FOO-NEXT: Interface Unit 'A' is at index #1 +// CHECK-FOO-NEXT: Exports: +// CHECK-FOO-NEXT: Interface Unit 'B' is at index #2