cxx-ada-empty-class-storage

C++ Ada empty-class storage

The C++ Ada mapper gives complete empty-class objects zero storage and maps overlapping no_unique_address members as ordinary Ada storage.

StagedApplies in patchset orderStaged in 1.2.0

Why this is staged.

This patch rewrites needs_tail_padding_layout and reuses the as-base size machinery introduced by the staged tail-padding bundle, and does not apply without it. Held out of the published patchset with the rest of the layout family.

Depends on

Where it applies.

How each patchset treats this bundle on each GCC major
PatchsetGCC 13GCC 14GCC 15GCC 16
1.2.0 (latest)Staged13.2.0Staged14.2.0Staged15.3.0Staged16.2.0
1.1.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.2.0
1.0.1Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0
1.0.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0

Explanation.

C++ gives a complete empty-class object a nonzero size so distinct objects have distinct addresses. An empty base may consume no storage, while a named [[no_unique_address]] member remains a real subobject with an address and may need storage to remain distinct from another member of the same type. The unpatched mapper loses the complete-object rule.

For example, this code combines all three forms with ordinary member and array storage:

struct Empty {};

struct With_Empty_Base : Empty
{
  int value;
};

struct With_Empty_Member
{
  Empty member;
  int value;
};

struct With_No_Unique_Address
{
  [[no_unique_address]] Empty ignored;
  int value;
};

The unpatched mapper gives Empty a zero Object_Size. That happens to make the base, array, and [[no_unique_address]] layouts agree through GNAT's existing component padding, but standalone objects and ordinary empty members are too small:

type Empty is record
   null;
end record
with Convention => C_Pass_By_Copy;

type With_Empty_Base is record
   parent : aliased Empty;
   value : aliased int;
end record
with Convention => C_Pass_By_Copy;

type With_No_Unique_Address is record
   ignored : aliased Empty;
   value : aliased int;
end record
with Convention => C_Pass_By_Copy;

The corrected output separates the zero-size value used for empty-base optimization from the one-byte complete-object allocation. Artificial empty base fields are omitted. A named [[no_unique_address]] member is retained when it has distinct storage, but is omitted when C++ actually overlaps it with another field: Ada rejects overlapping selectable record components.

type Empty is record
   null;
end record
with Convention => C_Pass_By_Copy;

for Empty'Size use 0;
for Empty'Object_Size use 8;

type With_Empty_Base is record
   value : aliased int;
end record
with Convention => C_Pass_By_Copy;

type With_Empty_Member is record
   member : aliased Empty;
   value : aliased int;
end record
with Convention => C_Pass_By_Copy;

type With_No_Unique_Address is record
   value : aliased int;
end record
with Convention => C_Pass_By_Copy;

For two same-type overlapping members, C++ requires distinct addresses and GCC places them at bytes 0 and 1. They do not overlap one another, so both stay visible as ordinary one-byte Ada components and the two-byte object remains correct. The mapper therefore does not treat DECL_FIELD_ABI_IGNORED as an unconditional instruction to erase a field; it checks the actual field ranges.

The omitted single ignored selector is an access limitation, not hidden storage drift. Code that must name or take the address of that C++ subobject needs a C++ accessor wrapper. The direct Ada record still has the correct size and exposes every non-overlapped field.

The executable regression runs at -O0 and -O2. It compares C++ and Ada size, alignment, and available field positions for a standalone empty object, empty-base optimization, an ordinary empty member, an empty-object array, and C++20 single and repeated [[no_unique_address]] members. It also verifies that only the actually overlapping selector is omitted. A method-bearing empty class is a known-good control for the separate generated class-package form.

Patch.

Variant gcc-13-14

Applies to 13.2.0, 14.2.0. Source flavors: linux, darwin_arm64.

