Implement P0840, language support for empty objects.
authorJason Merrill <jason@redhat.com>
Wed, 3 Oct 2018 15:56:29 +0000 (11:56 -0400)
committerJason Merrill <jason@gcc.gnu.org>
Wed, 3 Oct 2018 15:56:29 +0000 (11:56 -0400)
The [[no_unique_address]] attribute on a non-static data member
enables the equivalent of the empty base optimization.

gcc/cp/
* tree.c (handle_no_unique_addr_attribute): New.
(cxx_attribute_table): Add [[no_unique_address]].
* class.c (field_poverlapping_p): New.
(layout_class_type): Check it.  Adjust DECL_SIZE of potentially
overlapping fields.
(layout_empty_base_or_field): Rename from layout_empty_base, handle
FIELD_DECL as well.
(build_base_field, record_subobject_offsets): Adjust.
c-family/
* c-lex.c (c_common_has_attribute): Add no_unique_address.

From-SVN: r264813

12 files changed:
gcc/c-family/ChangeLog
gcc/c-family/c-lex.c
gcc/cp/ChangeLog
gcc/cp/class.c
gcc/cp/tree.c
gcc/testsuite/g++.dg/abi/empty4.C
gcc/testsuite/g++.dg/abi/empty5.C
gcc/testsuite/g++.dg/abi/empty8.C
gcc/testsuite/g++.dg/abi/no_unique_address1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/abi/no_unique_address2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/abi/no_unique_address3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/no_unique_address1.C [new file with mode: 0644]

index 015ca8a2fb99636bca9542a497503f7d4dc6a15c..f2c1f0988f919e914c138e63d6ea8a3edb24e161 100644 (file)
@@ -1,3 +1,7 @@
+2018-10-01  Jason Merrill  <jason@redhat.com>
+
+       * c-lex.c (c_common_has_attribute): Add no_unique_address.
+
 2018-10-01  Eric Botcazou  <ebotcazou@adacore.com>
 
        * c-ada-spec.c (get_underlying_decl): Get to the main type variant.
index ac58335cf3ad908509c321556402cb98b378826e..78a132484fc8df176d5913b7e0951587ad1875cf 100644 (file)
@@ -356,6 +356,8 @@ c_common_has_attribute (cpp_reader *pfile)
                       || is_attribute_p ("nodiscard", attr_name)
                       || is_attribute_p ("fallthrough", attr_name))
                result = 201603;
+             else if (is_attribute_p ("no_unique_address", attr_name))
+               result = 20180312;
              if (result)
                attr_name = NULL_TREE;
            }
index 53ff0eb0a27d5e13e2bf8b015bf52f0ad3620fc0..40fa94fee4e4562f251feb2ced31eb901e7572c6 100644 (file)
@@ -1,3 +1,15 @@
+2018-10-03  Jason Merrill  <jason@redhat.com>
+
+       Implement P0840, language support for empty objects.
+       * tree.c (handle_no_unique_addr_attribute): New.
+       (cxx_attribute_table): Add [[no_unique_address]].
+       * class.c (field_poverlapping_p): New.
+       (layout_class_type): Check it.  Adjust DECL_SIZE of potentially
+       overlapping fields.
+       (layout_empty_base_or_field): Rename from layout_empty_base, handle
+       FIELD_DECL as well.
+       (build_base_field, record_subobject_offsets): Adjust.
+
 2018-10-03  Martin Liska  <mliska@suse.cz>
 
        PR gcov-profile/86109
index 9ca464418717f36439ddacd1a172c71853d2e1c1..1789d1ecb70ad34a9637fc5198a3169af5bb533e 100644 (file)
@@ -180,8 +180,6 @@ static tree build_vtable (tree, tree, tree);
 static void initialize_vtable (tree, vec<constructor_elt, va_gc> *);
 static void layout_nonempty_base_or_field (record_layout_info,
                                           tree, tree, splay_tree);
