https://gcc.gnu.org/g:fcca05f5c87cc80d82122724d042dca39356969a

commit r17-5198-gfcca05f5c87cc80d82122724d042dca39356969a
Author: From: Jonas Hansen <[email protected]>
Date:   Wed Oct 14 07:27:14 2026 -0600

    [PATCH v2 1/2] middle-end: Account for implicit zero padding in string 
initializers
    
    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]>

Diff:
---
 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(-)

diff --git a/gcc/expr.cc b/gcc/expr.cc
index 94e3281b796d..96e1bf6d19ae 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 000000000000..e2553bc7efff
--- /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 65f7ddb8cf00..a3dd41ff2eed 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);
 }

Reply via email to