For the following testcase with three similar functions we do different tree optimizations: #include <new> struct Foo { Foo() { i[0] = 1; } int i[2]; }; int foo_char(void) { int i[2]; new (reinterpret_cast<char *>(i)) Foo(); return reinterpret_cast<Foo *>(i)->i[0]; } int foo_void(void) { int i[2]; new (reinterpret_cast<void *>(i)) Foo(); return reinterpret_cast<Foo *>(i)->i[0]; } int foo_void_offset(void) { int i[2]; new (reinterpret_cast<void *>(&i[0])) Foo(); return reinterpret_cast<Foo *>(&i[0])->i[0]; } We only can optimize the foo_void_offset() variant to return 1, the foo_void() variant results in <bb 0>: this = (struct Foo *) &i[0]; this->i[0] = 1; i.6 = (struct Foo *) &i; return i.6->i[0]; where the difference starts in what the frontend produces: (void) (TARGET_EXPR <D.1791, (struct Foo *) operator new (8, (void *) &i[0])>; and return <retval> = ((struct Foo *) &i[0])->i[0]; vs. (void) (TARGET_EXPR <D.1783, (struct Foo *) operator new (8, (void *) (int *) &i)>; and return <retval> = ((struct Foo *) (int *) &i)->i[0]; note that mixing &i[0] and i does not allow folding. For the char* variant we even cannot prove that &i is non-null (!?): <bb 0>: i.2 = (char *) &i; __p = i.2; this = (struct Foo *) __p; if (__p != 0B) goto <L1>; else goto <L3>; <L1>:; this->i[0] = 1; <L3>:; i.4 = (struct Foo *) &i; return i.4->i[0]; though this might be somehow related to type-based aliasing rules(?). Note that the char variant does not care if &i[0] or plain i is specified.
It's CCP that for foo_void is able to propagate &i[0] into the comparison here: struct Foo * const this; void * D.1798; size_t D.1795; void * __p; int i[2]; struct Foo * i.6; int D.1786; struct Foo * iftmp.5; void * D.1784; struct Foo * D.1783; <bb 0>: __p_2 = &i[0]; this_6 = (struct Foo *) __p_2; if (__p_2 != 0B) goto <L1>; else goto <L3>; but not for foo_char, as here: struct Foo * const this; void * D.1821; size_t D.1818; void * __p; int i[2]; struct Foo * i.4; int D.1778; struct Foo * iftmp.3; void * D.1776; char * i.2; struct Foo * D.1765; <bb 0>: i.2_1 = (char *) &i; __p_3 = i.2_1; this_7 = (struct Foo *) __p_3; if (__p_3 != 0B) goto <L1>; else goto <L3>; I guess CCP is confused by the cast. So much for the char*/void* difference.
The difference between foo_void and foo_void_offset is that for foo_void PRE cannot see that (struct Foo *) &i[0] is equivalent to (struct Foo *) &i. As such, for foo_void we end up with <bb 0>: __p_2 = &i[0]; this_6 = (struct Foo *) &i[0]; this_6->i[0] = 1; <L3>:; i.3_7 = (struct Foo *) &i; D.1777_8 = i.3_7->i[0]; return D.1777_8; while for foo_void_offset the two uses of i are redundant and one is removed: <bb 0>: __p_2 = &i[0]; this_6 = (struct Foo *) &i[0]; this_6->i[0] = 1; <L3>:; D.1786_7 = this_6; D.1785_8 = D.1786_7->i[0]; return D.1785_8; So it seems we either need to teach PRE the equivalency between (struct Foo *) &i[0] and (struct Foo *) &i, or fold should canonicalize them to one form (which one? I guess &i[0]).
The C++ frontend doesn't give us the opportunity to canonicalize &i to &i[0] as it doesn't call fold in typeck:build_address or decay_conversion. I'm lost here.
Confirmed.
Notice that the frontend shouldn't *ever* call fold. It's the optimizers' job to adjust these things.
Note these are all questionable in aliasing.
The non-null check is now removed for the foo_char() case by VRP (note that the non-null check for the two viod-cases are _not_ removed by VRP but by DOM1. The propagation of the initialization in foo_void_offset is layed ground for by FRE, which changes iftmp.3_6 = (struct Foo *) __p_5; if (iftmp.3_6 != 0B) goto <L0>; else goto <L1>; <L0>:; this_11 = (struct Foo * const) iftmp.3_6; this_11->i[0] = 1; iftmp.3_12 = iftmp.3_6; goto <bb 3> (<L2>); <L1>:; iftmp.3_10 = iftmp.3_6; <L2>:; D.2114_7 = (struct Foo *) &i[0]; D.2113_8 = D.2114_7->i[0]; return D.2113_8; to iftmp.3_6 = (struct Foo *) __p_5; if (iftmp.3_6 != 0B) goto <L0>; else goto <L1>; <L0>:; this_11 = iftmp.3_6; this_11->i[0] = 1; iftmp.3_12 = iftmp.3_6; goto <bb 3> (<L2>); <L1>:; iftmp.3_10 = iftmp.3_6; <L2>:; D.2114_7 = iftmp.3_6; D.2113_8 = D.2114_7->i[0]; which can then be optimized further. This doesn't happen for the exact same example with void * exchanged with char * -- is FRE paying attention to some strict-aliasing rules here? Note there is no difference in alias information after CCP1. For the record, the char* example mentioned would look like int foo_char(void) { int i[2]; new (reinterpret_cast<char *>(&i[0])) Foo(); return reinterpret_cast<Foo *>(&i[0])->i[0]; }
Another one is the following (without any possible aliasing problems): int foo_charchar(void) { char i[2*sizeof(int)]; new (i) Foo(); return reinterpret_cast<Foo*>(&i[0])->i[0]; } where we miss the FRE opportunity for the cast: <bb 0>: iftmp.0 = (struct Foo *) &i[0]; iftmp.0->i[0] = 1; D.2097 = (struct Foo *) &i[0]; return D.2097->i[0];
(In reply to comment #8) > Another one is the following (without any possible aliasing problems): Actually that is still a violation of the aliasing rules, as you are acessing a character as a Foo. Yes that is violation, though I think GCC does not define it as one.
(In reply to comment #9) > Actually that is still a violation of the aliasing rules, as you are acessing a > character as a Foo. Yes that is violation, though I think GCC does not define > it as one. I think the code is fine - memory is accessed only as Foo (though it is declared as char). This simulates int foo_charchar(void) { void *i = alloca(2*sizeof(int)); new (i) Foo(); return reinterpret_cast<Foo*>(i)->i[0]; } by making the storage explicit.
Subject: Bug number PR19637 A patch for this bug has been added to the patch tracker. The mailing list url for the patch is http://gcc.gnu.org/ml/gcc-patches/2007-03/msg00295.html
Subject: Bug 19637 Author: rguenth Date: Mon Mar 17 14:34:21 2008 New Revision: 133291 URL: http://gcc.gnu.org/viewcvs?root=gcc&view=rev&rev=133291 Log: 2008-03-17 Richard Guenther <rguenther@suse.de> PR tree-optimization/19637 * fold-const.c (fold_unary): Remove restrictions of removing intermediate pointer-conversions (P2)(P1)P0. * tree-ssa-ccp.c (maybe_fold_stmt_addition): Recover from conversion to void pointer. (get_maxval_strlen): Handle addresses of the form &(*p)[0]. * g++.dg/tree-ssa/pr19637.C: New testcase. Added: trunk/gcc/testsuite/g++.dg/tree-ssa/pr19637.C Modified: trunk/gcc/ChangeLog trunk/gcc/fold-const.c trunk/gcc/testsuite/ChangeLog trunk/gcc/tree-ssa-ccp.c
Fixed.