Bug 2117528

Summary: GCC 11.2.1 segmentation fault of binary produced
Product: Red Hat Enterprise Linux 8 Reporter: Ankur deDev <ankur.dedev>
Component: gcc-toolset-11Assignee: Marek Polacek <mpolacek>
Status: CLOSED NOTABUG QA Contact: qe-baseos-tools-bugs
Severity: unspecified Docs Contact:
Priority: unspecified    
Version: 8.6CC: sipoyare
Target Milestone: rcKeywords: Bugfix, Triaged
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: No Doc Update
Doc Text:
If this bug requires documentation, please select an appropriate Doc Type value.
Story Points: ---
Clone Of: Environment:
Last Closed: 2022-08-15 22:47:43 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Attachments:
Description Flags
test case none

Description Ankur deDev 2022-08-11 09:05:58 UTC
Created attachment 1904874 [details]
test case

With GCC 11.2.1 the binary produced by this code ends up with a segmentation fault. There is no problem when compiled with -O1 or -O3. From what I can see this is also the case with GCC 11.3 and GCC 12.1. On the other end, it works fine (even with -O2) on GCC 10.3.1.

To reproduce, please download the attachment and run:
/opt/rh/gcc-toolset-11/root/bin/g++ -Wall -O2 -std=c++11 test.cpp && ./a.out

Comment 1 Marek Polacek 2022-08-15 18:16:02 UTC
This looks like a bug in IPA modref, since it goes away with -fno-ipa-modref.  Indeed, started with r11-3308.

Reproduced with trunk as well.  I'm going to have to file an upstream bug.

Comment 2 Marek Polacek 2022-08-15 18:48:34 UTC
The test also doesn't crash with -fno-strict-aliasing.

Comment 3 Marek Polacek 2022-08-15 22:47:43 UTC
Sorry, I actually think it's a bug in the program; I think the reinterpret_cast violates TBAA.

The function sum takes a reference to BarInner, but it's passed a BarOuter:

  int sum(BarInner& bar) { ... }

  BarOuter bar{ d0, d1, d2 };
  int res{ sum(bar) };

The conversion from BarOuter to BarInner uses the reinterpret_cast from

Bar<Foo<Inner<0>, Outer<0> >, Foo<Inner<1>, Outer<1> >, Foo<Inner<2>, Outer<2> >> *

to

Bar<Foo<Inner<0>, Inner<0> >, Foo<Inner<1>, Inner<1> >, Foo<Inner<2>, Inner<2> >> *

but these types are not similar, and the result pointer is dereferenced.

DSE then thinks that sum doesn't use memory refs to bar, like MEM[(struct Foo *)&bar] (this
was figured by the new modref_may_conflict in ref_maybe_used_by_call_p_1), and it removes
dead stores, in effect, the initialization of bar.  The fix should be just to

-  BarOuter bar{ d0, d1, d2 };
+  BarInner bar{ d0, d1, d2 };


.dse1 removes:

