From: Jonas Hansen <[email protected]>

PR127416 exposes folded character-array initializers as STRING_CSTs. When
one initializes only a short prefix of an aggregate member, counting the
string length as both the total and nonzero element counts hides the
implicit trailing zeros from the constructor density heuristic. This can
replace clearing and stores with a copy of a large read-only constant.

Include the known array size in the total element count while retaining
the existing nonzero estimates and completeness handling. This lets the
existing density heuristic choose between clearing and copying without
penalizing aggregates that are mostly nonzero.

For the original ssa-dse-1.C on Cortex-M0 at -O1, .text plus .rodata falls
from 276 to 124 bytes, compared with 164 before PR127416. Use byte-sized
enum elements in the DSE test to preserve its original optimization check,
and add a regression covering sparse and dense aggregates and their
runtime contents.

Regtested with the complete configured native C/C++ and runtime-library
testsuites on x86_64-pc-linux-gnu before rebasing onto current trunk,
including all supported C++ compiler standard modes. No new failures:
the remaining failures reproduce before the follow-up, and sandbox-related
sanitizer failures pass outside the sandbox.

After updating to trunk 4d22d3ead95, rebuilt the compiler and reran the
related C++ tests and the new upstream C aggregate-range tests: 92 expected
passes, 3 unsupported, no unexpected results. The full testsuite was not
repeated on the updated base. Before rebasing, also tested with 20
arm-none-eabi Cortex-M0 compile checks and native and Arm compiler
self-tests.

gcc/ChangeLog:

        PR tree-optimization/127416
        * expr.cc (categorize_ctor_elements_1): Include implicit trailing
        zeros in the total element count for string initializers.

gcc/testsuite/ChangeLog:

        PR tree-optimization/127416
        * g++.dg/tree-ssa/constexpr-array-padding.C: New test.
        * g++.dg/tree-ssa/ssa-dse-1.C (byte): New enum.
        (fixbuf): Use byte as the buffer element type.

Signed-off-by: Jonas Hansen <[email protected]>
---
This follows up #232 and [PR 
tree-optimization/127416](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127416), 
addressing the arm-none-eabi regression reported by Torbjörn.

Folded character-array initializers can become short STRING_CSTs with implicit 
trailing zeros. Counting only the string length hides those zeros from the 
constructor density heuristic, which can select a copy of a large read-only 
constant. Include the known array size in the total element count while 
retaining the existing nonzero estimates and completeness handling.

For the original, unmodified ssa-dse-1.C on Cortex-M0 at -O1, this reduces 
.text plus .rodata from 276 to 124 bytes, compared with 164 before PR127416. 
These are assembled object section sizes, excluding library and linker effects.

Keep ssa-dse-1.C focused on partial dead-store elimination by using a 
byte-sized enum and preserving its existing dump assertion. Add 
constexpr-array-padding.C to check sparse and dense character-containing 
aggregates and their runtime contents. The dense case guards against clearing 
aggregates that are mostly nonzero.

Validation:

- Before updating to trunk, the complete configured native C/C++ and 
runtime-library testsuites on x86_64-pc-linux-gnu recorded 952,639 expected 
passes. No new failures were found: 14 failures and two unexpected passes 
reproduced before this follow-up, and all 588 sandbox-related sanitizer 
failures passed outside the sandbox.
- The existing native bootstrap stages were rebuilt and stages 2 and 3 compared 
successfully on 524847fec983 plus this patch. After updating to 4d22d3ead95, 
the compiler was rebuilt and focused C/C++ regressions produced 92 expected 
passes and three unsupported cases, with no unexpected results. Native compiler 
self-tests also passed.
- Earlier arm-none-eabi Cortex-M0 compile checks produced 20 expected passes; 
Arm compiler self-tests passed. Native runtime checks of the original testcase 
covered all returned bytes and unaligned destinations.

Testing was limited to the configured C/C++ languages. The full testsuite was 
not repeated after the upstream updates, and Arm programs were not executed.



This is a forge pull request published on the gcc-patches mailing list mailing 
list
as requested by Jonas Hansen via Sourceware Forge 
<[email protected]>.
Forge discussion: https://forge.sourceware.org/gcc/gcc/pulls/237

Get it locally using:
```
git fetch forge-upstream "+refs/versioned_pull/237/*:refs/versioned_pull/237/*"
git switch -c "pr-237-v1" "refs/versioned_pull/237/1/head"
```
Or, download the patch at: https://forge.sourceware.org/gcc/gcc/pulls/237.diff
Created on: 2026-10-02 11:57:02+00:00
Latest update: 2026-10-02 11:57:29+00:00
Changes: 3 changed files, 93 additions, 6 deletions
Head revision: hrjonashansen/gcc ref fix-string-initializer-padding commit 
18906abab061e468dcec4c7823666f3c25da6f6b 
Base revision: gcc/gcc ref trunk commit 
4d22d3ead95ec862afd6726a10230e27587f4663 r17-4827-g4d22d3ead95ec8
Merge base: 4d22d3ead95ec862afd6726a10230e27587f4663
Requested Reviewers: jason

