https://gcc.gnu.org/g:0a7eae02dea519403669ffb1b754f998ce7cf56f

commit r16-3046-g0a7eae02dea519403669ffb1b754f998ce7cf56f
Author: Patrick Palka <ppa...@redhat.com>
Date:   Wed Aug 6 09:41:01 2025 -0400

    c++: mangling cNTTP object w/ implicit non-trailing zeros [PR121231]
    
    Here the results of A::make(0, 0, 1), (0, 1, 0) and (1, 0, 0) are each
    represented as a single-element CONSTRUCTOR with CONSTRUCTOR_NO_CLEARING
    cleared, and when used as as a class NTTP argument we end up mangling them
    all as A{1}, i.e. eliding both the implicit initial and trailing zeros.
    Mangling them all the same seems clearly wrong since they're logically
    different values.
    
    It turns out the mangling also omits intermediate zeros, e.g.
    A::make(1, 0, 1) is mangled as A{1, 1}, the same as A::make(1, 1, 0).
    
    It seems we can't omit both trailing and non-trailing implicit zeros
    without introducing mangling ambiguities.  This patch makes us include
    non-trailing zeros in these manglings (while continuing to omit trailing
    zeros).
    
    This also manifests as a wrong-code bug where the specializations table
    would conflate different specializations that are in terms of different
    class NTTP arguments, since the identity of such arguments is tied to
    their mangling.
    
            PR c++/121231
            PR c++/119688
            PR c++/94511
    
    gcc/ChangeLog:
    
            * common.opt: Document additional ABI version 21 change.
            * doc/invoke.texi: Likewise.
    
    gcc/cp/ChangeLog:
    
            * mangle.cc (write_expression): Write out implicit non-trailing
            zeroes of a CONSTRUCTOR when the ABI version is at least 21.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/abi/mangle82.C: New test.
            * g++.dg/cpp2a/nontype-class73.C: New test.
    
    Reviewed-by: Jason Merrill <ja...@redhat.com>

Diff:
---
 gcc/common.opt                               |  2 +
 gcc/cp/mangle.cc                             | 51 +++++++++++++++++
 gcc/doc/invoke.texi                          |  5 +-
 gcc/testsuite/g++.dg/abi/mangle82.C          | 85 ++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/cpp2a/nontype-class73.C | 30 ++++++++++
 5 files changed, 171 insertions(+), 2 deletions(-)

diff --git a/gcc/common.opt b/gcc/common.opt
index 70659fabebd5..bf38f60d194b 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1067,6 +1067,8 @@ Driver Undocumented
 ;
 ; 21: Fix noexcept lambda capture pruning.
 ;     Fix C++20 layout of base with all explicitly defaulted constructors.
+;     Fix mangling of class and array objects with implicitly
+;     zero-initialized non-trailing subojects.
 ;     Default in G++ 16.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
index 13d5dedebd29..fd69099c8fd5 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -3745,11 +3745,59 @@ write_expression (tree expr)
                      || !zero_init_expr_p (ce->value))
                    last_nonzero = i;
 
