Skip to content

Conversation

@Michael137
Copy link
Member

@Michael137 Michael137 commented Jan 9, 2025

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 then 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 IgnoreUnlessSpelledInSource so it unwraps implicit CallExprs. It's almost identical to how we treat implicit constructor calls (unfortunately the code can't quite be re-used because a CXXConstructExpr is-not a CallExpr, and we check isElidable, which doesn't exist for regular function calls. So I added a new IgnoreImplicitCallSingleStep).

Fixes #122028

@llvmbot llvmbot added clang Clang issues not falling into any other category lldb clang:codegen IR generation bugs: mangling, exceptions, etc. debuginfo labels Jan 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 9, 2025

@llvm/pr-subscribers-lldb

@llvm/pr-subscribers-clang-codegen

Author: Michael Buch (Michael137)

Changes

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 then 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&lt;size_t I&gt; 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&lt;0, B&gt;::type &amp;&amp;' cinit
`-ExprWithCleanups &lt;col:10&gt; 'int' xvalue
  `-MaterializeTemporaryExpr &lt;col:10&gt; 'int' xvalue extended by Var 0x11d110cf8 'z1' 'std::tuple_element&lt;0, B&gt;::type &amp;&amp;'
    `-CXXMemberCallExpr &lt;col:10&gt; 'int'
      `-MemberExpr &lt;col:10&gt; '&lt;bound member function type&gt;' .get 0x11d104390
        `-ImplicitCastExpr &lt;col:10&gt; 'B' xvalue &lt;NoOp&gt;
          `-DeclRefExpr &lt;col:10&gt; '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&lt;0, const std::tuple&lt;int, int&gt;&gt;::type &amp;' cinit
`-CallExpr &lt;col:16&gt; 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type':'const int' lvalue adl
  |-ImplicitCastExpr &lt;col:16&gt; 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type &amp;(*)(const tuple&lt;int, int&gt; &amp;) noexcept' &lt;FunctionToPointerDecay&gt;
  | `-DeclRefExpr &lt;col:16&gt; 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type &amp;(const tuple&lt;int, int&gt; &amp;) noexcept' lvalue Function 0x1210262d8 'get' 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type &amp;(const tuple&lt;int, int&gt; &amp;) noexcept' (FunctionTemplate 0x11d068088 'get')
  `-DeclRefExpr &lt;col:16&gt; 'const std::tuple&lt;int, int&gt;' lvalue Decomposition 0x121021518 '' 'const std::tuple&lt;int, int&gt; &amp;'

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


Full diff: https://github.com/llvm/llvm-project/pull/122265.diff

4 Files Affected:

  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+27-1)
  • (modified) clang/test/CodeGenCXX/debug-info-structured-binding.cpp (+27-1)
  • (modified) lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py (+11)
  • (modified) lldb/test/API/lang/cpp/structured-binding/main.cpp (+75-6)
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 560d4ce293365e..e0fdde460c8758 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -85,12 +85,38 @@ static bool IsDecomposedVarDecl(VarDecl const *VD) {
   if (!Init)
     return false;
 
+  Init = Init->IgnoreUnlessSpelledInSource();
+  if (!Init)
+    return false;
+
+  // For tuple-like decompositions, if the `get` function
+  // is not a member of the decomposed type, but instead a
+  // free function (e.g., how std::get is specialized for
+  // std::pair), then the initializer is a `CallExpr` and
+  // we need to dig into the argument before unwrapping it
+  // with IgnoreUnlessSpelledInSource.
+  if (auto const *CE = llvm::dyn_cast<CallExpr>(Init)) {
+    if (CE->getNumArgs() == 0)
+      return false;
+
+    // The first argument will be the type we're decomposing.
+    // Technically the getter could have more than 1 argument
+    // (e.g., if they're defaulted arguments), but we don't care
+    // about them because we just need to check whether the
+    // first argument is a DecompositionDecl.
+    auto const *Arg = CE->getArg(0);
+    if (!Arg)
+      return false;
+
+    Init = Arg;
+  }
+
   auto const *RefExpr =
       llvm::dyn_cast_or_null<DeclRefExpr>(Init->IgnoreUnlessSpelledInSource());
   if (!RefExpr)
     return false;
 
-  return llvm::dyn_cast_or_null<DecompositionDecl>(RefExpr->getDecl());
+  return llvm::isa_and_nonnull<DecompositionDecl>(RefExpr->getDecl());
 }
 
 /// Returns true if \ref VD is a compiler-generated variable
diff --git a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp
index 5fbd54c16382c0..55a84a65015842 100644
--- a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp
+++ b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp
@@ -1,5 +1,6 @@
 // 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"
 
+// CHECK: define noundef i32 @_Z1fv
 // CHECK: #dbg_declare(ptr %{{[a-z]+}}, ![[VAR_0:[0-9]+]], !DIExpression(),
 // CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_1:[0-9]+]], !DIExpression(),
 // CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_2:[0-9]+]], !DIExpression(DW_OP_plus_uconst, 4),
@@ -7,6 +8,9 @@
 // CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_4:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4),
 // CHECK: #dbg_declare(ptr %z1, ![[VAR_5:[0-9]+]], !DIExpression()
 // CHECK: #dbg_declare(ptr %z2, ![[VAR_6:[0-9]+]], !DIExpression()
+// CHECK: #dbg_declare(ptr %k, ![[VAR_7:[0-9]+]], !DIExpression()
+// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
+// CHECK: #dbg_declare(ptr %w, ![[VAR_9:[0-9]+]], !DIExpression()
 // CHECK: ![[VAR_0]] = !DILocalVariable(name: "a"
 // CHECK: ![[VAR_1]] = !DILocalVariable(name: "x1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 // CHECK: ![[VAR_2]] = !DILocalVariable(name: "y1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
@@ -14,6 +18,9 @@
 // CHECK: ![[VAR_4]] = !DILocalVariable(name: "y2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 // CHECK: ![[VAR_5]] = !DILocalVariable(name: "z1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 // CHECK: ![[VAR_6]] = !DILocalVariable(name: "z2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
+// CHECK: ![[VAR_7]] = !DILocalVariable(name: "k", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
+// CHECK: ![[VAR_8]] = !DILocalVariable(name: "v", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
+// CHECK: ![[VAR_9]] = !DILocalVariable(name: "w", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 
 struct A {
   int x;
@@ -39,6 +46,24 @@ struct tuple_size<B> {
 };
 
 template<unsigned, typename T> struct tuple_element { using type = int; };
+
+// Decomposition of tuple-like bindings but where the `get` methods
+// are declared as free functions.
+struct triple {
+  int k;
+  int v;
+  int w;
+};
+
+template<>
+struct tuple_size<triple> {
+    static constexpr unsigned value = 3;
+};
+
+template <unsigned I> int get(triple);
+template <> int get<0>(triple p) { return p.k; }
+template <> int get<1>(triple p) { return p.v; }
+template <> int get<2>(triple p) { return p.w; }
 } // namespace std
 
 int f() {
@@ -46,5 +71,6 @@ int f() {
   auto [x1, y1] = a;
   auto &[x2, y2] = a;
   auto [z1, z2] = B{1, 2};
-  return x1 + y1 + x2 + y2 + z1 + z2;
+  auto [k, v, w] = std::triple{3, 4, 5};
+  return x1 + y1 + x2 + y2 + z1 + z2 + k + v + w;
 }
diff --git a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py
index 4b26d9f139c01e..cfa01b30068b8f 100644
--- a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py
+++ b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py
@@ -98,3 +98,14 @@ def test(self):
         self.expect_expr("tx2", result_value="4")
         self.expect_expr("ty2", result_value="'z'")
         self.expect_expr("tz2", result_value="10")
+
+        self.expect("frame variable",
+            substrs=[
+                "tx1 =",
+                "ty1 =",
+                "tz1 =",
+                "tx2 =",
+                "ty2 =",
+                "tz2 =",
+                "mp1 =",
+                "mp2 ="])
diff --git a/lldb/test/API/lang/cpp/structured-binding/main.cpp b/lldb/test/API/lang/cpp/structured-binding/main.cpp
index 3fbfb18dbeff0c..b649358ebdf667 100644
--- a/lldb/test/API/lang/cpp/structured-binding/main.cpp
+++ b/lldb/test/API/lang/cpp/structured-binding/main.cpp
@@ -1,13 +1,80 @@
 // Structured binding in C++ can bind identifiers to subobjects of an object.
 //
-// There are three cases we need to test:
+// There are four cases we need to test:
 // 1) arrays
-// 2) tuples like objects
-// 3) non-static data members
+// 2) tuple-like objects with `get` member functions
+// 3) tuple-like objects with `get` free functions
+// 4) non-static data members
 //
 // They can also bind by copy, reference or rvalue reference.
 
-#include <tuple>
+struct MyPair {
+  int m1;
+  int m2;
+
+  // Helpers to enable tuple-like decomposition.
+  template <unsigned> int get();
+  template <> int get<0>() { return m1; }
+  template <> int get<1>() { return m2; }
+};
+
+namespace std {
+template <typename T1, typename T2, typename T3> struct mock_tuple {
+  T1 m1;
+  T2 m2;
+  T3 m3;
+};
+
+template <typename T> struct tuple_size;
+
+template <unsigned, typename T> struct tuple_element;
+
+// Helpers to enable tuple-like decomposition for MyPair
+template <unsigned I> struct tuple_element<I, MyPair> {
+  using type = int;
+};
+
+template <> struct tuple_size<MyPair> {
+  static constexpr unsigned value = 2;
+};
+
+// Helpers to enable tuple-like decomposition for mock_tuple
+template <typename T1, typename T2, typename T3>
+struct tuple_element<0, mock_tuple<T1, T2, T3>> {
+  using type = T1;
+};
+
+template <typename T1, typename T2, typename T3>
+struct tuple_element<1, mock_tuple<T1, T2, T3>> {
+  using type = T2;
+};
+
+template <typename T1, typename T2, typename T3>
+struct tuple_element<2, mock_tuple<T1, T2, T3>> {
+  using type = T3;
+};
+
+template <typename T1, typename T2, typename T3>
+struct tuple_size<mock_tuple<T1, T2, T3>> {
+  static constexpr unsigned value = 3;
+};
+
+template <unsigned I, typename T1, typename T2, typename T3>
+typename tuple_element<I, mock_tuple<T1, T2, T3>>::type
+get(mock_tuple<T1, T2, T3> p) {
+  switch (I) {
+  case 0:
+    return p.m1;
+  case 1:
+    return p.m2;
+  case 2:
+    return p.m3;
+  default:
+    __builtin_trap();
+  }
+}
+
+} // namespace std
 
 struct A {
   int x;
@@ -54,10 +121,12 @@ int main() {
   char y{'z'};
   int z{10};
 
-  std::tuple<float, char, int> tpl(x, y, z);
+  std::mock_tuple<float, char, int> tpl{.m1 = x, .m2 = y, .m3 = z};
   auto [tx1, ty1, tz1] = tpl;
   auto &[tx2, ty2, tz2] = tpl;
 
+  auto [mp1, mp2] = MyPair{.m1 = 1, .m2 = 2};
+
   return a1.x + b1 + c1 + d1 + e1 + f1 + a2.y + b2 + c2 + d2 + e2 + f2 + a3.x +
          b3 + c3 + d3 + e3 + f3 + carr_copy1 + carr_copy2 + carr_copy3 +
          sarr_copy1 + sarr_copy2 + sarr_copy3 + iarr_copy1 + iarr_copy2 +
@@ -65,5 +134,5 @@ int main() {
          sarr_ref2 + sarr_ref3 + iarr_ref1 + iarr_ref2 + iarr_ref3 +
          carr_rref1 + carr_rref2 + carr_rref3 + sarr_rref1 + sarr_rref2 +
          sarr_rref3 + iarr_rref1 + iarr_rref2 + iarr_rref3 + tx1 + ty1 + tz1 +
-         tx2 + ty2 + tz2; // break here
+         tx2 + ty2 + tz2 + mp1 + mp2; // break here
 }

@llvmbot
Copy link
Member

llvmbot commented Jan 9, 2025

@llvm/pr-subscribers-debuginfo

Author: Michael Buch (Michael137)

Changes

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 then 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&lt;size_t I&gt; 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&lt;0, B&gt;::type &amp;&amp;' cinit
`-ExprWithCleanups &lt;col:10&gt; 'int' xvalue
  `-MaterializeTemporaryExpr &lt;col:10&gt; 'int' xvalue extended by Var 0x11d110cf8 'z1' 'std::tuple_element&lt;0, B&gt;::type &amp;&amp;'
    `-CXXMemberCallExpr &lt;col:10&gt; 'int'
      `-MemberExpr &lt;col:10&gt; '&lt;bound member function type&gt;' .get 0x11d104390
        `-ImplicitCastExpr &lt;col:10&gt; 'B' xvalue &lt;NoOp&gt;
          `-DeclRefExpr &lt;col:10&gt; '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&lt;0, const std::tuple&lt;int, int&gt;&gt;::type &amp;' cinit
`-CallExpr &lt;col:16&gt; 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type':'const int' lvalue adl
  |-ImplicitCastExpr &lt;col:16&gt; 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type &amp;(*)(const tuple&lt;int, int&gt; &amp;) noexcept' &lt;FunctionToPointerDecay&gt;
  | `-DeclRefExpr &lt;col:16&gt; 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type &amp;(const tuple&lt;int, int&gt; &amp;) noexcept' lvalue Function 0x1210262d8 'get' 'const typename tuple_element&lt;0UL, tuple&lt;int, int&gt;&gt;::type &amp;(const tuple&lt;int, int&gt; &amp;) noexcept' (FunctionTemplate 0x11d068088 'get')
  `-DeclRefExpr &lt;col:16&gt; 'const std::tuple&lt;int, int&gt;' lvalue Decomposition 0x121021518 '' 'const std::tuple&lt;int, int&gt; &amp;'

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


