On Fri, Sep 27, 2024 at 12:14:47PM +0200, Richard Biener wrote:
> I can investigate a bit when there's a testcase showing the issue.

The testcase is pr78687.C with Marek's cp-gimplify.cc patch.

Or the
struct S { union U { struct T { void *a, *b, *c, *d, *e; } t; struct V {} v; } 
u; unsigned long w; };
void bar (struct S *);

void
foo ()
{
  struct S s = { .u = { .v = {} }, .w = 2 };
  bar (&s);
}
I've mentioned shows the same behavior except for SRA (which
is there of course prevented through the object escaping).
Though, not really sure right now if this reduced testcase
in C or C++ actually isn't supposed to clear the whole object rather than
just the initialized fields and what exactly is CONSTRUCTOR_NO_CLEARING
vs. !CONSTRUCTOR_NO_CLEARING supposed to mean.

On pr78687.C with Marek's patch, CONSTRUCTOR_NO_CLEARING is cleared with
  /* The result of a constexpr function must be completely initialized.

     However, in C++20, a constexpr constructor doesn't necessarily have
     to initialize all the fields, so we don't clear CONSTRUCTOR_NO_CLEARING
     in order to detect reading an unitialized object in constexpr instead
     of value-initializing it.  (reduced_constant_expression_p is expected to
     take care of clearing the flag.)  */
  if (TREE_CODE (result) == CONSTRUCTOR
      && (cxx_dialect < cxx20
          || !DECL_CONSTRUCTOR_P (fun)))
    clear_no_implicit_zero (result);

generic.texi says:
"Unrepresented fields will be cleared (zeroed), unless the
CONSTRUCTOR_NO_CLEARING flag is set, in which case their value becomes
undefined."
Now, for RECORD_TYPE, I think the !CONSTRUCTOR_NO_CLEARING meaning is clear,
if the flag isn't set, then if there is no constructor_elt for certain
FIELD_DECL, that FIELD_DECL is implicitly zeroed.  The state of padding bits
is fuzzy, we don't really have a flag whether the CONSTRUCTOR clears also
padding bits (aka C++ zero initialization) or not.
The problem above is with UNION_TYPE.  If the CONSTRUCTOR for it is empty,
that should IMHO still imply zero initialization of the whole thing, we
don't really know which union member is active.  But if the CONSTRUCTOR
has one elt (it should never have more than one), shouldn't that mean
(unless there is a new flag which says that padding bits are cleared too)
that CONSTRUCTOR_NO_CLEARING and !CONSTRUCTOR_NO_CLEARING behave the same,
in particular that the selected FIELD_DECL is initialized to whatever
the initializer is but nothing else is?