--- test.cpp.041t.mergephi1	2022-08-15 17:22:11.592002137 -0400
+++ test.cpp.042t.dse1	2022-08-15 17:22:11.593002138 -0400
@@ -90,24 +90,18 @@ void Foo<Inner<2>, Outer<2> >::Foo (stru
 
 void Bar<Foo<Inner<0>, Outer<0> >, Foo<Inner<1>, Outer<1> >, Foo<Inner<2>, Outer<2> > >::Bar (struct Bar * const this, struct Outer_t & objs#0, struct Outer_t & objs#1, struct Outer_t & objs#2)
 {
-  struct Foo * _1;
-  struct Foo * _2;
-  struct Foo * _3;
   struct Inner * _13;
   struct Inner * _14;
   struct Inner * _15;
 
   <bb 2> :
   *this_5(D) ={v} {CLOBBER};
-  _1 = &this_5(D)->D.2854;
   MEM[(struct Foo *)this_5(D)] ={v} {CLOBBER};
   _15 = &MEM[(struct Outer *)objs#0_7(D)].D.2554;
   MEM[(struct Foo *)this_5(D)].obj_p = _15;
-  _2 = &this_5(D)->D.2855;
   MEM[(struct Foo *)this_5(D) + 8B] ={v} {CLOBBER};
   _14 = &MEM[(struct Outer *)objs#1_9(D)].D.2635;
   MEM[(struct Foo *)this_5(D) + 8B].obj_p = _14;
-  _3 = &this_5(D)->D.2856;
   MEM[(struct Foo *)this_5(D) + 16B] ={v} {CLOBBER};
   _13 = &MEM[(struct Outer *)objs#2_11(D)].D.2716;
   MEM[(struct Foo *)this_5(D) + 16B].obj_p = _13;
@@ -234,13 +228,6 @@ int main ()
   d1.D.2635.ptr = &i1;
   d2 ={v} {CLOBBER};
   d2.D.2716.ptr = &i2;
-  MEM[(struct Bar *)&bar] ={v} {CLOBBER};
-  MEM[(struct Foo *)&bar] ={v} {CLOBBER};
-  MEM[(struct Foo *)&bar].obj_p = &d0.D.2554;
-  MEM[(struct Foo *)&bar + 8B] ={v} {CLOBBER};
-  MEM[(struct Foo *)&bar + 8B].obj_p = &d1.D.2635;
-  MEM[(struct Foo *)&bar + 16B] ={v} {CLOBBER};
-  MEM[(struct Foo *)&bar + 16B].obj_p = &d2.D.2716;
   res_12 = sum (&bar);
   i0 ={v} {CLOBBER(eol)};
   i1 ={v} {CLOBBER(eol)};

because it thinks that

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Foo *)&bar + 16B].obj_p alias sets: 13->12
  Deleted dead store: MEM[(struct Foo *)&bar + 16B].obj_p = &d2.D.2716;

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Foo *)&bar + 16B] alias sets: 13->13
  Deleted dead store: MEM[(struct Foo *)&bar + 16B] ={v} {CLOBBER};

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Foo *)&bar + 8B].obj_p alias sets: 11->10
  Deleted dead store: MEM[(struct Foo *)&bar + 8B].obj_p = &d1.D.2635;

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Foo *)&bar + 8B] alias sets: 11->11
  Deleted dead store: MEM[(struct Foo *)&bar + 8B] ={v} {CLOBBER};

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Foo *)&bar].obj_p alias sets: 9->8
  Deleted dead store: MEM[(struct Foo *)&bar].obj_p = &d0.D.2554;

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Foo *)&bar] alias sets: 9->9
  Deleted dead store: MEM[(struct Foo *)&bar] ={v} {CLOBBER};

ipa-modref: call stmt res_12 = sum (&bar);
ipa-modref: call to int sum(BarInner&)/0 does not use ref: MEM[(struct Bar *)&bar] alias sets: 17->17
  Deleted dead store: MEM[(struct Bar *)&bar] ={v} {CLOBBER};

Comment 4 Ankur deDev 2022-08-16 14:20:14 UTC
Thanks a lot for looking in detail at the report.

The use of a BarOuter object and the reinterpret_cast were put there on purpose but I understand your point and I agree that the C++ strict type aliasing rule is not respected in this case. I imagine that the code has been working well until now (including other compilers like Clang or ICC) because the source type (BarOuter) and the destination type (BarInner) of the cast have the exact same layout, that is three pointers where the first one is of type Inner<0>*, the second one is of type Inner<1>* and the third one is of type Inner<2>*. On top of this, the accesses made in the function "sum" through those three pointers are valid. This (simplified) test case does not convey the rationale for this construct but with the details you provided on the reasoning used in the newer versions of GCC, I will try to find a workaround that is compatible with the strict type aliasing rule of C++. I will let you know if I cannot get rid of the segmentation fault with this approach. Again, thanks a lot for your help.

Comment 5 Marek Polacek 2022-08-16 14:30:20 UTC
FWIW, it may be possible to use __attribute__ ((__may_alias__)) to work around this one specific problem.  -fno-strict-aliasing would also help, but it's a big hammer.