Full diff: https://github.com/llvm/llvm-project/pull/122265.diff

4 Files Affected:

  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+27-1)
  • (modified) clang/test/CodeGenCXX/debug-info-structured-binding.cpp (+27-1)
  • (modified) lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py (+11)
  • (modified) lldb/test/API/lang/cpp/structured-binding/main.cpp (+75-6)
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 560d4ce293365e..e0fdde460c8758 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -85,12 +85,38 @@ static bool IsDecomposedVarDecl(VarDecl const *VD) {
   if (!Init)
     return false;
 
+  Init = Init->IgnoreUnlessSpelledInSource();
+  if (!Init)
+    return false;
+
+  // For tuple-like decompositions, if the `get` function
+  // is not a member of the decomposed type, but instead a
+  // free function (e.g., how std::get is specialized for
+  // std::pair), then the initializer is a `CallExpr` and
+  // we need to dig into the argument before unwrapping it
+  // with IgnoreUnlessSpelledInSource.
+  if (auto const *CE = llvm::dyn_cast<CallExpr>(Init)) {
+    if (CE->getNumArgs() == 0)
+      return false;
+
+    // The first argument will be the type we're decomposing.
+    // Technically the getter could have more than 1 argument
+    // (e.g., if they're defaulted arguments), but we don't care
+    // about them because we just need to check whether the
+    // first argument is a DecompositionDecl.
+    auto const *Arg = CE->getArg(0);
+    if (!Arg)
+      return false;
+
+    Init = Arg;
+  }
+
   auto const *RefExpr =
       llvm::dyn_cast_or_null<DeclRefExpr>(Init->IgnoreUnlessSpelledInSource());
   if (!RefExpr)
     return false;
 
-  return llvm::dyn_cast_or_null<DecompositionDecl>(RefExpr->getDecl());
+  return llvm::isa_and_nonnull<DecompositionDecl>(RefExpr->getDecl());
 }
 
 /// Returns true if \ref VD is a compiler-generated variable
diff --git a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp
index 5fbd54c16382c0..55a84a65015842 100644
--- a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp
+++ b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp
@@ -1,5 +1,6 @@
 // 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"
 
+// CHECK: define noundef i32 @_Z1fv
 // CHECK: #dbg_declare(ptr %{{[a-z]+}}, ![[VAR_0:[0-9]+]], !DIExpression(),
 // CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_1:[0-9]+]], !DIExpression(),
 // CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_2:[0-9]+]], !DIExpression(DW_OP_plus_uconst, 4),
@@ -7,6 +8,9 @@
 // CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_4:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4),
 // CHECK: #dbg_declare(ptr %z1, ![[VAR_5:[0-9]+]], !DIExpression()
 // CHECK: #dbg_declare(ptr %z2, ![[VAR_6:[0-9]+]], !DIExpression()
+// CHECK: #dbg_declare(ptr %k, ![[VAR_7:[0-9]+]], !DIExpression()
+// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
+// CHECK: #dbg_declare(ptr %w, ![[VAR_9:[0-9]+]], !DIExpression()
 // CHECK: ![[VAR_0]] = !DILocalVariable(name: "a"
 // CHECK: ![[VAR_1]] = !DILocalVariable(name: "x1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 // CHECK: ![[VAR_2]] = !DILocalVariable(name: "y1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
@@ -14,6 +18,9 @@
 // CHECK: ![[VAR_4]] = !DILocalVariable(name: "y2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 // CHECK: ![[VAR_5]] = !DILocalVariable(name: "z1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 // CHECK: ![[VAR_6]] = !DILocalVariable(name: "z2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
