On Wed, 30 Jul 2025, Patrick Palka wrote:

> On Wed, 30 Jul 2025, Patrick Palka wrote:
> 
> > On Wed, 30 Jul 2025, Patrick Palka wrote:
> > 
> > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> > > OK for trunk?
> > > 
> > > -- >8 --
> > > 
> > > Here the result 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 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.
> > 
> > Just realized that A::make(1, 0, 1) is also mangled incorrectly -- as
> > A{1, 1} instead of A{1, 0, 1}.  So we need to consider intermediate
> > implicit zeroes as well, not just initial zeroes...
> 
> Here's an updated patch that also considers intermediate implicit zeros.
> 
> Bootstrap and regtest on x86_64-pc-linux-gnu in progress, does this look OK 
> for
> trunk if successful?
> 
> -- >8 --
> 
> Subject: [PATCH] c++: mangling cNTTP object w/ implicit non-trailing zeros
>  [PR121231]
> 
> Here the result 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 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 we also omit intermediate zeros, e.g. A::make(1, 0, 1) is
> mangled as A{1, 1} 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, which matches e.g. Clang.
> 
>       PR c++/121231
> 
> 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.
> ---
>  gcc/common.opt                      |  2 +
>  gcc/cp/mangle.cc                    | 49 ++++++++++++++++
>  gcc/doc/invoke.texi                 |  5 +-
>  gcc/testsuite/g++.dg/abi/mangle82.C | 86 +++++++++++++++++++++++++++++
>  4 files changed, 140 insertions(+), 2 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/abi/mangle82.C
> 
> 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..296e0f34552e 100644
> --- a/gcc/cp/mangle.cc
> +++ b/gcc/cp/mangle.cc
> @@ -3745,9 +3745,55 @@ write_expression (tree expr)
>                     || !zero_init_expr_p (ce->value))
>                   last_nonzero = i;
>  
> +           tree prev_field = NULL_TREE;
> +           if (TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE)
> +             prev_field = first_field (etype);
> +           else if (TREE_CODE (TREE_TYPE (expr)) == ARRAY_TYPE)
> +             prev_field = size_int (0);

On second thought I think the logic is a little clearer if prev_field is
NULL_TREE on the first iteration, and we adjust the i == 0 logic
accordingly.  Like so (passes initial testisg, full testing in progress):

-- >8 --

Here the result 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 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 we also omit 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, which matches e.g. Clang.

        PR c++/121231

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.
---
 gcc/common.opt                      |  2 +
 gcc/cp/mangle.cc                    | 50 +++++++++++++++++
 gcc/doc/invoke.texi                 |  5 +-
 gcc/testsuite/g++.dg/abi/mangle82.C | 86 +++++++++++++++++++++++++++++
 4 files changed, 141 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/abi/mangle82.C

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..23b21c0a5b7a 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -3745,11 +3745,58 @@ 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; field = DECL_CHAIN (field))
+                             {
+                               field = next_subobject_field (field);
+                               if (!field || field == ce->index)
+                                 break;
+                               if (abi_check (21))
+                                 write_expression (build_zero_cst
+                                                   (TREE_TYPE (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 +3841,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 e442a9cb73e4..61fcf5deebb8 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..b93db9e77ddb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/mangle82.C
@@ -0,0 +1,86 @@
+// 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" } }
-- 
2.50.1.469.ge813a0200a



>             if (undigested || last_nonzero != UINT_MAX)
>               for (HOST_WIDE_INT i = 0; vec_safe_iterate (elts, i, &ce); ++i)
>                 {
> +                 if (!undigested && !CONSTRUCTOR_NO_CLEARING (expr)
> +                     && prev_field)
> +                   {
> +                     /* Write out any implicit non-trailing zeros
> +                        (which we neglected to do before v21).  */
> +                     if (TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE)
> +                       {
> +                         tree field = prev_field;
> +                         if (i != 0)
> +                           field = DECL_CHAIN (field);
> +                         for (; field; field = DECL_CHAIN (field))
> +                           {
> +                             field = next_subobject_field (field);
> +                             if (!field || field == ce->index)
> +                               break;
> +                             if (abi_check (21))
> +                               write_expression (build_zero_cst
> +                                                 (TREE_TYPE (field)));
> +                           }
> +                       }
> +                     else if (TREE_CODE (TREE_TYPE (expr)) == ARRAY_TYPE)
> +                       {
> +                         unsigned HOST_WIDE_INT j = tree_to_uhwi 
> (prev_field);
> +                         if (i != 0)
> +                           ++j;
> +                         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
> +                                                        (TREE_TYPE (expr)));
> +                               write_expression (zero);
> +                             }
> +                       }
> +                   }
>                   if (i > last_nonzero)
>                     break;
>                   if (!undigested && TREE_CODE (etype) == UNION_TYPE)
> @@ -3794,6 +3840,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 e442a9cb73e4..61fcf5deebb8 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..b93db9e77ddb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/abi/mangle82.C
> @@ -0,0 +1,86 @@
> +// 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" } }
> -- 
> 2.50.1.469.ge813a0200a
> 
> 

Reply via email to