Changed files:
- A: gcc/testsuite/g++.dg/tree-ssa/constexpr-array-padding.C
- M: gcc/expr.cc
- M: gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C

 gcc/expr.cc                                   | 14 +++-
 .../g++.dg/tree-ssa/constexpr-array-padding.C | 75 +++++++++++++++++++
 gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C     | 10 ++-
 3 files changed, 93 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/constexpr-array-padding.C

diff --git a/gcc/expr.cc b/gcc/expr.cc
index a9157dc5d415b..dae4afccdf08b 100644
--- a/gcc/expr.cc
+++ b/gcc/expr.cc
@@ -7224,9 +7224,17 @@ categorize_ctor_elements_1 (const_tree ctor, 
HOST_WIDE_INT *p_nz_elts,
          break;
 
        case STRING_CST:
-         nz_elts += mult * TREE_STRING_LENGTH (value);
-         unique_nz_elts += TREE_STRING_LENGTH (value);
-         init_elts += mult * TREE_STRING_LENGTH (value);
+         {
+           HOST_WIDE_INT len = TREE_STRING_LENGTH (value);
+           nz_elts += mult * len;
+           unique_nz_elts += len;
+           /* Count implicit trailing zeros as well, so that the density
+              heuristic can prefer clearing to copying a mostly-zero
+              aggregate from read-only data.  */
+           if (tree_fits_shwi_p (TYPE_SIZE_UNIT (elt_type)))
+             len = MAX (len, tree_to_shwi (TYPE_SIZE_UNIT (elt_type)));
+           init_elts += mult * len;
+         }
          break;
 
        case RAW_DATA_CST:
diff --git a/gcc/testsuite/g++.dg/tree-ssa/constexpr-array-padding.C 
b/gcc/testsuite/g++.dg/tree-ssa/constexpr-array-padding.C
new file mode 100644
index 0000000000000..e2553bc7efff7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/constexpr-array-padding.C
@@ -0,0 +1,75 @@
+// Keep implicit zero padding out of constant aggregate copies.
+// { dg-do run { target c++14 } }
+// { dg-options "-O1 -fdump-tree-gimple" }
+
+struct Buffer
+{
+  unsigned before;
+  char data[4096];
+  unsigned after;
+};
+
+constexpr Buffer make_buffer ()
+{
+  Buffer b = {};
+  b.before = 123;
+  b.data[0] = 'a';
+  b.data[2] = 'b';
+  b.data[4] = 'c';
+  b.after = 456;
+  return b;
+}
+
+__attribute__((noipa)) Buffer foo ()
+{
+  constexpr auto b = make_buffer ();
+  return b;
+}
+
+// A small amount of zero padding should not cause a mostly nonzero aggregate
+// to be cleared and initialized element by element.
+struct Dense
+{
+  unsigned values[64];
+  char label[32];
+};
+
+constexpr Dense make_dense ()
+{
+  Dense b = {};
+  for (unsigned i = 0; i < 64; ++i)
+    b.values[i] = i + 1;
+  b.label[0] = 'a';
+  b.label[1] = 'b';
+  b.label[2] = 'c';
+  return b;
+}
+
+__attribute__((noipa)) Dense bar ()
+{
+  constexpr auto b = make_dense ();
+  return b;
+}
+
+int main ()
+{
+  auto b = foo ();
+  const char expected[] = { 'a', 0, 'b', 0, 'c' };
+  if (b.before != 123 || b.after != 456)
+    __builtin_abort ();
+  for (unsigned i = 0; i < sizeof b.data; ++i)
+    if (b.data[i] != (i < sizeof expected ? expected[i] : 0))
+      __builtin_abort ();
+
+  auto d = bar ();
+  for (unsigned i = 0; i < 64; ++i)
+    if (d.values[i] != i + 1)
+      __builtin_abort ();
+  const char label[] = "abc";
+  for (unsigned i = 0; i < sizeof d.label; ++i)
+    if (d.label[i] != (i < sizeof label ? label[i] : 0))
+      __builtin_abort ();
+}
+
+// Clear only the sparse aggregate instead of copying its zero padding.
+// { dg-final { scan-tree-dump-times "<retval> = {}" 1 "gimple" } }
diff --git a/gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C 
b/gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C
index 65f7ddb8cf00b..a3dd41ff2eed7 100644
--- a/gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C
+++ b/gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C
@@ -3,6 +3,10 @@
 
 using uint = unsigned int;
 
+// Use a byte-sized enum so the initializer remains a CONSTRUCTOR rather than
+// becoming a STRING_CST, preserving the stores this test exercises.
+enum class byte : unsigned char {};
+
 template<typename C, uint S>
 struct FixBuf
 {
@@ -71,10 +75,10 @@ template<uint N, uint S, bool BOS = 0>
 constexpr
 auto fixbuf()
 {
-       FixBuf<char, S> fb;
-       OutBuf<char> ob{fb};
+       FixBuf<byte, S> fb;
+       OutBuf<byte> ob{fb};
        for (uint i = 0; i <= N; ++i) {
-               ob << i << static_cast<char>(i == N ? 0 : ' ');
+               ob << i << static_cast<byte>(i == N ? 0 : ' ');
        }
        return BufOrSize<BOS>::Select(fb, ob);
 }
-- 
2.54.0

Reply via email to