https://github.com/yuxuanchen1997 updated 
https://github.com/llvm/llvm-project/pull/223877

>From e91a3b7a6dfc2c7f032cd3d37534a246af9fd58e Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <[email protected]>
Date: Tue, 15 Sep 2026 16:10:37 -0700
Subject: [PATCH] [clang][CodeGen] Skip re-emission of already-defined globals
 in EmitDeferred

When a deferred decl was already emitted and its existing definition has a 
different LLVM type than the requested one (e.g. an inline variable whose 
initializer folded to an anonymous union type), EmitDeferred called 
GetAddrOfGlobal with ForDefinition, which replaced and erased the existing 
definition. Erasing the defined global dangles pointers held by callers, e.g. 
the AssociatedData of entries in llvm.global_ctors, leading to a use-after-free 
during EmitCtorList.

Check for an existing definition of the same entity before requesting a 
correctly typed value, and skip the re-emission. This keeps 
GetOrCreateLLVMGlobal's invariant that a ForDefinition request returns a global 
of exactly the requested type.

Add the OpenMP non-modules reproducer as a regression test alongside the 
header-unit test, and assert single emission via the position of the guard 
variable in both.
---
 clang/lib/CodeGen/CodeGenModule.cpp           | 29 +++++++++++-----
 ...header-unit-inline-var-double-emission.cpp | 34 +++++++++++++++++++
 ...ine-var-declare-target-double-emission.cpp | 27 +++++++++++++++
 3 files changed, 81 insertions(+), 9 deletions(-)
 create mode 100644 
clang/test/CodeGenCXX/header-unit-inline-var-double-emission.cpp
 create mode 100644 
clang/test/OpenMP/inline-var-declare-target-double-emission.cpp

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index bce2106a74988..2d8892de40bf1 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4232,28 +4232,39 @@ void CodeGenModule::EmitDeferred() {
       }
     }
 
+    // Check to see if we've already emitted this. This is necessary for a
+    // couple of reasons: first, decls can end up in the deferred-decls queue
+    // multiple times, and second, decls can end up with definitions in unusual
+    // ways (e.g. by an extern inline function acquiring a strong function
+    // redefinition). Just ignore these cases. Do this before requesting a
+    // correctly typed value for the definition, since that operation may
+    // replace an existing global whose LLVM type differs.
+    StringRef MangledName = getMangledName(D);
+    llvm::GlobalValue *GV = GetGlobalValue(MangledName);
+    if (GV && !GV->isDeclaration()) {
+      GlobalDecl ExistingGD;
+      if (lookupRepresentativeDecl(MangledName, ExistingGD) &&
+          D.getCanonicalDecl().getDecl() ==
+              ExistingGD.getCanonicalDecl().getDecl())
+        continue;
+    }
+
     // We should call GetAddrOfGlobal with IsForDefinition set to true in order
     // to get GlobalValue with exactly the type we need, not something that
     // might had been created for another decl with the same mangled name but
     // different type.
-    llvm::GlobalValue *GV = dyn_cast<llvm::GlobalValue>(
-        GetAddrOfGlobal(D, ForDefinition));
+    GV = dyn_cast<llvm::GlobalValue>(GetAddrOfGlobal(D, ForDefinition));
 
     // In case of different address spaces, we may still get a cast, even with
     // IsForDefinition equal to true. Query mangled names table to get
     // GlobalValue.
     if (!GV)
-      GV = GetGlobalValue(getMangledName(D));
+      GV = GetGlobalValue(MangledName);
 
     // Make sure GetGlobalValue returned non-null.
     assert(GV);
 
-    // Check to see if we've already emitted this.  This is necessary
-    // for a couple of reasons: first, decls can end up in the
-    // deferred-decls queue multiple times, and second, decls can end
-    // up with definitions in unusual ways (e.g. by an extern inline
-    // function acquiring a strong function redefinition).  Just
-    // ignore these cases.
+    // GetAddrOfGlobal may itself cause the declaration to be emitted.
     if (!GV->isDeclaration())
       continue;
 
diff --git a/clang/test/CodeGenCXX/header-unit-inline-var-double-emission.cpp 
b/clang/test/CodeGenCXX/header-unit-inline-var-double-emission.cpp
new file mode 100644
index 0000000000000..7e5cb1d1096cd
--- /dev/null
+++ b/clang/test/CodeGenCXX/header-unit-inline-var-double-emission.cpp
@@ -0,0 +1,34 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 -triple %itanium_abi_triple -xc++-user-header 
-emit-header-unit %t/a.h -o %t/a.pcm
+// RUN: %clang_cc1 -std=c++20 -triple %itanium_abi_triple -xc++-user-header 
-emit-header-unit -fmodule-file=%t/a.pcm %t/b.h -o %t/b.pcm
+// RUN: %clang_cc1 -std=c++20 -triple %itanium_abi_triple 
-fmodule-file=%t/a.pcm -fmodule-file=%t/b.pcm %t/tu.cpp \
+// RUN:   -emit-llvm -disable-llvm-passes -o - | FileCheck %t/tu.cpp
+
+//--- a.h
+struct P {
+  constexpr P() : x(0) {}
+  ~P();
+  union {
+    int x;
+    long y;
+  };
+};
+inline P S;
+
+//--- b.h
+import "a.h";
+inline const int *PTR = &S.x;
+
+//--- tu.cpp
+import "a.h";
+import "b.h";
+
+// CHECK: @S = linkonce_odr{{.*}} global { { i32, [4 x i8] } }
+// S must be emitted before its guard variable: a second emission erases and
+// re-creates @S, moving it after the surviving @_ZGV1S.
+// CHECK-NEXT: @_ZGV1S = linkonce_odr global i64 0
+// CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ 
i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @S }]
+// CHECK: define internal void @__cxx_global_var_init()
+// CHECK-NOT: define {{.*}} @__cxx_global_var_init
diff --git a/clang/test/OpenMP/inline-var-declare-target-double-emission.cpp 
b/clang/test/OpenMP/inline-var-declare-target-double-emission.cpp
new file mode 100644
index 0000000000000..cea6d7f9b364f
--- /dev/null
+++ b/clang/test/OpenMP/inline-var-declare-target-double-emission.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -std=c++20 -triple %itanium_abi_triple -fopenmp \
+// RUN:   -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+struct P {
+  constexpr P() : x(0) {}
+  ~P();
+
+  union {
+    int x;
+    long y;
+  };
+};
+
+inline P S;
+#pragma omp declare target(S)
+
+inline const int *PTR = &S.x;
+int use() { return S.x + *PTR; }
+
+// CHECK: @S = linkonce_odr{{.*}} global { { i32, [4 x i8] } }
+// S must be emitted before its guard variable: a second emission erases and
+// re-creates @S, moving it after the surviving @_ZGV1S.
+// CHECK-NEXT: @_ZGV1S = linkonce_odr global i64 0
+// CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }]
+// CHECK-SAME: ptr @__cxx_global_var_init, ptr @S
+// CHECK: define internal void @__cxx_global_var_init()
+// CHECK-NOT: define {{.*}} @__cxx_global_var_init

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

Reply via email to