Skip to content

Commit a9e13ad

Browse files
committed
[clang][DebugInfo] Expand detection of structured bindings to account for std::get free function
When we generate the debug-info for a `VarDecl` we try to determine whether it was introduced as part of a structure binding (aka a "holding var"). If it was, we don't mark it as `artificial`. The heuristic to determine a holding var uses `IgnoreUnlessSpelledInSource` to unwrap the `VarDecl` initializer until we hit a `DeclRefExpr` that refers to a `Decomposition`. For "tuple-like decompositions", Clang will generate a call to a `template<size_t I> Foo get(Bar)` function that retrieves the `Ith` element from the tuple-like structure. If that function is a member function, we get an AST that looks as follows: ``` VarDecl implicit used z1 'std::tuple_element<0, B>::type &&' cinit `-ExprWithCleanups <col:10> 'int' xvalue `-MaterializeTemporaryExpr <col:10> 'int' xvalue extended by Var 0x11d110cf8 'z1' 'std::tuple_element<0, B>::type &&' `-CXXMemberCallExpr <col:10> 'int' `-MemberExpr <col:10> '<bound member function type>' .get 0x11d104390 `-ImplicitCastExpr <col:10> 'B' xvalue <NoOp> `-DeclRefExpr <col:10> 'B' lvalue Decomposition 0x11d1100a8 '' 'B' ``` `IgnoreUnlessSpelledInSource` happily unwraps this down to the `DeclRefExpr`. However, when the `get` helper is a free function (which it is for `std::pair` in libc++ for example), then the AST is: ``` VarDecl col:16 implicit used k 'std::tuple_element<0, const std::tuple<int, int>>::type &' cinit `-CallExpr <col:16> 'const typename tuple_element<0UL, tuple<int, int>>::type':'const int' lvalue adl |-ImplicitCastExpr <col:16> 'const typename tuple_element<0UL, tuple<int, int>>::type &(*)(const tuple<int, int> &) noexcept' <FunctionToPointerDecay> | `-DeclRefExpr <col:16> 'const typename tuple_element<0UL, tuple<int, int>>::type &(const tuple<int, int> &) noexcept' lvalue Function 0x1210262d8 'get' 'const typename tuple_element<0UL, tuple<int, int>>::type &(const tuple<int, int> &) noexcept' (FunctionTemplate 0x11d068088 'get') `-DeclRefExpr <col:16> 'const std::tuple<int, int>' lvalue Decomposition 0x121021518 '' 'const std::tuple<int, int> &' ``` `IgnoreUnlessSpelledInSource` doesn't unwrap this `CallExpr`, so we incorrectly mark the binding as `artificial` in debug-info. This patch adjusts our heuristic to account for `CallExpr` nodes. Fixes #122028
1 parent 3576f05 commit a9e13ad

File tree

4 files changed

+140
-7
lines changed

4 files changed

+140
-7
lines changed

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,38 @@ static bool IsDecomposedVarDecl(VarDecl const *VD) {
8787
if (!Init)
8888
return false;
8989

90+
Init = Init->IgnoreUnlessSpelledInSource();
91+
if (!Init)
92+
return false;
93+
94+
// For tuple-like decompositions, if the `get` function
95+
// is not a member of the decomposed type, but instead a
96+
// free function (e.g., how std::get is specialized for
97+
// std::pair), then the initializer is a `CallExpr` and
98+
// we need to dig into the argument before unwrapping it
99+
// with IgnoreUnlessSpelledInSource.
100+
if (auto const *CE = llvm::dyn_cast<CallExpr>(Init)) {
101+
if (CE->getNumArgs() == 0)
102+
return false;
103+
104+
// The first argument will be the type we're decomposing.
105+
// Technically the getter could have more than 1 argument
106+
// (e.g., if they're defaulted arguments), but we don't care
107+
// about them because we just need to check whether the
108+
// first argument is a DecompositionDecl.
109+
auto const *Arg = CE->getArg(0);
110+
if (!Arg)
111+
return false;
112+
113+
Init = Arg;
114+
}
115+
90116
auto const *RefExpr =
91117
llvm::dyn_cast_or_null<DeclRefExpr>(Init->IgnoreUnlessSpelledInSource());
92118
if (!RefExpr)
93119
return false;
94120

95-
return llvm::dyn_cast_or_null<DecompositionDecl>(RefExpr->getDecl());
121+
return llvm::isa_and_nonnull<DecompositionDecl>(RefExpr->getDecl());
96122
}
97123

