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