+// CHECK: ![[VAR_7]] = !DILocalVariable(name: "k", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
+// CHECK: ![[VAR_8]] = !DILocalVariable(name: "v", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
+// CHECK: ![[VAR_9]] = !DILocalVariable(name: "w", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
 
 struct A {
   int x;
@@ -39,6 +46,24 @@ struct tuple_size<B> {
 };
 
 template<unsigned, typename T> struct tuple_element { using type = int; };
+
+// Decomposition of tuple-like bindings but where the `get` methods
+// are declared as free functions.
+struct triple {
+  int k;
+  int v;
+  int w;
+};
+
+template<>
+struct tuple_size<triple> {
+    static constexpr unsigned value = 3;
+};
+
+template <unsigned I> int get(triple);
+template <> int get<0>(triple p) { return p.k; }
+template <> int get<1>(triple p) { return p.v; }
+template <> int get<2>(triple p) { return p.w; }
 } // namespace std
 
 int f() {
@@ -46,5 +71,6 @@ int f() {
   auto [x1, y1] = a;
   auto &[x2, y2] = a;
   auto [z1, z2] = B{1, 2};
-  return x1 + y1 + x2 + y2 + z1 + z2;
+  auto [k, v, w] = std::triple{3, 4, 5};
+  return x1 + y1 + x2 + y2 + z1 + z2 + k + v + w;
 }
diff --git a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py
index 4b26d9f139c01e..cfa01b30068b8f 100644
--- a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py
+++ b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py
@@ -98,3 +98,14 @@ def test(self):
         self.expect_expr("tx2", result_value="4")
         self.expect_expr("ty2", result_value="'z'")
         self.expect_expr("tz2", result_value="10")
+
+        self.expect("frame variable",
+            substrs=[
+                "tx1 =",
+                "ty1 =",
+                "tz1 =",
+                "tx2 =",
+                "ty2 =",
+                "tz2 =",
+                "mp1 =",
+                "mp2 ="])
diff --git a/lldb/test/API/lang/cpp/structured-binding/main.cpp b/lldb/test/API/lang/cpp/structured-binding/main.cpp
index 3fbfb18dbeff0c..b649358ebdf667 100644
--- a/lldb/test/API/lang/cpp/structured-binding/main.cpp
+++ b/lldb/test/API/lang/cpp/structured-binding/main.cpp
@@ -1,13 +1,80 @@
 // Structured binding in C++ can bind identifiers to subobjects of an object.
 //
-// There are three cases we need to test:
+// There are four cases we need to test:
 // 1) arrays
-// 2) tuples like objects
-// 3) non-static data members
+// 2) tuple-like objects with `get` member functions
+// 3) tuple-like objects with `get` free functions
+// 4) non-static data members
 //
 // They can also bind by copy, reference or rvalue reference.
 
-#include <tuple>
+struct MyPair {
+  int m1;
+  int m2;
+
+  // Helpers to enable tuple-like decomposition.
+  template <unsigned> int get();
+  template <> int get<0>() { return m1; }
+  template <> int get<1>() { return m2; }
+};
+
+namespace std {
+template <typename T1, typename T2, typename T3> struct mock_tuple {
+  T1 m1;
+  T2 m2;
+  T3 m3;
+};
+
+template <typename T> struct tuple_size;
+
+template <unsigned, typename T> struct tuple_element;
+
+// Helpers to enable tuple-like decomposition for MyPair
+template <unsigned I> struct tuple_element<I, MyPair> {
+  using type = int;
+};
+
+template <> struct tuple_size<MyPair> {
+  static constexpr unsigned value = 2;
+};
+
+// Helpers to enable tuple-like decomposition for mock_tuple
+template <typename T1, typename T2, typename T3>
+struct tuple_element<0, mock_tuple<T1, T2, T3>> {
+  using type = T1;
+};
+
+template <typename T1, typename T2, typename T3>
+struct tuple_element<1, mock_tuple<T1, T2, T3>> {
+  using type = T2;
+};
+
+template <typename T1, typename T2, typename T3>
+struct tuple_element<2, mock_tuple<T1, T2, T3>> {
+  using type = T3;
+};
+
+template <typename T1, typename T2, typename T3>
+struct tuple_size<mock_tuple<T1, T2, T3>> {
+  static constexpr unsigned value = 3;
+};
+
+template <unsigned I, typename T1, typename T2, typename T3>
+typename tuple_element<I, mock_tuple<T1, T2, T3>>::type
+get(mock_tuple<T1, T2, T3> p) {
+  switch (I) {
+  case 0:
+    return p.m1;
+  case 1:
+    return p.m2;
+  case 2:
+    return p.m3;
+  default:
+    __builtin_trap();
+  }
+}
+
+} // namespace std
 
 struct A {
   int x;
@@ -54,10 +121,12 @@ int main() {
   char y{'z'};
   int z{10};
 
-  std::tuple<float, char, int> tpl(x, y, z);
+  std::mock_tuple<float, char, int> tpl{.m1 = x, .m2 = y, .m3 = z};
   auto [tx1, ty1, tz1] = tpl;
   auto &[tx2, ty2, tz2] = tpl;
 
+  auto [mp1, mp2] = MyPair{.m1 = 1, .m2 = 2};
+
   return a1.x + b1 + c1 + d1 + e1 + f1 + a2.y + b2 + c2 + d2 + e2 + f2 + a3.x +
          b3 + c3 + d3 + e3 + f3 + carr_copy1 + carr_copy2 + carr_copy3 +
          sarr_copy1 + sarr_copy2 + sarr_copy3 + iarr_copy1 + iarr_copy2 +
@@ -65,5 +134,5 @@ int main() {
          sarr_ref2 + sarr_ref3 + iarr_ref1 + iarr_ref2 + iarr_ref3 +
          carr_rref1 + carr_rref2 + carr_rref3 + sarr_rref1 + sarr_rref2 +
          sarr_rref3 + iarr_rref1 + iarr_rref2 + iarr_rref3 + tx1 + ty1 + tz1 +
-         tx2 + ty2 + tz2; // break here
+         tx2 + ty2 + tz2 + mp1 + mp2; // break here
 }

@Michael137
Copy link
Member Author

CC'd @AaronBallman and @cor3ntin to sanity check my assumptions about structured bindings here (and how IgnoreUnlessSpelledInSource is supposed to behave).

//
// They can also bind by copy, reference or rvalue reference.

#include <tuple>
Copy link
Member Author

@Michael137 Michael137 Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in favour of mock_tuple so we can better control what kind of tuple-like decomposition we're testing here.

@github-actions
Copy link

github-actions bot commented Jan 9, 2025

✅ With the latest revision this PR passed the Python code formatter.

@cor3ntin
Copy link
Contributor

cor3ntin commented Jan 9, 2025

Did you try to modify IgnoreUnlessSpelledInSource to support CallExpr?

@Michael137
Copy link
Member Author

Did you try to modify IgnoreUnlessSpelledInSource to support CallExpr?

Yea I was thinking about it initially. But I wasn't sure what that would look like tbh. IIUC IgnoreImplicitMemberCallSingleStep will unwrap CXXMemberCallExpr into the underlying MemberExpr. And then further until we hit the implicit object argument? But what would we do with CallExprs? Do we return the CalleeDecl? In our use-case, the Decomposition decl is the first argument of the CallExpr.

@zwuis
Copy link
Contributor

zwuis commented Jan 10, 2025

Do we need a test case about explicit object member function get? i.e.

template<unsigned> int get(this triple);
template<> int get<0>(this triple t) { return /* ... */; }

@cor3ntin
Copy link
Contributor

Did you try to modify IgnoreUnlessSpelledInSource to support CallExpr?

Yea I was thinking about it initially. But I wasn't sure what that would look like tbh. IIUC IgnoreImplicitMemberCallSingleStep will unwrap CXXMemberCallExpr into the underlying MemberExpr. And then further until we hit the implicit object argument? But what would we do with CallExprs? Do we return the CalleeDecl? In our use-case, the Decomposition decl is the first argument of the CallExpr.

same thing we do for constructor ?

if (auto *C = dyn_cast<CXXConstructExpr>(E)) {
      auto NumArgs = C->getNumArgs();
      if (NumArgs == 1 ||
          (NumArgs > 1 && isa<CXXDefaultArgExpr>(C->getArg(1)))) {
        Expr *A = C->getArg(0);
        if (A->getSourceRange() == E->getSourceRange() || C->isElidable())
          return A;
      }
    }
    return E;

Note that I think it would only be useful to get (afaik) - but it would be in the same place, which seems desirable

@Michael137
Copy link
Member Author

Did you try to modify IgnoreUnlessSpelledInSource to support CallExpr?

Yea I was thinking about it initially. But I wasn't sure what that would look like tbh. IIUC IgnoreImplicitMemberCallSingleStep will unwrap CXXMemberCallExpr into the underlying MemberExpr. And then further until we hit the implicit object argument? But what would we do with CallExprs? Do we return the CalleeDecl? In our use-case, the Decomposition decl is the first argument of the CallExpr.

same thing we do for constructor ?

if (auto *C = dyn_cast<CXXConstructExpr>(E)) {
      auto NumArgs = C->getNumArgs();
      if (NumArgs == 1 ||
          (NumArgs > 1 && isa<CXXDefaultArgExpr>(C->getArg(1)))) {
        Expr *A = C->getArg(0);
        if (A->getSourceRange() == E->getSourceRange() || C->isElidable())
          return A;
      }
    }
    return E;

Note that I think it would only be useful to get (afaik) - but it would be in the same place, which seems desirable

Hah good point! That's pretty much exactly the heuristic we want. Let me try this

Do we need a test case about explicit object member function get? i.e.

Good question. I think it's worth having one

@Michael137 Michael137 force-pushed the clang/decomposed-var-free-function-get branch from 15c529b to 60cfb2c Compare January 13, 2025 13:03
@llvmbot llvmbot added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Jan 13, 2025
@Michael137
Copy link
Member Author

Did you try to modify IgnoreUnlessSpelledInSource to support CallExpr?

Updated to use this approach in latest commit. Seems to work well (including the explicit object parameter case). Didn't find a great way to test this in the clang AST unit-tests yet. Trying to understand how the other IgnoreXXX codepaths are tested.

@Michael137 Michael137 changed the title [clang][DebugInfo] Expand detection of structured bindings to account for std::get free function [clang][Expr] Teach IgnoreUnlessSpelledInSource about implicit calls to std::get free function Jan 13, 2025
@github-actions
Copy link

github-actions bot commented Jan 13, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@Michael137
Copy link
Member Author

Added tests and updated PR description.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo comment request.
Thanks

Comment on lines 3186 to 3177

auto IgnoreImplicitCallSingleStep = [](Expr *E) {
if (auto *C = dyn_cast<CallExpr>(E)) {
auto NumArgs = C->getNumArgs();
if (NumArgs == 1 ||
(NumArgs > 1 && isa<CXXDefaultArgExpr>(C->getArg(1)))) {
Expr *A = C->getArg(0);
if (A->getSourceRange() == E->getSourceRange())
return A;
}
}
return E;
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment explaining it's used for std::get/structured binding?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@Michael137
Copy link
Member Author

Hmm there's a clang-tidy test failing because this codepath gets hit for UserDefinedLiteral, which is a CallExpr with a single argument:

(lldb) p E->dump()
UserDefinedLiteral 0x15402b738 'unsigned long long'
|-ImplicitCastExpr 0x15402b720 'unsigned long long (*)(unsigned long long)' <FunctionToPointerDecay>
| `-DeclRefExpr 0x15402b6c8 'unsigned long long (unsigned long long)' lvalue Function 0x154010278 'operator""_ull' 'unsigned long long (unsigned long long)'
`-IntegerLiteral 0x15402b6a8 'unsigned long long' 1
(lldb) p E->getSourceRange()
(clang::SourceRange) {
  B = (ID = 99)
  E = (ID = 99)
}
(lldb) p A->getSourceRange()
(clang::SourceRange) {
  B = (ID = 99)
  E = (ID = 99)
}

Adding a special case for UserDefinedLiteral doesn't seem like the right thing to do. Trying to wrap my head around what clang-tidy is doing here

… 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 llvm#122028
@Michael137 Michael137 force-pushed the clang/decomposed-var-free-function-get branch from 0fdd5b9 to 549d18c Compare September 3, 2025 13:19
@cor3ntin
Copy link
Contributor

cor3ntin commented Sep 3, 2025

I'm still happy with this. I did not notice it was not merged

@Michael137
Copy link
Member Author

Michael137 commented Sep 3, 2025

I'm still happy with this. I did not notice it was not merged

Yea I was just clearing out my open PRs and noticed this was still open. There is still that clang-tidy failure I haven't figured out (#122265 (comment)). Might need a more targeted heuristic for expressions coming from binding decls.

@Michael137
Copy link
Member Author

Latest commit narrows the heuristics so it specifically applies to CallExprs generated for structured bindings. Here's what the AST looks like when stopped in IgnoreImplicitCallSingleStep for a structured binding:

(lldb) p E->dump()                                                                                                                                                                   
CallExpr 0x912dd6cb8 'int' adl                                                                                                                                                       
|-ImplicitCastExpr 0x912dd6ca0 'int (*)(triple)' <FunctionToPointerDecay>                                                                                                            
| `-DeclRefExpr 0x912dd6be8 'int (triple)' lvalue Function 0x912dbdb98 'get' 'int (triple)' (FunctionTemplate 0x912dbd990 'get')                                                     
`-CXXConstructExpr 0x912dd80e0 'triple' 'void (triple &&) noexcept'                                                                                                                  
  `-ImplicitCastExpr 0x912dd64b0 'std::triple' xvalue <NoOp>                                                                                                                         
    `-DeclRefExpr 0x912dd6490 'std::triple' lvalue Decomposition 0x912dd4f68 first_binding 'k' 'std::triple'                                                                         

(lldb) p A->dump()                                                                                                                                                                   
CXXConstructExpr 0x912dd80e0 'triple' 'void (triple &&) noexcept'                                                                                                                    
`-ImplicitCastExpr 0x912dd64b0 'std::triple' xvalue <NoOp>                                                                                                                           
  `-DeclRefExpr 0x912dd6490 'std::triple' lvalue Decomposition 0x912dd4f68 first_binding 'k' 'std::triple'                                                                           

What my latest change does is try and get to that DeclRefExpr 0x912dd6490 'std::triple' lvalue Decomposition node. And only for those cases ignore the CallExpr.

Let me know what you think @cor3ntin @AaronBallman. There might be a better way of doing this. Particularly, I wasn't sure how to unwrap the DeclRefExpr, so I just used IgnoreUnlessSpelledInSource again (recursively).

@Michael137
Copy link
Member Author

gentle ping

@Michael137
Copy link
Member Author

any objections to latest version @cor3ntin ?

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as few nits (sorry for the delay and thanks for the pings!)

/// for structured bindings.
bool IsDecompositionDeclRefExpr(const Expr *E) {
const Expr *Unrwapped = E->IgnoreUnlessSpelledInSource();
const DeclRefExpr *Ref = llvm::dyn_cast_or_null<DeclRefExpr>(Unrwapped);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const DeclRefExpr *Ref = llvm::dyn_cast_or_null<DeclRefExpr>(Unrwapped);
const auto *Ref = dyn_cast_or_null<DeclRefExpr>(Unrwapped);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think E can be null, so Unwrapped probably cannot be null either, it should just be dyn_cast

if (!Ref)
return false;

return llvm::isa_and_nonnull<DecompositionDecl>(Ref->getDecl());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return llvm::isa_and_nonnull<DecompositionDecl>(Ref->getDecl());
return isa_and_nonnull<DecompositionDecl>(Ref->getDecl());

/// a DecompositionDecl. Used to skip Clang-generated calls to std::get
/// for structured bindings.
bool IsDecompositionDeclRefExpr(const Expr *E) {
const Expr *Unrwapped = E->IgnoreUnlessSpelledInSource();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const Expr *Unrwapped = E->IgnoreUnlessSpelledInSource();
const Expr *Unwrapped = E->IgnoreUnlessSpelledInSource();

/// Helper to determine wether \c E is a CXXConstructExpr constructing
/// a DecompositionDecl. Used to skip Clang-generated calls to std::get
/// for structured bindings.
bool IsDecompositionDeclRefExpr(const Expr *E) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use static rather than an anonymous namespace here

@Michael137
Copy link
Member Author

Just as few nits (sorry for the delay and thanks for the pings!)

No worries! Thanks for the review

@Michael137 Michael137 merged commit 012680f into llvm:main Sep 20, 2025
9 checks passed
@Michael137 Michael137 deleted the clang/decomposed-var-free-function-get branch September 20, 2025 17:30
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 20, 2025

LLVM Buildbot has detected a new failure on builder sanitizer-aarch64-linux running on sanitizer-buildbot8 while building clang,lldb at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/51/builds/23865

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
[182/186] Generating MSAN_INST_TEST_OBJECTS.msan_test.cpp.aarch64-with-call.o
[183/186] Generating Msan-aarch64-with-call-Test
[184/186] Generating MSAN_INST_TEST_OBJECTS.msan_test.cpp.aarch64.o
[185/186] Generating Msan-aarch64-Test
[185/186] Running compiler_rt regression tests
llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/llvm/utils/lit/lit/discovery.py:276: warning: input '/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/interception/Unit' contained no tests
llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/llvm/utils/lit/lit/discovery.py:276: warning: input '/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/sanitizer_common/Unit' contained no tests
llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/llvm/utils/lit/lit/main.py:74: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 5972 tests, 72 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90.
FAIL: libFuzzer-aarch64-libcxx-Linux :: reduce_inputs.test (5747 of 5972)
******************** TEST 'libFuzzer-aarch64-libcxx-Linux :: reduce_inputs.test' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
rm -rf /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C # RUN: at line 3
+ rm -rf /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
mkdir -p /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C # RUN: at line 4
+ mkdir -p /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
/home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang    -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer  -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta  /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowSimpleTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest # RUN: at line 5
+ /home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowSimpleTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest
/home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang    -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer  -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta  /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowTest # RUN: at line 6
+ /home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowTest
/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest  -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60   -runs=1000000 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C 2>&1 | FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test # RUN: at line 7
+ /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60 -runs=1000000 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
+ FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test
/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest -runs=0 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C 2>&1 | FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test --check-prefix=COUNT # RUN: at line 11
+ /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest -runs=0 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
+ FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test --check-prefix=COUNT
/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test:12:8: error: COUNT: expected string not found in input
COUNT: seed corpus: files: 4
       ^
<stdin>:1:1: note: scanning from here
INFO: Running with entropic power schedule (0xFF, 100).
^
<stdin>:7:7: note: possible intended match here
INFO: seed corpus: files: 3 min: 2b max: 3b total: 7b rss: 31Mb
      ^

Input file: <stdin>
Check file: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test

-dump-input=help explains the following input dump.

Input was:
<<<<<<
            1: INFO: Running with entropic power schedule (0xFF, 100). 
check:12'0     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
Step 14 (test compiler-rt default) failure: test compiler-rt default (failure)
...
[182/186] Generating MSAN_INST_TEST_OBJECTS.msan_test.cpp.aarch64-with-call.o
[183/186] Generating Msan-aarch64-with-call-Test
[184/186] Generating MSAN_INST_TEST_OBJECTS.msan_test.cpp.aarch64.o
[185/186] Generating Msan-aarch64-Test
[185/186] Running compiler_rt regression tests
llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/llvm/utils/lit/lit/discovery.py:276: warning: input '/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/interception/Unit' contained no tests
llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/llvm/utils/lit/lit/discovery.py:276: warning: input '/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/sanitizer_common/Unit' contained no tests
llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/llvm/utils/lit/lit/main.py:74: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 5972 tests, 72 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90.
FAIL: libFuzzer-aarch64-libcxx-Linux :: reduce_inputs.test (5747 of 5972)
******************** TEST 'libFuzzer-aarch64-libcxx-Linux :: reduce_inputs.test' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
rm -rf /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C # RUN: at line 3
+ rm -rf /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
mkdir -p /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C # RUN: at line 4
+ mkdir -p /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
/home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang    -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer  -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta  /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowSimpleTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest # RUN: at line 5
+ /home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowSimpleTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest
/home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang    -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer  -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta  /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowTest # RUN: at line 6
+ /home/b/sanitizer-aarch64-linux/build/build_default/./bin/clang -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta --driver-mode=g++ -O2 -gline-tables-only -fsanitize=address,fuzzer -I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/lib/fuzzer -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/ShrinkControlFlowTest.cpp -o /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowTest
/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest  -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60   -runs=1000000 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C 2>&1 | FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test # RUN: at line 7
+ /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest -exit_on_item=0eb8e4ed029b774d80f2b66408203801cb982a60 -runs=1000000 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
+ FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test
/home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest -runs=0 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C 2>&1 | FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test --check-prefix=COUNT # RUN: at line 11
+ /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp-ShrinkControlFlowSimpleTest -runs=0 /home/b/sanitizer-aarch64-linux/build/build_default/runtimes/runtimes-bins/compiler-rt/test/fuzzer/AARCH64LibcxxLinuxConfig/Output/reduce_inputs.test.tmp/C
+ FileCheck /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test --check-prefix=COUNT
/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test:12:8: error: COUNT: expected string not found in input
COUNT: seed corpus: files: 4
       ^
<stdin>:1:1: note: scanning from here
INFO: Running with entropic power schedule (0xFF, 100).
^
<stdin>:7:7: note: possible intended match here
INFO: seed corpus: files: 3 min: 2b max: 3b total: 7b rss: 31Mb
      ^

Input file: <stdin>
Check file: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/fuzzer/reduce_inputs.test

-dump-input=help explains the following input dump.

Input was:
<<<<<<
            1: INFO: Running with entropic power schedule (0xFF, 100). 
check:12'0     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found

@mgorny
Copy link
Member

mgorny commented Sep 23, 2025

I'm afraid I'm seeing a test regression with this change (confirmed via bisect), on Gentoo amd64:

FAIL: Clang :: DebugInfo/CXX/structured-binding.cpp (11221 of 22437)
******************** TEST 'Clang :: DebugInfo/CXX/structured-binding.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/clang -cc1 -internal-isystem /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/../../../../lib/clang/22/include -nostdsysteminc -std=c++23 -emit-llvm -debug-info-kind=standalone -triple i686-pc-linux-gnu /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp -o - | /usr/lib/llvm/22/bin/FileCheck /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp --implicit-check-not="call void @llvm.dbg.declare" # RUN: at line 1
+ /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/clang -cc1 -internal-isystem /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/../../../../lib/clang/22/include -nostdsysteminc -std=c++23 -emit-llvm -debug-info-kind=standalone -triple i686-pc-linux-gnu /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp -o -
+ /usr/lib/llvm/22/bin/FileCheck /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp '--implicit-check-not=call void @llvm.dbg.declare'
/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp:12:11: error: CHECK: expected string not found in input
// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
          ^
<stdin>:152:44: note: scanning from here
 #dbg_declare(ptr %k, !133, !DIExpression(), !139)
                                           ^
<stdin>:163:2: note: possible intended match here
 #dbg_declare(ptr %v6, !140, !DIExpression(), !145)
 ^

Input file: <stdin>
Check file: /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
            .
            .
            .
          147:  store <2 x i32> <i32 1, i32 2>, ptr %vctr, align 8, !dbg !126 
          148:  #dbg_declare(ptr %5, !127, !DIExpression(DW_OP_deref), !128) 
          149:  #dbg_declare(ptr %5, !129, !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4), !130) 
          150:  store ptr %vctr, ptr %5, align 4, !dbg !131 
          151:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %6, ptr align 4 @__const._Z1fv..1, i32 12, i1 false), !dbg !132 
          152:  #dbg_declare(ptr %k, !133, !DIExpression(), !139) 
check:12'0                                                X~~~~~~~ error: no match found
          153:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp, ptr align 4 %6, i32 12, i1 false), !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          154:  %k4 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 0, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          155:  %9 = load i32, ptr %k4, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          156:  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 1, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          157:  %10 = load i32, ptr %v, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          158:  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 2, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          159:  %11 = load i32, ptr %w, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          160:  %call5 = call noundef i32 @_ZSt3getILj0EEiSt6triple(i32 %9, i32 %10, i32 %11), !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          161:  store i32 %call5, ptr %ref.tmp3, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          162:  store ptr %ref.tmp3, ptr %k, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          163:  #dbg_declare(ptr %v6, !140, !DIExpression(), !145) 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
check:12'1      ?                                                   possible intended match
          164:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp8, ptr align 4 %6, i32 12, i1 false), !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          165:  %k9 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 0, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          166:  %12 = load i32, ptr %k9, align 4, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          167:  %v10 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 1, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          168:  %13 = load i32, ptr %v10, align 4, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            .
            .
            .
>>>>>>

--

********************

@Michael137
Copy link
Member Author

I'm afraid I'm seeing a test regression with this change (confirmed via bisect), on Gentoo amd64:

FAIL: Clang :: DebugInfo/CXX/structured-binding.cpp (11221 of 22437)
******************** TEST 'Clang :: DebugInfo/CXX/structured-binding.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/clang -cc1 -internal-isystem /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/../../../../lib/clang/22/include -nostdsysteminc -std=c++23 -emit-llvm -debug-info-kind=standalone -triple i686-pc-linux-gnu /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp -o - | /usr/lib/llvm/22/bin/FileCheck /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp --implicit-check-not="call void @llvm.dbg.declare" # RUN: at line 1
+ /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/clang -cc1 -internal-isystem /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/../../../../lib/clang/22/include -nostdsysteminc -std=c++23 -emit-llvm -debug-info-kind=standalone -triple i686-pc-linux-gnu /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp -o -
+ /usr/lib/llvm/22/bin/FileCheck /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp '--implicit-check-not=call void @llvm.dbg.declare'
/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp:12:11: error: CHECK: expected string not found in input
// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
          ^
<stdin>:152:44: note: scanning from here
 #dbg_declare(ptr %k, !133, !DIExpression(), !139)
                                           ^
<stdin>:163:2: note: possible intended match here
 #dbg_declare(ptr %v6, !140, !DIExpression(), !145)
 ^

Input file: <stdin>
Check file: /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
            .
            .
            .
          147:  store <2 x i32> <i32 1, i32 2>, ptr %vctr, align 8, !dbg !126 
          148:  #dbg_declare(ptr %5, !127, !DIExpression(DW_OP_deref), !128) 
          149:  #dbg_declare(ptr %5, !129, !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4), !130) 
          150:  store ptr %vctr, ptr %5, align 4, !dbg !131 
          151:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %6, ptr align 4 @__const._Z1fv..1, i32 12, i1 false), !dbg !132 
          152:  #dbg_declare(ptr %k, !133, !DIExpression(), !139) 
check:12'0                                                X~~~~~~~ error: no match found
          153:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp, ptr align 4 %6, i32 12, i1 false), !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          154:  %k4 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 0, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          155:  %9 = load i32, ptr %k4, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          156:  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 1, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          157:  %10 = load i32, ptr %v, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          158:  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 2, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          159:  %11 = load i32, ptr %w, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          160:  %call5 = call noundef i32 @_ZSt3getILj0EEiSt6triple(i32 %9, i32 %10, i32 %11), !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          161:  store i32 %call5, ptr %ref.tmp3, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          162:  store ptr %ref.tmp3, ptr %k, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          163:  #dbg_declare(ptr %v6, !140, !DIExpression(), !145) 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
check:12'1      ?                                                   possible intended match
          164:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp8, ptr align 4 %6, i32 12, i1 false), !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          165:  %k9 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 0, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          166:  %12 = load i32, ptr %k9, align 4, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          167:  %v10 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 1, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          168:  %13 = load i32, ptr %v10, align 4, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            .
            .
            .