+255 −4 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +116−4modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -39 +39 @@static int dump_ada_declaration (pretty_printer *, tree, tree, int);
3939static void dump_ada_structure (pretty_printer *, tree, tree, bool, int);
4040static void dump_nested_types (pretty_printer *, tree, int);
4141static char *to_ada_name (const char *, bool *);
42Added line. static bool needs_empty_class_layout (tree);
4243
4344#define INDENT(SPACE) \
4445 do { int i; for (i = 0; i<SPACE; i++) pp_space (buffer); } while (0)
@@ -2319 +2320 @@dump_ada_template (pretty_printer *buffer, tree t, int spc)
23192320 pp_string (buffer, " is limited ");
23202321
23212322 dump_ada_node (buffer, instance, t, spc, false, false);
2322Removed line. if (!has_nontrivial_methods (instance) && !has_static_fields (instance))
2323Added line. if (!has_nontrivial_methods (instance) && !has_static_fields (instance)
2324Added line. && !needs_empty_class_layout (instance))
23232325 pp_semicolon (buffer);
23242326 pp_newline (buffer);
23252327 spc -= INDENT_INCR;
@@ -2819 +2821 @@needs_tail_padding_layout (tree node)
2819Removed line. return (data_size >= 0 && object_size > data_size)
2820Removed line. || cpp_check (node, REUSES_BASE_TAIL_PADDING);
2821Added line. if ((data_size >= 0 && object_size > data_size)
2822Added line. || cpp_check (node, REUSES_BASE_TAIL_PADDING))
2823Added line. return true;
2824Added line.
2825Added line. for (tree field = TYPE_FIELDS (node); field; field = TREE_CHAIN (field))
2826Added line. if (TREE_CODE (field) == FIELD_DECL
2827Added line. && DECL_NAME (field)
2828Added line. && DECL_FIELD_ABI_IGNORED (field))
2829Added line. return true;
2830Added line.
2831Added line. return false;
28212832}
28222833
2834Added line. /* Return true if NODE is a C++ empty class. Such a class has a nonzero
2835Added line. complete-object size but contributes no storage as an empty base. */
2836Added line.
2837Added line. static bool
2838Added line. needs_empty_class_layout (tree node)
2839Added line. {
2840Added line. if (!cpp_check || !RECORD_OR_UNION_TYPE_P (node))
2841Added line. return false;
2842Added line.
2843Added line. const int data_size = cpp_check (node, GET_AS_BASE_SIZE);
2844Added line. return data_size == 0 && int_size_in_bytes (node) > 0;
2845Added line. }
2846Added line.
2847Added line. /* Return true if FIELD is an artificial empty C++ base, or a named empty
2848Added line. [[no_unique_address]] member whose storage overlaps another field. Ada
2849Added line. cannot expose two overlapping record components, so omit only the member
2850Added line. that contributes no unique storage. */
2851Added line.
2852Added line. static bool
2853Added line. is_abi_ignored_empty_field (tree node, tree field)
2854Added line. {
2855Added line. if (!cpp_check || TREE_CODE (field) != FIELD_DECL
2856Added line. || !DECL_FIELD_ABI_IGNORED (field)
2857Added line. || !RECORD_OR_UNION_TYPE_P (TREE_TYPE (field))
2858Added line. || cpp_check (TREE_TYPE (field), GET_AS_BASE_SIZE) != 0)
2859Added line. return false;
2860Added line.
2861Added line. if (!DECL_NAME (field))
2862Added line. return true;
2863Added line.
2864Added line. if (!DECL_FIELD_OFFSET (field) || !DECL_FIELD_BIT_OFFSET (field)
2865Added line. || !tree_fits_uhwi_p (DECL_FIELD_OFFSET (field))
2866Added line. || !tree_fits_uhwi_p (DECL_FIELD_BIT_OFFSET (field)))
2867Added line. return false;
2868Added line.
2869Added line. const unsigned HOST_WIDE_INT field_first
2870Added line. = tree_to_uhwi (DECL_FIELD_OFFSET (field)) * BITS_PER_UNIT
2871Added line. + tree_to_uhwi (DECL_FIELD_BIT_OFFSET (field));
2872Added line. const unsigned HOST_WIDE_INT field_last
2873Added line. = field_first + int_size_in_bytes (TREE_TYPE (field)) * BITS_PER_UNIT;
2874Added line.
2875Added line. for (tree other = TYPE_FIELDS (node); other; other = TREE_CHAIN (other))
2876Added line. if (other != field && TREE_CODE (other) == FIELD_DECL
2877Added line. && !DECL_VIRTUAL_P (other) && DECL_FIELD_OFFSET (other)
2878Added line. && DECL_FIELD_BIT_OFFSET (other) && DECL_SIZE (other)
2879Added line. && tree_fits_uhwi_p (DECL_FIELD_OFFSET (other))
2880Added line. && tree_fits_uhwi_p (DECL_FIELD_BIT_OFFSET (other))
2881Added line. && tree_fits_uhwi_p (DECL_SIZE (other)))
2882Added line. {
2883Added line. const unsigned HOST_WIDE_INT other_first
2884Added line. = tree_to_uhwi (DECL_FIELD_OFFSET (other)) * BITS_PER_UNIT
2885Added line. + tree_to_uhwi (DECL_FIELD_BIT_OFFSET (other));
2886Added line. const unsigned HOST_WIDE_INT other_last
2887Added line. = other_first + tree_to_uhwi (DECL_SIZE (other));
2888Added line. if (field_first < other_last && other_first < field_last)
2889Added line. return true;
2890Added line. }
2891Added line.
2892Added line. return false;
2893Added line. }
2894Added line.
2895Added line. /* Dump the value and object sizes of an empty C++ class. Ada's value size is
2896Added line. zero so an empty base consumes no storage, while Object_Size preserves the
2897Added line. byte occupied by a complete C++ object. */
2898Added line.
2899Added line. static void
2900Added line. dump_ada_empty_class_layout (pretty_printer *buffer, tree node, tree type,
2901Added line. int spc)
2902Added line. {
2903Added line. const HOST_WIDE_INT object_size = int_size_in_bytes (node);
2904Added line.
2905Added line. gcc_assert (object_size > 0);
2906Added line.
2907Added line. newline_and_indent (buffer, spc);
2908Added line. pp_string (buffer, "for ");
2909Added line. dump_ada_node (buffer, TYPE_NAME (node), type, spc, false, true);
2910Added line. pp_string (buffer, "'Size use 0;");
2911Added line.
2912Added line. newline_and_indent (buffer, spc);
2913Added line. pp_string (buffer, "for ");
2914Added line. dump_ada_node (buffer, TYPE_NAME (node), type, spc, false, true);
2915Added line. pp_string (buffer, "'Object_Size use ");
2916Added line. pp_wide_integer (buffer, object_size * BITS_PER_UNIT);
2917Added line. pp_semicolon (buffer);
2918Added line. }
2919Added line.
28232920/* Return true if all of NODE's fields that are visible in the generated Ada
28242921 record have constant positions and sizes suitable for component clauses. */
28252922
@@ -3792 +3889 @@dump_ada_structure (pretty_printer *buffer, tree node, tree type, bool nested,
37923889 /* Print the non-static fields of the structure. */
37933890 for (tree tmp = TYPE_FIELDS (node); tmp; tmp = TREE_CHAIN (tmp))
37943891 {
3892Added line. if (is_abi_ignored_empty_field (node, tmp))
3893Added line. continue;
3894Added line.
37953895 /* Add parent field if needed. */
37963896 if (!DECL_NAME (tmp))
37973897 {
@@ -3912 +4012 @@dump_ada_structure (pretty_printer *buffer, tree node, tree type, bool nested,
39124012
39134013 need_semicolon = !dump_ada_methods (buffer, node, spc);
39144014
3915Removed line. if (needs_tail_padding_layout (node) && has_constant_field_layout (node))
4015Added line. if (needs_empty_class_layout (node))
4016Added line. {
4017Added line. if (need_semicolon)
4018Added line. {
4019Added line. need_semicolon = false;
4020Added line. pp_semicolon (buffer);
4021Added line. }
4022Added line.
4023Added line. pp_newline (buffer);
4024Added line. dump_ada_empty_class_layout (buffer, node, type, spc);
4025Added line. }
4026Added line. else if (needs_tail_padding_layout (node)
4027Added line. && has_constant_field_layout (node))
39164028 {
39174029 if (need_semicolon)
39184030 {
gcc/testsuite/g++.dg/ada-spec/empty-class-storage.C +139−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/empty-class-storage.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-require-effective-target lp64 } */
3Added line. /* { dg-options "-std=c++20 -fdump-ada-spec-slim" } */
4Added line. /* { dg-final { scan-file empty_class_storage_c.ads "for Empty'Size use 0;" } } */
5Added line. /* { dg-final { scan-file empty_class_storage_c.ads "for Empty'Object_Size use 8;" } } */
6Added line. /* { dg-final { scan-file-not empty_class_storage_c.ads "parent : aliased Empty_Base;" } } */
7Added line. /* { dg-final { scan-file-not empty_class_storage_c.ads "ignored : aliased Empty;" } } */
8Added line. /* { dg-final { scan-file empty_class_storage_c.ads "first : aliased Empty;" } } */
9Added line. /* { dg-final { scan-file empty_class_storage_c.ads "second : aliased Empty;" } } */
10Added line.
11Added line. struct Empty
12Added line. {};
13Added line.
14Added line. struct Empty_Base
15Added line. {};
16Added line.
17Added line. struct With_Empty_Base : Empty_Base
18Added line. {
19Added line. int value;
20Added line. };
21Added line.
22Added line. struct With_Empty_Member
23Added line. {
24Added line. Empty member;
25Added line. int value;
26Added line. };
27Added line.
28Added line. struct With_Empty_Array
29Added line. {
30Added line. Empty values[3];
31Added line. };
32Added line.
33Added line. struct With_No_Unique_Address
34Added line. {
35Added line. [[no_unique_address]] Empty ignored;
36Added line. int value;
37Added line. };
38Added line.
39Added line. struct With_Repeated_No_Unique_Address
40Added line. {
41Added line. [[no_unique_address]] Empty first;
42Added line. [[no_unique_address]] Empty second;
43Added line. };
44Added line.
45Added line. struct Method_Empty
46Added line. {
47Added line. Method_Empty ();
48Added line. void ping ();
49Added line. };
50Added line.
51Added line. Method_Empty::Method_Empty () {}
52Added line. void Method_Empty::ping () {}
53Added line.
54Added line. extern "C" unsigned long cpp_empty_size () { return sizeof (Empty); }
55Added line. extern "C" unsigned long cpp_empty_align () { return alignof (Empty); }
56Added line. extern "C" unsigned long cpp_method_empty_size ()
57Added line. {
58Added line. return sizeof (Method_Empty);
59Added line. }
60Added line.
61Added line. extern "C" unsigned long
62Added line. cpp_with_empty_base_size ()
63Added line. {
64Added line. return sizeof (With_Empty_Base);
65Added line. }
66Added line.
67Added line. extern "C" unsigned long
68Added line. cpp_with_empty_base_value_offset ()
69Added line. {
70Added line. return __builtin_offsetof (With_Empty_Base, value);
71Added line. }
72Added line.
73Added line. extern "C" unsigned long
74Added line. cpp_with_empty_member_size ()
75Added line. {
76Added line. return sizeof (With_Empty_Member);
77Added line. }
78Added line.
79Added line. extern "C" unsigned long
80Added line. cpp_with_empty_member_member_offset ()
81Added line. {
82Added line. return __builtin_offsetof (With_Empty_Member, member);
83Added line. }
84Added line.
85Added line. extern "C" unsigned long
86Added line. cpp_with_empty_member_value_offset ()
87Added line. {
88Added line. return __builtin_offsetof (With_Empty_Member, value);
89Added line. }
90Added line.
91Added line. extern "C" unsigned long
92Added line. cpp_with_empty_array_size ()
93Added line. {
94Added line. return sizeof (With_Empty_Array);
95Added line. }
96Added line.
97Added line. extern "C" unsigned long
98Added line. cpp_with_empty_array_values_offset ()
99Added line. {
100Added line. return __builtin_offsetof (With_Empty_Array, values);
101Added line. }
102Added line.
103Added line. extern "C" unsigned long
104Added line. cpp_with_no_unique_address_size ()
105Added line. {
106Added line. return sizeof (With_No_Unique_Address);
107Added line. }
108Added line.
109Added line. extern "C" unsigned long
110Added line. cpp_with_no_unique_address_value_offset ()
111Added line. {
112Added line. return __builtin_offsetof (With_No_Unique_Address, value);
113Added line. }
114Added line.
115Added line. extern "C" unsigned long
116Added line. cpp_with_no_unique_address_ignored_offset ()
117Added line. {
118Added line. return __builtin_offsetof (With_No_Unique_Address, ignored);
119Added line. }
120Added line.
121Added line. extern "C" unsigned long
122Added line. cpp_with_repeated_no_unique_address_size ()
123Added line. {
124Added line. return sizeof (With_Repeated_No_Unique_Address);
125Added line. }
126Added line.
127Added line. extern "C" unsigned long
128Added line. cpp_with_repeated_no_unique_address_first_offset ()
129Added line. {
130Added line. return __builtin_offsetof (With_Repeated_No_Unique_Address, first);
131Added line. }
132Added line.
133Added line. extern "C" unsigned long
134Added line. cpp_with_repeated_no_unique_address_second_offset ()
135Added line. {
136Added line. return __builtin_offsetof (With_Repeated_No_Unique_Address, second);
137Added line. }
138Added line.
139Added line. /* { dg-final { cleanup-ada-spec } } */
140

Variant gcc-15-16

Applies to 15.3.0, 16.1.0, 16.2.0. Source flavors: linux, darwin_arm64.

+254 −4 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +115−4modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -39 +39 @@static int dump_ada_declaration (pretty_printer *, tree, tree, int);
3939static void dump_ada_structure (pretty_printer *, tree, tree, bool, int);
4040static void dump_nested_types (pretty_printer *, tree, int);
4141static char *to_ada_name (const char *, bool *);
42Added line. static bool needs_empty_class_layout (tree);
4243
4344#define INDENT(SPACE) \
4445 do { int i; for (i = 0; i<SPACE; i++) pp_space (pp); } while (0)
@@ -2365 +2366 @@dump_ada_template (pretty_printer *pp, tree t, int spc)
23652366 pp_string (pp, " is limited ");
23662367
23672368 dump_ada_node (pp, instance, t, spc, false, false);
2368Removed line. if (!has_nontrivial_methods (instance) && !has_static_fields (instance))
2369Added line. if (!has_nontrivial_methods (instance) && !has_static_fields (instance)
2370Added line. && !needs_empty_class_layout (instance))
23692371 pp_semicolon (pp);
23702372 pp_newline (pp);
23712373 spc -= INDENT_INCR;
@@ -2890 +2892 @@needs_tail_padding_layout (tree node)
2890Removed line. return (data_size >= 0 && object_size > data_size)
2891Removed line. || cpp_check (node, REUSES_BASE_TAIL_PADDING);
2892Added line. if ((data_size >= 0 && object_size > data_size)
2893Added line. || cpp_check (node, REUSES_BASE_TAIL_PADDING))
2894Added line. return true;
2895Added line.
2896Added line. for (tree field = TYPE_FIELDS (node); field; field = TREE_CHAIN (field))
2897Added line. if (TREE_CODE (field) == FIELD_DECL
2898Added line. && DECL_NAME (field)
2899Added line. && DECL_FIELD_ABI_IGNORED (field))
2900Added line. return true;
2901Added line.
2902Added line. return false;
28922903}
28932904
2905Added line. /* Return true if NODE is a C++ empty class. Such a class has a nonzero
2906Added line. complete-object size but contributes no storage as an empty base. */
2907Added line.
2908Added line. static bool
2909Added line. needs_empty_class_layout (tree node)
2910Added line. {
2911Added line. if (!cpp_check || !RECORD_OR_UNION_TYPE_P (node))
2912Added line. return false;
2913Added line.
2914Added line. const int data_size = cpp_check (node, GET_AS_BASE_SIZE);
2915Added line. return data_size == 0 && int_size_in_bytes (node) > 0;
2916Added line. }
2917Added line.
2918Added line. /* Return true if FIELD is an artificial empty C++ base, or a named empty
2919Added line. [[no_unique_address]] member whose storage overlaps another field. Ada
2920Added line. cannot expose two overlapping record components, so omit only the member
2921Added line. that contributes no unique storage. */
2922Added line.
2923Added line. static bool
2924Added line. is_abi_ignored_empty_field (tree node, tree field)
2925Added line. {
2926Added line. if (!cpp_check || TREE_CODE (field) != FIELD_DECL
2927Added line. || !DECL_FIELD_ABI_IGNORED (field)
2928Added line. || !RECORD_OR_UNION_TYPE_P (TREE_TYPE (field))
2929Added line. || cpp_check (TREE_TYPE (field), GET_AS_BASE_SIZE) != 0)
2930Added line. return false;
2931Added line.
2932Added line. if (!DECL_NAME (field))
2933Added line. return true;
2934Added line.
2935Added line. if (!DECL_FIELD_OFFSET (field) || !DECL_FIELD_BIT_OFFSET (field)
2936Added line. || !tree_fits_uhwi_p (DECL_FIELD_OFFSET (field))
2937Added line. || !tree_fits_uhwi_p (DECL_FIELD_BIT_OFFSET (field)))
2938Added line. return false;
2939Added line.
2940Added line. const unsigned HOST_WIDE_INT field_first
2941Added line. = tree_to_uhwi (DECL_FIELD_OFFSET (field)) * BITS_PER_UNIT
2942Added line. + tree_to_uhwi (DECL_FIELD_BIT_OFFSET (field));
2943Added line. const unsigned HOST_WIDE_INT field_last
2944Added line. = field_first + int_size_in_bytes (TREE_TYPE (field)) * BITS_PER_UNIT;
2945Added line.
2946Added line. for (tree other = TYPE_FIELDS (node); other; other = TREE_CHAIN (other))
2947Added line. if (other != field && TREE_CODE (other) == FIELD_DECL
2948Added line. && !DECL_VIRTUAL_P (other) && DECL_FIELD_OFFSET (other)
2949Added line. && DECL_FIELD_BIT_OFFSET (other) && DECL_SIZE (other)
2950Added line. && tree_fits_uhwi_p (DECL_FIELD_OFFSET (other))
2951Added line. && tree_fits_uhwi_p (DECL_FIELD_BIT_OFFSET (other))
2952Added line. && tree_fits_uhwi_p (DECL_SIZE (other)))
2953Added line. {
2954Added line. const unsigned HOST_WIDE_INT other_first
2955Added line. = tree_to_uhwi (DECL_FIELD_OFFSET (other)) * BITS_PER_UNIT
2956Added line. + tree_to_uhwi (DECL_FIELD_BIT_OFFSET (other));
2957Added line. const unsigned HOST_WIDE_INT other_last
2958Added line. = other_first + tree_to_uhwi (DECL_SIZE (other));
2959Added line. if (field_first < other_last && other_first < field_last)
2960Added line. return true;
2961Added line. }
2962Added line.
2963Added line. return false;
2964Added line. }
2965Added line.
2966Added line. /* Dump the value and object sizes of an empty C++ class. Ada's value size is
2967Added line. zero so an empty base consumes no storage, while Object_Size preserves the
2968Added line. byte occupied by a complete C++ object. */
2969Added line.
2970Added line. static void
2971Added line. dump_ada_empty_class_layout (pretty_printer *pp, tree node, tree type, int spc)
2972Added line. {
2973Added line. const HOST_WIDE_INT object_size = int_size_in_bytes (node);
2974Added line.
2975Added line. gcc_assert (object_size > 0);
2976Added line.
2977Added line. newline_and_indent (pp, spc);
2978Added line. pp_string (pp, "for ");
2979Added line. dump_ada_node (pp, TYPE_NAME (node), type, spc, false, true);
2980Added line. pp_string (pp, "'Size use 0;");
2981Added line.
2982Added line. newline_and_indent (pp, spc);
2983Added line. pp_string (pp, "for ");
2984Added line. dump_ada_node (pp, TYPE_NAME (node), type, spc, false, true);
2985Added line. pp_string (pp, "'Object_Size use ");
2986Added line. pp_wide_integer (pp, object_size * BITS_PER_UNIT);
2987Added line. pp_semicolon (pp);
2988Added line. }
2989Added line.
28942990/* Return true if all of NODE's fields that are visible in the generated Ada
28952991 record have constant positions and sizes suitable for component clauses. */
28962992
@@ -3872 +3968 @@dump_ada_structure (pretty_printer *pp, tree node, tree type, bool nested,
38723968 /* Print the non-static fields of the structure. */
38733969 for (tree tmp = TYPE_FIELDS (node); tmp; tmp = TREE_CHAIN (tmp))
38743970 {
3971Added line. if (is_abi_ignored_empty_field (node, tmp))
3972Added line. continue;
3973Added line.
38753974 /* Add parent field if needed. */
38763975 if (!DECL_NAME (tmp))
38773976 {
@@ -3992 +4091 @@dump_ada_structure (pretty_printer *pp, tree node, tree type, bool nested,
39924091
39934092 need_semicolon = !dump_ada_methods (pp, node, spc);
39944093
3995Removed line. if (needs_tail_padding_layout (node) && has_constant_field_layout (node))
4094Added line. if (needs_empty_class_layout (node))
4095Added line. {
4096Added line. if (need_semicolon)
4097Added line. {
4098Added line. need_semicolon = false;
4099Added line. pp_semicolon (pp);
4100Added line. }
4101Added line.
4102Added line. pp_newline (pp);
4103Added line. dump_ada_empty_class_layout (pp, node, type, spc);
4104Added line. }
4105Added line. else if (needs_tail_padding_layout (node)
4106Added line. && has_constant_field_layout (node))
39964107 {
39974108 if (need_semicolon)
39984109 {
gcc/testsuite/g++.dg/ada-spec/empty-class-storage.C +139−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/empty-class-storage.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-require-effective-target lp64 } */
3Added line. /* { dg-options "-std=c++20 -fdump-ada-spec-slim" } */
4Added line. /* { dg-final { scan-file empty_class_storage_c.ads "for Empty'Size use 0;" } } */
5Added line. /* { dg-final { scan-file empty_class_storage_c.ads "for Empty'Object_Size use 8;" } } */
6Added line. /* { dg-final { scan-file-not empty_class_storage_c.ads "parent : aliased Empty_Base;" } } */
7Added line. /* { dg-final { scan-file-not empty_class_storage_c.ads "ignored : aliased Empty;" } } */
8Added line. /* { dg-final { scan-file empty_class_storage_c.ads "first : aliased Empty;" } } */
9Added line. /* { dg-final { scan-file empty_class_storage_c.ads "second : aliased Empty;" } } */
10Added line.
11Added line. struct Empty
12Added line. {};
13Added line.
14Added line. struct Empty_Base
15Added line. {};
16Added line.
17Added line. struct With_Empty_Base : Empty_Base
18Added line. {
19Added line. int value;
20Added line. };
21Added line.
22Added line. struct With_Empty_Member
23Added line. {
24Added line. Empty member;
25Added line. int value;
26Added line. };
27Added line.
28Added line. struct With_Empty_Array
29Added line. {
30Added line. Empty values[3];
31Added line. };
32Added line.
33Added line. struct With_No_Unique_Address
34Added line. {
35Added line. [[no_unique_address]] Empty ignored;
36Added line. int value;
37Added line. };
38Added line.
39Added line. struct With_Repeated_No_Unique_Address
40Added line. {
41Added line. [[no_unique_address]] Empty first;
42Added line. [[no_unique_address]] Empty second;
43Added line. };
44Added line.
45Added line. struct Method_Empty
46Added line. {
47Added line. Method_Empty ();
48Added line. void ping ();
49Added line. };
50Added line.
51Added line. Method_Empty::Method_Empty () {}
52Added line. void Method_Empty::ping () {}
53Added line.
54Added line. extern "C" unsigned long cpp_empty_size () { return sizeof (Empty); }
55Added line. extern "C" unsigned long cpp_empty_align () { return alignof (Empty); }
56Added line. extern "C" unsigned long cpp_method_empty_size ()
57Added line. {
58Added line. return sizeof (Method_Empty);
59Added line. }
60Added line.
61Added line. extern "C" unsigned long
62Added line. cpp_with_empty_base_size ()
63Added line. {
64Added line. return sizeof (With_Empty_Base);
65Added line. }
66Added line.
67Added line. extern "C" unsigned long
68Added line. cpp_with_empty_base_value_offset ()
69Added line. {
70Added line. return __builtin_offsetof (With_Empty_Base, value);
71Added line. }
72Added line.
73Added line. extern "C" unsigned long
74Added line. cpp_with_empty_member_size ()
75Added line. {
76Added line. return sizeof (With_Empty_Member);
77Added line. }
78Added line.
79Added line. extern "C" unsigned long
80Added line. cpp_with_empty_member_member_offset ()
81Added line. {
82Added line. return __builtin_offsetof (With_Empty_Member, member);
83Added line. }
84Added line.
85Added line. extern "C" unsigned long
86Added line. cpp_with_empty_member_value_offset ()
87Added line. {
88Added line. return __builtin_offsetof (With_Empty_Member, value);
89Added line. }
90Added line.
91Added line. extern "C" unsigned long
92Added line. cpp_with_empty_array_size ()
93Added line. {
94Added line. return sizeof (With_Empty_Array);
95Added line. }
96Added line.
97Added line. extern "C" unsigned long
98Added line. cpp_with_empty_array_values_offset ()
99Added line. {
100Added line. return __builtin_offsetof (With_Empty_Array, values);
101Added line. }
102Added line.
103Added line. extern "C" unsigned long
104Added line. cpp_with_no_unique_address_size ()
105Added line. {
106Added line. return sizeof (With_No_Unique_Address);
107Added line. }
108Added line.
109Added line. extern "C" unsigned long
110Added line. cpp_with_no_unique_address_value_offset ()
111Added line. {
112Added line. return __builtin_offsetof (With_No_Unique_Address, value);
113Added line. }
114Added line.
115Added line. extern "C" unsigned long
116Added line. cpp_with_no_unique_address_ignored_offset ()
117Added line. {
118Added line. return __builtin_offsetof (With_No_Unique_Address, ignored);
119Added line. }
120Added line.
121Added line. extern "C" unsigned long
122Added line. cpp_with_repeated_no_unique_address_size ()
123Added line. {
124Added line. return sizeof (With_Repeated_No_Unique_Address);
125Added line. }
126Added line.
127Added line. extern "C" unsigned long
128Added line. cpp_with_repeated_no_unique_address_first_offset ()
129Added line. {
130Added line. return __builtin_offsetof (With_Repeated_No_Unique_Address, first);
131Added line. }
132Added line.
133Added line. extern "C" unsigned long
134Added line. cpp_with_repeated_no_unique_address_second_offset ()
135Added line. {
136Added line. return __builtin_offsetof (With_Repeated_No_Unique_Address, second);
137Added line. }
138Added line.
139Added line. /* { dg-final { cleanup-ada-spec } } */
140

Tests.

empty-class-storage.C C++ · 139 lines
/* { dg-do compile } */
/* { dg-require-effective-target lp64 } */
/* { dg-options "-std=c++20 -fdump-ada-spec-slim" } */
/* { dg-final { scan-file empty_class_storage_c.ads "for Empty'Size use 0;" } } */
/* { dg-final { scan-file empty_class_storage_c.ads "for Empty'Object_Size use 8;" } } */
/* { dg-final { scan-file-not empty_class_storage_c.ads "parent : aliased Empty_Base;" } } */
/* { dg-final { scan-file-not empty_class_storage_c.ads "ignored : aliased Empty;" } } */
/* { dg-final { scan-file empty_class_storage_c.ads "first : aliased Empty;" } } */
/* { dg-final { scan-file empty_class_storage_c.ads "second : aliased Empty;" } } */

struct Empty
{};

struct Empty_Base
{};

struct With_Empty_Base : Empty_Base
{
  int value;
};

struct With_Empty_Member
{
  Empty member;
  int value;
};

struct With_Empty_Array
{
  Empty values[3];
};

struct With_No_Unique_Address
{
  [[no_unique_address]] Empty ignored;
  int value;
};

struct With_Repeated_No_Unique_Address
{
  [[no_unique_address]] Empty first;
  [[no_unique_address]] Empty second;
};

struct Method_Empty
{
  Method_Empty ();
  void ping ();
};

Method_Empty::Method_Empty () {}
void Method_Empty::ping () {}

extern "C" unsigned long cpp_empty_size () { return sizeof (Empty); }
extern "C" unsigned long cpp_empty_align () { return alignof (Empty); }
extern "C" unsigned long cpp_method_empty_size ()
{
  return sizeof (Method_Empty);
}

extern "C" unsigned long
cpp_with_empty_base_size ()
{
  return sizeof (With_Empty_Base);
}

extern "C" unsigned long
cpp_with_empty_base_value_offset ()
{
  return __builtin_offsetof (With_Empty_Base, value);
}

extern "C" unsigned long
cpp_with_empty_member_size ()
{
  return sizeof (With_Empty_Member);
}

extern "C" unsigned long
cpp_with_empty_member_member_offset ()
{
  return __builtin_offsetof (With_Empty_Member, member);
}

extern "C" unsigned long
cpp_with_empty_member_value_offset ()
{
  return __builtin_offsetof (With_Empty_Member, value);
}

extern "C" unsigned long
cpp_with_empty_array_size ()
{
  return sizeof (With_Empty_Array);
}

extern "C" unsigned long
cpp_with_empty_array_values_offset ()
{
  return __builtin_offsetof (With_Empty_Array, values);
}

extern "C" unsigned long
cpp_with_no_unique_address_size ()
{
  return sizeof (With_No_Unique_Address);
}

extern "C" unsigned long
cpp_with_no_unique_address_value_offset ()
{
  return __builtin_offsetof (With_No_Unique_Address, value);
}

extern "C" unsigned long
cpp_with_no_unique_address_ignored_offset ()
{
  return __builtin_offsetof (With_No_Unique_Address, ignored);
}

extern "C" unsigned long
cpp_with_repeated_no_unique_address_size ()
{
  return sizeof (With_Repeated_No_Unique_Address);
}

extern "C" unsigned long
cpp_with_repeated_no_unique_address_first_offset ()
{
  return __builtin_offsetof (With_Repeated_No_Unique_Address, first);
}

extern "C" unsigned long
cpp_with_repeated_no_unique_address_second_offset ()
{
  return __builtin_offsetof (With_Repeated_No_Unique_Address, second);
}

/* { dg-final { cleanup-ada-spec } } */

Download · View in repository

empty_class_storage_consumer.adb Ada · 72 lines
with Ada.Text_IO;
with Empty_Class_Storage_C;
with Interfaces.C; use Interfaces.C;
with System;

procedure Empty_Class_Storage_Consumer is
   package Bindings renames Empty_Class_Storage_C;
   With_Base : Bindings.With_Empty_Base;
   With_Member : Bindings.With_Empty_Member;
   With_Array : Bindings.With_Empty_Array;
   With_NUA : Bindings.With_No_Unique_Address;
   With_Repeated_NUA : Bindings.With_Repeated_No_Unique_Address;

   function Bytes (Bits : Natural) return unsigned_long is
     (unsigned_long (Bits / System.Storage_Unit));

   procedure Report (Label : String; Matches : Boolean) is
   begin
      Ada.Text_IO.Put_Line (Label & (if Matches then " MATCH" else " MISMATCH"));
   end Report;
begin
   Report
     ("EMPTY",
      Bindings.Empty'Size = 0
      and then Bytes (Bindings.Empty'Object_Size) = Bindings.cpp_empty_size
      and then unsigned_long (Bindings.Empty'Alignment) =
        Bindings.cpp_empty_align);

   Report
     ("METHOD_EMPTY",
      Bytes (Bindings.Class_Method_Empty.Method_Empty'Object_Size) =
        Bindings.cpp_method_empty_size);

   Report
     ("EBO",
      Bytes (Bindings.With_Empty_Base'Object_Size) =
        Bindings.cpp_with_empty_base_size
      and then unsigned_long (With_Base.value'Position) =
        Bindings.cpp_with_empty_base_value_offset);

   Report
     ("MEMBER",
      Bytes (Bindings.With_Empty_Member'Object_Size) =
        Bindings.cpp_with_empty_member_size
      and then unsigned_long (With_Member.member'Position) =
        Bindings.cpp_with_empty_member_member_offset
      and then unsigned_long (With_Member.value'Position) =
        Bindings.cpp_with_empty_member_value_offset);

   Report
     ("ARRAY",
      Bytes (Bindings.With_Empty_Array'Object_Size) =
        Bindings.cpp_with_empty_array_size
      and then unsigned_long (With_Array.values'Position) =
        Bindings.cpp_with_empty_array_values_offset);

   Report
     ("NO_UNIQUE_ADDRESS",
      Bytes (Bindings.With_No_Unique_Address'Object_Size) =
        Bindings.cpp_with_no_unique_address_size
      and then unsigned_long (With_NUA.value'Position) =
        Bindings.cpp_with_no_unique_address_value_offset);

   Report
     ("REPEATED_NO_UNIQUE_ADDRESS",
      Bytes (Bindings.With_Repeated_No_Unique_Address'Object_Size) =
        Bindings.cpp_with_repeated_no_unique_address_size
      and then unsigned_long (With_Repeated_NUA.first'Position) =
        Bindings.cpp_with_repeated_no_unique_address_first_offset
      and then unsigned_long (With_Repeated_NUA.second'Position) =
        Bindings.cpp_with_repeated_no_unique_address_second_offset);
end Empty_Class_Storage_Consumer;

Download · View in repository

run-test.sh shell · 84 lines
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 3 ]]; then
  echo "usage: $0 TOOLCHAIN_ROOT GCC_VERSION unpatched|patched" >&2
  exit 2
fi

root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
source "$root/scripts/regression-common.sh"
resolve_regression_toolchain "$1"
version=$2
state=$3
[[ "$state" == unpatched || "$state" == patched ]] || {
  echo "error: state must be unpatched or patched" >&2
  exit 2
}

gxx="$REGRESSION_TOOLCHAIN/bin/g++"
[[ -x "$gxx" ]] || {
  echo "error: no g++ in $REGRESSION_TOOLCHAIN" >&2
  exit 1
}

cxx_fixture="$root/bundles/cxx-ada-empty-class-storage/tests/empty-class-storage.C"
ada_fixture="$root/bundles/cxx-ada-empty-class-storage/tests/empty_class_storage_consumer.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-cxx-ada-empty-storage.XXXXXX")
trap 'rm -rf "$work"' EXIT

for optimization in 0 2; do
  case_dir="$work/O$optimization"
  mkdir -p "$case_dir"
  cp "$cxx_fixture" "$ada_fixture" "$case_dir/"
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$gxx" -std=c++20 -c "-O$optimization" \
      -fdump-ada-spec-slim empty-class-storage.C
    "${REGRESSION_ENV[@]}" "$REGRESSION_GNATMAKE" -q -f \
      "-O$optimization" empty_class_storage_consumer.adb \
      -largs empty-class-storage.o -lstdc++
    "${REGRESSION_ENV[@]}" ./empty_class_storage_consumer
  ) >"$case_dir/output.log" 2>&1 || {
    cat "$case_dir/output.log"
    exit 1
  }

  spec="$case_dir/empty_class_storage_c.ads"
  if [[ "$state" == unpatched ]]; then
    grep -Fx "EMPTY MISMATCH" "$case_dir/output.log"
    grep -Fx "METHOD_EMPTY MATCH" "$case_dir/output.log"
    grep -Fx "EBO MATCH" "$case_dir/output.log"
    grep -Fx "MEMBER MISMATCH" "$case_dir/output.log"
    grep -Fx "ARRAY MATCH" "$case_dir/output.log"
    grep -Fx "NO_UNIQUE_ADDRESS MATCH" "$case_dir/output.log"
    grep -Fx "REPEATED_NO_UNIQUE_ADDRESS MISMATCH" "$case_dir/output.log"
    if grep -Fq "for Empty'Object_Size use" "$spec"; then
      echo "error: unpatched mapper unexpectedly emitted empty-class size" >&2
      exit 1
    fi
    echo "cxx-ada-empty-class-storage -O$optimization: expected mismatches (GCC $version)"
    continue
  fi

  grep -F "for Empty'Size use 0;" "$spec"
  grep -F "for Empty'Object_Size use 8;" "$spec"
  if grep -Fq "parent : aliased Empty_Base;" "$spec"; then
    echo "error: patched mapper retained an artificial empty base field" >&2
    exit 1
  fi
  if grep -Fq "ignored : aliased Empty;" "$spec"; then
    echo "error: patched mapper retained an overlapping no_unique_address field" >&2
    exit 1
  fi
  grep -F "first : aliased Empty;" "$spec"
  grep -F "second : aliased Empty;" "$spec"
  grep -Fx "EMPTY MATCH" "$case_dir/output.log"
  grep -Fx "METHOD_EMPTY MATCH" "$case_dir/output.log"
  grep -Fx "EBO MATCH" "$case_dir/output.log"
  grep -Fx "MEMBER MATCH" "$case_dir/output.log"
  grep -Fx "ARRAY MATCH" "$case_dir/output.log"
  grep -Fx "NO_UNIQUE_ADDRESS MATCH" "$case_dir/output.log"
  grep -Fx "REPEATED_NO_UNIQUE_ADDRESS MATCH" "$case_dir/output.log"
  echo "cxx-ada-empty-class-storage -O$optimization: patched (GCC $version)"
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/cxx-ada-empty-class-storage/patches/VARIANT.patch
Build the compiler
PATH=/path/to/bootstrap/bin:$PATH ./scripts/build-gnat.sh SOURCE BUILD INSTALL
Run the regression
./scripts/run-regressions.sh INSTALL 1.2.0 GCC_MAJOR patched

Metadata.