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 > >