>>>>>>

--

********************

Thanks for reporting. Hmm looks like on this target the IR variable names just differ slightly. We expect v but got v6. Should be easy enough to adjust

@Michael137
Copy link
Member Author

@mgorny Any chance you have access to the full IR when compiling it with the clang on that bot?

@mgorny
Copy link
Member

mgorny commented Sep 23, 2025

Sure:

; ModuleID = '/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp'
source_filename = "/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686-pc-linux-gnu"

%struct.A = type { i32, i32 }
%struct.B = type { i32, i32 }
%"struct.std::triple" = type { i32, i32, i32 }
%struct.C = type { i32, i32 }
%struct.D = type { i32, i32 }

$_ZN1B3getILi0EEEiv = comdat any

$_ZN1B3getILi1EEEiv = comdat any

$_ZNH1C3getILi0EEEiOS_ = comdat any

$_ZNH1C3getILi1EEEiOS_ = comdat any

$_ZN1D3getILi0EEEii = comdat any

$_ZN1D3getILi1EEEii = comdat any

@__const._Z1fv.a = private unnamed_addr constant %struct.A { i32 10, i32 20 }, align 4
@__const._Z1fv. = private unnamed_addr constant %struct.B { i32 1, i32 2 }, align 4
@__const._Z1fv.array = private unnamed_addr constant [2 x i32] [i32 3, i32 4], align 4
@__const._Z1fv..1 = private unnamed_addr constant %"struct.std::triple" { i32 3, i32 4, i32 5 }, align 4
@__const._Z1fv..2 = private unnamed_addr constant %struct.C { i32 2, i32 3 }, align 4
@__const._Z1fv..3 = private unnamed_addr constant %struct.D { i32 2, i32 3 }, align 4

