Subject: bug#76559: 31.0.50; [-O3 + PGTK] Crash when 'copying as kill'/'killing word'

From: Po Lu <[email-masked]>
Date: Sat, 01 Mar 2025 19:25:06 +0800

Pip Cet <[email-masked]> writes:

> My understanding is it is safe to access an entire union at once, as the
> old code did, and that GCC concluded based on the undefined behavior
> elsewhere that the only possible union members were those with the holes
> in the right place.

I have reached the bottom of this unpleasant business.  The following
reproducer does not require enabling LTO or type punning (or casts at
all, come to that), just "gcc-15 -std=c99 -O3".

GCC's intraprocedural SRA pass operates by collecting all references to
decls of aggregate type in a procedure, and creating a tree in which
every node represents all references to each decl which share an
identical offset and size such that the most general reference appears
nearest to the top.  In creating such a tree, it assumes that a sorting
function will arrange a vector of references in decreasing order of
generality: larger references appear before smaller ones, aggregate
references before complex or vector references, integral reference
before reals, references to types of greater precision before those of
lesser precision, and, all else being equal, the sorting is stabilized
by reference to the order in which the operand types are defined.
Accordingly, the first element of the vector is assumed to provide the
root of a tree.  The compiler proceeds by "totally scalarizing" a decl's
references from its root, if it is of record type and every field is
itself scalar or scalarizable, and scalarizing all statements that
reference the same decl with the same offset and size in the manner
previously defined.

Many properties of every reference to a declaration are fixed at this
time, such as whether the declaration is completely encompassed by
scalarized variables, which fails when a union or a struct is excluded
from a decl's access tree for being identical in offset and size, but
has a different data layout from the type of the access at its root.

In Emacs's case it should be possible to circumvent this problem by
creating an incomplete declaration of `union buffered_input_event'
before `struct input_event', so that the union may be defined first and
inhibit total scalarization, but this will not address the oversight in
GCC.  This eliminates the miscompilation, but also a minor class of
optimizations.  I believe this optimization must be re-engineered if it
is optimally to scalarize combinations of union and member references.

diff --git a/gcc/tree-sra.cc b/gcc/tree-sra.cc
index c26559edc66..edd9b477b39 100644
--- a/gcc/tree-sra.cc
+++ b/gcc/tree-sra.cc
@@ -2483,6 +2483,19 @@ sort_and_splice_var_accesses (tree var)
 		}
 	      unscalarizable_region = true;
 	    }
+	  else if (AGGREGATE_TYPE_P (access->type)
+		   && AGGREGATE_TYPE_P (ac2->type)
+		   && !types_compatible_p (access->type, ac2->type))
+	    {
+	      if (dump_file && (dump_flags & TDF_DETAILS))
+		{
+		  fprintf (dump_file, "Cannot scalarize the following region "
+			   "because data may be scalarized in an incompatible "
+			   "format.\n");
+		  dump_access (dump_file, access, false);
+		}
+	      unscalarizable_region = true;
+	    }
 	  else if (types_risk_mangled_binary_repr_p (access->type, ac2->type))
 	    {
 	      if (dump_file && (dump_flags & TDF_DETAILS))