[v1] middle-end: Account for implicit zero padding in string initializers

Message ID bmm.hmu3ig7wcq.gcc.gcc.hrjonashansen.237.1.0@forge-stage.sourceware.org
State New
Headers
Series [v1] middle-end: Account for implicit zero padding in string initializers |

Commit Message

Jonas Hansen via Sourceware Forge Oct. 2, 2026, 11:57 a.m. UTC
  From: Jonas Hansen <jhansen@roku.com>

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 <jhansen@roku.com>
---
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 <forge-bot+hrjonashansen@forge-stage.sourceware.org>.
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
  

Comments

Drea Pinski via Sourceware Forge Oct. 2, 2026, 12:14 p.m. UTC | #1
Drea Pinski (pinskia) <pinskia@gcc.gnu.org>) commented on the code:


> +++ gcc/testsuite/g++.dg/tree-ssa/ssa-dse-1.C
> @@ -5,1 +5,4 @@
>  
> +// 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 {};
> 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.

I think we want to keep both versions of the testcase. More to the point both versions should be optimized.

Can you show how dse1 output differs with/without the patch? And maybe we need to change the scan and add a new version of the testcase. 


--
https://forge.sourceware.org/gcc/gcc/pulls/237#issuecomment-7651
  

Patch

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);
 }