https://gcc.gnu.org/g:27778979c9a1e32a6ca74e5b5f377385225449b1
commit r15-5595-g27778979c9a1e32a6ca74e5b5f377385225449b1 Author: Jakub Jelinek <ja...@redhat.com> Date: Fri Nov 22 19:52:35 2024 +0100 Add -f{,no-}assume-sane-operators-new-delete options [PR110137] The following patch adds a new option for optimizations related to replaceable global operators new/delete. The option isn't called -fassume-sane-operator-new (which clang++ implements), because 1) clang++ option means something different; initially it was an option to add malloc attribute to those declarations (but we have malloc attribute on all <new> calls already unconditionally); later it was changed to add noalias attribute rather than malloc, whatever it means, but it is certainly about the return value from the operator new (whether it can alias with other pointers); we already assume malloc-ish behavior that it doesn't alias any other pointers 2) the option only affects operator new, we want it affect also operator delete The option basically allows to choose between pre-PR101480 behavior (now the default, more optimistic) and post-PR101480 behavior (safer but penalizing most of the code in the wild for rare needs). I've tried to explain stuff in the documentation too. 2024-11-22 Jakub Jelinek <ja...@redhat.com> PR c++/110137 PR middle-end/101480 gcc/ * doc/invoke.texi (-fassume-sane-operators-new-delete, -fno-assume-sane-operators-new-delete): Document. * gimple.cc (gimple_call_fnspec): Handle -f{,no-}assume-sane-operators-new-delete. * ipa-inline-transform.cc (inline_call): Also clear flag_assume_sane_operators_new_delete on caller when inlining -fno-assume-sane-operators-new-delete callee into -fassume-sane-operators-new-delete caller. gcc/c-family/ * c.opt (fassume-sane-operators-new-delete): New option. gcc/testsuite/ * g++.dg/tree-ssa/pr110137-1.C: New test. * g++.dg/tree-ssa/pr110137-2.C: New test. * g++.dg/tree-ssa/pr110137-3.C: New test. * g++.dg/tree-ssa/pr110137-4.C: New test. * g++.dg/torture/pr10148.C: Add -fno-assume-sane-operators-new-delete as dg-additional-options. * g++.dg/warn/Warray-bounds-16.C: Revert 2021-11-10 changes. Diff: --- gcc/c-family/c.opt | 4 + gcc/doc/invoke.texi | 33 ++++++- gcc/gimple.cc | 14 ++- gcc/ipa-inline-transform.cc | 28 ++++-- gcc/testsuite/g++.dg/torture/pr10148.C | 1 + gcc/testsuite/g++.dg/tree-ssa/pr110137-1.C | 74 ++++++++++++++++ gcc/testsuite/g++.dg/tree-ssa/pr110137-2.C | 74 ++++++++++++++++ gcc/testsuite/g++.dg/tree-ssa/pr110137-3.C | 76 ++++++++++++++++ gcc/testsuite/g++.dg/tree-ssa/pr110137-4.C | 124 +++++++++++++++++++++++++++ gcc/testsuite/g++.dg/warn/Warray-bounds-16.C | 6 +- 10 files changed, 422 insertions(+), 12 deletions(-) diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 65cb8e5c6969..268725471329 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1690,6 +1690,10 @@ fasm C ObjC C++ ObjC++ Var(flag_no_asm, 0) Recognize the \"asm\" keyword. +fassume-sane-operators-new-delete +C++ ObjC++ Optimization Var(flag_assume_sane_operators_new_delete) Init(1) +Assume C++ replaceable global operators new, new[], delete, delete[] don't read or write visible global state. + ; Define extra predefined macros for use in libgcc. fbuilding-libgcc C ObjC C++ ObjC++ Undocumented Var(flag_building_libgcc) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 0951901f50af..a8662efb5cb2 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -213,7 +213,9 @@ in the following sections. @item C++ Language Options @xref{C++ Dialect Options,,Options Controlling C++ Dialect}. @gccoptlist{-fabi-version=@var{n} -fno-access-control --faligned-new=@var{n} -fargs-in-order=@var{n} -fchar8_t -fcheck-new +-faligned-new=@var{n} -fargs-in-order=@var{n} +-fno-assume-sane-operators-new-delete +-fchar8_t -fcheck-new -fconcepts -fconstexpr-depth=@var{n} -fconstexpr-cache-depth=@var{n} -fconstexpr-loop-limit=@var{n} -fconstexpr-ops-limit=@var{n} -fno-elide-constructors @@ -3163,6 +3165,35 @@ but few users will need to override the default of This flag is enabled by default for @option{-std=c++17}. +@opindex fno-assume-sane-operators-new-delete +@opindex fassume-sane-operators-new-delete +@item -fno-assume-sane-operators-new +The C++ standard allows replacing the global @code{new}, @code{new[]}, +@code{delete} and @code{delete[]} operators, though a lot of C++ programs +don't replace them and just use the implementation provided version. +Furthermore, the C++ standard allows omitting those calls if they are +made from new or delete expressions (and by extension the same is +assumed if @code{__builtin_operator_new} or @code{__builtin_operator_delete} +functions are used). +This option allows control over some optimizations around calls +to those operators. +With @code{-fassume-sane-operators-new-delete} option GCC may assume that +calls to the replaceable global operators from new or delete expressions or +from @code{__builtin_operator_new} or @code{__builtin_operator_delete} calls +don't read or modify any global variables or variables whose address could +escape to the operators (global state; except for @code{errno} for the +@code{new} and @code{new[]} operators). +This allows most optimizations across those calls and is something that +the implementation provided operators satisfy unless @code{malloc} +implementation details are observable in the code or unless @code{malloc} +hooks are used, but might not be satisfied if a program replaces those +operators. This behavior is enabled by default. +With @code{-fno-assume-sane-operators-new-delete} option GCC must +assume all these calls (whether from new or delete expressions or called +directly) may read and write global state unless proven otherwise (e.g.@: +when GCC compiles their implementation). Use this option if those +operators are or may be replaced and code needs to expect such behavior. + @opindex fchar8_t @opindex fno-char8_t @item -fchar8_t diff --git a/gcc/gimple.cc b/gcc/gimple.cc index add4a645b454..6e8ac05c8776 100644 --- a/gcc/gimple.cc +++ b/gcc/gimple.cc @@ -1615,12 +1615,22 @@ gimple_call_fnspec (const gcall *stmt) && DECL_IS_OPERATOR_DELETE_P (fndecl) && DECL_IS_REPLACEABLE_OPERATOR (fndecl) && gimple_call_from_new_or_delete (stmt)) - return ". o "; + { + if (flag_assume_sane_operators_new_delete) + return ".co "; + else + return ". o "; + } /* Similarly operator new can be treated as malloc. */ if (fndecl && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl) && gimple_call_from_new_or_delete (stmt)) - return "m "; + { + if (flag_assume_sane_operators_new_delete) + return "mC"; + else + return "m "; + } return ""; } diff --git a/gcc/ipa-inline-transform.cc b/gcc/ipa-inline-transform.cc index bf34e172b7be..cfa902b4dd3f 100644 --- a/gcc/ipa-inline-transform.cc +++ b/gcc/ipa-inline-transform.cc @@ -391,17 +391,33 @@ inline_call (struct cgraph_edge *e, bool update_original, = DECL_FUNCTION_PERSONALITY (callee->decl); bool reload_optimization_node = false; - if (!opt_for_fn (callee->decl, flag_strict_aliasing) - && opt_for_fn (to->decl, flag_strict_aliasing)) + bool remove_strict_aliasing + = (!opt_for_fn (callee->decl, flag_strict_aliasing) + && opt_for_fn (to->decl, flag_strict_aliasing)); + bool remove_assume_sane_operators_new_delete + = (!opt_for_fn (callee->decl, flag_assume_sane_operators_new_delete) + && opt_for_fn (to->decl, flag_assume_sane_operators_new_delete)); + if (remove_strict_aliasing || remove_assume_sane_operators_new_delete) { struct gcc_options opts = global_options; struct gcc_options opts_set = global_options_set; cl_optimization_restore (&opts, &opts_set, opts_for_fn (to->decl)); - opts.x_flag_strict_aliasing = false; - if (dump_file) - fprintf (dump_file, "Dropping flag_strict_aliasing on %s\n", - to->dump_name ()); + if (remove_strict_aliasing) + { + opts.x_flag_strict_aliasing = false; + if (dump_file) + fprintf (dump_file, "Dropping flag_strict_aliasing on %s\n", + to->dump_name ()); + } + if (remove_assume_sane_operators_new_delete) + { + opts.x_flag_assume_sane_operators_new_delete = false; + if (dump_file) + fprintf (dump_file, + "Dropping flag_assume_sane_operators_new_delete on %s\n", + to->dump_name ()); + } DECL_FUNCTION_SPECIFIC_OPTIMIZATION (to->decl) = build_optimization_node (&opts, &opts_set); reload_optimization_node = true; diff --git a/gcc/testsuite/g++.dg/torture/pr10148.C b/gcc/testsuite/g++.dg/torture/pr10148.C index b5483f3ca315..23a53916ebd1 100644 --- a/gcc/testsuite/g++.dg/torture/pr10148.C +++ b/gcc/testsuite/g++.dg/torture/pr10148.C @@ -1,5 +1,6 @@ /* { dg-do run } */ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-assume-sane-operators-new-delete" } */ #include <stdlib.h> #include <assert.h> diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr110137-1.C b/gcc/testsuite/g++.dg/tree-ssa/pr110137-1.C new file mode 100644 index 000000000000..18f8d23cd79a --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/pr110137-1.C @@ -0,0 +1,74 @@ +// PR c++/110137 +// { dg-do compile } +// { dg-options "-O2 -fdump-tree-optimized" } +// { dg-final { scan-tree-dump "j = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump "m = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "q = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "t = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "k = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-times "r = 1;" 2 "optimized" } } */ + +int i, j, k, l, m, n, o, q, r, s, t, u; + +void * +foo () +{ + i = 1; + j = 2; + void *p = ::operator new (32); + j = 3; + k = i; + return p; +} + +void +bar (void *p) +{ + l = 1; + m = 2; + ::operator delete (p); + m = 3; + n = l; +} + +int * +baz () +{ + o = 1; + q = 2; + int *p = new int; + q = 3; + r = o; + return p; +} + +void +qux (int *p) +{ + s = 1; + t = 2; + delete p; + t = 3; + u = s; +} + +void * +corge () +{ + o = 1; + q = 2; + void *p = __builtin_operator_new (32); + q = 3; + r = o; + return p; +} + +void +waldo (void *p) +{ + s = 1; + t = 2; + __builtin_operator_delete (p); + t = 3; + u = s; +} diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr110137-2.C b/gcc/testsuite/g++.dg/tree-ssa/pr110137-2.C new file mode 100644 index 000000000000..1bb3eabba9c7 --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/pr110137-2.C @@ -0,0 +1,74 @@ +// PR c++/110137 +// { dg-do compile } +// { dg-options "-O2 -fdump-tree-optimized -fassume-sane-operators-new-delete" } +// { dg-final { scan-tree-dump "j = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump "m = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "q = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "t = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "k = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-times "r = 1;" 2 "optimized" } } */ + +int i, j, k, l, m, n, o, q, r, s, t, u; + +void * +foo () +{ + i = 1; + j = 2; + void *p = ::operator new (32); + j = 3; + k = i; + return p; +} + +void +bar (void *p) +{ + l = 1; + m = 2; + ::operator delete (p); + m = 3; + n = l; +} + +int * +baz () +{ + o = 1; + q = 2; + int *p = new int; + q = 3; + r = o; + return p; +} + +void +qux (int *p) +{ + s = 1; + t = 2; + delete p; + t = 3; + u = s; +} + +void * +corge () +{ + o = 1; + q = 2; + void *p = __builtin_operator_new (32); + q = 3; + r = o; + return p; +} + +void +waldo (void *p) +{ + s = 1; + t = 2; + __builtin_operator_delete (p); + t = 3; + u = s; +} diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr110137-3.C b/gcc/testsuite/g++.dg/tree-ssa/pr110137-3.C new file mode 100644 index 000000000000..e46cd8470f4e --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/pr110137-3.C @@ -0,0 +1,76 @@ +// PR c++/110137 +// { dg-do compile } +// { dg-options "-O2 -fdump-tree-optimized -fno-assume-sane-operators-new-delete" } +// { dg-final { scan-tree-dump "j = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump "m = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-times "q = 2;" 2 "optimized" } } */ +// { dg-final { scan-tree-dump-times "t = 2;" 2 "optimized" } } */ +// { dg-final { scan-tree-dump-not "k = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "n = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "r = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "u = 1;" "optimized" } } */ + +int i, j, k, l, m, n, o, q, r, s, t, u; + +void * +foo () +{ + i = 1; + j = 2; + void *p = ::operator new (32); + j = 3; + k = i; + return p; +} + +void +bar (void *p) +{ + l = 1; + m = 2; + ::operator delete (p); + m = 3; + n = l; +} + +int * +baz () +{ + o = 1; + q = 2; + int *p = new int; + q = 3; + r = o; + return p; +} + +void +qux (int *p) +{ + s = 1; + t = 2; + delete p; + t = 3; + u = s; +} + +void * +corge () +{ + o = 1; + q = 2; + void *p = __builtin_operator_new (32); + q = 3; + r = o; + return p; +} + +void +waldo (void *p) +{ + s = 1; + t = 2; + __builtin_operator_delete (p); + t = 3; + u = s; +} diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr110137-4.C b/gcc/testsuite/g++.dg/tree-ssa/pr110137-4.C new file mode 100644 index 000000000000..705dd38870d2 --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/pr110137-4.C @@ -0,0 +1,124 @@ +// PR c++/110137 +// { dg-do compile } +// { dg-options "-O2 -fdump-tree-optimized" } +// { dg-final { scan-tree-dump "j = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump "m = 2;" "optimized" } } */ +// { dg-final { scan-tree-dump-times "q = 2;" 2 "optimized" } } */ +// { dg-final { scan-tree-dump-times "t = 2;" 2 "optimized" } } */ +// { dg-final { scan-tree-dump-not "k = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "n = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "r = 1;" "optimized" } } */ +// { dg-final { scan-tree-dump-not "u = 1;" "optimized" } } */ + +int i, j, k, l, m, n, o, q, r, s, t, u; + +static inline +__attribute__((always_inline, + optimize ("no-assume-sane-operators-new-delete"))) void * +foo () +{ + i = 1; + j = 2; + void *p = ::operator new (32); + j = 3; + k = i; + return p; +} + +static inline +__attribute__((always_inline, + optimize ("no-assume-sane-operators-new-delete"))) void +bar (void *p) +{ + l = 1; + m = 2; + ::operator delete (p); + m = 3; + n = l; +} + +static inline +__attribute__((always_inline, + optimize ("no-assume-sane-operators-new-delete"))) int * +baz () +{ + o = 1; + q = 2; + int *p = new int; + q = 3; + r = o; + return p; +} + +static inline +__attribute__((always_inline, + optimize ("no-assume-sane-operators-new-delete"))) void +qux (int *p) +{ + s = 1; + t = 2; + delete p; + t = 3; + u = s; +} + +static inline +__attribute__((always_inline, + optimize ("no-assume-sane-operators-new-delete"))) void * +corge () +{ + o = 1; + q = 2; + void *p = __builtin_operator_new (32); + q = 3; + r = o; + return p; +} + +static inline +__attribute__((always_inline, + optimize ("no-assume-sane-operators-new-delete"))) void +waldo (void *p) +{ + s = 1; + t = 2; + __builtin_operator_delete (p); + t = 3; + u = s; +} + +void * +foo2 () +{ + return foo (); +} + +void +bar2 (void *p) +{ + bar (p); +} + +int * +baz2 () +{ + return baz (); +} + +void +qux2 (int *p) +{ + qux (p); +} + +void * +corge2 () +{ + return corge (); +} + +void +waldo2 (void *p) +{ + waldo (p); +} diff --git a/gcc/testsuite/g++.dg/warn/Warray-bounds-16.C b/gcc/testsuite/g++.dg/warn/Warray-bounds-16.C index 89cbadb91c7b..17b4d0d194e7 100644 --- a/gcc/testsuite/g++.dg/warn/Warray-bounds-16.C +++ b/gcc/testsuite/g++.dg/warn/Warray-bounds-16.C @@ -19,11 +19,11 @@ struct S p = (int*) new unsigned char [sizeof (int) * m]; for (int i = 0; i < m; i++) - new (p + i) int (); /* { dg-bogus "bounds" "pr102690" { xfail *-*-* } } */ + new (p + i) int (); } }; S a (0); -/* The loop cannot be eliminated since the global 'new' can change 'm'. */ -/* { dg-final { scan-tree-dump-not "goto" "optimized" { xfail *-*-* } } } */ +/* Verify the loop has been eliminated. + { dg-final { scan-tree-dump-not "goto" "optimized" } } */