; Function Attrs: mustprogress noinline nounwind optnone
define dso_local noundef i32 @_ZSt3getILj0EEiSt6triple(i32 %p.0, i32 %p.1, i32 %p.2) #0 !dbg !46 {
entry:
  %p = alloca %"struct.std::triple", align 4
  %k = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 0
  store i32 %p.0, ptr %k, align 4
  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 1
  store i32 %p.1, ptr %v, align 4
  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 2
  store i32 %p.2, ptr %w, align 4
    #dbg_declare(ptr %p, !53, !DIExpression(), !54)
  %k1 = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 0, !dbg !55
  %0 = load i32, ptr %k1, align 4, !dbg !55
  ret i32 %0, !dbg !56
}

; Function Attrs: mustprogress noinline nounwind optnone
define dso_local noundef i32 @_ZSt3getILj1EEiSt6triple(i32 %p.0, i32 %p.1, i32 %p.2) #0 !dbg !57 {
entry:
  %p = alloca %"struct.std::triple", align 4
  %k = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 0
  store i32 %p.0, ptr %k, align 4
  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 1
  store i32 %p.1, ptr %v, align 4
  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 2
  store i32 %p.2, ptr %w, align 4
    #dbg_declare(ptr %p, !60, !DIExpression(), !61)
  %v1 = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 1, !dbg !62
  %0 = load i32, ptr %v1, align 4, !dbg !62
  ret i32 %0, !dbg !63
}

; Function Attrs: mustprogress noinline nounwind optnone
define dso_local noundef i32 @_ZSt3getILj2EEiSt6triple(i32 %p.0, i32 %p.1, i32 %p.2) #0 !dbg !64 {
entry:
  %p = alloca %"struct.std::triple", align 4
  %k = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 0
  store i32 %p.0, ptr %k, align 4
  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 1
  store i32 %p.1, ptr %v, align 4
  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 2
  store i32 %p.2, ptr %w, align 4
    #dbg_declare(ptr %p, !67, !DIExpression(), !68)
  %w1 = getelementptr inbounds nuw %"struct.std::triple", ptr %p, i32 0, i32 2, !dbg !69
  %0 = load i32, ptr %w1, align 4, !dbg !69
  ret i32 %0, !dbg !70
}

; Function Attrs: mustprogress noinline nounwind optnone
define dso_local noundef i32 @_Z1fv() #0 !dbg !71 {
entry:
  %a = alloca %struct.A, align 4
  %0 = alloca %struct.A, align 4
  %1 = alloca ptr, align 4
  %2 = alloca %struct.B, align 4
  %z1 = alloca ptr, align 4
  %ref.tmp = alloca i32, align 4
  %z2 = alloca ptr, align 4
  %ref.tmp1 = alloca i32, align 4
  %array = alloca [2 x i32], align 4
  %3 = alloca ptr, align 4
  %cmplx = alloca { i32, i32 }, align 4
  %4 = alloca ptr, align 4
  %vctr = alloca <2 x i32>, align 8
  %5 = alloca ptr, align 4
  %6 = alloca %"struct.std::triple", align 4
  %k = alloca ptr, align 4
  %ref.tmp3 = alloca i32, align 4
  %agg.tmp = alloca %"struct.std::triple", align 4
  %v6 = alloca ptr, align 4
  %ref.tmp7 = alloca i32, align 4
  %agg.tmp8 = alloca %"struct.std::triple", align 4
  %w13 = alloca ptr, align 4
  %ref.tmp14 = alloca i32, align 4
  %agg.tmp15 = alloca %"struct.std::triple", align 4
  %7 = alloca %struct.C, align 4
  %m = alloca ptr, align 4
  %ref.tmp20 = alloca i32, align 4
  %n = alloca ptr, align 4
  %ref.tmp22 = alloca i32, align 4
  %8 = alloca %struct.D, align 4
  %s = alloca ptr, align 4
  %ref.tmp24 = alloca i32, align 4
  %p = alloca ptr, align 4
  %ref.tmp26 = alloca i32, align 4
    #dbg_declare(ptr %a, !74, !DIExpression(), !79)
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %a, ptr align 4 @__const._Z1fv.a, i32 8, i1 false), !dbg !79
    #dbg_declare(ptr %0, !80, !DIExpression(), !81)
    #dbg_declare(ptr %0, !82, !DIExpression(DW_OP_plus_uconst, 4), !83)
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %0, ptr align 4 %a, i32 8, i1 false), !dbg !84
    #dbg_declare(ptr %1, !85, !DIExpression(DW_OP_deref), !86)
    #dbg_declare(ptr %1, !87, !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4), !88)
  store ptr %a, ptr %1, align 4, !dbg !89
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %2, ptr align 4 @__const._Z1fv., i32 8, i1 false), !dbg !90
    #dbg_declare(ptr %z1, !91, !DIExpression(), !98)
  %call = call noundef i32 @_ZN1B3getILi0EEEiv(ptr noundef nonnull align 4 dereferenceable(8) %2), !dbg !90
  store i32 %call, ptr %ref.tmp, align 4, !dbg !98
  store ptr %ref.tmp, ptr %z1, align 4, !dbg !98
    #dbg_declare(ptr %z2, !99, !DIExpression(), !105)
  %call2 = call noundef i32 @_ZN1B3getILi1EEEiv(ptr noundef nonnull align 4 dereferenceable(8) %2), !dbg !90
  store i32 %call2, ptr %ref.tmp1, align 4, !dbg !105
  store ptr %ref.tmp1, ptr %z2, align 4, !dbg !105
    #dbg_declare(ptr %array, !106, !DIExpression(), !110)
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %array, ptr align 4 @__const._Z1fv.array, i32 8, i1 false), !dbg !110
    #dbg_declare(ptr %3, !111, !DIExpression(DW_OP_deref), !112)
    #dbg_declare(ptr %3, !113, !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4), !114)
  store ptr %array, ptr %3, align 4, !dbg !115
    #dbg_declare(ptr %cmplx, !116, !DIExpression(), !118)
  %cmplx.realp = getelementptr inbounds nuw { i32, i32 }, ptr %cmplx, i32 0, i32 0, !dbg !118
  %cmplx.imagp = getelementptr inbounds nuw { i32, i32 }, ptr %cmplx, i32 0, i32 1, !dbg !118
  store i32 1, ptr %cmplx.realp, align 4, !dbg !118
  store i32 2, ptr %cmplx.imagp, align 4, !dbg !118
    #dbg_declare(ptr %4, !119, !DIExpression(DW_OP_deref), !120)
    #dbg_declare(ptr %4, !121, !DIExpression(DW_OP_deref), !122)
  store ptr %cmplx, ptr %4, align 4, !dbg !123
    #dbg_declare(ptr %vctr, !124, !DIExpression(), !126)
  store <2 x i32> <i32 1, i32 2>, ptr %vctr, align 8, !dbg !126
    #dbg_declare(ptr %5, !127, !DIExpression(DW_OP_deref), !128)
    #dbg_declare(ptr %5, !129, !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4), !130)
  store ptr %vctr, ptr %5, align 4, !dbg !131
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %6, ptr align 4 @__const._Z1fv..1, i32 12, i1 false), !dbg !132
    #dbg_declare(ptr %k, !133, !DIExpression(), !139)
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp, ptr align 4 %6, i32 12, i1 false), !dbg !139
  %k4 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 0, !dbg !139
  %9 = load i32, ptr %k4, align 4, !dbg !139
  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 1, !dbg !139
  %10 = load i32, ptr %v, align 4, !dbg !139
  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 2, !dbg !139
  %11 = load i32, ptr %w, align 4, !dbg !139
  %call5 = call noundef i32 @_ZSt3getILj0EEiSt6triple(i32 %9, i32 %10, i32 %11), !dbg !139
  store i32 %call5, ptr %ref.tmp3, align 4, !dbg !139
  store ptr %ref.tmp3, ptr %k, align 4, !dbg !139
    #dbg_declare(ptr %v6, !140, !DIExpression(), !145)
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp8, ptr align 4 %6, i32 12, i1 false), !dbg !145
  %k9 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 0, !dbg !145
  %12 = load i32, ptr %k9, align 4, !dbg !145
  %v10 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 1, !dbg !145
  %13 = load i32, ptr %v10, align 4, !dbg !145
  %w11 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 2, !dbg !145
  %14 = load i32, ptr %w11, align 4, !dbg !145
  %call12 = call noundef i32 @_ZSt3getILj1EEiSt6triple(i32 %12, i32 %13, i32 %14), !dbg !145
  store i32 %call12, ptr %ref.tmp7, align 4, !dbg !145
  store ptr %ref.tmp7, ptr %v6, align 4, !dbg !145
    #dbg_declare(ptr %w13, !146, !DIExpression(), !152)
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp15, ptr align 4 %6, i32 12, i1 false), !dbg !152
  %k16 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp15, i32 0, i32 0, !dbg !152
  %15 = load i32, ptr %k16, align 4, !dbg !152
  %v17 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp15, i32 0, i32 1, !dbg !152
  %16 = load i32, ptr %v17, align 4, !dbg !152
  %w18 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp15, i32 0, i32 2, !dbg !152
  %17 = load i32, ptr %w18, align 4, !dbg !152
  %call19 = call noundef i32 @_ZSt3getILj2EEiSt6triple(i32 %15, i32 %16, i32 %17), !dbg !152
  store i32 %call19, ptr %ref.tmp14, align 4, !dbg !152
  store ptr %ref.tmp14, ptr %w13, align 4, !dbg !152
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %7, ptr align 4 @__const._Z1fv..2, i32 8, i1 false), !dbg !153
    #dbg_declare(ptr %m, !154, !DIExpression(), !160)
  %call21 = call noundef i32 @_ZNH1C3getILi0EEEiOS_(ptr noundef nonnull align 4 dereferenceable(8) %7), !dbg !160
  store i32 %call21, ptr %ref.tmp20, align 4, !dbg !160
  store ptr %ref.tmp20, ptr %m, align 4, !dbg !160
    #dbg_declare(ptr %n, !161, !DIExpression(), !166)
  %call23 = call noundef i32 @_ZNH1C3getILi1EEEiOS_(ptr noundef nonnull align 4 dereferenceable(8) %7), !dbg !166
  store i32 %call23, ptr %ref.tmp22, align 4, !dbg !166
  store ptr %ref.tmp22, ptr %n, align 4, !dbg !166
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %8, ptr align 4 @__const._Z1fv..3, i32 8, i1 false), !dbg !167
    #dbg_declare(ptr %s, !168, !DIExpression(), !174)
  %call25 = call noundef i32 @_ZN1D3getILi0EEEii(ptr noundef nonnull align 4 dereferenceable(8) %8, i32 noundef 0), !dbg !167
  store i32 %call25, ptr %ref.tmp24, align 4, !dbg !174
  store ptr %ref.tmp24, ptr %s, align 4, !dbg !174
    #dbg_declare(ptr %p, !175, !DIExpression(), !180)
  %call27 = call noundef i32 @_ZN1D3getILi1EEEii(ptr noundef nonnull align 4 dereferenceable(8) %8, i32 noundef 0), !dbg !167
  store i32 %call27, ptr %ref.tmp26, align 4, !dbg !180
  store ptr %ref.tmp26, ptr %p, align 4, !dbg !180
  %x = getelementptr inbounds nuw %struct.A, ptr %0, i32 0, i32 0, !dbg !181
  %18 = load i32, ptr %x, align 4, !dbg !181
  %y = getelementptr inbounds nuw %struct.A, ptr %0, i32 0, i32 1, !dbg !182
  %19 = load i32, ptr %y, align 4, !dbg !182
  %add = add nsw i32 %18, %19, !dbg !183
  %20 = load ptr, ptr %1, align 4, !dbg !184, !nonnull !49, !align !185
  %x28 = getelementptr inbounds nuw %struct.A, ptr %20, i32 0, i32 0, !dbg !184
  %21 = load i32, ptr %x28, align 4, !dbg !184
  %add29 = add nsw i32 %add, %21, !dbg !186
  %22 = load ptr, ptr %1, align 4, !dbg !187, !nonnull !49, !align !185
  %y30 = getelementptr inbounds nuw %struct.A, ptr %22, i32 0, i32 1, !dbg !187
  %23 = load i32, ptr %y30, align 4, !dbg !187
  %add31 = add nsw i32 %add29, %23, !dbg !188
  %24 = load ptr, ptr %z1, align 4, !dbg !189, !nonnull !49, !align !185
  %25 = load i32, ptr %24, align 4, !dbg !189
  %add32 = add nsw i32 %add31, %25, !dbg !190
  %26 = load ptr, ptr %z2, align 4, !dbg !191, !nonnull !49, !align !185
  %27 = load i32, ptr %26, align 4, !dbg !191
  %add33 = add nsw i32 %add32, %27, !dbg !192
  %28 = load ptr, ptr %3, align 4, !dbg !193, !nonnull !49, !align !185
  %arrayidx = getelementptr inbounds [2 x i32], ptr %28, i32 0, i32 0, !dbg !193
  %29 = load i32, ptr %arrayidx, align 4, !dbg !193
  %add34 = add nsw i32 %add33, %29, !dbg !194
  %30 = load ptr, ptr %3, align 4, !dbg !195, !nonnull !49, !align !185
  %arrayidx35 = getelementptr inbounds [2 x i32], ptr %30, i32 0, i32 1, !dbg !195
  %31 = load i32, ptr %arrayidx35, align 4, !dbg !195
  %add36 = add nsw i32 %add34, %31, !dbg !196
  %32 = load ptr, ptr %4, align 4, !dbg !197, !nonnull !49, !align !185
  %.realp = getelementptr inbounds nuw { i32, i32 }, ptr %32, i32 0, i32 0, !dbg !197
  %33 = load i32, ptr %.realp, align 4, !dbg !197
  %add37 = add nsw i32 %add36, %33, !dbg !198
  %34 = load ptr, ptr %4, align 4, !dbg !199, !nonnull !49, !align !185
  %.imagp = getelementptr inbounds nuw { i32, i32 }, ptr %34, i32 0, i32 1, !dbg !199
  %35 = load i32, ptr %.imagp, align 4, !dbg !199
  %add38 = add nsw i32 %add37, %35, !dbg !200
  %36 = load ptr, ptr %5, align 4, !dbg !201, !nonnull !49, !align !202
  %37 = load <2 x i32>, ptr %36, align 8, !dbg !201
  %vecext = extractelement <2 x i32> %37, i32 0, !dbg !201
  %add39 = add nsw i32 %add38, %vecext, !dbg !203
  %38 = load ptr, ptr %5, align 4, !dbg !204, !nonnull !49, !align !202
  %39 = load <2 x i32>, ptr %38, align 8, !dbg !204
  %vecext40 = extractelement <2 x i32> %39, i32 1, !dbg !204
  %add41 = add nsw i32 %add39, %vecext40, !dbg !205
  ret i32 %add41, !dbg !206
}

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i32, i1 immarg) #1

