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

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

>From 0354510b724d7a58e2b424f4079f0ec85154453b Mon Sep 17 00:00:00 2001
From: Mathew Joseph <[email protected]>
Date: Mon, 5 Oct 2026 04:53:21 +0530
Subject: [PATCH] [Clang][Modules] Emit GMF GNU-inline bodies for inlining

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

Allow GNU-inline definitions owned by global module fragments through
that restriction without changing their linkage or the remaining
emission checks. Cover both declaration orders at O0 and O2, named-
module ownership, and escaping function addresses.

Fixes #228800
Assisted-by: GitHub Copilot
---
 clang/docs/ReleaseNotes.md                    |  4 +
 clang/lib/CodeGen/CodeGenModule.cpp           |  3 +-
 .../gnu-inline-global-module-fragment.cppm    | 75 +++++++++++++++++++
 3 files changed, 81 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CodeGenCXX/gnu-inline-global-module-fragment.cppm

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

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to