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...
>
> This patch makes us include initial zeros in such mangling, which seems
> like the natural behavior (and matches e.g. Clang).
>
> PR c++/121231
>
> gcc/ChangeLog:
>
> * common.opt: Document additional -fabi-version=21 change.
> * doc/invoke.texi: Likewise.
>
> gcc/cp/ChangeLog:
>
> * mangle.cc (write_expression):
>
> 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 | 79 +++++++++++++++++++++++++++++
> 4 files changed, 128 insertions(+), 7 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/abi/mangle82.C
>
> diff --git a/gcc/common.opt b/gcc/common.opt
> index 70659fabebd5..97b1e27456ce 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 initial 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..f317dfd978ae 100644
> --- a/gcc/cp/mangle.cc
> +++ b/gcc/cp/mangle.cc
> @@ -3739,11 +3739,50 @@ write_expression (tree expr)
> constructor_elt *ce;
>
> if (!undigested)
> - for (HOST_WIDE_INT i = 0; vec_safe_iterate (elts, i, &ce); ++i)
> - if ((TREE_CODE (etype) == UNION_TYPE
> - && ce->index != first_field (etype))
> - || !zero_init_expr_p (ce->value))
> - last_nonzero = i;
> + {
> + for (HOST_WIDE_INT i = 0; vec_safe_iterate (elts, i, &ce);
> ++i)
> + {
> + if (i == 0
> + && !CONSTRUCTOR_NO_CLEARING (expr))
> + {
> + /* Write out any implicit initial zeros. */
> + if (TREE_CODE (TREE_TYPE (expr)) == RECORD_TYPE)
> + for (tree field = TYPE_FIELDS (TREE_TYPE (expr));
> + 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;
> + if (TREE_CODE (ce->index) == RANGE_EXPR)
> + j = tree_to_uhwi (TREE_OPERAND (ce->index, 0));
> + else
> + j = tree_to_uhwi (ce->index);
> + tree zero = NULL_TREE;
> + for (; j != 0; --j)
> + if (abi_check (21))
> + {
> + if (!zero)
> + zero = build_zero_cst (TREE_TYPE
> + (TREE_TYPE
> (expr)));
> + write_expression (zero);
> + }
> + }
> + }
> +
> + if ((TREE_CODE (etype) == UNION_TYPE
> + && ce->index != first_field (etype))
> + || !zero_init_expr_p (ce->value))
> + last_nonzero = i;
> + }
> +
> + }
>
> if (undigested || last_nonzero != UINT_MAX)
> for (HOST_WIDE_INT i = 0; vec_safe_iterate (elts, i, &ce); ++i)
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index e442a9cb73e4..23cd736e5fd1 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 initial subojects (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..529c75ee8997
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/abi/mangle82.C
> @@ -0,0 +1,79 @@
> +// Test mangling of C++20 class NTTP objects with implicitly zeroed initial
> 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{}>()
> +
> + 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{}>()
> +
> + 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{}>()
> +}
> +
> +// { dg-final { scan-assembler "_Z1fILi0EXtl1ALi0ELi0ELi1EEEEvv" } }
> +// { dg-final { scan-assembler "_Z1fILi1EXtl1ALi0ELi1EEEEvv" } }
> +// { dg-final { scan-assembler "_Z1fILi2EXtl1AEEEvv" } }
> +
> +// { dg-final { scan-assembler "_Z1gILi0EXtl1Btl1AELi1EEEEvv" } }
> +// { dg-final { scan-assembler "_Z1gILi1EXtl1Btl1ALi0ELi0ELi1EEEEEvv" } }
> +// { dg-final { scan-assembler "_Z1gILi2EXtl1Btl1ALi0ELi1EEEEEvv" } }
> +// { dg-final { scan-assembler "_Z1gILi3EXtl1BEEEvv" } }
> +
> +// { dg-final { scan-assembler "_Z1hILi0EXtl1CtlA3_iLi0ELi0ELi1EEEEEvv" } }
> +// { dg-final { scan-assembler "_Z1hILi1EXtl1CtlA3_iLi0ELi1EEEEEvv" } }
> +// { dg-final { scan-assembler "_Z1hILi2EXtl1CEEEvv" } }
> --
> 2.50.1.469.ge813a0200a
>
>