[clang] [Clang][Modules] Emit GMF GNU-inline bodies for inlining (PR #228962)

via cfe-commits cfe-commits at lists.llvm.org
Sun Oct 4 19:29:24 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Mathew Joseph (mathew1046)

<details>
<summary>Changes</summary>

Importing a named module before including a GNU-inline wrapper header can retain a definition owned by the global module fragment. The foreign-module body restriction then suppresses its available-external body, leaving a call that cannot be inlined and can fail to link.

Allow GNU-inline definitions owned by global module fragments through that restriction while preserving their linkage and the remaining emission checks. Add regression coverage for both declaration orders at `-O0` and `-O2`, named-module ownership, and escaping function addresses, plus a release note.

Testing:
- Built assertion-enabled Clang 24 from upstream `f4bf388b08af`.
- Confirmed the new regression fails before the fix and passes afterward.
- Ran the complete `Modules`, `CodeGen`, and `CodeGenCXX` suites locally: 4,521 passed, 8 expected failures, 3,884 unsupported, and no unexpected failures.
- Verified the original reproducer links in both declaration orders and both executables return 42.
- Verified address-taking leaves the wrapper symbols undefined in object files at `-O0` and `-O2`.
- Formatting and whitespace checks passed.

Testing used an X86-only Linux build; tests requiring unavailable targets or platform features were skipped.

Fixes #<!-- -->228800

---
Full diff: https://github.com/llvm/llvm-project/pull/228962.diff


3 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+4) 
- (modified) clang/lib/CodeGen/CodeGenModule.cpp (+2-1) 
- (added) clang/test/CodeGenCXX/gnu-inline-global-module-fragment.cppm (+75) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 7ec126a065ae5..b76114c04d67c 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -645,6 +645,10 @@ features cannot lower the translation-unit ABI level;
 
 #### Bug Fixes to C++ Support
 
+- Fixed an undefined reference to a GNU-inline function when its defining
+  header is included after importing a C++20 named module whose global module
+  fragment includes the same header. (#GH228800)
+
 - Fixed lambdas with specifiers or attributes after the capture list being
   misparsed as function declarations in direct-initialization contexts under
   `-fms-extensions` or in HLSL mode.
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index b4ebc6b6abff0..fb43a103f9c92 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -5069,7 +5069,8 @@ bool CodeGenModule::shouldEmitFunction(GlobalDecl GD) {
   // We don't import function bodies from other named module units since that
   // behavior may break ABI compatibility of the current unit.
   if (const Module *M = F->getOwningModule();
-      M && M->getTopLevelModule()->isNamedModule() &&
+      M && !(M->isGlobalModule() && F->hasAttr<GNUInlineAttr>()) &&
+      M->getTopLevelModule()->isNamedModule() &&
       getContext().getCurrentNamedModule() != M->getTopLevelModule()) {
     // There are practices to mark template member function as always-inline
     // and mark the template as extern explicit instantiation but not give
diff --git a/clang/test/CodeGenCXX/gnu-inline-global-module-fragment.cppm b/clang/test/CodeGenCXX/gnu-inline-global-module-fragment.cppm
new file mode 100644
index 0000000000000..d44cba73b78e7
--- /dev/null
+++ b/clang/test/CodeGenCXX/gnu-inline-global-module-fragment.cppm
@@ -0,0 +1,75 @@
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -emit-llvm -disable-llvm-passes -o - | FileCheck %s --check-prefixes=RAW,O0
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -DINCLUDE_FIRST %t/use.cpp -emit-llvm -disable-llvm-passes -o - | FileCheck %s --check-prefixes=RAW,O0
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -O2 %t/use.cpp -emit-llvm -disable-llvm-passes -o - | FileCheck %s --check-prefixes=RAW,O2
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -O2 -DINCLUDE_FIRST %t/use.cpp -emit-llvm -disable-llvm-passes -o - | FileCheck %s --check-prefixes=RAW,O2
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -emit-llvm -o - | FileCheck %s --check-prefix=INLINE --implicit-check-not=always_wrapper
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -DINCLUDE_FIRST %t/use.cpp -emit-llvm -o - | FileCheck %s --check-prefix=INLINE --implicit-check-not=always_wrapper
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -O2 %t/use.cpp -emit-llvm -o - | FileCheck %s --check-prefix=INLINE --implicit-check-not=always_wrapper
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -O2 -DINCLUDE_FIRST %t/use.cpp -emit-llvm -o - | FileCheck %s --check-prefix=INLINE --implicit-check-not=always_wrapper
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t %t/address.cpp -emit-llvm -o - | FileCheck %s --check-prefix=ADDRESS
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -DINCLUDE_FIRST %t/address.cpp -emit-llvm -o - | FileCheck %s --check-prefix=ADDRESS
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -O2 %t/address.cpp -emit-llvm -o - | FileCheck %s --check-prefix=ADDRESS
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fprebuilt-module-path=%t -O2 -DINCLUDE_FIRST %t/address.cpp -emit-llvm -o - | FileCheck %s --check-prefix=ADDRESS
+
+// RAW-DAG: define available_externally{{.*}} @always_wrapper(
+// RAW-DAG: attributes #{{[0-9]+}} = { alwaysinline
+// RAW-DAG: declare{{.*}} @{{.*}}named_wrapper{{.*}}(
+// O0-DAG: declare{{.*}} @plain_wrapper(
+// O2-DAG: define available_externally{{.*}} @plain_wrapper(
+// INLINE-LABEL: define{{.*}} @test_always(
+// INLINE: call{{.*}} @real_fn(
+// ADDRESS-LABEL: define{{.*}} @address_always(
+// ADDRESS: ret ptr @always_wrapper
+// ADDRESS-LABEL: define{{.*}} @address_plain(
+// ADDRESS: ret ptr @plain_wrapper
+
+//--- wrappers.h
+extern "C" {
+long real_fn(long);
+
+extern inline __attribute__((gnu_inline, always_inline))
+long always_wrapper(long value) { return real_fn(value); }
+
+extern inline __attribute__((gnu_inline))
+long plain_wrapper(long value) { return real_fn(value); }
+}
+
+//--- M.cppm
+module;
+#include "wrappers.h"
+export module M;
+
+export extern inline __attribute__((gnu_inline, always_inline))
+long named_wrapper(long value) { return value + 1; }
+
+//--- use.cpp
+#ifdef INCLUDE_FIRST
+#include "wrappers.h"
+import M;
+#else
+import M;
+#include "wrappers.h"
+#endif
+
+extern "C" long test_always() { return always_wrapper(41); }
+extern "C" long test_plain() { return plain_wrapper(42); }
+extern "C" long test_named() { return named_wrapper(43); }
+
+//--- address.cpp
+#ifdef INCLUDE_FIRST
+#include "wrappers.h"
+import M;
+#else
+import M;
+#include "wrappers.h"
+#endif
+
+extern "C" auto address_always() -> long (*)(long) {
+	return &always_wrapper;
+}
+
+extern "C" auto address_plain() -> long (*)(long) {
+	return &plain_wrapper;
+}
\ No newline at end of file

``````````

</details>


https://github.com/llvm/llvm-project/pull/228962


More information about the cfe-commits mailing list