-static tree end_of_class (tree, int);
-static bool layout_empty_base (record_layout_info, tree, tree, splay_tree);
 static void accumulate_vtbl_inits (tree, tree, tree, tree, tree,
                                   vec<constructor_elt, va_gc> **);
 static void dfs_accumulate_vtbl_inits (tree, tree, tree, tree, tree,
@@ -202,7 +200,6 @@ static int record_subobject_offset (tree, tree, splay_tree);
 static int check_subobject_offset (tree, tree, splay_tree);
 static int walk_subobject_offsets (tree, subobject_offset_fn,
                                   tree, splay_tree, tree, int);
-static void record_subobject_offsets (tree, tree, splay_tree, bool);
 static int layout_conflict_p (tree, tree, splay_tree, int);
 static int splay_tree_compare_integer_csts (splay_tree_key k1,
                                            splay_tree_key k2);
@@ -3960,20 +3957,52 @@ walk_subobject_offsets (tree type,
   return 0;
 }
 
-/* Record all of the empty subobjects of TYPE (either a type or a
-   binfo).  If IS_DATA_MEMBER is true, then a non-static data member
-   is being placed at OFFSET; otherwise, it is a base class that is
-   being placed at OFFSET.  */
+/* Return true iff FIELD_DECL DECL is potentially overlapping.  */
+
+static bool
+field_poverlapping_p (tree decl)
+{
+  /* Base fields are actually potentially overlapping, but C++ bases go through
+     a different code path based on binfos, and ObjC++ base fields are laid out
+     in objc-act, so we don't want layout_class_type to mess with them.  */
+  if (DECL_FIELD_IS_BASE (decl))
+    {
+      gcc_checking_assert (c_dialect_objc ());
+      return false;
+    }
+
+  return lookup_attribute ("no_unique_address",
+                          DECL_ATTRIBUTES (decl));
+}
+
+/* Record all of the empty subobjects of DECL_OR_BINFO.  */
 
 static void
-record_subobject_offsets (tree type,
-                         tree offset,
-                         splay_tree offsets,
-                         bool is_data_member)
+record_subobject_offsets (tree decl_or_binfo,
+                         splay_tree offsets)
 {
+  tree type, offset;
+  bool overlapping, vbases_p;
+
+  if (DECL_P (decl_or_binfo))
+    {
+      tree decl = decl_or_binfo;
+      type = TREE_TYPE (decl);
+      offset = byte_position (decl);
+      overlapping = field_poverlapping_p (decl);
+      vbases_p = true;
+    }
+  else
+    {
+      type = BINFO_TYPE (decl_or_binfo);
+      offset = BINFO_OFFSET (decl_or_binfo);
+      overlapping = true;
+      vbases_p = false;
+    }
+
   tree max_offset;
   /* If recording subobjects for a non-static data member or a
-     non-empty base class , we do not need to record offsets beyond
+     non-empty base class, we do not need to record offsets beyond
      the size of the biggest empty class.  Additional data members
      will go at the end of the class.  Additional base classes will go
      either at offset zero (if empty, in which case they cannot
@@ -3985,13 +4014,13 @@ record_subobject_offsets (tree type,
      other empty classes might later be placed) or at the end of the
      class (where other objects might then be placed, so other empty
      subobjects might later overlap).  */
-  if (is_data_member
-      || !is_empty_class (BINFO_TYPE (type)))
+  if (!overlapping
+      || !is_empty_class (type))
     max_offset = sizeof_biggest_empty_class;
   else
     max_offset = NULL_TREE;
   walk_subobject_offsets (type, record_subobject_offset, offset,
-                         offsets, max_offset, is_data_member);
+                         offsets, max_offset, vbases_p);
 }
 
 /* Returns nonzero if any of the empty subobjects of TYPE (located at
@@ -4151,55 +4180,80 @@ empty_base_at_nonzero_offset_p (tree type,
    type.  Return nonzero iff we added it at the end.  */
 
 static bool