; Function Attrs: mustprogress noinline nounwind optnone
define linkonce_odr noundef i32 @_ZN1B3getILi0EEEiv(ptr noundef nonnull align 4 dereferenceable(8) %this) #0 comdat align 2 !dbg !207 {
entry:
  %this.addr = alloca ptr, align 4
  store ptr %this, ptr %this.addr, align 4
    #dbg_declare(ptr %this.addr, !208, !DIExpression(), !210)
  %this1 = load ptr, ptr %this.addr, align 4
  %w = getelementptr inbounds nuw %struct.B, ptr %this1, i32 0, i32 0, !dbg !211
  %0 = load i32, ptr %w, align 4, !dbg !211
  ret i32 %0, !dbg !212
}

; Function Attrs: mustprogress noinline nounwind optnone
define linkonce_odr noundef i32 @_ZN1B3getILi1EEEiv(ptr noundef nonnull align 4 dereferenceable(8) %this) #0 comdat align 2 !dbg !213 {
entry:
  %this.addr = alloca ptr, align 4
  store ptr %this, ptr %this.addr, align 4
    #dbg_declare(ptr %this.addr, !214, !DIExpression(), !215)
  %this1 = load ptr, ptr %this.addr, align 4
  %z = getelementptr inbounds nuw %struct.B, ptr %this1, i32 0, i32 1, !dbg !216
  %0 = load i32, ptr %z, align 4, !dbg !216
  ret i32 %0, !dbg !217
}

; Function Attrs: mustprogress noinline nounwind optnone
define linkonce_odr noundef i32 @_ZNH1C3getILi0EEEiOS_(ptr noundef nonnull align 4 dereferenceable(8) %self) #0 comdat align 2 !dbg !218 {
entry:
  %self.addr = alloca ptr, align 4
  store ptr %self, ptr %self.addr, align 4
    #dbg_declare(ptr %self.addr, !219, !DIExpression(), !221)
  %0 = load ptr, ptr %self.addr, align 4, !dbg !222, !nonnull !49, !align !185
  %w = getelementptr inbounds nuw %struct.C, ptr %0, i32 0, i32 0, !dbg !223
  %1 = load i32, ptr %w, align 4, !dbg !223
  ret i32 %1, !dbg !224
}

; Function Attrs: mustprogress noinline nounwind optnone
define linkonce_odr noundef i32 @_ZNH1C3getILi1EEEiOS_(ptr noundef nonnull align 4 dereferenceable(8) %self) #0 comdat align 2 !dbg !225 {
entry:
  %self.addr = alloca ptr, align 4
  store ptr %self, ptr %self.addr, align 4
    #dbg_declare(ptr %self.addr, !226, !DIExpression(), !227)
  %0 = load ptr, ptr %self.addr, align 4, !dbg !228, !nonnull !49, !align !185
  %z = getelementptr inbounds nuw %struct.C, ptr %0, i32 0, i32 1, !dbg !229
  %1 = load i32, ptr %z, align 4, !dbg !229
  ret i32 %1, !dbg !230
}

; Function Attrs: mustprogress noinline nounwind optnone
define linkonce_odr noundef i32 @_ZN1D3getILi0EEEii(ptr noundef nonnull align 4 dereferenceable(8) %this, i32 noundef %unused) #0 comdat align 2 !dbg !231 {
entry:
  %this.addr = alloca ptr, align 4
  %unused.addr = alloca i32, align 4
  store ptr %this, ptr %this.addr, align 4
    #dbg_declare(ptr %this.addr, !232, !DIExpression(), !234)
  store i32 %unused, ptr %unused.addr, align 4
    #dbg_declare(ptr %unused.addr, !235, !DIExpression(), !236)
  %this1 = load ptr, ptr %this.addr, align 4
  %w = getelementptr inbounds nuw %struct.D, ptr %this1, i32 0, i32 0, !dbg !237
  %0 = load i32, ptr %w, align 4, !dbg !237
  ret i32 %0, !dbg !238
}

; Function Attrs: mustprogress noinline nounwind optnone
define linkonce_odr noundef i32 @_ZN1D3getILi1EEEii(ptr noundef nonnull align 4 dereferenceable(8) %this, i32 noundef %unused) #0 comdat align 2 !dbg !239 {
entry:
  %this.addr = alloca ptr, align 4
  %unused.addr = alloca i32, align 4
  store ptr %this, ptr %this.addr, align 4
    #dbg_declare(ptr %this.addr, !240, !DIExpression(), !241)
  store i32 %unused, ptr %unused.addr, align 4
    #dbg_declare(ptr %unused.addr, !242, !DIExpression(), !243)
  %this1 = load ptr, ptr %this.addr, align 4
  %z = getelementptr inbounds nuw %struct.D, ptr %this1, i32 0, i32 1, !dbg !244
  %0 = load i32, ptr %z, align 4, !dbg !244
  ret i32 %0, !dbg !245
}

attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+x87" }
attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!42, !43, !44}
!llvm.ident = !{!45}

