In the following example, the call to the function `g` is guarded by the `f(0)` call and is never evaluated but the optimizer moved it over the guard while hoisting it from the loop: ---------------------------------------------------------------------- #include <stdlib.h> __attribute__((noipa)) void f(int i) { exit(i); } __attribute__((const,noipa)) int g(int i) { return 1 / i; } int main() { while (1) { f(0); f(g(0)); } } ---------------------------------------------------------------------- $ gcc -std=c11 -pedantic -Wall -Wextra test.c && ./a.out $ gcc -std=c11 -pedantic -Wall -Wextra -O3 test.c && ./a.out Floating point exception ---------------------------------------------------------------------- gcc x86-64 version: gcc (GCC) 10.0.1 20200129 (experimental) ----------------------------------------------------------------------
Const functions by definition dont trap or throw. So adding const to a function that traps makes the testcase undefined. Do you have a testcase were gcc does this optimize without the user adding const and still traps?
(In reply to Andrew Pinski from comment #1) > Const functions by definition dont trap or throw. They don't trap in all cases when they are really called. But this doesn't mean they must have a sane behavior for other combinations of arguments. At least that's what I got from the definition[1] of this attribute. In particular, it reads: "Declaring such functions with the const attribute allows GCC to avoid emitting some calls in repeated invocations of the function with the same argument values." That is, it allow gcc to eliminate some calls, but not to introduce new ones. But this is a gcc extension so it could be assigned any meaning that is seemed beneficial to gcc. In such a case this pr should be considered as a documentation clarification request. [1] https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute > So adding const to a function that traps makes the testcase undefined. Let me describe the testcase like this: the function `g` is defined only for non-zero arguments, conforms to the rules of the const attribute for non-zero arguments, and is called only with non-zero arguments, but the optimizer introduces a call to it with the zero argument. Perhaps the minimized example that I posted looks weird. The situation could be more subtle in real life. For instance, the bad call could be due to an unitialized variable used as the argument. I mean this variable could be uninitialized only during speculative execution but perfectly initialized during ordinary execution. Or it could be something like this: ---------------------------------------------------------------------- #include <stdlib.h> #include <stdio.h> __attribute__((noipa)) void check_value(int x) { if (x == 0) { puts("Fatal error: got invalid value. Shutting down cleanly!"); exit(0); } } __attribute__((const,noipa)) int calc(int x) { return 1 / x; } int main(int c, char **v) { (void)v; int x = 0; // invalid value by default // some complex initialization of x that doesn't always succeed if (c > 5) x = 123; // etc. for (int i = 0; i < 10; i++) { check_value(x); printf("result = %d\n", calc(x)); } } ---------------------------------------------------------------------- $ gcc -std=c11 -pedantic -Wall -Wextra test.c && ./a.out Fatal error: got invalid value. Shutting down cleanly! $ gcc -std=c11 -pedantic -Wall -Wextra -O3 test.c && ./a.out Floating point exception ---------------------------------------------------------------------- gcc x86-64 version: gcc (GCC) 10.0.1 20200129 (experimental) ---------------------------------------------------------------------- Again, this doesn't mean that gcc have to support such use cases. > Do you have a testcase were gcc does this optimize without the user adding > const and still traps? No. I'll file a separate bug if I stumble upon one, so please disregard this possibility for now.
On Wed, 29 Jan 2020, pinskia at gcc dot gnu.org wrote: > Const functions by definition dont trap or throw. So adding const to a > function that traps makes the testcase undefined. It's not clear to me if the definition of const means const for all possible arguments as opposed to const for arguments actually passed when the program is executed in the abstract machine (see what I said in bug 19828 comment 12).
(In reply to Alexander Cherepanov from comment #2) > > Do you have a testcase were gcc does this optimize without the user adding > > const and still traps? > > No. I'll file a separate bug if I stumble upon one, so please disregard this > possibility for now. GCC will deduce that g is const just fine, it even tells you so with -Wsuggest-attribute=const __attribute__((noipa)) void f(int i) { __builtin_exit(i); } __attribute__((noinline)) int g(int i) { return 1 / i; } int main() { while (1) { f(0); f(g(0)); } } Thus removing WAITING and confirming.
'const' and trapping/EH are distinct bits, and definiteily tree/gimple_could_trap_p reports true for the 1 / i; stmt. Note that traps are not considered a "side-effect" and thus passes just checking for gimple_has_side_effects will not get this. But traps in general are difficult beasts, and what to do when facing them depends on the actual transform. In this case it is tree PRE hoisting the g(0) call. It's vn_reference_may_trap that handles calls optimistically: /* Return true if the reference operation REF may trap. */ bool vn_reference_may_trap (vn_reference_t ref) { switch (ref->operands[0].opcode) { case MODIFY_EXPR: case CALL_EXPR: /* We do not handle calls. */ case ADDR_EXPR: /* And toplevel address computations never trap. */ return false; but note that tree_could_trap_p for example only looks at the actual call instruction, not at as to whether the called function execution might trap. So calls need to be handled specially and we don't have a way to test/record for whether a called function might trap.
Fixing this FAILs the testcase for PR88087 FAIL: gcc.dg/tree-ssa/pr88087.c scan-tree-dump pre "Eliminated: 1" which exactly asks for this kind of transform: int f(); int d; void c() { for (;;) { f(); int (*fp)() __attribute__((const)) = (void *)f; d = fp(); } } /* We shouldn't ICE and hoist the const call of fp out of the loop. */
The master branch has been updated by Richard Biener <rguenth@gcc.gnu.org>: https://gcc.gnu.org/g:13a43a90aea368a25da50762eba4873bafb4e448 commit r12-3284-g13a43a90aea368a25da50762eba4873bafb4e448 Author: Richard Biener <rguenther@suse.de> Date: Wed Sep 1 11:49:39 2021 +0200 tree-optimization/93491 - avoid PRE of trapping calls across exits This makes us avoid PREing calls that could trap across other calls that might not return. The PR88087 testcase has exactly such case so I've refactored the testcase to contain a valid PRE. I've also adjusted PRE to not consider pure calls possibly not returning in line with what we do elsewhere. Note we don't have a good idea whether a function always returns normally or whether its body is known to never trap. That's something IPA could compute. 2021-09-01 Richard Biener <rguenther@suse.de> PR tree-optimization/93491 * tree-ssa-pre.c (compute_avail): Set BB_MAY_NOTRETURN after processing the stmt itself. Do not consider pure functions possibly not returning. Properly avoid adding possibly trapping calls to EXP_GEN when there's a preceeding possibly not returning call. * tree-ssa-sccvn.c (vn_reference_may_trap): Conservatively not handle calls. * gcc.dg/torture/pr93491.c: New testcase. * gcc.dg/tree-ssa/pr88087.c: Change to valid PRE opportunity.
Fixed.