Bug 60336 - empty struct value is passed differently in C and C++
Summary: empty struct value is passed differently in C and C++
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 7.2.0
: P3 normal
Target Milestone: 8.0
Assignee: Marek Polacek
URL:
Keywords: ABI, wrong-code
: 68355 69846 82693 (view as bug list)
Depends on:
Blocks: 67239 68355
  Show dependency treegraph
 
Reported: 2014-02-25 09:36 UTC by Andrey H.
Modified: 2023-06-05 09:39 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail: 5.4.0, 6.4.0, 7.2.0, 8.0
Last reconfirmed: 2017-10-24 00:00:00


Attachments
va_list corruption simple example (221 bytes, text/x-csrc)
2014-02-25 09:36 UTC, Andrey H.
Details
pre-processed test case, valid C and C++ (187 bytes, text/plain)
2014-02-25 19:53 UTC, Mikael Pettersson
Details
HJ's test in dejagnu form (486 bytes, application/x-bzip)
2014-02-27 18:41 UTC, Jason Merrill
Details
A patch (2.98 KB, patch)
2015-11-15 21:56 UTC, H.J. Lu
Details | Diff
A patch (3.89 KB, patch)
2015-11-16 00:19 UTC, H.J. Lu
Details | Diff
An updated patch to use is_really_empty_class (4.00 KB, patch)
2015-11-16 06:08 UTC, H.J. Lu
Details | Diff
An updated patch to add empty_record_p (3.34 KB, patch)
2015-11-16 06:52 UTC, H.J. Lu
Details | Diff
A patch to support LTO (6.05 KB, patch)
2015-11-16 23:50 UTC, H.J. Lu
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Andrey H. 2014-02-25 09:36:21 UTC
Created attachment 32208 [details]
va_list corruption simple example

