Skip to content

Commit 478c205

Browse files
authored
[SYCL] Fix Lambda Mangling in Namespace-Scope Variable Initializers. (#20176)
The community counterpart to this PR can be found here: llvm/llvm-project#159115. In that PR, we propose a fix for the lambda mangling issue in namespace-scope variable initializers. This fix has uncovered a number of related problems. @tahonermann has identified several scenarios where the lambda mangling is either incorrect or diverges from `GCC`’s behavior. While the current patch addresses a high-priority issue flagged in our downstream compiler, it requires additional time and effort to thoroughly address the full range of scenarios—some of which are partially listed in llvm/llvm-project#159115. The current change successfully fixes the issue affecting our downstream compiler. Therefore, we propose to “cherry-pick” the community PR now, while continuing work in the community repository to complete the fix. For convenience here is the description of llvm/llvm-project#159115: This PR addresses an issue with the mangling of lambdas used as initializers for global variables within namespaces. According to the Itanium C++ ABI, lambdas should be uniquely mangled based on their context. GCC correctly includes the name of the declared variable in the mangling context for such lambdas, avoiding the need for discriminators since each lambda is scoped to its respective variable (see https://godbolt.org/z/38Y8qvvj3). When C++ code is compiled without CUDA, HIP, or SYCL enabled, lambdas that don't require external linkage are given internal linkage and mangled with a $ in their name. When CUDA, HIP, or SYCL support is enabled, separate compilation for host and device requires that the same mangled names are observed, even for symbols that have internal linkage. Code to use external mangling was already present, but the mangled names for lambdas were incorrectly generated in some cases: Lambdas in the initializers of global variables used the enclosing namespace as the mangling context rather than the global variable. When such global variables were declared in different partial namespace definitions, discriminators were allocated in the context of a specific partial namespace definition rather than in the primary namespace definition leading to mangled name clashes, some of which provoked an error, some of which resulted in an additional .<n> disambiguator being silently added to the symbol. This PR ensures that lambdas used as global variable initializers are mangled in the context of the declared variable and that a canonical namespace is used for allocation of mangling discriminators.
1 parent 8bfe4e0 commit 478c205

File tree

6 files changed

+163
-4
lines changed

6 files changed

+163
-4
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9183,7 +9183,13 @@ class Sema final : public SemaBase {
91839183
};
91849184

91859185
/// Compute the mangling number context for a lambda expression or
9186-
/// block literal. Also return the extra mangling decl if any.
9186+
/// block literal that appears in the specified declaration context in
9187+
/// consideration of the current expression evaluation and template
9188+
/// instantiation contexts. If the mangling context requires external linkage,
9189+
/// then a mangling number context is returned in the first tuple
9190+
/// element. If the mangling context is non-normal (specialized for
9191+
/// lambda and block types relative to other entities), the overriding
9192+
/// declaration is returned in the second tuple element.
91879193
///
91889194
/// \param DC - The DeclContext containing the lambda expression or
91899195
/// block literal.

clang/lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13356,6 +13356,7 @@ MangleNumberingContext &
1335613356
ASTContext::getManglingNumberContext(const DeclContext *DC) {
1335713357
assert(LangOpts.CPlusPlus); // We don't need mangling numbers for plain C.
1335813358
std::unique_ptr<MangleNumberingContext> &MCtx = MangleNumberingContexts[DC];
13359+
DC = DC->getPrimaryContext();
1335913360
if (!MCtx)
1336013361
MCtx = createMangleNumberingContext();
1336113362
return *MCtx;

clang/lib/Sema/SemaLambda.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,14 @@ Sema::getCurrentMangleNumberContext(const DeclContext *DC) {
356356
return std::make_tuple(&Context.getManglingNumberContext(DC), nullptr);
357357
}
358358

359-
return std::make_tuple(nullptr, nullptr);
359+
if (ManglingContextDecl) {
360+
// Lambdas defined in the initializer of a local variable are mangled
361+
// in the enclosing function context.
362+
if (auto *VD = dyn_cast<VarDecl>(ManglingContextDecl);
363+
VD && !VD->hasGlobalStorage())
364+
ManglingContextDecl = nullptr;
365+
}
366+
return std::make_tuple(nullptr, ManglingContextDecl);
360367
}
361368

362369
case NonInlineInModulePurview:
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// This test checks that lambdas assigned to variables (including inline and
2+
// templated cases) in the same namespace are uniquely mangled and callable via
3+
// template functions. It ensures that the compiler generates distinct symbols
4+
// for each lambda and resolves them correctly in function calls.
5+
6+
// RUN: %clang_cc1 -O0 -triple x86_64-unknown-unknown \
7+
// RUN: -emit-llvm %s -o - | FileCheck %s
8+
9+
// RUN: %clang_cc1 -O0 -triple x86_64-pc-windows-msvc \
10+
// RUN: -emit-llvm %s -o - | FileCheck %s --check-prefix=MSVC
11+
12+
namespace QL {
13+
auto dg1 = [] { return 1; };
14+
inline auto dg_inline1 = [] { return 1; };
15+
}
16+
17+
namespace QL {
18+
auto dg2 = [] { return 2; };
19+
template<int N>
20+
auto dg_template = [] { return N; };
21+
}
22+
23+
using namespace QL;
24+
template<typename T>
25+
void f(T t) {
26+
t();
27+
}
28+
29+
void g() {
30+
f(dg1);
31+
f(dg2);
32+
f(dg_inline1);
33+
f(dg_template<3>);
34+
}
35+
36+
// CHECK: @_ZN2QL3dg1E = internal global %class.anon undef, align 1
37+
// CHECK: @_ZN2QL3dg2E = internal global %class.anon.0 undef, align 1
38+
// CHECK: @_ZN2QL10dg_inline1E = linkonce_odr global %class.anon.2 undef, comdat, align 1
39+
// CHECK: @_ZN2QL11dg_templateILi3EEE = linkonce_odr global %class.anon.4 undef, comdat, align 1
40+
41+
// MSVC: @"?dg1@QL@@3V<lambda_0>@1@A" = internal global %class.anon undef, align 1
42+
// MSVC: @"?dg2@QL@@3V<lambda_1>@1@A" = internal global %class.anon.0 undef, align 1
43+
// MSVC: @"?dg_inline1@QL@@3V<lambda_1>@01@A" = linkonce_odr dso_local global %class.anon.2 undef, comdat, align 1
44+
// MSVC: @"??$dg_template@$02@QL@@3V<lambda_1>@01@A" = linkonce_odr dso_local global %class.anon.4 undef, comdat, align 1
45+
46+
47+
// CHECK: define internal void @"_Z1fIN2QL3$_0EEvT_"
48+
// CHECK: call noundef i32 @"_ZNK2QL3$_0clEv"
49+
// CHECK: define internal void @"_Z1fIN2QL3$_1EEvT_"
50+
// CHECK: define linkonce_odr void @_Z1fIN2QL10dg_inline1MUlvE_EEvT_
51+
// CHECK: call noundef i32 @_ZNK2QL10dg_inline1MUlvE_clEv
52+
// CHECK: define linkonce_odr void @_Z1fIN2QL11dg_templateILi3EEMUlvE_EEvT_
53+
// CHECK: call noundef i32 @_ZNK2QL11dg_templateILi3EEMUlvE_clEv
54+
// CHECK: define internal noundef i32 @"_ZNK2QL3$_0clEv"
55+
// CHECK: define internal noundef i32 @"_ZNK2QL3$_1clEv"
56+
// CHECK: define linkonce_odr noundef i32 @_ZNK2QL10dg_inline1MUlvE_clEv
57+
// CHECK: define linkonce_odr noundef i32 @_ZNK2QL11dg_templateILi3EEMUlvE_clEv
58+
59+
// MSVC: define linkonce_odr dso_local void @"??$f@V<lambda_1>@dg_inline1@QL@@@@YAXV<lambda_1>@dg_inline1@QL@@@Z"
60+
// MSVC: call noundef i32 @"??R<lambda_1>@dg_inline1@QL@@QEBA?A?<auto>@@XZ"
61+
// MSVC: define linkonce_odr dso_local void @"??$f@V<lambda_1>@?$dg_template@$02@QL@@@@YAXV<lambda_1>@?$dg_template@$02@QL@@@Z"
62+
// MSVC: call noundef i32 @"??R<lambda_1>@?$dg_template@$02@QL@@QEBA?A?<auto>@@XZ"
63+
// MSVC: define internal noundef i32 @"??R<lambda_0>@QL@@QEBA?A?<auto>@@XZ"
64+
// MSVC: define internal noundef i32 @"??R<lambda_1>@QL@@QEBA?A?<auto>@@XZ"
65+
// MSVC: define linkonce_odr dso_local noundef i32 @"??R<lambda_1>@dg_inline1@QL@@QEBA?A?<auto>@@XZ"
66+
// MSVC: define linkonce_odr dso_local noundef i32 @"??R<lambda_1>@?$dg_template@$02@QL@@QEBA?A?<auto>@@XZ"

clang/test/CodeGenCUDA/anon-ns.cu

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626

2727
// HIP-DAG: define weak_odr {{.*}}void @[[KERN:_ZN12_GLOBAL__N_16kernelEv\.intern\.b04fd23c98500190]](
2828
// HIP-DAG: define weak_odr {{.*}}void @[[KTX:_Z2ktIN12_GLOBAL__N_11XEEvT_\.intern\.b04fd23c98500190]](
29-
// HIP-DAG: define weak_odr {{.*}}void @[[KTL:_Z2ktIN12_GLOBAL__N_1UlvE_EEvT_\.intern\.b04fd23c98500190]](
29+
// HIP-DAG: define weak_odr {{.*}}void @[[KTL:_Z2ktIN12_GLOBAL__N_16lambdaMUlvE_EEvT_.intern\.b04fd23c98500190]](
3030
// HIP-DAG: @[[VM:_ZN12_GLOBAL__N_12vmE\.static\.b04fd23c98500190]] = addrspace(1) externally_initialized global
3131
// HIP-DAG: @[[VC:_ZN12_GLOBAL__N_12vcE\.static\.b04fd23c98500190]] = addrspace(4) externally_initialized constant
3232
// HIP-DAG: @[[VT:_Z2vtIN12_GLOBAL__N_11XEE\.static\.b04fd23c98500190]] = addrspace(1) externally_initialized global
3333

3434
// CUDA-DAG: define weak_odr {{.*}}void @[[KERN:_ZN12_GLOBAL__N_16kernelEv__intern__b04fd23c98500190]](
3535
// CUDA-DAG: define weak_odr {{.*}}void @[[KTX:_Z2ktIN12_GLOBAL__N_11XEEvT___intern__b04fd23c98500190]](
36-
// CUDA-DAG: define weak_odr {{.*}}void @[[KTL:_Z2ktIN12_GLOBAL__N_1UlvE_EEvT___intern__b04fd23c98500190]](
36+
// CUDA-DAG: define weak_odr {{.*}}void @[[KTL:_Z2ktIN12_GLOBAL__N_16lambdaMUlvE_EEvT___intern__b04fd23c98500190]](
3737
// CUDA-DAG: @[[VC:_ZN12_GLOBAL__N_12vcE__static__b04fd23c98500190]] = addrspace(4) externally_initialized constant
3838
// CUDA-DAG: @[[VT:_Z2vtIN12_GLOBAL__N_11XEE__static__b04fd23c98500190]] = addrspace(1) externally_initialized global
3939

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// This test checks that lambdas assigned to variables (including inline and
2+
// templated cases) in the same namespace are uniquely mangled and callable via
3+
// template functions. It ensures that the compiler generates distinct symbols
4+
// for each lambda and resolves them correctly in function calls.
5+
6+
// RUN: %clang_cc1 -fsycl-is-device -O0 -triple spirv64-unknown-unknown \
7+
// RUN: -emit-llvm %s -o - | FileCheck %s --check-prefix=DEVICE
8+
9+
// RUN: %clang_cc1 -fsycl-is-host -O0 -triple spirv64-unknown-unknown \
10+
// RUN: -emit-llvm %s -o - | FileCheck %s --check-prefix=HOST
11+
12+
// RUN: %clang_cc1 -fsycl-is-device -emit-llvm \
13+
// RUN: -aux-triple x86_64-pc-windows-msvc -triple spir64-unknown--unknown \
14+
// RUN: %s -o - | FileCheck %s --check-prefix=MSVC
15+
16+
namespace QL {
17+
auto dg1 = [] { return 1; };
18+
inline auto dg_inline1 = [] { return 1; };
19+
}
20+
21+
namespace QL {
22+
auto dg2 = [] { return 2; };
23+
template<int N>
24+
auto dg_template = [] { return N; };
25+
}
26+
27+
using namespace QL;
28+
template<typename T>
29+
[[clang::sycl_kernel_entry_point(T)]] void f(T t) {
30+
t();
31+
}
32+
33+
void g() {
34+
f(dg1);
35+
f(dg2);
36+
f(dg_inline1);
37+
f(dg_template<3>);
38+
}
39+
40+
// HOST: @_ZN2QL3dg1E = internal global %class.anon undef, align 1
41+
// HOST: @_ZN2QL3dg2E = internal global %class.anon.0 undef, align 1
42+
// HOST: @_ZN2QL10dg_inline1E = linkonce_odr global %class.anon.2 undef, comdat, align 1
43+
// HOST: @_ZN2QL11dg_templateILi3EEE = linkonce_odr global %class.anon.4 undef, comdat, align 1
44+
45+
// DEVICE: define spir_kernel void @_ZTSN2QL3dg1MUlvE_E
46+
// DEVICE: call spir_func noundef i32 @_ZNK2QL3dg1MUlvE_clEv
47+
// DEVICE: define internal spir_func noundef i32 @_ZNK2QL3dg1MUlvE_clEv
48+
// DEVICE: define spir_kernel void @_ZTSN2QL3dg2MUlvE_E
49+
// DEVICE: call spir_func noundef i32 @_ZNK2QL3dg2MUlvE_clEv
50+
// DEVICE: define internal spir_func noundef i32 @_ZNK2QL3dg2MUlvE_clEv
51+
// DEVICE: define spir_kernel void @_ZTSN2QL10dg_inline1MUlvE_E
52+
// DEVICE: call spir_func noundef i32 @_ZNK2QL10dg_inline1MUlvE_clEv
53+
// DEVICE: define linkonce_odr spir_func noundef i32 @_ZNK2QL10dg_inline1MUlvE_clEv
54+
// DEVICE: define spir_kernel void @_ZTSN2QL11dg_templateILi3EEMUlvE_E
55+
// DEVICE: call spir_func noundef i32 @_ZNK2QL11dg_templateILi3EEMUlvE_clEv
56+
// DEVICE: define linkonce_odr spir_func noundef i32 @_ZNK2QL11dg_templateILi3EEMUlvE_clEv
57+
58+
// HOST: define spir_func void @_Z1gv
59+
// HOST: call spir_func void @_Z1fIN2QL3dg1MUlvE_EEvT_
60+
// HOST: call spir_func void @_Z1fIN2QL3dg2MUlvE_EEvT_
61+
// HOST: call spir_func void @_Z1fIN2QL10dg_inline1MUlvE_EEvT_
62+
// HOST: call spir_func void @_Z1fIN2QL11dg_templateILi3EEMUlvE_EEvT_
63+
// HOST: define internal spir_func void @_Z1fIN2QL3dg1MUlvE_EEvT
64+
// HOST: define internal spir_func void @_Z1fIN2QL3dg2MUlvE_EEvT_
65+
// HOST: define linkonce_odr spir_func void @_Z1fIN2QL10dg_inline1MUlvE_EEvT_
66+
// HOST: define linkonce_odr spir_func void @_Z1fIN2QL11dg_templateILi3EEMUlvE_EEvT_
67+
68+
// MSVC: define dso_local spir_kernel void @_ZTSN2QL3dg1MUlvE_E
69+
// MSVC: call spir_func noundef i32 @_ZNK2QL3dg1MUlvE_clEv
70+
// MSVC: define internal spir_func noundef i32 @_ZNK2QL3dg1MUlvE_clEv
71+
// MSVC: define dso_local spir_kernel void @_ZTSN2QL3dg2MUlvE_E
72+
// MSVC: call spir_func noundef i32 @_ZNK2QL3dg2MUlvE_clEv
73+
// MSVC: define internal spir_func noundef i32 @_ZNK2QL3dg2MUlvE_clEv
74+
// MSVC: define dso_local spir_kernel void @_ZTSN2QL10dg_inline1MUlvE_E
75+
// MSVC: call spir_func noundef i32 @_ZNK2QL10dg_inline1MUlvE_clEv
76+
// MSVC: define linkonce_odr spir_func noundef i32 @_ZNK2QL10dg_inline1MUlvE_clEv
77+
// MSVC: define dso_local spir_kernel void @_ZTSN2QL11dg_templateILi3EEMUlvE_E
78+
// MSVC: call spir_func noundef i32 @_ZNK2QL11dg_templateILi3EEMUlvE_clEv
79+
// MSVC: define linkonce_odr spir_func noundef i32 @_ZNK2QL11dg_templateILi3EEMUlvE_clEv

0 commit comments

Comments
 (0)