The following testcase: int p(void) __attribute__((const)); void g(int); void f() { for (;;) g(p()); } is optimized when compiled as C: f: pushq %rbx call p movl %eax, %ebx .L2: movl %ebx, %edi call g jmp .L2 and not optimized when compiled as C++: _Z1fv: subq $8, %rsp .L2: call _Z1pv movl %eax, %edi call _Z1gi jmp .L2 For C the motion is performed by the PRE pass; tree dumps for PRE diverge after: @@ -95,76 +95,52 @@ Value numbering .MEM_4 stmt = g (_3); Setting value number of .MEM_4 to .MEM_4 Processing SCC needed 3 iterations Value numbers: [...] avail_out[2] := { } -exp_gen[3] := { {call_expr<p>} (0003) } +exp_gen[3] := { } phi_gen[3] := { }
Because p may throw. What we miss here is the fact that it should only matter if p throws internally for IL consistency. Of course it still matters for observing other side-effects if p throws and after the transform now does so before side-effects that should be observed otherwise. Consider for (;;) { printf("foo"); g(p()) } and p throwing. So you miss a nothrow attribute or a throw() specification here. const does _not_ imply nothrow.
In that case I'd like to contribute a documentation patch to make that clear in the pure/const attribute information, but I need more explanation. I see that int p(void) __attribute__((const)); void f() { p(); p(); } is optimized to an empty function, even though p may throw. Is that not a bug? Also, could you please expand your explanation in the first paragraph, i.e. this: What we miss here is the fact that it should only matter if p throws internally for IL consistency. Of course it still matters for observing other side-effects if p throws and after the transform now does so before side-effects that should be observed otherwise. I'm probably missing a lot of contextual knowledge to understand that. TIA.
(In reply to Alexander Monakov from comment #2) > In that case I'd like to contribute a documentation patch to make that clear > in the pure/const attribute information, but I need more explanation. I see > that > > int p(void) __attribute__((const)); > void f() > { > p(); > p(); > } > > is optimized to an empty function, even though p may throw. Is that not a > bug? I would say so. But this has quite some implementation issues, also with... > Also, could you please expand your explanation in the first paragraph, i.e. > this: > > What we miss here is the fact that it should only matter > if p throws internally for IL consistency. Of course it > still matters for observing other side-effects if p throws > and after the transform now does so before side-effects > that should be observed otherwise. > > I'm probably missing a lot of contextual knowledge to understand that. TIA. ... this, the side-effect ordering of stores and the throwing p() call is not properly represented.
Should this be reopened? https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html 'const' is not clarified on its interaction with threads (https://gcc.gnu.org/legacy-ml/gcc/2015-09/msg00365.html) and void f() { for (;;) g(p()); } is still pessimized for C++ (I tend to agree that 'const' should imply 'nothrow'; even if no, the #c2 case should be resolved properly)