The code in attachment (for convenience see here http://coliru.stacked-crooked.com/a/14319753d1667629) prints garbage instead of 6-th argument if Dummy struct is empty. Adding some member to this struct "fixes" the problem. Not sure, whether problem is in Gcc code generation or libc, but probably related to ABI.

Tested on Ubuntu 13.04 64bit.
Comment 1 Mikael Pettersson 2014-02-25 19:53:06 UTC
Created attachment 32212 [details]
pre-processed test case, valid C and C++

The following is a pre-processed and simplified test case, tweaked to compile as both C and C++.  As C it succeeds, as C++ it fails.  Tested with gcc/g++ 4.8.2 on x86_64-linux.
Comment 2 H.J. Lu 2014-02-25 21:41:43 UTC
Should g++ put pass the empty struct on stack?
Comment 3 Andrew Pinski 2014-02-25 22:35:53 UTC
(In reply to H.J. Lu from comment #2)
> Should g++ put pass the empty struct on stack?

It is a target bug if it is passing on the stack.  Note in C++, the size of the struct is 1 while in C, the size is 0.
Comment 4 H.J. Lu 2014-02-26 17:03:26 UTC
(In reply to Andrew Pinski from comment #3)
> (In reply to H.J. Lu from comment #2)
> > Should g++ put pass the empty struct on stack?
> 
> It is a target bug if it is passing on the stack.  Note in C++, the size of
> the struct is 1 while in C, the size is 0.

Can someone try this on non-x86 targets?

[hjl@gnu-6 pr60336]$ cat x.ii 
struct dummy { };
struct foo
{
  int i1;
  int i2;
  int i3;
  int i4;
  int i5;
};

extern "C" void fun(struct dummy, struct foo);

int main()
{
  struct dummy d;
  struct foo f = { -1, -2, -3, -4, -5 };

  fun(d, f);
  return 0;
}
[hjl@gnu-6 pr60336]$ cat fun.i 
struct dummy { };
struct foo
{
  int i1;
  int i2;
  int i3;
  int i4;
  int i5;
};

void fun(struct dummy d, struct foo f)
{
  if (f.i1 != -1)
    __builtin_abort();
}
[hjl@gnu-6 pr60336]$ gcc -c fun.i 
[hjl@gnu-6 pr60336]$ gcc -c x.ii
[hjl@gnu-6 pr60336]$ g++ fun.o x.o
[hjl@gnu-6 pr60336]$ ./a.out 
Aborted
[hjl@gnu-6 pr60336]$ 

Is this test valid? BTW, clang works fine on x86.
Comment 5 Uroš Bizjak 2014-02-26 18:09:46 UTC
(In reply to Andrew Pinski from comment #3)

> It is a target bug if it is passing on the stack.  Note in C++, the size of
> the struct is 1 while in C, the size is 0.

Changing the testcase a bit:

  fun(d, 2, 3, 4, 5, 6, 7, 8);

cc1plus -O2 -maccumulate-outgoing-args:

        xorl    %eax, %eax
        movl    $7, %r9d
        movl    $8, 8(%rsp)
        movb    $0, (%rsp)  <--- this shouldn't be there.
        movl    $6, %r8d
        movl    $5, %ecx
        movl    $4, %edx
        movl    $3, %esi
        movl    $2, %edi
        call    _Z3fun5dummyiz

clang -O2:

        movl    $8, (%rsp)
        movl    $3, %esi
        movl    $4, %edx
        movl    $5, %ecx
        movl    $6, %r8d
        movl    $7, %r9d
        xorl    %eax, %eax
        callq   _Z3fun5dummyiz
Comment 6 Uroš Bizjak 2014-02-26 18:18:03 UTC
(In reply to H.J. Lu from comment #4)

> Can someone try this on non-x86 targets?

I get abort on alpha:

$ gcc -c fun.i
$ gcc -c x.ii
$ g++ fun.o x.o
$ ./a.out
Aborted
Comment 7 Andrew Pinski 2014-02-26 18:23:27 UTC
(In reply to H.J. Lu from comment #4)
> Is this test valid? BTW, clang works fine on x86.

No this testcase is not valid at all.  See http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Empty-Structures.html#Empty-Structures where it is documented it is not valid.
Comment 8 H.J. Lu 2014-02-26 19:06:29 UTC
This works:

diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index 00773d8..16669b9 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -7193,6 +7193,7 @@ function_arg_advance_64 (CUMULATIVE_ARGS *cum, enum machine_mode mode,
     return;
 
   if (examine_argument (mode, type, 0, &int_nregs, &sse_nregs)
+      && (sse_nregs || int_nregs)
       && sse_nregs <= cum->sse_nregs && int_nregs <= cum->nregs)
     {
       cum->nregs -= int_nregs;
Comment 9 vagran 2014-02-26 19:25:12 UTC
(In reply to Andrew Pinski from comment #7)
> (In reply to H.J. Lu from comment #4)
> > Is this test valid? BTW, clang works fine on x86.
> 
> No this testcase is not valid at all.  See
> http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Empty-Structures.html#Empty-
> Structures where it is documented it is not valid.

How it proves that the test case is invalid? Independently on what size has empty structure the called function absolutely legally can expect it receives the values which were passed by the caller. A compiler should care it works for both C and C++. Am I wrong?

Also as I remember in C++ size of empty structure is sizeof(char) except the case some class is derived from the empty structure. In this case it adds no overhead to the size of the derived class itself (thus virtually has zero size in this case).
Comment 10 Andrew Pinski 2014-02-26 19:31:37 UTC
(In reply to vagran from comment #9)
> (In reply to Andrew Pinski from comment #7)
> > (In reply to H.J. Lu from comment #4)
> > > Is this test valid? BTW, clang works fine on x86.
> > 
> > No this testcase is not valid at all.  See
> > http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Empty-Structures.html#Empty-
> > Structures where it is documented it is not valid.
> 
> How it proves that the test case is invalid?

I am not saying the original testcase (variable argument case) is invalid, just the testcase where you are passing an empty struct between C and C++ one is invalid.
Comment 11 H.J. Lu 2014-02-26 19:32:46 UTC
This patch may be better:

diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index 00773d8..426146a 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -6842,7 +6842,7 @@ examine_argument (enum machine_mode mode, const_tree type, int in_return,
       case X86_64_MEMORY_CLASS:
   gcc_unreachable ();
       }
-  return 1;
+  return *int_nregs || *sse_nregs;
 }
 
 /* Construct container for the argument used by GCC interface.  See
Comment 12 Marc Glisse 2014-02-26 19:51:32 UTC
It is a bit alarming that gcc, clang and clang++ use one ABI and g++ uses a different (inferior) one (the incompatibility with clang++ should affect some standard library functions, though they are often inlined). Also, extern "C" is a calling convention marker, and it is rather disrespectful of g++ to ignore it, even if this is only about a non-standard (but documented) extension.
Comment 13 H.J. Lu 2014-02-26 19:55:28 UTC
Passing

struct dummy { };

is still odd for g++.  It is supposed to have a single member of type char,
which should be passed in register, not on stack.
Comment 14 H.J. Lu 2014-02-26 20:47:43 UTC
(In reply to H.J. Lu from comment #13)
> Passing
> 
> struct dummy { };
> 
> is still odd for g++.  It is supposed to have a single member of type char,
> which should be passed in register, not on stack.

This passes it in register:

diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index 00773d8..6975ff1 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -6613,6 +6613,8 @@ classify_argument (enum machine_mode mode, const_tree type,
         return 0;
       }
   }
+      if (mode == QImode && words == 1 && classes[0] == X86_64_NO_CLASS)
+  classes[0] = X86_64_INTEGERSI_CLASS;
       return words;
     }
Comment 15 Jason Merrill 2014-02-27 14:37:36 UTC
(In reply to Andrew Pinski from comment #7)
> No this testcase is not valid at all.  See
> http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Empty-Structures.html#Empty-
> Structures where it is documented it is not valid.

That only documents that sizeof is different for C and C++, the calling convention should be the same.  And it seems like classify_argument should already be returning 1 with classes[0]==NO_CLASS for both C and C++.  Why are we getting different results?
Comment 16 H.J. Lu 2014-02-27 16:04:16 UTC
(In reply to Jason Merrill from comment #15)
> (In reply to Andrew Pinski from comment #7)
> > No this testcase is not valid at all.  See
> > http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Empty-Structures.html#Empty-
> > Structures where it is documented it is not valid.
> 
> That only documents that sizeof is different for C and C++, the calling
> convention should be the same.  And it seems like classify_argument should
> already be returning 1 with classes[0]==NO_CLASS for both C and C++.  Why
> are we getting different results?

X86 backend has some issues.  But the C/C++ calling convention issue
affects most, if not all, targets.  You can try the test case in
comment 4 on non-x86 targets.
Comment 17 Uroš Bizjak 2014-02-27 16:17:07 UTC
(In reply to Jason Merrill from comment #15)

> That only documents that sizeof is different for C and C++, the calling
> convention should be the same.  And it seems like classify_argument should
> already be returning 1 with classes[0]==NO_CLASS for both C and C++.  Why
> are we getting different results?

classify_argument has an early exit:

      /* Zero sized arrays or structures are NO_CLASS.  We return 0 to
	 signalize memory class, so handle it as special case.  */

      if (!words)
	{
	  classes[0] = X86_64_NO_CLASS;
	  return 1;
	}

but it doesn't trigger for c++, where words gets calculated as 1.
Comment 18 Jason Merrill 2014-02-27 17:58:39 UTC
(In reply to Uroš Bizjak from comment #17)
> classify_argument has an early exit:
> 
>       /* Zero sized arrays or structures are NO_CLASS.  We return 0 to
> 	 signalize memory class, so handle it as special case.  */
> 
>       if (!words)
> 	{
> 	  classes[0] = X86_64_NO_CLASS;
> 	  return 1;
> 	}
> 
> but it doesn't trigger for c++, where words gets calculated as 1.

Yes, it looks like this special case is in order to return the same thing that C++ does through the normal logic: normally we return 'words', and since there are no fields classes[0] is still initialized to NO_CLASS.
Comment 19 Jason Merrill 2014-02-27 18:41:37 UTC
Created attachment 32228 [details]
HJ's test in dejagnu form

Here's HJ's testcase in a form that can be dropped into g++.dg/abi.
Comment 20 Uroš Bizjak 2014-02-27 20:53:08 UTC
(In reply to H.J. Lu from comment #4)
 
> Can someone try this on non-x86 targets?

gcc110 (ppc64) from the compile farm:

[uros@gcc1-power7 ~]$ gcc -c fun.i
[uros@gcc1-power7 ~]$ gcc -c x.ii
[uros@gcc1-power7 ~]$ g++ fun.o x.o
[uros@gcc1-power7 ~]$ ./a.out
Aborted

I don't think this is a target bug.
Comment 21 Andrew Pinski 2014-02-27 21:21:08 UTC
(In reply to Marc Glisse from comment #12)
> It is a bit alarming that gcc, clang and clang++ use one ABI and g++ uses a
> different (inferior) one (the incompatibility with clang++ should affect
> some standard library functions, though they are often inlined). Also,
> extern "C" is a calling convention marker, and it is rather disrespectful of
> g++ to ignore it, even if this is only about a non-standard (but documented)
> extension.

See http://gcc.gnu.org/ml/gcc-patches/2006-05/msg00868.html which was about adding a warning for the extern "C" case.
And http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10208 for the ABI difference in the past about C/C++.

There is most likely more discussion about this in other threads too.
Comment 22 H.J. Lu 2014-08-12 19:21:17 UTC
This is a target independent issue.  Clang++ skips empty struct argument
and g++ passes it. Skip empty struct argument requires middle-end changes.
Comment 23 Andrew Pinski 2014-08-12 22:57:56 UTC
(In reply to H.J. Lu from comment #22)
> This is a target independent issue.  Clang++ skips empty struct argument
> and g++ passes it. Skip empty struct argument requires middle-end changes.

Except in c++, the struct is non zero in size.
Comment 24 vagran 2014-08-13 05:05:37 UTC
Just to be on a safe side, please, also do not forget that empty struct (or class) is really zero in the case when another structure (or class) is derived from it. For example, such test would be useful after fix:

struct A {};

struct B: A {
    int i;
};

assert(sizeof(B) == sizeof(int));

And something like this:

struct A {};
struct B: A {};
struct C: B {};
struct D: C {};

assert(sizeof(D) == 1);
Comment 25 H.J. Lu 2015-11-15 11:35:02 UTC
(In reply to Andrew Pinski from comment #23)
> (In reply to H.J. Lu from comment #22)
> > This is a target independent issue.  Clang++ skips empty struct argument
> > and g++ passes it. Skip empty struct argument requires middle-end changes.
> 
> Except in c++, the struct is non zero in size.

I think empty struct should be considered as zero size for
argument passing, like clang does.  This bug causes PR 67239.
Comment 26 H.J. Lu 2015-11-15 21:56:00 UTC
Created attachment 36720 [details]
A patch

I am testing this patch.
Comment 27 Andrew Pinski 2015-11-15 21:57:49 UTC
Comment on attachment 36720 [details]
A patch

How does this interact with LTO where lang_hooks.decls.empty_record_p is not defined?
Comment 28 H.J. Lu 2015-11-16 00:19:54 UTC
Created attachment 36721 [details]
A patch

An updated patch with middle end changes.
Comment 29 H.J. Lu 2015-11-16 00:22:54 UTC
(In reply to Andrew Pinski from comment #27)
> Comment on attachment 36720 [details]
> A patch
> 
> How does this interact with LTO where lang_hooks.decls.empty_record_p is not
> defined?

We can stream out empty record info and use it to in lto_empty_record_p.
Comment 30 Andrew Pinski 2015-11-16 00:50:11 UTC
(In reply to H.J. Lu from comment #29)
> (In reply to Andrew Pinski from comment #27)
> > Comment on attachment 36720 [details]
> > A patch
> > 
> > How does this interact with LTO where lang_hooks.decls.empty_record_p is not
> > defined?
> 
> We can stream out empty record info and use it to in lto_empty_record_p.

Isn't an empty record is one without any field decls?
Comment 31 H.J. Lu 2015-11-16 01:03:55 UTC
(In reply to Andrew Pinski from comment #30)
> 
> Isn't an empty record is one without any field decls?

I thin it is language specific.
Comment 32 Andrew Pinski 2015-11-16 01:20:11 UTC
(In reply to H.J. Lu from comment #31)
> (In reply to Andrew Pinski from comment #30)
> > 
> > Isn't an empty record is one without any field decls?
> 
> I thin it is language specific.

How so?  An record without any field decls is empty and most of the rest of the middle-end treats it that way.
Comment 33 H.J. Lu 2015-11-16 02:04:53 UTC
(In reply to Andrew Pinski from comment #32)
> (In reply to H.J. Lu from comment #31)
> > (In reply to Andrew Pinski from comment #30)
> > > 
> > > Isn't an empty record is one without any field decls?
> > 
> > I thin it is language specific.
> 
> How so?  An record without any field decls is empty and most of the rest of
> the middle-end treats it that way.

This is a C++ empty class with field decls from PR 68355:

(gdb) cal debug_tree (type)
 <record_type 0x7ffff164e540 integral_constant type_5 type_6 QI
    size <integer_cst 0x7ffff14e5f90 type <integer_type 0x7ffff14e92a0 bitsizetype> constant 8>
    unit size <integer_cst 0x7ffff14e5fa8 type <integer_type 0x7ffff14e91f8 sizetype> constant 1>
    align 8 symtab 0 alias set -1 canonical type 0x7ffff164e540
    fields <var_decl 0x7ffff7ff9d80 value
        type <boolean_type 0x7ffff164e9d8 bool readonly unsigned type_6 QI size <integer_cst 0x7ffff14e5f90 8> unit size <integer_cst 0x7ffff14e5fa8 1>
            align 8 symtab 0 alias set -1 canonical type 0x7ffff164e9d8 precision 1 min <integer_cst 0x7ffff1506210 0> max <integer_cst 0x7ffff1506240 1>>
        readonly constant used public static tree_3 unsigned external nonlocal decl_3 decl_6 QI file bar.ii line 4 col 24 size <integer_cst 0x7ffff14e5f90 8> unit size <integer_cst 0x7ffff14e5fa8 1>
        align 8 context <record_type 0x7ffff164e540 integral_constant>
        template-info 0x7ffff164c220
        chain <type_decl 0x7ffff1645850 integral_constant type <record_type 0x7ffff164e930 integral_constant>
            external nonlocal suppress-debug decl_4 VOID file bar.ii line 3 col 1
            align 8 context <record_type 0x7ffff164e540 integral_constant> result <record_type 0x7ffff164e540 integral_constant>
            chain <type_decl 0x7ffff16458e8 value_type>>> context <translation_unit_decl 0x7ffff14f1168 D.1>
    full-name "struct integral_constant<bool, true>"
    X() has-type-conversion X(constX&) this=(X&) n_parents=0 use_template=1 interface-unknown
    pointer_to_this <pointer_type 0x7ffff1650000> reference_to_this <reference_type 0x7ffff1650498> chain <type_decl 0x7ffff1645720 integral_constant>>
(gdb)
Comment 34 Andrew Pinski 2015-11-16 02:35:13 UTC
(In reply to H.J. Lu from comment #33)
> (In reply to Andrew Pinski from comment #32)
> > (In reply to H.J. Lu from comment #31)
> > > (In reply to Andrew Pinski from comment #30)
> > > > 
> > > > Isn't an empty record is one without any field decls?
> > > 
> > > I thin it is language specific.
> > 
> > How so?  An record without any field decls is empty and most of the rest of
> > the middle-end treats it that way.
> 
> This is a C++ empty class with field decls from PR 68355:
> 
> (gdb) cal debug_tree (type)
>  <record_type 0x7ffff164e540 integral_constant type_5 type_6 QI
>     size <integer_cst 0x7ffff14e5f90 type <integer_type 0x7ffff14e92a0
> bitsizetype> constant 8>
>     unit size <integer_cst 0x7ffff14e5fa8 type <integer_type 0x7ffff14e91f8
> sizetype> constant 1>
>     align 8 symtab 0 alias set -1 canonical type 0x7ffff164e540
>     fields <var_decl 0x7ffff7ff9d80 value
>         type <boolean_type 0x7ffff164e9d8 bool readonly unsigned type_6 QI
> size <integer_cst 0x7ffff14e5f90 8> unit size <integer_cst 0x7ffff14e5fa8 1>
>             align 8 symtab 0 alias set -1 canonical type 0x7ffff164e9d8
> precision 1 min <integer_cst 0x7ffff1506210 0> max <integer_cst
> 0x7ffff1506240 1>>
>         readonly constant used public static tree_3 unsigned external
> nonlocal decl_3 decl_6 QI file bar.ii line 4 col 24 size <integer_cst
> 0x7ffff14e5f90 8> unit size <integer_cst 0x7ffff14e5fa8 1>
>         align 8 context <record_type 0x7ffff164e540 integral_constant>
>         template-info 0x7ffff164c220
>         chain <type_decl 0x7ffff1645850 integral_constant type <record_type
> 0x7ffff164e930 integral_constant>
>             external nonlocal suppress-debug decl_4 VOID file bar.ii line 3
> col 1
>             align 8 context <record_type 0x7ffff164e540 integral_constant>
> result <record_type 0x7ffff164e540 integral_constant>
>             chain <type_decl 0x7ffff16458e8 value_type>>> context
> <translation_unit_decl 0x7ffff14f1168 D.1>
>     full-name "struct integral_constant<bool, true>"
>     X() has-type-conversion X(constX&) this=(X&) n_parents=0 use_template=1
> interface-unknown
>     pointer_to_this <pointer_type 0x7ffff1650000> reference_to_this
> <reference_type 0x7ffff1650498> chain <type_decl 0x7ffff1645720
> integral_constant>>
> (gdb)

So no FIELD_DECL as part of fields; only var or type or method_decl?
Comment 35 H.J. Lu 2015-11-16 06:02:00 UTC
(In reply to Andrew Pinski from comment #34)
> 
> So no FIELD_DECL as part of fields; only var or type or method_decl?

struct dummy { };
struct true_type { struct dummy i; };

gave:

(gdb) cal debug_tree (type)
 <record_type 0x7ffff1647dc8 true_type type_5 type_6 QI
    size <integer_cst 0x7ffff14e5f90 type <integer_type 0x7ffff14e92a0 bitsizetype> constant 8>
    unit size <integer_cst 0x7ffff14e5fa8 type <integer_type 0x7ffff14e91f8 sizetype> constant 1>
    align 8 symtab 0 alias set -1 canonical type 0x7ffff1647dc8
    fields <field_decl 0x7ffff1645720 i
        type <record_type 0x7ffff1647bd0 dummy type_5 type_6 QI size <integer_cst 0x7ffff14e5f90 8> unit size <integer_cst 0x7ffff14e5fa8 1>
            align 8 symtab 0 alias set -1 canonical type 0x7ffff1647bd0 fields <type_decl 0x7ffff1645428 dummy> context <translation_unit_decl 0x7ffff14f1168 D.1>
            full-name "struct dummy"
            X() X(constX&) this=(X&) n_parents=0 use_template=0 interface-unknown
            pointer_to_this <pointer_type 0x7ffff164b348> reference_to_this <reference_type 0x7ffff164b1f8> chain <type_decl 0x7ffff1645390 dummy>>
        nonlocal decl_3 QI file z.ii line 2 col 33 size <integer_cst 0x7ffff14e5f90 8> unit size <integer_cst 0x7ffff14e5fa8 1>
        align 8 offset_align 128
        offset <integer_cst 0x7ffff14e5e88 constant 0>
        bit offset <integer_cst 0x7ffff14e5ee8 constant 0> context <record_type 0x7ffff1647dc8 true_type>
        chain <type_decl 0x7ffff1645688 true_type type <record_type 0x7ffff1647e70 true_type>
            nonlocal decl_4 VOID file z.ii line 2 col 18
            align 1 context <record_type 0x7ffff1647dc8 true_type> result <record_type 0x7ffff1647dc8 true_type>
           >> context <translation_unit_decl 0x7ffff14f1168 D.1>
    full-name "struct true_type"
    X() X(constX&) this=(X&) n_parents=0 use_template=0 interface-unknown
    pointer_to_this <pointer_type 0x7ffff164b150> reference_to_this <reference_type 0x7ffff164f1f8> chain <type_decl 0x7ffff16455f0 true_type>>
(gdb)
Comment 36 H.J. Lu 2015-11-16 06:08:58 UTC
Created attachment 36723 [details]
An updated patch to use is_really_empty_class

I am testing this.
Comment 37 Andrew Pinski 2015-11-16 06:39:20 UTC
(In reply to H.J. Lu from comment #35)
> (In reply to Andrew Pinski from comment #34)
> > 
> > So no FIELD_DECL as part of fields; only var or type or method_decl?
> 
> struct dummy { };
> struct true_type { struct dummy i; };

This is two empty structs.  The same is true even in GNU C.  So a struct which contains no field decls or field decls which is an empty struct is also an empty struct.
Comment 38 H.J. Lu 2015-11-16 06:52:12 UTC
Created attachment 36724 [details]
An updated patch to add empty_record_p

I am testing it now.
Comment 39 Andrew Pinski 2015-11-16 08:54:49 UTC
You should also update gimplify.c's zero_sized_type to be the same as your empty_record_p.
Comment 40 H.J. Lu 2015-11-16 11:19:50 UTC
(In reply to H.J. Lu from comment #38)
> Created attachment 36724 [details]
> An updated patch to add empty_record_p
> 
> I am testing it now.

It doesn't work since it misses

    for (binfo = TYPE_BINFO (type), i = 0;
           BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i)
        if (!is_really_empty_class (BINFO_TYPE (base_binfo)))
          return false;

in is_really_empty_class in cp/class.c.  It is language specific.
Comment 41 H.J. Lu 2015-11-16 11:22:35 UTC
(In reply to Andrew Pinski from comment #39)
> You should also update gimplify.c's zero_sized_type to be the same as your
> empty_record_p.

It won't work since the size of empty class isn't zero in C++.  We
just pass and return it like zero size record in C++.
Comment 42 H.J. Lu 2015-11-16 23:04:22 UTC
(In reply to H.J. Lu from comment #29)
> (In reply to Andrew Pinski from comment #27)
> > Comment on attachment 36720 [details]
> > A patch
> > 
> > How does this interact with LTO where lang_hooks.decls.empty_record_p is not
> > defined?
> 
> We can stream out empty record info and use it to in lto_empty_record_p.

There are 7 bits we can use for LTO:

/* These flags are available for each language front end to use internally.  */
#define TYPE_LANG_FLAG_0(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_0)
#define TYPE_LANG_FLAG_1(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_1)
#define TYPE_LANG_FLAG_2(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_2)
#define TYPE_LANG_FLAG_3(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_3)
#define TYPE_LANG_FLAG_4(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_4)
#define TYPE_LANG_FLAG_5(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_5)
#define TYPE_LANG_FLAG_6(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_6)
Comment 43 H.J. Lu 2015-11-16 23:50:41 UTC
Created attachment 36730 [details]
A patch to support LTO

I am testing this patch.
Comment 44 H.J. Lu 2015-11-17 01:03:15 UTC
*** Bug 68355 has been marked as a duplicate of this bug. ***
Comment 45 H.J. Lu 2016-01-27 13:50:07 UTC
I opened a clang bug:

https://llvm.org/bugs/show_bug.cgi?id=26337

I propose the following definitions:

i. An empty record is:
   1. A class without member.  Or
   2. An array of empty records.  Or
   3. A class with empty records.
ii. An empty record type for parameter passing is POD for the purpose of
layout and
   1. A class without member.  Or
   2. A class with empty records.
Comment 46 Jakub Jelinek 2016-04-27 10:57:44 UTC
GCC 6.1 has been released.
Comment 47 Jakub Jelinek 2016-12-21 10:57:45 UTC
GCC 6.3 is being released, adjusting target milestone.
Comment 48 Richard Biener 2017-07-04 08:47:39 UTC
GCC 6.4 is being released, adjusting target milestone.
Comment 49 Ivan Sorokin 2017-10-23 23:42:34 UTC
*** Bug 82693 has been marked as a duplicate of this bug. ***
Comment 50 Marek Polacek 2017-11-22 16:06:50 UTC
Author: mpolacek
Date: Wed Nov 22 16:06:18 2017
New Revision: 255066

URL: https://gcc.gnu.org/viewcvs?rev=255066&root=gcc&view=rev
Log:
	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* c-decl.c (grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.

	* class.c (layout_class_type): Set DECL_PADDING_P on padding.
	* decl.c (cxx_init_decl_processing): Set TRANSLATION_UNIT_WARN_EMPTY_P.
	(grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.

	* lto.c (compare_tree_sccs_1): Compare TYPE_EMPTY_P and DECL_PADDING_P.

	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
	int_size_in_bytes.
	(ix86_is_empty_record): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* dwarf2out.c (get_ultimate_context): Move to tree.c.
	* explow.c (hard_function_value): Call arg_int_size_in_bytes
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (aggregate_value_p): Return 0 for empty types.
	(assign_parm_find_entry_rtl): Call warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call arg size_in_bytes instead
	size_in_bytes.
	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and DECL_PADDING_P.
	* stor-layout.c (finalize_type_size): Set TYPE_EMPTY_P.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hooks.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	arg_size_in_bytes instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree-core.h (tree_type_common): Add empty_flag.
	(tree_decl_common): Update comments.
	* tree-streamer-in.c (unpack_ts_decl_common_value_fields): Stream
	DECL_PADDING_P.
	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
	* tree-streamer-out.c (pack_ts_decl_common_value_fields): Stream
	DECL_PADDING_P.
	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
	* tree.c (default_is_empty_type): New function.
	(default_is_empty_record): New function.
	(arg_int_size_in_bytes): New function.
	(arg_size_in_bytes): New function.
	(get_ultimate_context): New function.
	* tree.h: Define TYPE_EMPTY_P, DECL_PADDING_P and
	TRANSLATION_UNIT_WARN_EMPTY_P.
	(default_is_empty_record, arg_int_size_in_bytes,
	arg_size_in_bytes, get_ultimate_context): Declare.

	* g++.dg/abi/empty12.C: New test.
	* g++.dg/abi/empty12.h: New test.
	* g++.dg/abi/empty12a.c: New test.
	* g++.dg/abi/empty13.C: New test.
	* g++.dg/abi/empty13.h: New test.
	* g++.dg/abi/empty13a.c: New test.
	* g++.dg/abi/empty14.C: New test.
	* g++.dg/abi/empty14.h: New test.
	* g++.dg/abi/empty14a.c: New test.
	* g++.dg/abi/empty15.C: New test.
	* g++.dg/abi/empty15.h: New test.
	* g++.dg/abi/empty15a.c: New test.
	* g++.dg/abi/empty16.C: New test.
	* g++.dg/abi/empty16.h: New test.
	* g++.dg/abi/empty16a.c: New test.
	* g++.dg/abi/empty17.C: New test.
	* g++.dg/abi/empty17.h: New test.
	* g++.dg/abi/empty17a.c: New test.
	* g++.dg/abi/empty18.C: New test.
	* g++.dg/abi/empty18.h: New test.
	* g++.dg/abi/empty18a.c: New test.
	* g++.dg/abi/empty19.C: New test.
	* g++.dg/abi/empty19.h: New test.
	* g++.dg/abi/empty19a.c: New test.
	* g++.dg/abi/empty20.C: New test.
	* g++.dg/abi/empty21.C: New test.
	* g++.dg/abi/empty22.C: New test.
	* g++.dg/abi/empty22.h: New test.
	* g++.dg/abi/empty22a.c: New test.
	* g++.dg/abi/empty23.C: New test.
	* g++.dg/abi/empty24.C: New test.
	* g++.dg/abi/empty25.C: New test.
	* g++.dg/abi/empty25.h: New test.
	* g++.dg/abi/empty25a.c: New test.
	* g++.dg/abi/empty26.C: New test.
	* g++.dg/abi/empty26.h: New test.
	* g++.dg/abi/empty26a.c: New test.
	* g++.dg/abi/empty27.C: New test.
	* g++.dg/abi/empty28.C: New test.
	* g++.dg/abi/pr60336-1.C: New test.
	* g++.dg/abi/pr60336-10.C: New test.
	* g++.dg/abi/pr60336-11.C: New test.
	* g++.dg/abi/pr60336-12.C: New test.
	* g++.dg/abi/pr60336-2.C: New test.
	* g++.dg/abi/pr60336-3.C: New test.
	* g++.dg/abi/pr60336-4.C: New test.
	* g++.dg/abi/pr60336-5.C: New test.
	* g++.dg/abi/pr60336-6.C: New test.
	* g++.dg/abi/pr60336-7.C: New test.
	* g++.dg/abi/pr60336-8.C: New test.
	* g++.dg/abi/pr60336-9.C: New test.
	* g++.dg/abi/pr68355.C: New test.
	* g++.dg/lto/pr60336_0.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/abi/empty12.C
    trunk/gcc/testsuite/g++.dg/abi/empty12.h
    trunk/gcc/testsuite/g++.dg/abi/empty12a.c
    trunk/gcc/testsuite/g++.dg/abi/empty13.C
    trunk/gcc/testsuite/g++.dg/abi/empty13.h
    trunk/gcc/testsuite/g++.dg/abi/empty13a.c
    trunk/gcc/testsuite/g++.dg/abi/empty14.C
    trunk/gcc/testsuite/g++.dg/abi/empty14.h
    trunk/gcc/testsuite/g++.dg/abi/empty14a.c
    trunk/gcc/testsuite/g++.dg/abi/empty15.C
    trunk/gcc/testsuite/g++.dg/abi/empty15.h
    trunk/gcc/testsuite/g++.dg/abi/empty15a.c
    trunk/gcc/testsuite/g++.dg/abi/empty16.C
    trunk/gcc/testsuite/g++.dg/abi/empty16.h
    trunk/gcc/testsuite/g++.dg/abi/empty16a.c
    trunk/gcc/testsuite/g++.dg/abi/empty17.C
    trunk/gcc/testsuite/g++.dg/abi/empty17.h
    trunk/gcc/testsuite/g++.dg/abi/empty17a.c
    trunk/gcc/testsuite/g++.dg/abi/empty18.C
    trunk/gcc/testsuite/g++.dg/abi/empty18.h
    trunk/gcc/testsuite/g++.dg/abi/empty18a.c
    trunk/gcc/testsuite/g++.dg/abi/empty19.C
    trunk/gcc/testsuite/g++.dg/abi/empty19.h
    trunk/gcc/testsuite/g++.dg/abi/empty19a.c
    trunk/gcc/testsuite/g++.dg/abi/empty20.C
    trunk/gcc/testsuite/g++.dg/abi/empty21.C
    trunk/gcc/testsuite/g++.dg/abi/empty22.C
    trunk/gcc/testsuite/g++.dg/abi/empty22.h
    trunk/gcc/testsuite/g++.dg/abi/empty22a.c
    trunk/gcc/testsuite/g++.dg/abi/empty23.C
    trunk/gcc/testsuite/g++.dg/abi/empty24.C
    trunk/gcc/testsuite/g++.dg/abi/empty25.C
    trunk/gcc/testsuite/g++.dg/abi/empty25.h
    trunk/gcc/testsuite/g++.dg/abi/empty25a.c
    trunk/gcc/testsuite/g++.dg/abi/empty26.C
    trunk/gcc/testsuite/g++.dg/abi/empty26.h
    trunk/gcc/testsuite/g++.dg/abi/empty26a.c
    trunk/gcc/testsuite/g++.dg/abi/empty27.C
    trunk/gcc/testsuite/g++.dg/abi/empty28.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-1.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-10.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-11.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-12.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-2.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-3.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-4.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-5.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-6.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-7.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-8.C
    trunk/gcc/testsuite/g++.dg/abi/pr60336-9.C
    trunk/gcc/testsuite/g++.dg/abi/pr68355.C
    trunk/gcc/testsuite/g++.dg/lto/pr60336_0.C
Modified:
    trunk/gcc/ChangeLog
    trunk/gcc/c/ChangeLog
    trunk/gcc/c/c-decl.c
    trunk/gcc/calls.c
    trunk/gcc/common.opt
    trunk/gcc/config/i386/i386.c
    trunk/gcc/config/i386/i386.h
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/class.c
    trunk/gcc/cp/decl.c
    trunk/gcc/doc/tm.texi
    trunk/gcc/doc/tm.texi.in
    trunk/gcc/dwarf2out.c
    trunk/gcc/explow.c
    trunk/gcc/expr.c
    trunk/gcc/function.c
    trunk/gcc/lto-streamer-out.c
    trunk/gcc/lto/ChangeLog
    trunk/gcc/lto/lto.c
    trunk/gcc/stor-layout.c
    trunk/gcc/target.def
    trunk/gcc/targhooks.c
    trunk/gcc/targhooks.h
    trunk/gcc/testsuite/ChangeLog
    trunk/gcc/tree-core.h
    trunk/gcc/tree-streamer-in.c
    trunk/gcc/tree-streamer-out.c
    trunk/gcc/tree.c
    trunk/gcc/tree.h
Comment 51 Marek Polacek 2017-11-22 16:09:44 UTC
Implemented for GCC 8.
Comment 52 H.J. Lu 2018-01-26 21:05:49 UTC
*** Bug 69846 has been marked as a duplicate of this bug. ***
Comment 53 Jonathan Wakely 2018-09-05 09:33:18 UTC
This is a pure C++ testcase that gives the wrong answer in GCC 7, presumably because libc's vsnprintf is expecting the C calling convention.

struct foo { };

char buf[128];

int MySnprintf(struct foo, const char *pFormat, ...)
{
  __builtin_va_list arguments;
  __builtin_va_start(arguments, pFormat);
  __builtin_vsnprintf(buf, sizeof(buf), pFormat, arguments);
  __builtin_va_end(arguments);

  return 0;
}

int main(void) {
  struct foo blah;
  MySnprintf(blah, "%d:%d:%d:%d:%d:%d:%d:%d:%d:%d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

  __builtin_puts(buf);
}

GCC 7 prints:
1:2:3:4:5:4195847:6:7:8:9

GCC 8.1.0 prints:
1:2:3:4:5:6:7:8:9:10
Comment 54 Jonathan Wakely 2018-09-05 09:37:15 UTC
Oh, and for completeness, GCC 8.1.0 -fabi-version=11 also prints a garbage value of course:

1:2:3:4:5:4195842:6:7:8:9