-layout_empty_base (record_layout_info rli, tree binfo,
-                  tree eoc, splay_tree offsets)
+layout_empty_base_or_field (record_layout_info rli, tree binfo_or_decl,
+                           splay_tree offsets)
 {
   tree alignment;
-  tree basetype = BINFO_TYPE (binfo);
   bool atend = false;
+  tree binfo = NULL_TREE;
+  tree decl = NULL_TREE;
+  tree type;
+  if (TREE_CODE (binfo_or_decl) == TREE_BINFO)
+    {
+      binfo = binfo_or_decl;
+      type = BINFO_TYPE (binfo);
+    }
+  else
+    {
+      decl = binfo_or_decl;
+      type = TREE_TYPE (decl);
+    }
 
-  /* This routine should only be used for empty classes.  */
-  gcc_assert (is_empty_class (basetype));
-  alignment = ssize_int (CLASSTYPE_ALIGN_UNIT (basetype));
+  /* On some platforms (ARM), even empty classes will not be
+     byte-aligned.  */
+  tree eoc = round_up_loc (input_location,
+                          rli_size_unit_so_far (rli),
+                          CLASSTYPE_ALIGN_UNIT (type));
 
-  if (!integer_zerop (BINFO_OFFSET (binfo)))
-    propagate_binfo_offsets
-      (binfo, size_diffop_loc (input_location,
-                              size_zero_node, BINFO_OFFSET (binfo)));
+  /* This routine should only be used for empty classes.  */
+  gcc_assert (is_empty_class (type));
+  alignment = size_int (CLASSTYPE_ALIGN_UNIT (type));
 
   /* This is an empty base class.  We first try to put it at offset
      zero.  */
-  if (layout_conflict_p (binfo,
-                        BINFO_OFFSET (binfo),
+  tree offset = size_zero_node;
+  if (layout_conflict_p (type,
+                        offset,
                         offsets,
                         /*vbases_p=*/0))
     {
       /* That didn't work.  Now, we move forward from the next
         available spot in the class.  */
       atend = true;
-      propagate_binfo_offsets (binfo, fold_convert (ssizetype, eoc));
+      offset = eoc;
       while (1)
        {
-         if (!layout_conflict_p (binfo,
-                                 BINFO_OFFSET (binfo),
+         if (!layout_conflict_p (type,
+                                 offset,
                                  offsets,
                                  /*vbases_p=*/0))
            /* We finally found a spot where there's no overlap.  */
            break;
 
          /* There's overlap here, too.  Bump along to the next spot.  */
-         propagate_binfo_offsets (binfo, alignment);
+         offset = size_binop (PLUS_EXPR, offset, alignment);
        }
     }
 
-  if (CLASSTYPE_USER_ALIGN (basetype))
+  if (CLASSTYPE_USER_ALIGN (type))
     {
-      rli->record_align = MAX (rli->record_align, CLASSTYPE_ALIGN (basetype));
+      rli->record_align = MAX (rli->record_align, CLASSTYPE_ALIGN (type));
       if (warn_packed)
-       rli->unpacked_align = MAX (rli->unpacked_align, CLASSTYPE_ALIGN (basetype));
+       rli->unpacked_align = MAX (rli->unpacked_align, CLASSTYPE_ALIGN (type));
       TYPE_USER_ALIGN (rli->t) = 1;
     }
 
+  if (binfo)
+    /* Adjust BINFO_OFFSET (binfo) to be exactly OFFSET.  */
+    propagate_binfo_offsets (binfo,
+                            size_diffop (offset, BINFO_OFFSET (binfo)));
+  else
+    {
+      DECL_FIELD_OFFSET (decl) = offset;
+      DECL_FIELD_BIT_OFFSET (decl) = bitsize_zero_node;
+      SET_DECL_OFFSET_ALIGN (decl, BITS_PER_UNIT);
+    }
+
   return atend;
 }
 
@@ -4277,15 +4331,7 @@ build_base_field (record_layout_info rli, tree binfo,
     }
   else
     {
-      tree eoc;
-      bool atend;
-
-      /* On some platforms (ARM), even empty classes will not be
-        byte-aligned.  */
-      eoc = round_up_loc (input_location,
-                     rli_size_unit_so_far (rli),
-                     CLASSTYPE_ALIGN_UNIT (basetype));
-      atend = layout_empty_base (rli, binfo, eoc, offsets);
+      bool atend = layout_empty_base_or_field (rli, binfo, offsets);
       /* A nearly-empty class "has no proper base class that is empty,
         not morally virtual, and at an offset other than zero."  */
       if (!BINFO_VIRTUAL_P (binfo) && CLASSTYPE_NEARLY_EMPTY_P (t))
@@ -4323,10 +4369,7 @@ build_base_field (record_layout_info rli, tree binfo,
     }
 
   /* Record the offsets of BINFO and its base subobjects.  */
-  record_subobject_offsets (binfo,
-                           BINFO_OFFSET (binfo),
-                           offsets,
-                           /*is_data_member=*/false);
+  record_subobject_offsets (binfo, offsets);
 
   return next_field;
 }
@@ -5886,10 +5929,11 @@ end_of_base (tree binfo)
 
 /* Returns the offset of the byte just past the end of the base class
    with the highest offset in T.  If INCLUDE_VIRTUALS_P is zero, then
-   only non-virtual bases are included.  */
+   only non-virtual bases are included.  If INCLUDE_FIELDS_P is true,
+   then also consider non-static data members.  */
 
 static tree
-end_of_class (tree t, int include_virtuals_p)
+end_of_class (tree t, bool include_virtuals_p, bool include_fields_p = false)
 {
   tree result = size_zero_node;
   vec<tree, va_gc> *vbases;
@@ -5912,6 +5956,16 @@ end_of_class (tree t, int include_virtuals_p)
        result = offset;
     }
 
+  if (include_fields_p)
+    for (tree field = TYPE_FIELDS (t); field; field = DECL_CHAIN (field))
+      if (TREE_CODE (field) == FIELD_DECL)
+       {
+         offset = size_binop (PLUS_EXPR, DECL_FIELD_OFFSET (field),
+                              DECL_SIZE_UNIT (field));
+         if (tree_int_cst_lt (result, offset))
+           result = offset;
+       }
+
   if (include_virtuals_p)
     for (vbases = CLASSTYPE_VBASECLASSES (t), i = 0;
         vec_safe_iterate (vbases, i, &base_binfo); i++)
@@ -6098,6 +6152,27 @@ layout_class_type (tree t, tree *virtuals_p)
 
       padding = NULL_TREE;
 
+      bool might_overlap = field_poverlapping_p (field);
+
+      if (might_overlap && CLASS_TYPE_P (type)
+         && CLASSTYPE_NON_LAYOUT_POD_P (type))
+       {
+         /* if D is a potentially-overlapping data member, update sizeof(C) to
+            max (sizeof(C), offset(D)+max (nvsize(D), dsize(D))).  */
+         tree nvsize = CLASSTYPE_SIZE_UNIT (type);
+         tree dsize = end_of_class (type, /*vbases*/true, /*fields*/true);
+         if (tree_int_cst_le (dsize, nvsize))
+           {
+             DECL_SIZE_UNIT (field) = nvsize;
+             DECL_SIZE (field) = CLASSTYPE_SIZE (type);
+           }
+         else
+           {
+             DECL_SIZE_UNIT (field) = dsize;
+             DECL_SIZE (field) = bit_from_pos (dsize, bitsize_zero_node);
+           }
+       }
+
       /* If this field is a bit-field whose width is greater than its
         type, then there are some special rules for allocating
         it.  */
@@ -6164,15 +6239,14 @@ layout_class_type (tree t, tree *virtuals_p)
          /* We must also reset the DECL_MODE of the field.  */
          SET_DECL_MODE (field, TYPE_MODE (type));
        }
+      else if (might_overlap && is_empty_class (type))
+       layout_empty_base_or_field (rli, field, empty_base_offsets);
       else
        layout_nonempty_base_or_field (rli, field, NULL_TREE,
                                       empty_base_offsets);
 
       /* Remember the location of any empty classes in FIELD.  */
-      record_subobject_offsets (TREE_TYPE (field),
-                               byte_position(field),
-                               empty_base_offsets,
-                               /*is_data_member=*/true);
+      record_subobject_offsets (field, empty_base_offsets);
 
       /* If a bit-field does not immediately follow another bit-field,
         and yet it starts in the middle of a byte, we have failed to
index c6f216dab4b907a224ce9e8dccc887abde685a86..e74b79c44e06236daccc7eaf1e2daaac81b2e2a9 100644 (file)
@@ -4428,6 +4428,31 @@ handle_nodiscard_attribute (tree *node, tree name, tree /*args*/,
   return NULL_TREE;
 }
 
+/* Handle a C++2a "no_unique_address" attribute; arguments as in
+   struct attribute_spec.handler.  */
+static tree
+handle_no_unique_addr_attribute (tree* node,
+                                tree name,
+                                tree /*args*/,
+                                int /*flags*/,
+                                bool* no_add_attrs)
+{
+  if (TREE_CODE (*node) != FIELD_DECL)
+    {
+      warning (OPT_Wattributes, "%qE attribute can only be applied to "
+              "non-static data members", name);
+      *no_add_attrs = true;
+    }
+  else if (DECL_C_BIT_FIELD (*node))
+    {
+      warning (OPT_Wattributes, "%qE attribute cannot be applied to "
+              "a bit-field", name);
+      *no_add_attrs = true;
+    }
+
+  return NULL_TREE;
+}
+
 /* Table of valid C++ attributes.  */
 const struct attribute_spec cxx_attribute_table[] =
 {
@@ -4449,6 +4474,8 @@ const struct attribute_spec std_attribute_table[] =
     handle_unused_attribute, NULL },
   { "nodiscard", 0, 0, false, false, false, false,
     handle_nodiscard_attribute, NULL },
+  { "no_unique_address", 0, 0, true, false, false, false,
+    handle_no_unique_addr_attribute, NULL },
   { NULL, 0, 0, false, false, false, false, NULL, NULL }
 };
 
index d20a55cf7fcaa4c72156c251973d43c49d922a8f..8cc10a9fb0f42e5897e190b31a5f6cd9cb59b67e 100644 (file)
@@ -37,6 +37,16 @@ struct B
   B (long c) {m = c;}
 };
 