!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 22.0.0git012680fa", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !2, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/<stdin>", directory: "")
!2 = !{!3, !18, !24, !33}
!3 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "B", file: !4, line: 44, size: 64, flags: DIFlagTypePassByValue, elements: !5, identifier: "_ZTS1B")
!4 = !DIFile(filename: "/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp", directory: "")
!5 = !{!6, !8, !9, !15}
!6 = !DIDerivedType(tag: DW_TAG_member, name: "w", scope: !3, file: !4, line: 45, baseType: !7, size: 32)
!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!8 = !DIDerivedType(tag: DW_TAG_member, name: "z", scope: !3, file: !4, line: 46, baseType: !7, size: 32, offset: 32)
!9 = !DISubprogram(name: "get<0>", linkageName: "_ZN1B3getILi0EEEiv", scope: !3, file: !4, line: 48, type: !10, scopeLine: 48, flags: DIFlagPrototyped, spFlags: 0, templateParams: !13)
!10 = !DISubroutineType(types: !11)
!11 = !{!7, !12}
!12 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !3, size: 32, flags: DIFlagArtificial | DIFlagObjectPointer)
!13 = !{!14}
!14 = !DITemplateValueParameter(type: !7, value: i32 0)
!15 = !DISubprogram(name: "get<1>", linkageName: "_ZN1B3getILi1EEEiv", scope: !3, file: !4, line: 49, type: !10, scopeLine: 49, flags: DIFlagPrototyped, spFlags: 0, templateParams: !16)
!16 = !{!17}
!17 = !DITemplateValueParameter(type: !7, value: i32 1)
!18 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "triple", scope: !19, file: !4, line: 92, size: 96, flags: DIFlagTypePassByValue, elements: !20, identifier: "_ZTSSt6triple")
!19 = !DINamespace(name: "std", scope: null)
!20 = !{!21, !22, !23}
!21 = !DIDerivedType(tag: DW_TAG_member, name: "k", scope: !18, file: !4, line: 93, baseType: !7, size: 32)
!22 = !DIDerivedType(tag: DW_TAG_member, name: "v", scope: !18, file: !4, line: 94, baseType: !7, size: 32, offset: 32)
!23 = !DIDerivedType(tag: DW_TAG_member, name: "w", scope: !18, file: !4, line: 95, baseType: !7, size: 32, offset: 64)
!24 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "C", file: !4, line: 52, size: 64, flags: DIFlagTypePassByValue, elements: !25, identifier: "_ZTS1C")
!25 = !{!26, !27, !28, !32}
!26 = !DIDerivedType(tag: DW_TAG_member, name: "w", scope: !24, file: !4, line: 53, baseType: !7, size: 32)
!27 = !DIDerivedType(tag: DW_TAG_member, name: "z", scope: !24, file: !4, line: 54, baseType: !7, size: 32, offset: 32)
!28 = !DISubprogram(name: "get<0>", linkageName: "_ZNH1C3getILi0EEEiOS_", scope: !24, file: !4, line: 56, type: !29, scopeLine: 56, flags: DIFlagPrototyped, spFlags: 0, templateParams: !13)
!29 = !DISubroutineType(types: !30)
!30 = !{!7, !31}
!31 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !24, size: 32, flags: DIFlagObjectPointer)
!32 = !DISubprogram(name: "get<1>", linkageName: "_ZNH1C3getILi1EEEiOS_", scope: !24, file: !4, line: 57, type: !29, scopeLine: 57, flags: DIFlagPrototyped, spFlags: 0, templateParams: !16)
!33 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "D", file: !4, line: 60, size: 64, flags: DIFlagTypePassByValue, elements: !34, identifier: "_ZTS1D")
!34 = !{!35, !36, !37, !41}
!35 = !DIDerivedType(tag: DW_TAG_member, name: "w", scope: !33, file: !4, line: 61, baseType: !7, size: 32)
!36 = !DIDerivedType(tag: DW_TAG_member, name: "z", scope: !33, file: !4, line: 62, baseType: !7, size: 32, offset: 32)
!37 = !DISubprogram(name: "get<0>", linkageName: "_ZN1D3getILi0EEEii", scope: !33, file: !4, line: 64, type: !38, scopeLine: 64, flags: DIFlagPrototyped, spFlags: 0, templateParams: !13)
!38 = !DISubroutineType(types: !39)
!39 = !{!7, !40, !7}
!40 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !33, size: 32, flags: DIFlagArtificial | DIFlagObjectPointer)
!41 = !DISubprogram(name: "get<1>", linkageName: "_ZN1D3getILi1EEEii", scope: !33, file: !4, line: 65, type: !38, scopeLine: 65, flags: DIFlagPrototyped, spFlags: 0, templateParams: !16)
!42 = !{i32 1, !"NumRegisterParameters", i32 0}
!43 = !{i32 2, !"Debug Info Version", i32 3}
!44 = !{i32 1, !"wchar_size", i32 4}
!45 = !{!"clang version 22.0.0git012680fa"}
!46 = distinct !DISubprogram(name: "get<0U>", linkageName: "_ZSt3getILj0EEiSt6triple", scope: !19, file: !4, line: 104, type: !47, scopeLine: 104, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !50, retainedNodes: !49)
!47 = !DISubroutineType(types: !48)
!48 = !{!7, !18}
!49 = !{}
!50 = !{!51}
!51 = !DITemplateValueParameter(name: "I", type: !52, value: i32 0)
!52 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
!53 = !DILocalVariable(name: "p", arg: 1, scope: !46, file: !4, line: 104, type: !18)
!54 = !DILocation(line: 104, column: 31, scope: !46)
!55 = !DILocation(line: 104, column: 45, scope: !46)
!56 = !DILocation(line: 104, column: 36, scope: !46)
!57 = distinct !DISubprogram(name: "get<1U>", linkageName: "_ZSt3getILj1EEiSt6triple", scope: !19, file: !4, line: 105, type: !47, scopeLine: 105, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !58, retainedNodes: !49)
!58 = !{!59}
!59 = !DITemplateValueParameter(name: "I", type: !52, value: i32 1)
!60 = !DILocalVariable(name: "p", arg: 1, scope: !57, file: !4, line: 105, type: !18)
!61 = !DILocation(line: 105, column: 31, scope: !57)
!62 = !DILocation(line: 105, column: 45, scope: !57)
!63 = !DILocation(line: 105, column: 36, scope: !57)
!64 = distinct !DISubprogram(name: "get<2U>", linkageName: "_ZSt3getILj2EEiSt6triple", scope: !19, file: !4, line: 106, type: !47, scopeLine: 106, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !65, retainedNodes: !49)
!65 = !{!66}
!66 = !DITemplateValueParameter(name: "I", type: !52, value: i32 2)
!67 = !DILocalVariable(name: "p", arg: 1, scope: !64, file: !4, line: 106, type: !18)
!68 = !DILocation(line: 106, column: 31, scope: !64)
!69 = !DILocation(line: 106, column: 45, scope: !64)
!70 = !DILocation(line: 106, column: 36, scope: !64)
!71 = distinct !DISubprogram(name: "f", linkageName: "_Z1fv", scope: !4, file: !4, line: 109, type: !72, scopeLine: 109, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !49)
!72 = !DISubroutineType(types: !73)
!73 = !{!7}
!74 = !DILocalVariable(name: "a", scope: !71, file: !4, line: 110, type: !75)
!75 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "A", file: !4, line: 39, size: 64, flags: DIFlagTypePassByValue, elements: !76, identifier: "_ZTS1A")
!76 = !{!77, !78}
!77 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !75, file: !4, line: 40, baseType: !7, size: 32)
!78 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !75, file: !4, line: 41, baseType: !7, size: 32, offset: 32)
!79 = !DILocation(line: 110, column: 5, scope: !71)
!80 = !DILocalVariable(name: "x1", scope: !71, file: !4, line: 111, type: !7)
!81 = !DILocation(line: 111, column: 9, scope: !71)
!82 = !DILocalVariable(name: "y1", scope: !71, file: !4, line: 111, type: !7)
!83 = !DILocation(line: 111, column: 13, scope: !71)
!84 = !DILocation(line: 111, column: 19, scope: !71)
!85 = !DILocalVariable(name: "x2", scope: !71, file: !4, line: 112, type: !7)
!86 = !DILocation(line: 112, column: 10, scope: !71)
!87 = !DILocalVariable(name: "y2", scope: !71, file: !4, line: 112, type: !7)
!88 = !DILocation(line: 112, column: 14, scope: !71)
!89 = !DILocation(line: 112, column: 9, scope: !71)
!90 = !DILocation(line: 113, column: 8, scope: !71)
!91 = !DILocalVariable(name: "z1", scope: !71, file: !4, line: 113, type: !92)
!92 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !93, size: 32)
!93 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !94, file: !4, line: 88, baseType: !7)
!94 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<0U, B>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !95, identifier: "_ZTSSt13tuple_elementILj0E1BE")
!95 = !{!96, !97}
!96 = !DITemplateValueParameter(type: !52, value: i32 0)
!97 = !DITemplateTypeParameter(name: "T", type: !3)
!98 = !DILocation(line: 113, column: 9, scope: !71)
!99 = !DILocalVariable(name: "z2", scope: !71, file: !4, line: 113, type: !100)
!100 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !101, size: 32)
!101 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !102, file: !4, line: 88, baseType: !7)
!102 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<1U, B>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !103, identifier: "_ZTSSt13tuple_elementILj1E1BE")
!103 = !{!104, !97}
!104 = !DITemplateValueParameter(type: !52, value: i32 1)
!105 = !DILocation(line: 113, column: 13, scope: !71)
!106 = !DILocalVariable(name: "array", scope: !71, file: !4, line: 114, type: !107)
!107 = !DICompositeType(tag: DW_TAG_array_type, baseType: !7, size: 64, elements: !108)
!108 = !{!109}
!109 = !DISubrange(count: 2)
!110 = !DILocation(line: 114, column: 7, scope: !71)
!111 = !DILocalVariable(name: "a1", scope: !71, file: !4, line: 115, type: !7)
!112 = !DILocation(line: 115, column: 10, scope: !71)
!113 = !DILocalVariable(name: "a2", scope: !71, file: !4, line: 115, type: !7)
!114 = !DILocation(line: 115, column: 14, scope: !71)
!115 = !DILocation(line: 115, column: 9, scope: !71)
!116 = !DILocalVariable(name: "cmplx", scope: !71, file: !4, line: 116, type: !117)
!117 = !DIBasicType(name: "complex", size: 64, encoding: 128)
!118 = !DILocation(line: 116, column: 16, scope: !71)
!119 = !DILocalVariable(name: "c1", scope: !71, file: !4, line: 117, type: !7)
!120 = !DILocation(line: 117, column: 10, scope: !71)
!121 = !DILocalVariable(name: "c2", scope: !71, file: !4, line: 117, type: !7)
!122 = !DILocation(line: 117, column: 14, scope: !71)
!123 = !DILocation(line: 117, column: 9, scope: !71)
!124 = !DILocalVariable(name: "vctr", scope: !71, file: !4, line: 118, type: !125)
!125 = !DICompositeType(tag: DW_TAG_array_type, baseType: !7, size: 64, flags: DIFlagVector, elements: !108)
!126 = !DILocation(line: 118, column: 7, scope: !71)
!127 = !DILocalVariable(name: "v1", scope: !71, file: !4, line: 119, type: !7)
!128 = !DILocation(line: 119, column: 10, scope: !71)
!129 = !DILocalVariable(name: "v2", scope: !71, file: !4, line: 119, type: !7)
!130 = !DILocation(line: 119, column: 14, scope: !71)
!131 = !DILocation(line: 119, column: 9, scope: !71)
!132 = !DILocation(line: 120, column: 8, scope: !71)
!133 = !DILocalVariable(name: "k", scope: !71, file: !4, line: 120, type: !134)
!134 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !135, size: 32)
!135 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !136, file: !4, line: 88, baseType: !7)
!136 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<0U, std::triple>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !137, identifier: "_ZTSSt13tuple_elementILj0ESt6tripleE")
!137 = !{!96, !138}
!138 = !DITemplateTypeParameter(name: "T", type: !18)
!139 = !DILocation(line: 120, column: 9, scope: !71)
!140 = !DILocalVariable(name: "v", scope: !71, file: !4, line: 120, type: !141)
!141 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !142, size: 32)
!142 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !143, file: !4, line: 88, baseType: !7)
!143 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<1U, std::triple>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !144, identifier: "_ZTSSt13tuple_elementILj1ESt6tripleE")
!144 = !{!104, !138}
!145 = !DILocation(line: 120, column: 12, scope: !71)
!146 = !DILocalVariable(name: "w", scope: !71, file: !4, line: 120, type: !147)
!147 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !148, size: 32)
!148 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !149, file: !4, line: 88, baseType: !7)
!149 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<2U, std::triple>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !150, identifier: "_ZTSSt13tuple_elementILj2ESt6tripleE")
!150 = !{!151, !138}
!151 = !DITemplateValueParameter(type: !52, value: i32 2)
!152 = !DILocation(line: 120, column: 15, scope: !71)
!153 = !DILocation(line: 121, column: 8, scope: !71)
!154 = !DILocalVariable(name: "m", scope: !71, file: !4, line: 121, type: !155)
!155 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !156, size: 32)
!156 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !157, file: !4, line: 88, baseType: !7)
!157 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<0U, C>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !158, identifier: "_ZTSSt13tuple_elementILj0E1CE")
!158 = !{!96, !159}
!159 = !DITemplateTypeParameter(name: "T", type: !24)
!160 = !DILocation(line: 121, column: 9, scope: !71)
!161 = !DILocalVariable(name: "n", scope: !71, file: !4, line: 121, type: !162)
!162 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !163, size: 32)
!163 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !164, file: !4, line: 88, baseType: !7)
!164 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<1U, C>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !165, identifier: "_ZTSSt13tuple_elementILj1E1CE")
!165 = !{!104, !159}
!166 = !DILocation(line: 121, column: 12, scope: !71)
!167 = !DILocation(line: 122, column: 8, scope: !71)
!168 = !DILocalVariable(name: "s", scope: !71, file: !4, line: 122, type: !169)
!169 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !170, size: 32)
!170 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !171, file: !4, line: 88, baseType: !7)
!171 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<0U, D>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !172, identifier: "_ZTSSt13tuple_elementILj0E1DE")
!172 = !{!96, !173}
!173 = !DITemplateTypeParameter(name: "T", type: !33)
!174 = !DILocation(line: 122, column: 9, scope: !71)
!175 = !DILocalVariable(name: "p", scope: !71, file: !4, line: 122, type: !176)
!176 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !177, size: 32)
!177 = !DIDerivedType(tag: DW_TAG_typedef, name: "type", scope: !178, file: !4, line: 88, baseType: !7)
!178 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "tuple_element<1U, D>", scope: !19, file: !4, line: 88, size: 8, flags: DIFlagTypePassByValue, elements: !49, templateParams: !179, identifier: "_ZTSSt13tuple_elementILj1E1DE")
!179 = !{!104, !173}
!180 = !DILocation(line: 122, column: 12, scope: !71)
!181 = !DILocation(line: 124, column: 6, scope: !71)
!182 = !DILocation(line: 127, column: 6, scope: !71)
!183 = !DILocation(line: 125, column: 6, scope: !71)
!184 = !DILocation(line: 129, column: 6, scope: !71)
!185 = !{i64 4}
!186 = !DILocation(line: 128, column: 6, scope: !71)
!187 = !DILocation(line: 132, column: 6, scope: !71)
!188 = !DILocation(line: 130, column: 6, scope: !71)
!189 = !DILocation(line: 134, column: 6, scope: !71)
!190 = !DILocation(line: 133, column: 6, scope: !71)
!191 = !DILocation(line: 137, column: 6, scope: !71)
!192 = !DILocation(line: 135, column: 6, scope: !71)
!193 = !DILocation(line: 139, column: 6, scope: !71)
!194 = !DILocation(line: 138, column: 6, scope: !71)
!195 = !DILocation(line: 142, column: 6, scope: !71)
!196 = !DILocation(line: 140, column: 6, scope: !71)
!197 = !DILocation(line: 144, column: 6, scope: !71)
!198 = !DILocation(line: 143, column: 6, scope: !71)
!199 = !DILocation(line: 147, column: 6, scope: !71)
!200 = !DILocation(line: 145, column: 6, scope: !71)
!201 = !DILocation(line: 149, column: 6, scope: !71)
!202 = !{i64 8}
!203 = !DILocation(line: 148, column: 6, scope: !71)
!204 = !DILocation(line: 152, column: 6, scope: !71)
!205 = !DILocation(line: 150, column: 6, scope: !71)
!206 = !DILocation(line: 123, column: 3, scope: !71)
!207 = distinct !DISubprogram(name: "get<0>", linkageName: "_ZN1B3getILi0EEEiv", scope: !3, file: !4, line: 48, type: !10, scopeLine: 48, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !13, declaration: !9, retainedNodes: !49)
!208 = !DILocalVariable(name: "this", arg: 1, scope: !207, type: !209, flags: DIFlagArtificial | DIFlagObjectPointer)
!209 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !3, size: 32)
!210 = !DILocation(line: 0, scope: !207)
!211 = !DILocation(line: 48, column: 36, scope: !207)
!212 = !DILocation(line: 48, column: 29, scope: !207)
!213 = distinct !DISubprogram(name: "get<1>", linkageName: "_ZN1B3getILi1EEEiv", scope: !3, file: !4, line: 49, type: !10, scopeLine: 49, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !16, declaration: !15, retainedNodes: !49)
!214 = !DILocalVariable(name: "this", arg: 1, scope: !213, type: !209, flags: DIFlagArtificial | DIFlagObjectPointer)
!215 = !DILocation(line: 0, scope: !213)
!216 = !DILocation(line: 49, column: 36, scope: !213)
!217 = !DILocation(line: 49, column: 29, scope: !213)
!218 = distinct !DISubprogram(name: "get<0>", linkageName: "_ZNH1C3getILi0EEEiOS_", scope: !24, file: !4, line: 56, type: !29, scopeLine: 56, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !13, declaration: !28, retainedNodes: !49)
!219 = !DILocalVariable(name: "self", arg: 1, scope: !218, file: !4, line: 56, type: !220, flags: DIFlagObjectPointer)
!220 = !DIDerivedType(tag: DW_TAG_rvalue_reference_type, baseType: !24, size: 32)
!221 = !DILocation(line: 56, column: 34, scope: !218)
!222 = !DILocation(line: 56, column: 49, scope: !218)
!223 = !DILocation(line: 56, column: 54, scope: !218)
!224 = !DILocation(line: 56, column: 42, scope: !218)
!225 = distinct !DISubprogram(name: "get<1>", linkageName: "_ZNH1C3getILi1EEEiOS_", scope: !24, file: !4, line: 57, type: !29, scopeLine: 57, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !16, declaration: !32, retainedNodes: !49)
!226 = !DILocalVariable(name: "self", arg: 1, scope: !225, file: !4, line: 57, type: !220, flags: DIFlagObjectPointer)
!227 = !DILocation(line: 57, column: 34, scope: !225)
!228 = !DILocation(line: 57, column: 49, scope: !225)
!229 = !DILocation(line: 57, column: 54, scope: !225)
!230 = !DILocation(line: 57, column: 42, scope: !225)
!231 = distinct !DISubprogram(name: "get<0>", linkageName: "_ZN1D3getILi0EEEii", scope: !33, file: !4, line: 64, type: !38, scopeLine: 64, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !13, declaration: !37, retainedNodes: !49)
!232 = !DILocalVariable(name: "this", arg: 1, scope: !231, type: !233, flags: DIFlagArtificial | DIFlagObjectPointer)
!233 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !33, size: 32)
!234 = !DILocation(line: 0, scope: !231)
!235 = !DILocalVariable(name: "unused", arg: 2, scope: !231, file: !4, line: 64, type: !7)
!236 = !DILocation(line: 64, column: 29, scope: !231)
!237 = !DILocation(line: 64, column: 46, scope: !231)
!238 = !DILocation(line: 64, column: 39, scope: !231)
!239 = distinct !DISubprogram(name: "get<1>", linkageName: "_ZN1D3getILi1EEEii", scope: !33, file: !4, line: 65, type: !38, scopeLine: 65, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, templateParams: !16, declaration: !41, retainedNodes: !49)
!240 = !DILocalVariable(name: "this", arg: 1, scope: !239, type: !233, flags: DIFlagArtificial | DIFlagObjectPointer)
!241 = !DILocation(line: 0, scope: !239)
!242 = !DILocalVariable(name: "unused", arg: 2, scope: !239, file: !4, line: 65, type: !7)
!243 = !DILocation(line: 65, column: 29, scope: !239)
!244 = !DILocation(line: 65, column: 46, scope: !239)
!245 = !DILocation(line: 65, column: 39, scope: !239)