The reason why the gimplifier clears the whole struct is because
on (with Marek's patch on the pr78687.C testcase)
D.10137 = 
{._storage={.D.9542={.D.9123={._tail={.D.9181={._tail={.D.9240={._head={}}}}}}},
 ._which=2}};
or that
s = {.u={.v={}}, .w=2};
in the above testcase, categorize_ctor_elements yields
valid_const_initializer = true
num_nonzero_elements = 1
num_unique_nonzero_elements = 1
num_ctor_elements = 1
complete_p = false
- there is a single non-empty initializer in both CONSTRUCTORs,
they aren't CONSTRUCTOR_NO_CLEARING and the reason complete_p is false
is that categorize_ctor_elements_1 does:
  if (*p_complete && !complete_ctor_at_level_p (TREE_TYPE (ctor),
                                                num_fields, elt_type))
    *p_complete = 0;
  else if (*p_complete > 0
           && type_has_padding_at_level_p (TREE_TYPE (ctor)))
    *p_complete = -1;
and for UNION_TYPE/QUAL_UNION_TYPE complete_ctor_at_level_p does:
      if (num_elts == 0)
        return false;
...
      /* ??? We could look at each element of the union, and find the
         largest element.  Which would avoid comparing the size of the
         initialized element against any tail padding in the union.
         Doesn't seem worth the effort...  */
      return simple_cst_equal (TYPE_SIZE (type), TYPE_SIZE (last_type)) == 1;
Now, given the documentation of categorize_ctor_elements:
   * whether the constructor is complete -- in the sense that every
     meaningful byte is explicitly given a value --
     and place it in *P_COMPLETE:
     -  0 if any field is missing
     -  1 if all fields are initialized, and there's no padding
     - -1 if all fields are initialized, but there's padding
I'd argue this handling of UNION_TYPE/QUAL_UNION_TYPE is wrong
(though note that type_has_padding_at_level_p returns true if any of the
union members is smaller than the whole, rather than checking whether
the actually initialized one has the same size as whole), it will
set *p_complete to 0 as if any field is missing, even when no field
is missing, just the union has padding.

So, I think we should go with (but so far completely untested except
for pr78687.C which is optimized with Marek's patch and the above testcase
which doesn't have the clearing anymore) the following patch.

2024-09-27  Jakub Jelinek  <ja...@redhat.com>

        PR c++/116416
        * expr.cc (categorize_ctor_elements_1): Fix up union handling of
        *p_complete.  Clear it only if num_fields is 0 and the union has
        at least one FIELD_DECL, set to -1 if either union has no fields
        and non-zero size, or num_fields is 1 and complete_ctor_at_level_p
        returned false.
        * gimple-fold.cc (type_has_padding_at_level_p): Fix up UNION_TYPE
        handling, return also true for UNION_TYPE with no FIELD_DECLs
        and non-zero size, handle QUAL_UNION_TYPE like UNION_TYPE.

--- gcc/expr.cc.jj      2024-09-04 12:09:22.598808244 +0200
+++ gcc/expr.cc 2024-09-27 15:34:20.929282525 +0200
@@ -7218,7 +7218,36 @@ categorize_ctor_elements_1 (const_tree c
 
   if (*p_complete && !complete_ctor_at_level_p (TREE_TYPE (ctor),
                                                num_fields, elt_type))
-    *p_complete = 0;
+    {
+      if (TREE_CODE (TREE_TYPE (ctor)) == UNION_TYPE
+         || TREE_CODE (TREE_TYPE (ctor)) == QUAL_UNION_TYPE)
+       {
+         /* The union case is more complicated.  */
+         if (num_fields == 0)
+           {
+             /* If the CONSTRUCTOR doesn't have any elts, it is
+                incomplete if the union has at least one field.  */
+             for (tree f = TYPE_FIELDS (TREE_TYPE (ctor));
+                  f; f = DECL_CHAIN (f))
+               if (TREE_CODE (f) == FIELD_DECL)
+                 {
+                   *p_complete = 0;
+                   break;
+                 }
+             /* Otherwise it has padding if the union has non-zero size.  */
+             if (*p_complete > 0
+                 && !integer_zerop (TYPE_SIZE (TREE_TYPE (ctor))))
+               *p_complete = -1;
+           }
+         /* Otherwise the CONSTRUCTOR should have exactly one element.
+            It is then never incomplete, but if complete_ctor_at_level_p
+            returned false, it has padding.  */
+         else if (*p_complete > 0)
+           *p_complete = -1;
+       }
+      else
+       *p_complete = 0;
+    }
   else if (*p_complete > 0
           && type_has_padding_at_level_p (TREE_TYPE (ctor)))
     *p_complete = -1;
--- gcc/gimple-fold.cc.jj       2024-09-09 11:25:43.197048840 +0200
+++ gcc/gimple-fold.cc  2024-09-27 15:44:05.896355615 +0200
@@ -4814,12 +4814,22 @@ type_has_padding_at_level_p (tree type)
        return false;
       }
     case UNION_TYPE:
+    case QUAL_UNION_TYPE:
+      bool any_fields;
+      any_fields = false;
       /* If any of the fields is smaller than the whole, there is padding.  */
       for (tree f = TYPE_FIELDS (type); f; f = DECL_CHAIN (f))
-       if (TREE_CODE (f) == FIELD_DECL)
-         if (simple_cst_equal (TYPE_SIZE (TREE_TYPE (f)),
-                               TREE_TYPE (type)) != 1)
-           return true;
+       if (TREE_CODE (f) != FIELD_DECL)
+         continue;
+       else if (simple_cst_equal (TYPE_SIZE (TREE_TYPE (f)),
+                                  TYPE_SIZE (TREE_TYPE (type))) != 1)
+         return true;
+       else
+         any_fields = true;
+      /* If the union doesn't have any fields and still has non-zero size,
+        all of it is padding.  */
+      if (!any_fields && !integer_zerop (TYPE_SIZE (type)))
+       return true;
       return false;
     case ARRAY_TYPE:
     case COMPLEX_TYPE:


        Jakub

Reply via email to