98124
/// Returns true if \ref VD is a compiler-generated variable

clang/test/DebugInfo/CXX/structured-binding.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: %clang_cc1 -emit-llvm -debug-info-kind=standalone -triple %itanium_abi_triple %s -o - | FileCheck %s --implicit-check-not="call void @llvm.dbg.declare"
22

3+
// CHECK: define noundef i32 @_Z1fv
34
// CHECK: #dbg_declare(ptr %{{[a-z]+}}, ![[VAR_0:[0-9]+]], !DIExpression(),
45
// CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_1:[0-9]+]], !DIExpression(),
56
// CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_2:[0-9]+]], !DIExpression(DW_OP_plus_uconst, 4),
@@ -13,13 +14,19 @@
1314
// CHECK: getelementptr inbounds [2 x i32], ptr {{.*}}, i{{64|32}} 0, i{{64|32}} 1, !dbg ![[A2_DEBUG_LOC:[0-9]+]]
1415
// CHECK: getelementptr inbounds nuw { i32, i32 }, ptr {{.*}}, i32 0, i32 1, !dbg ![[C2_DEBUG_LOC:[0-9]+]]
1516
// CHECK: extractelement <2 x i32> {{.*}}, i32 1, !dbg ![[V2_DEBUG_LOC:[0-9]+]]
17+
// CHECK: #dbg_declare(ptr %k, ![[VAR_7:[0-9]+]], !DIExpression()
18+
// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
19+
// CHECK: #dbg_declare(ptr %w, ![[VAR_9:[0-9]+]], !DIExpression()
1620
// CHECK: ![[VAR_0]] = !DILocalVariable(name: "a"
1721
// CHECK: ![[VAR_1]] = !DILocalVariable(name: "x1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
1822
// CHECK: ![[VAR_2]] = !DILocalVariable(name: "y1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
1923
// CHECK: ![[VAR_3]] = !DILocalVariable(name: "x2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
2024
// CHECK: ![[VAR_4]] = !DILocalVariable(name: "y2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
2125
// CHECK: ![[VAR_5]] = !DILocalVariable(name: "z1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
2226
// CHECK: ![[VAR_6]] = !DILocalVariable(name: "z2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
27+
// CHECK: ![[VAR_7]] = !DILocalVariable(name: "k", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
28+
// CHECK: ![[VAR_8]] = !DILocalVariable(name: "v", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
29+
// CHECK: ![[VAR_9]] = !DILocalVariable(name: "w", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
2330

2431
struct A {
2532
int x;
@@ -45,6 +52,24 @@ struct tuple_size<B> {
4552
};
4653

4754
template<unsigned, typename T> struct tuple_element { using type = int; };
55+
56+
// Decomposition of tuple-like bindings but where the `get` methods
57+
// are declared as free functions.
58+
struct triple {
59+
int k;
60+
int v;
61+
int w;
62+
};
63+
64+
template<>
65+
struct tuple_size<triple> {
66+
static constexpr unsigned value = 3;
67+
};
68+
69+
template <unsigned I> int get(triple);
70+
template <> int get<0>(triple p) { return p.k; }
71+
template <> int get<1>(triple p) { return p.v; }
72+
template <> int get<2>(triple p) { return p.w; }
4873
} // namespace std
4974