+#if __cpp_attributes
+struct B2
+{
+  [[no_unique_address]] Inter empty;
+  NonPod m;
+
+  B2 (long c) {m = c;}
+};
+#endif
+
 struct C : NonPod, Inter
 {
   C (long c) : NonPod (c), Inter () {}
@@ -65,6 +75,7 @@ int main ()
   if (b2.m.m != 0x32333435)
     return 2;  // we copied padding, which clobbered b2.m.m
   
+  {
   B c (0x12131415);
   was = c.m.m;
   c = 0x22232425;
@@ -76,6 +87,22 @@ int main ()
 
   if (c.m.m != 0x22232425)
     return 4;
+  }
+#if __cpp_attributes
+  {
+  B2 c (0x12131415);
+  was = c.m.m;
+  c = 0x22232425;
+  if (was != now)
+    return 3;
+  
+  B2 d (0x32333435);
+  c.empty = d.empty;
+
+  if (c.m.m != 0x22232425)
+    return 4;
+  }    
+#endif
 
   C e (0x32333435);
 
index c3717727e21469e72f51025c007b81cc94f1cb2a..9ad66d91f61043e7c5b9e83a970726d0fa6bf6f5 100644 (file)
@@ -9,9 +9,20 @@ struct B {
 
 struct C : public B, public A {};
 
+#if __cpp_attributes
+struct C2 : public B
+{
+  [[no_unique_address]] A a;
+} c2;
+#endif
+
 C c;
 
 int main () {
   if ((void*) (A*) &c != &c)
     return 1;
+#if __cpp_attributes
+  if ((void*)&c2.a != &c2)
+    return 2;
+#endif
 }
index a5287b15fa0b3591768ce3a4ac9c49b8eb4d02e1..6d7954ec291b133235a2db874d098b322a15235d 100644 (file)
@@ -5,10 +5,20 @@ struct E1 {};
 struct E2 : public E1 {};
 struct S1 { int i; };
 struct S2 : public S1, E2 {};
+#if __cpp_attributes
+struct S22 : public S1
+{
+  [[no_unique_address]] E2 e2;
+} s22;
+#endif
 
 S2 s2;
 
 int main () {
   if ((char *)(E2*) &s2 != (char *)&s2)
     return 1;
+#if __cpp_attributes
+  if ((char *)&s22.e2 != (char *)&s22)
+    return 2;
+#endif
 }
diff --git a/gcc/testsuite/g++.dg/abi/no_unique_address1.C b/gcc/testsuite/g++.dg/abi/no_unique_address1.C
new file mode 100644 (file)
index 0000000..853726d
--- /dev/null
@@ -0,0 +1,38 @@
+// { dg-do run { target c++2a } }
+
+struct B { };
+
+struct A
+{
+  [[no_unique_address]] B b;
+  int i;
+};
+
+struct C
+{
+  B b;
+  int i;
+};
+
+struct D: B { };
+
+struct E
+{
+  B b [[no_unique_address]];
+  D d [[no_unique_address]];
+};
+
+constexpr bool same (void *x, void *y) { return x == y; }
+
+int main()
+{
+  A a;
+  if (!same(&a.b, &a.i))
+    __builtin_abort();
+  C c;
+  if (same(&c.b, &c.i))
+    __builtin_abort();
+  E e;
+  if (same (&e.b, &e.d))
+    __builtin_abort();
+}
diff --git a/gcc/testsuite/g++.dg/abi/no_unique_address2.C b/gcc/testsuite/g++.dg/abi/no_unique_address2.C
new file mode 100644 (file)
index 0000000..bef6d5b
--- /dev/null
@@ -0,0 +1,43 @@
+// { dg-do compile { target c++11 } }
+
+struct A
+{
+  virtual void f();
+  char c;
+};
+
+struct B1 : A
+{
+  char c2;
+};
+
+struct B2
+{
+  A a [[no_unique_address]];
+  char c2;
+};
+
+struct C
+{
+  char c;
+};
+
+struct D: virtual C
+{
+  virtual void f();
+};
+
+struct B3: D
+{
+  char c2;
+};
+
+struct B4
+{
+  D d [[no_unique_address]];
+  char c2;
+};
+
+#define SA(X) static_assert ((X), #X)
+SA (sizeof (B2) == sizeof (B1));
+SA (sizeof (B3) == sizeof (B4));
diff --git a/gcc/testsuite/g++.dg/abi/no_unique_address3.C b/gcc/testsuite/g++.dg/abi/no_unique_address3.C
new file mode 100644 (file)
index 0000000..0bda777
--- /dev/null
@@ -0,0 +1,19 @@
+// { dg-do compile { target c++11 } }
+
+struct A {
+  unsigned char i : 1;
+};
+
+struct B: A
+{
+  unsigned char j : 7;
+};
+
+struct B2
+{
+  [[no_unique_address]] A a;
+  unsigned char j : 7;
+};
+
+#define SA(X) static_assert ((X), #X)
+SA (sizeof (B) == sizeof (B2));
diff --git a/gcc/testsuite/g++.dg/cpp2a/no_unique_address1.C b/gcc/testsuite/g++.dg/cpp2a/no_unique_address1.C
new file mode 100644 (file)
index 0000000..dd01939
--- /dev/null
@@ -0,0 +1,13 @@
+// { dg-do compile { target c++2a } }
+
+[[no_unique_address]] struct B { }; // { dg-warning "attribute" }
+[[no_unique_address]] int i;       // { dg-warning "attribute" }
+[[no_unique_address]] void f();            // { dg-warning "attribute" }
+
+struct A
+{
+  [[no_unique_address]] B b;
+  [[no_unique_address]] void f();   // { dg-warning "attribute" }
+  [[no_unique_address]] static B c; // { dg-warning "attribute" }
+  int i;
+};