+             tree prev_field = NULL_TREE;
              if (undigested || last_nonzero != UINT_MAX)
                for (HOST_WIDE_INT i = 0; vec_safe_iterate (elts, i, &ce); ++i)
                  {
                    if (i > last_nonzero)
                      break;
+                   if (!undigested && !CONSTRUCTOR_NO_CLEARING (expr)
+                       && (TREE_CODE (etype) == RECORD_TYPE
+                           || TREE_CODE (etype) == ARRAY_TYPE))
+                     {
+                       /* Write out any implicit non-trailing zeros
+                          (which we neglected to do before v21).  */
+                       if (TREE_CODE (etype) == RECORD_TYPE)
+                         {
+                           tree field;
+                           if (i == 0)
+                             field = first_field (etype);
+                           else
+                             field = DECL_CHAIN (prev_field);
+                           for (;;)
+                             {
+                               field = next_subobject_field (field);
+                               if (field == ce->index)
+                                 break;
+                               if (abi_check (21))
+                                 write_expression (build_zero_cst
+                                                   (TREE_TYPE (field)));
+                               field = DECL_CHAIN (field);
+                             }
+                         }
+                       else if (TREE_CODE (etype) == ARRAY_TYPE)
+                         {
+                           unsigned HOST_WIDE_INT j;
+                           if (i == 0)
+                             j = 0;
+                           else
+                             j = 1 + tree_to_uhwi (prev_field);
+                           unsigned HOST_WIDE_INT k;
+                           if (TREE_CODE (ce->index) == RANGE_EXPR)
+                             k = tree_to_uhwi (TREE_OPERAND (ce->index, 0));
+                           else
+                             k = tree_to_uhwi (ce->index);
+                           tree zero = NULL_TREE;
+                           for (; j < k; ++j)
+                             if (abi_check (21))
+                               {
+                                 if (!zero)
+                                   zero = build_zero_cst (TREE_TYPE (etype));
+                                 write_expression (zero);
+                               }
+                         }
+                     }
+
                    if (!undigested && TREE_CODE (etype) == UNION_TYPE)
                      {
                        /* Express the active member as a designator.  */
@@ -3794,6 +3842,9 @@ write_expression (tree expr)
                    else
                      for (unsigned j = 0; j < reps; ++j)
                        write_expression (ce->value);
+                   prev_field = ce->index;
+                   if (prev_field && TREE_CODE (prev_field) == RANGE_EXPR)
+                     prev_field = TREE_OPERAND (prev_field, 1);
                  }
            }
          else
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index bc5b3e0b75ab..00468a72adaa 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -3016,8 +3016,9 @@ Version 20, which first appeared in G++ 15, fixes 
manglings of lambdas
 in static data member initializers.
 
 Version 21, which first appeared in G++ 16, fixes unnecessary captures
-in noexcept lambdas (c++/119764) and layout of a base class
-with all explicitly defaulted constructors (c++/120012).
+in noexcept lambdas (c++/119764), layout of a base class with all explicitly
+defaulted constructors (c++/120012), and mangling of class and array
+objects with implicitly zero-initialized non-trailing subobjects (c++/121231).
 
 See also @option{-Wabi}.
 