5075
int f() {
@@ -89,4 +114,6 @@ int f() {
89114
// CHECK: ![[V2_DEBUG_LOC]] = !DILocation(line: [[@LINE+1]]
90115
v2 //
91116
;
117+
auto [k, v, w] = std::triple{3, 4, 5};
118+
return x1 + y1 + x2 + y2 + z1 + z2 + k + v + w;
92119
}

lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,14 @@ def test(self):
9898
self.expect_expr("tx2", result_value="4")
9999
self.expect_expr("ty2", result_value="'z'")
100100
self.expect_expr("tz2", result_value="10")
101+
102+
self.expect("frame variable",
103+
substrs=[
104+
"tx1 =",
105+
"ty1 =",
106+
"tz1 =",
107+
"tx2 =",
108+
"ty2 =",
109+
"tz2 =",
110+
"mp1 =",
111+
"mp2 ="])

lldb/test/API/lang/cpp/structured-binding/main.cpp

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,80 @@
11
// Structured binding in C++ can bind identifiers to subobjects of an object.
22
//
3-
// There are three cases we need to test:
3+
// There are four cases we need to test:
44
// 1) arrays
5-
// 2) tuples like objects
6-
// 3) non-static data members
5+
// 2) tuple-like objects with `get` member functions
6+
// 3) tuple-like objects with `get` free functions
7+
// 4) non-static data members
78
//
89
// They can also bind by copy, reference or rvalue reference.
910

10-
#include <tuple>
11+
struct MyPair {
12+
int m1;
13+
int m2;
14+
15+
// Helpers to enable tuple-like decomposition.
16+
template <unsigned> int get();
17+
template <> int get<0>() { return m1; }
18+
template <> int get<1>() { return m2; }
19+
};
20+
21+
namespace std {
22+
template <typename T1, typename T2, typename T3> struct mock_tuple {
23+
T1 m1;
24+
T2 m2;
25+
T3 m3;
26+
};
27+
28+
template <typename T> struct tuple_size;
29+
30+
template <unsigned, typename T> struct tuple_element;
31+
32+
// Helpers to enable tuple-like decomposition for MyPair
33+
template <unsigned I> struct tuple_element<I, MyPair> {
34+
using type = int;
35+
};
36+
37+
template <> struct tuple_size<MyPair> {
38+
static constexpr unsigned value = 2;
39+
};
40+
41+
// Helpers to enable tuple-like decomposition for mock_tuple
42+
template <typename T1, typename T2, typename T3>
43+
struct tuple_element<0, mock_tuple<T1, T2, T3>> {
44+
using type = T1;
45+
};
46+
47+
template <typename T1, typename T2, typename T3>
48+
struct tuple_element<1, mock_tuple<T1, T2, T3>> {
49+
using type = T2;
50+
};
51+
52+
template <typename T1, typename T2, typename T3>
53+
struct tuple_element<2, mock_tuple<T1, T2, T3>> {
54+
using type = T3;
55+
};
56+
57+
template <typename T1, typename T2, typename T3>
58+
struct tuple_size<mock_tuple<T1, T2, T3>> {
59+
static constexpr unsigned value = 3;
60+
};
61+
62+
template <unsigned I, typename T1, typename T2, typename T3>
63+
typename tuple_element<I, mock_tuple<T1, T2, T3>>::type
64+
get(mock_tuple<T1, T2, T3> p) {
65+
switch (I) {
66+
case 0:
67+
return p.m1;
68+
case 1:
69+
return p.m2;
70+
case 2:
71+
return p.m3;
72+
default:
73+
__builtin_trap();
74+
}
75+
}
76+
77+
} // namespace std
1178

1279
struct A {
1380
int x;
@@ -54,16 +121,18 @@ int main() {
54121
char y{'z'};
55122
int z{10};
56123

57-
std::tuple<float, char, int> tpl(x, y, z);
124+
std::mock_tuple<float, char, int> tpl{.m1 = x, .m2 = y, .m3 = z};
58125
auto [tx1, ty1, tz1] = tpl;
59126
auto &[tx2, ty2, tz2] = tpl;
60127

128+
auto [mp1, mp2] = MyPair{.m1 = 1, .m2 = 2};
129+
61130
return a1.x + b1 + c1 + d1 + e1 + f1 + a2.y + b2 + c2 + d2 + e2 + f2 + a3.x +
62131
b3 + c3 + d3 + e3 + f3 + carr_copy1 + carr_copy2 + carr_copy3 +
63132
sarr_copy1 + sarr_copy2 + sarr_copy3 + iarr_copy1 + iarr_copy2 +
64133
iarr_copy3 + carr_ref1 + carr_ref2 + carr_ref3 + sarr_ref1 +
65134
sarr_ref2 + sarr_ref3 + iarr_ref1 + iarr_ref2 + iarr_ref3 +
66135
carr_rref1 + carr_rref2 + carr_rref3 + sarr_rref1 + sarr_rref2 +
67136
sarr_rref3 + iarr_rref1 + iarr_rref2 + iarr_rref3 + tx1 + ty1 + tz1 +
68-
tx2 + ty2 + tz2; // break here
137+
tx2 + ty2 + tz2 + mp1 + mp2; // break here
69138
}

0 commit comments

Comments
 (0)