[clang] [C++20][Modules] Omit imported PCM file info for explicitly mapped header units (PR #235597)

Michael Park via cfe-commits cfe-commits at lists.llvm.org
Sun Oct 25 23:38:23 PDT 2026


https://github.com/mpark updated https://github.com/llvm/llvm-project/pull/235597

>From fb470ada3fe787dae7c02711f575df83a0adc9e5 Mon Sep 17 00:00:00 2001
From: Michael Park <mcypark at gmail.com>
Date: Sun, 25 Oct 2026 21:30:05 -0700
Subject: [PATCH] [C++20][Modules] Omit imported PCM file info for explicitly
 mapped header units

C++20 named module dependencies are already serialized without PCM file
information. This PR applies the same behavior to header units loaded through
a named mapping (`-fmodule-file=<name>=<pcm>`). Header units loaded through
`-fmodule-file=<file>` retain their file information.

An explicit `IsCXX20ModuleOrPrebuiltHeaderUnit` field is added to
the `IMPORT` record rather than reusing `StandardCXXModule`, which
also controls imported-module lookup.

This also adds a missing test for the existing named-module behavior and
tests the corresponding header-unit behavior introduced by this change.
---
 clang/lib/Serialization/ASTReader.cpp         | 24 +++---
 clang/lib/Serialization/ASTWriter.cpp         | 19 ++++-
 clang/lib/Serialization/GlobalModuleIndex.cpp |  8 +-
 clang/test/Modules/cxx20-import-paths.cpp     | 80 +++++++++++++++++++
 4 files changed, 116 insertions(+), 15 deletions(-)
 create mode 100644 clang/test/Modules/cxx20-import-paths.cpp

diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 56f1aa8a7f932a..cd7d9f24924f82 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3538,6 +3538,7 @@ ASTReader::ReadControlBlock(ModuleFile &F,
       StringRef ImportedName = ReadStringBlob(Record, Idx, Blob);
 
       bool IsImportingStdCXXModule = Record[Idx++];
+      bool IsCXX20ModuleOrPrebuiltHeaderUnit = Record[Idx++];
 
       off_t StoredSize = 0;
       time_t StoredModTime = 0;
@@ -3557,12 +3558,12 @@ ASTReader::ReadControlBlock(ModuleFile &F,
         ImportedFile = PP.getHeaderSearchInfo().getPrebuiltModuleFileName(
             ImportedName, /*FileMapOnly*/ !IsImportingStdCXXModule);
 
-      if (IsImportingStdCXXModule && ImportedFile.empty()) {
+      if (IsCXX20ModuleOrPrebuiltHeaderUnit && ImportedFile.empty()) {
         Diag(diag::err_failed_to_find_module_file) << ImportedName;
         return Missing;
       }
 
-      if (!IsImportingStdCXXModule) {
+      if (!IsCXX20ModuleOrPrebuiltHeaderUnit) {
         StoredSize = (off_t)Record[Idx++];
         StoredModTime = (time_t)Record[Idx++];
         FileNameKind = (unsigned)Record[Idx++];
@@ -3604,10 +3605,12 @@ ASTReader::ReadControlBlock(ModuleFile &F,
                                 Loaded, StoredSize, StoredModTime,
                                 StoredSignature, Capabilities);
 
-      // Check the AST we just read from ImportedFile contains a different
-      // module than we expected (ImportedName). This can occur for C++20
-      // Modules when given a mismatch via -fmodule-file=<name>=<file>
-      if (IsImportingStdCXXModule) {
+      // Check whether the AST we just read from ImportedFile contains a
+      // different module than we expected (ImportedName).
+      //
+      // This can occur for C++20 Modules (including explicitly mapped header
+      // units) when given a mismatch in -fmodule-file=<name>=<file>.
+      if (IsCXX20ModuleOrPrebuiltHeaderUnit) {
         if (const auto *Imported =
                 getModuleManager().lookupByFileName(ImportedFile);
             Imported != nullptr && Imported->ModuleName != ImportedName) {
@@ -6329,11 +6332,12 @@ bool ASTReader::readASTFileControlBlock(
 
       StringRef ModuleName = ReadStringBlob(Record, Idx, Blob);
 
-      bool IsStandardCXXModule = Record[Idx++];
+      ++Idx; // Standard C++ module
+      bool IsCXX20ModuleOrPrebuiltHeaderUnit = Record[Idx++];
 
-      // In C++20 Modules, we don't record the path to imported
-      // modules in the BMI files.
-      if (IsStandardCXXModule) {
+      // For C++20 Modules (including explicitly mapped header units),
+      // we don't record the path to imported modules in the BMI files.
+      if (IsCXX20ModuleOrPrebuiltHeaderUnit) {
         Listener.visitImport(ModuleName, /*Filename=*/"");
         continue;
       }
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 0d9ec7a460dfbf..63ce3023964a86 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -1578,12 +1578,14 @@ void ASTWriter::WriteControlBlock(Preprocessor &PP, StringRef isysroot) {
 
   // Imports
   if (Chain) {
+    const auto &HSOpts = PP.getHeaderSearchInfo().getHeaderSearchOpts();
     auto Abbrev = std::make_shared<BitCodeAbbrev>();
     Abbrev->Add(BitCodeAbbrevOp(IMPORT));
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // Kind
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // ImportLoc
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Module name len
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // Standard C++ mod
+    Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // C++20/prebuilt HU
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // File size
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // File timestamp
     Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // File name raw kind
@@ -1607,9 +1609,20 @@ void ASTWriter::WriteControlBlock(Preprocessor &PP, StringRef isysroot) {
       AddStringBlob(M.ModuleName, Record, Blob);
       Record.push_back(M.StandardCXXModule);
 
-      // We don't want to hard code the information about imported modules
-      // in the C++20 named modules.
-      if (M.StandardCXXModule) {
+      // We don't want to hard code file information for C++20 Modules
+      // (including explicitly mapped header units).
+      const Module *ImportedModule =
+          PP.getHeaderSearchInfo().getModuleMap().findModule(M.ModuleName);
+      bool IsPrebuiltHeaderUnit =
+          ImportedModule && ImportedModule->isHeaderUnit() &&
+          M.Kind == MK_PrebuiltModule &&
+          HSOpts.PrebuiltModuleFiles.count(M.ModuleName);
+
+      bool IsCXX20ModuleOrPrebuiltHeaderUnit =
+          M.StandardCXXModule || IsPrebuiltHeaderUnit;
+      Record.push_back(IsCXX20ModuleOrPrebuiltHeaderUnit);
+
+      if (IsCXX20ModuleOrPrebuiltHeaderUnit) {
         Record.push_back(0);
         Record.push_back(0);
         Record.push_back(0);
diff --git a/clang/lib/Serialization/GlobalModuleIndex.cpp b/clang/lib/Serialization/GlobalModuleIndex.cpp
index 6d0678141fa73a..7ab9a3d299f649 100644
--- a/clang/lib/Serialization/GlobalModuleIndex.cpp
+++ b/clang/lib/Serialization/GlobalModuleIndex.cpp
@@ -630,8 +630,12 @@ llvm::Error GlobalModuleIndexBuilder::loadModuleFile(FileEntryRef File) {
       // modules while here we are only dealing with cached).
       Blob = Blob.substr(Record[Idx++]);
 
-      // Skip if it is standard C++ module
-      ++Idx;
+      ++Idx; // Standard C++ module
+      bool IsCXX20ModuleOrPrebuiltHeaderUnit = Record[Idx++];
+
+      // Skip C++20 modules and prebuilt header units.
+      if (IsCXX20ModuleOrPrebuiltHeaderUnit)
+        continue;
 
       // Load stored size/modification time.
       off_t StoredSize = (off_t)Record[Idx++];
diff --git a/clang/test/Modules/cxx20-import-paths.cpp b/clang/test/Modules/cxx20-import-paths.cpp
new file mode 100644
index 00000000000000..d535d84009eba2
--- /dev/null
+++ b/clang/test/Modules/cxx20-import-paths.cpp
@@ -0,0 +1,80 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t/one %t/two
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-01 -emit-header-unit \
+// RUN:   -xc++-user-header %t/hu-01.h -o %t/one/hu-01.pcm
+// RUN: cp %t/one/hu-01.pcm %t/two/hu-01.pcm
+
+// A header unit loaded through a named mapping does not serialize its PCM path.
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-02 -emit-header-unit \
+// RUN:   -xc++-user-header %t/hu-02.h -Wno-experimental-header-units \
+// RUN:   -fmodule-map-file=%t/hu-01.map \
+// RUN:   -fmodule-file=hu-01=%t/one/hu-01.pcm -o %t/hu-02-mapped.pcm
+// RUN: cp %t/hu-02-mapped.pcm %t/hu-02-mapped-from-one.pcm
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-02 -emit-header-unit \
+// RUN:   -xc++-user-header %t/hu-02.h -Wno-experimental-header-units \
+// RUN:   -fmodule-map-file=%t/hu-01.map \
+// RUN:   -fmodule-file=hu-01=%t/two/hu-01.pcm -o %t/hu-02-mapped.pcm
+// RUN: diff %t/hu-02-mapped-from-one.pcm %t/hu-02-mapped.pcm
+
+// The dependency is loaded through its named mapping.
+// RUN: %clang_cc1 -std=c++20 -module-file-info %t/hu-02-mapped.pcm \
+// RUN:   -fmodule-file=hu-01=%t/two/hu-01.pcm \
+// RUN:   | FileCheck %s --check-prefix=HU-MAPPED
+// HU-MAPPED: Imports module 'hu-01':{{ *}}{{$}}
+
+// A pathless dependency requires a matching -fmodule-file=<name>=<pcm>.
+// RUN: not %clang_cc1 -std=c++20 -module-file-info %t/hu-02-mapped.pcm 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=MISSING
+// MISSING: error: failed to find module file for module 'hu-01'
+
+// A named mapping must reference a PCM with the expected module name.
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-other -emit-header-unit \
+// RUN:   -xc++-user-header %t/hu-01.h -o %t/hu-other.pcm
+// RUN: not %clang_cc1 -std=c++20 -module-file-info %t/hu-02-mapped.pcm \
+// RUN:   -fmodule-file=hu-01=%t/hu-other.pcm 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=MISMATCH
+// MISMATCH: error: failed to find module file for module 'hu-01'
+
+// An eagerly loaded header unit retains its PCM path even if a named mapping
+// also exists.
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-02 -emit-header-unit \
+// RUN:   -xc++-user-header %t/hu-02.h -Wno-experimental-header-units \
+// RUN:   -fmodule-map-file=%t/hu-01.map -fmodule-file=%t/one/hu-01.pcm \
+// RUN:   -fmodule-file=hu-01=%t/one/hu-01.pcm -o %t/hu-02-eager.pcm
+// RUN: %clang_cc1 -std=c++20 -module-file-info %t/hu-02-eager.pcm \
+// RUN:   -fmodule-file=hu-01=%t/one/hu-01.pcm \
+// RUN:   | FileCheck %s --check-prefix=HU-EAGER -DPREFIX=%t
+// HU-EAGER: Imports module 'hu-01': [[PREFIX]]{{/|\\}}one{{/|\\}}hu-01.pcm
+
+// The PCM paths of C++20 named module dependencies are not serialized.
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/a.cppm \
+// RUN:   -o %t/A.pcm
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/foo.cppm \
+// RUN:   -fmodule-file=A=%t/A.pcm \
+// RUN:   -o %t/Foo.pcm
+// RUN: %clang_cc1 -std=c++20 -module-file-info %t/Foo.pcm \
+// RUN:   -fmodule-file=A=%t/A.pcm \
+// RUN:   | FileCheck %s --check-prefix=NAMED-MODULE
+// NAMED-MODULE: Module name: Foo
+// NAMED-MODULE-NEXT: Imports module 'A':{{ *}}{{$}}
+
+//--- hu-01.h
+inline void f() {}
+
+//--- hu-02.h
+import "hu-01.h";
+
+//--- hu-01.map
+module "hu-01" {
+  header "hu-01.h"
+  export *
+}
+
+//--- a.cppm
+export module A;
+
+//--- foo.cppm
+export module Foo;
+import A;



More information about the cfe-commits mailing list