@Michael137
Copy link
Member Author

I'm afraid I'm seeing a test regression with this change (confirmed via bisect), on Gentoo amd64:

FAIL: Clang :: DebugInfo/CXX/structured-binding.cpp (11221 of 22437)
******************** TEST 'Clang :: DebugInfo/CXX/structured-binding.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/clang -cc1 -internal-isystem /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/../../../../lib/clang/22/include -nostdsysteminc -std=c++23 -emit-llvm -debug-info-kind=standalone -triple i686-pc-linux-gnu /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp -o - | /usr/lib/llvm/22/bin/FileCheck /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp --implicit-check-not="call void @llvm.dbg.declare" # RUN: at line 1
+ /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/clang -cc1 -internal-isystem /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/x/y/clang-abi_x86_32.x86/bin/../../../../lib/clang/22/include -nostdsysteminc -std=c++23 -emit-llvm -debug-info-kind=standalone -triple i686-pc-linux-gnu /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp -o -
+ /usr/lib/llvm/22/bin/FileCheck /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp '--implicit-check-not=call void @llvm.dbg.declare'
/var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp:12:11: error: CHECK: expected string not found in input
// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
          ^
<stdin>:152:44: note: scanning from here
 #dbg_declare(ptr %k, !133, !DIExpression(), !139)
                                           ^
<stdin>:163:2: note: possible intended match here
 #dbg_declare(ptr %v6, !140, !DIExpression(), !145)
 ^

Input file: <stdin>
Check file: /var/tmp/portage/llvm-core/clang-22.0.0.9999/work/clang/test/DebugInfo/CXX/structured-binding.cpp

-dump-input=help explains the following input dump.

Input was:
<<<<<<
            .
            .
            .
          147:  store <2 x i32> <i32 1, i32 2>, ptr %vctr, align 8, !dbg !126 
          148:  #dbg_declare(ptr %5, !127, !DIExpression(DW_OP_deref), !128) 
          149:  #dbg_declare(ptr %5, !129, !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4), !130) 
          150:  store ptr %vctr, ptr %5, align 4, !dbg !131 
          151:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %6, ptr align 4 @__const._Z1fv..1, i32 12, i1 false), !dbg !132 
          152:  #dbg_declare(ptr %k, !133, !DIExpression(), !139) 
check:12'0                                                X~~~~~~~ error: no match found
          153:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp, ptr align 4 %6, i32 12, i1 false), !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          154:  %k4 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 0, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          155:  %9 = load i32, ptr %k4, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          156:  %v = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 1, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          157:  %10 = load i32, ptr %v, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          158:  %w = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp, i32 0, i32 2, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          159:  %11 = load i32, ptr %w, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          160:  %call5 = call noundef i32 @_ZSt3getILj0EEiSt6triple(i32 %9, i32 %10, i32 %11), !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          161:  store i32 %call5, ptr %ref.tmp3, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          162:  store ptr %ref.tmp3, ptr %k, align 4, !dbg !139 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          163:  #dbg_declare(ptr %v6, !140, !DIExpression(), !145) 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
check:12'1      ?                                                   possible intended match
          164:  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %agg.tmp8, ptr align 4 %6, i32 12, i1 false), !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          165:  %k9 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 0, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          166:  %12 = load i32, ptr %k9, align 4, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          167:  %v10 = getelementptr inbounds nuw %"struct.std::triple", ptr %agg.tmp8, i32 0, i32 1, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          168:  %13 = load i32, ptr %v10, align 4, !dbg !145 
check:12'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            .
            .
            .
>>>>>>

--

********************

Thanks! that was useful. Put up fix here: #160300

Michael137 added a commit that referenced this pull request Oct 2, 2025
…compatible compiler version

Requires a compiler with the changes in #122265
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Oct 2, 2025
… check for compatible compiler version

Requires a compiler with the changes in llvm/llvm-project#122265
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Oct 3, 2025
…compatible compiler version

Requires a compiler with the changes in llvm#122265
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category debuginfo lldb

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[LLDB] List structured bindings as frame variables

6 participants