diff --git a/gcc/testsuite/g++.dg/abi/mangle82.C 
b/gcc/testsuite/g++.dg/abi/mangle82.C
new file mode 100644
index 000000000000..39dd581285ef
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/mangle82.C
@@ -0,0 +1,85 @@
+// Test mangling of C++20 class NTTP objects with implicitly zeroed
+// non-trailing subojects.
+// PR c++/121231
+// { dg-do compile { target c++20 } }
+
+struct A {
+  int x, y, z;
+
+  static constexpr A make(int x, int y, int z) {
+    A a{};
+    if (x != 0)
+      a.x = x;
+    if (y != 0)
+      a.y = y;
+    if (z != 0)
+      a.z = z;
+    return a;
+  }
+};
+
+struct B : A {
+  int w;
+
+  static constexpr B make(int x, int y, int z, int w) {
+    B b{};
+    if (x != 0 || y != 0 || z != 0)
+      static_cast<A&>(b) = A::make(x, y, z);
+    if (w != 0)
+      b.w = w;
+    return b;
+  }
+};
+
+struct C {
+  int xyz[3];
+
+  static constexpr C make(int x, int y, int z) {
+    C c{};
+    if (x != 0)
+      c.xyz[0] = x;
+    if (y != 0)
+      c.xyz[1] = y;
+    if (z != 0)
+      c.xyz[2] = z;
+    return c;
+  }
+};
+
+template<int N, A a> void f();
+template<int N, B b> void g();
+template<int N, C c> void h();
+
+int main() {
+  f<0, A::make(0, 0, 1)>();    // void f<0, A{0, 0, 1}>()
+  f<1, A::make(0, 1, 0)>();    // void f<1, A{0, 1}>()
+  f<2, A::make(0, 0, 0)>();    // void f<2, A{}>()
+  f<3, A::make(1, 0, 1)>();    // void f<3, A{1, 0, 1}>()
+
+  g<0, B::make(0, 0, 0, 1)>(); // void g<0, B{A{}, 1}>()
+  g<1, B::make(0, 0, 1, 0)>(); // void g<1, B{A{0, 0, 1}}>()
+  g<2, B::make(0, 1, 0, 0)>(); // void g<2, B{A{0, 1}}>()
+  g<3, B::make(0, 0, 0, 0)>(); // void g<3, B{}>()
+  g<4, B::make(1, 0, 1, 0)>(); // void g<4, B{A{1, 0, 1}}>()
+
+  h<0, C::make(0, 0, 1)>();    // void h<0, C{int [3]{0, 0, 1}}>()
+  h<1, C::make(0, 1, 0)>();    // void h<1, C{int [3]{0, 1}}>()
+  h<2, C::make(0, 0, 0)>();    // void h<2, C{}>()
+  h<3, C::make(1, 0, 1)>();    // void h<3, C{int [3]{1, 0, 1}}>()
+}
+
+// { dg-final { scan-assembler "_Z1fILi0EXtl1ALi0ELi0ELi1EEEEvv" } }
+// { dg-final { scan-assembler "_Z1fILi1EXtl1ALi0ELi1EEEEvv" } }
+// { dg-final { scan-assembler "_Z1fILi2EXtl1AEEEvv" } }
+// { dg-final { scan-assembler "_Z1fILi3EXtl1ALi1ELi0ELi1EEEEvv" } }
+
+// { dg-final { scan-assembler "_Z1gILi0EXtl1Btl1AELi1EEEEvv" } }
+// { dg-final { scan-assembler "_Z1gILi1EXtl1Btl1ALi0ELi0ELi1EEEEEvv" } }
+// { dg-final { scan-assembler "_Z1gILi2EXtl1Btl1ALi0ELi1EEEEEvv" } }
+// { dg-final { scan-assembler "_Z1gILi3EXtl1BEEEvv" } }
+// { dg-final { scan-assembler "_Z1gILi4EXtl1Btl1ALi1ELi0ELi1EEEEEvv" } }
+
+// { dg-final { scan-assembler "_Z1hILi0EXtl1CtlA3_iLi0ELi0ELi1EEEEEvv" } }
+// { dg-final { scan-assembler "_Z1hILi1EXtl1CtlA3_iLi0ELi1EEEEEvv" } }
+// { dg-final { scan-assembler "_Z1hILi2EXtl1CEEEvv" } }
+// { dg-final { scan-assembler "_Z1hILi3EXtl1CtlA3_iLi1ELi0ELi1EEEEEvv" } }
diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class73.C 
b/gcc/testsuite/g++.dg/cpp2a/nontype-class73.C
new file mode 100644
index 000000000000..7f27cad00d53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class73.C
@@ -0,0 +1,30 @@
+// PR c++/119688
+// { dg-do compile { target c++20 } }
+
+template<int N>
+struct builder {
+  bool value[256]{};
+  constexpr builder(char const (&s)[N]) {
+    for(int i = 0 ; i < N; ++i)
+      value[static_cast<unsigned char>(s[i])] = true;
+  }
+};
+
+template<builder A>
+constexpr auto operator""_ar() {
+  return A.value;
+}
+
+constexpr auto first = "ab"_ar;
+static_assert( first['a']);
+static_assert( first['b']);
+static_assert(!first['c']);
+static_assert(!first['d']);
+static_assert(!first['z']);
+
+constexpr auto second = "cd"_ar;
+static_assert(!second['a']);
+static_assert(!second['b']);
+static_assert(!second['z']);
+static_assert( second['c']);
+static_assert( second['d']);

Reply via email to