On Fri, Nov 15, 2024 at 11:08 AM Jakub Jelinek <ja...@redhat.com> wrote: > > Hi! > > The following patch adds a new tristate 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 > In the past, before PR101480 fix in 2021, we treated the replaceable > global operators new/delete when called from new/delete expressions > similarly to malloc/free, i.e. we assumed beyond their described > behavior they act like const functions, don't read or modify global > state (observable in current TU). That is fine when we treat > malloc/free as a blackbox and hope people don't use malloc hooks or > don't do something weird in them, or when the replaceable global > operators use new/delete under the hood and don't do anything else > (except perhaps throwing exceptions) beyond that. > clang++ behavior (again only for replaceable global operators new/delete > when called from new/delete expressions (or __builtin_operator_{new,delete}) > is apparently that they act like pure functions beyond what they are > documented to do, i.e. can read global state (aka g++.dg/torture/pr101480.C > testcase is considered valid), but can't modify it. > I think that is because of the license from > https://eel.is/c++draft/expr.new#14 > https://eel.is/c++draft/expr.delete#6 > that those calls could be omitted, I think valid C++ programs just can't > rely on some observable global state modification done by those operators > happening. > > The patch below makes the clang++ behavior the default (i.e. pure-ish > operator new/delete when called from new/delete expressions, no assumptions > when called directly) and adds options to override this behavior in both > directions, to the pre-PR101480 state with e.g. > -fassume-sane-operators-new-delete (in fact a little bit further; because > the patch makes those assumptions even when calling the operators directly), > and to the current trunk state with e.g. > -fno-assume-sane-operators-new-delete. > > I've tried to explain stuff in the documentation too.
This all seems excessively complicated; can't it be simplified a bit? > > So far smoke tested, ok for trunk if it passes full bootstrap/regtest? > > 2024-11-15 Jakub Jelinek <ja...@redhat.com> > > PR c++/110137 > gcc/ > * doc/invoke.texi (-fassume-sane-operators-new-delete, > -fno-assume-sane-operators-new-delete, > -fassume-sane-operators-new-delete=): Document. > * gimple.cc (gimple_call_fnspec): Handle > -f{,no-}assume-sane-operators-new-delete{,={0,1,2}}. > gcc/c-family/ > * c.opt (fassume-sane-operators-new-delete, > fassume-sane-operators-new-delete=): New options. > 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/tree-ssa/pr110137-5.C: New test. > * g++.dg/tree-ssa/pr110137-6.C: New test. > > --- gcc/c-family/c.opt.jj 2024-11-15 13:13:29.021751829 +0100 > +++ gcc/c-family/c.opt 2024-11-15 15:35:02.401561741 +0100 > @@ -1686,6 +1686,14 @@ fasm > C ObjC C++ ObjC++ Var(flag_no_asm, 0) > Recognize the \"asm\" keyword. > > +fassume-sane-operators-new-delete > +C++ ObjC++ LTO Alias(fassume-sane-operators-new-delete=,2,0) > +Assume C++ replaceable global operators new, new[], delete, delete[] don't > read or write visible global state. > + > +fassume-sane-operators-new-delete= > +C++ ObjC++ LTO Joined RejectNegative > Var(flag_assume_sane_operators_new_delete) UInteger Init(1) IntegerRange(0, 2) > +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) > --- gcc/gimple.cc.jj 2024-11-14 18:26:16.877459015 +0100 > +++ gcc/gimple.cc 2024-11-15 16:24:13.093917285 +0100 > @@ -1598,14 +1598,29 @@ gimple_call_fnspec (const gcall *stmt) > such operator, then we can treat it as free. */ > if (fndecl > && DECL_IS_OPERATOR_DELETE_P (fndecl) > - && DECL_IS_REPLACEABLE_OPERATOR (fndecl) > - && gimple_call_from_new_or_delete (stmt)) > - return ". o "; > + && DECL_IS_REPLACEABLE_OPERATOR (fndecl)) > + { > + if (flag_assume_sane_operators_new_delete == 2) > + return ".co "; > + if (gimple_call_from_new_or_delete (stmt)) > + { > + if (flag_assume_sane_operators_new_delete == 1) > + return ".po "; > + 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 (fndecl && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)) > + { > + if (flag_assume_sane_operators_new_delete == 2) > + return "mC"; > + if (gimple_call_from_new_or_delete (stmt)) > + { > + if (flag_assume_sane_operators_new_delete == 1) > + return "mP"; > + return "m "; > + } > + } > return ""; > } > > --- gcc/doc/invoke.texi.jj 2024-11-15 13:13:29.031751688 +0100 > +++ gcc/doc/invoke.texi 2024-11-15 16:06:17.270100613 +0100 > @@ -213,7 +213,10 @@ 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 > +-fassume-sane-operators-new-delete=@var{n} > +-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 > @@ -3162,6 +3165,48 @@ but few users will need to override the > > This flag is enabled by default for @option{-std=c++17}. > > +@opindex fno-assume-sane-operators-new-delete > +@opindex fassume-sane-operators-new-delete > +@opindex fassume-sane-operators-new-delete=@var{n} > +@item -fno-assume-sane-operators-new > +@itemx -fassume-sane-operators-new-delete=@var{n} > +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). > +These options allow control over some optimizations around calls > +to those operators. > +With @code{-fassume-sane-operators-new-delete} option or equivalent > +@code{-fassume-sane-operators-new-delete=2} option GCC may assume that > +calls to the replaceable global operators (whether from new or delete > +expressions or called directly) 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. > +With @code{-fno-assume-sane-operators-new-delete} option or > +equivalent @code{-fassume-sane-operators-new-delete=0} 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 these options if those > +operators are or may be replaced and code needs to expect such behavior. > +With @code{-fassume-sane-operators-new-delete=1} option, which is > +the default, GCC must assume reads and writes of global state for > +direct calls to these operators, but if those calls are from new or > +delete expressions or calls to @code{__builtin_operator_new} or > +@code{__builtin_operator_delete}, it may assume global state is not > +modified but might be read. This is because those calls could be > +omitted by the implementation and if they would be omitted, code using > +new or delete expressions can't rely on global state modifications > +happening, while if they are not omitted, the replaced implementation > +can still observe global state. > + > @opindex fchar8_t > @opindex fno-char8_t > @item -fchar8_t > --- gcc/testsuite/g++.dg/tree-ssa/pr110137-1.C.jj 2024-11-15 > 16:16:56.296080508 +0100 > +++ gcc/testsuite/g++.dg/tree-ssa/pr110137-1.C 2024-11-15 16:30:43.596407861 > +0100 > @@ -0,0 +1,75 @@ > +// 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-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; > +} > --- gcc/testsuite/g++.dg/tree-ssa/pr110137-2.C.jj 2024-11-15 > 16:31:18.244919019 +0100 > +++ gcc/testsuite/g++.dg/tree-ssa/pr110137-2.C 2024-11-15 16:31:40.919599111 > +0100 > @@ -0,0 +1,75 @@ > +// PR c++/110137 > +// { dg-do compile } > +// { dg-options "-O2 -fdump-tree-optimized > -fassume-sane-operators-new-delete=1" } > +// { 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-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; > +} > --- gcc/testsuite/g++.dg/tree-ssa/pr110137-3.C.jj 2024-11-15 > 16:31:18.244919019 +0100 > +++ gcc/testsuite/g++.dg/tree-ssa/pr110137-3.C 2024-11-15 16:33:16.099256260 > +0100 > @@ -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-not "j = 2;" "optimized" } } */ > +// { dg-final { scan-tree-dump-not "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 "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; > +} > --- gcc/testsuite/g++.dg/tree-ssa/pr110137-4.C.jj 2024-11-15 > 16:31:18.244919019 +0100 > +++ gcc/testsuite/g++.dg/tree-ssa/pr110137-4.C 2024-11-15 16:33:39.034932674 > +0100 > @@ -0,0 +1,74 @@ > +// PR c++/110137 > +// { dg-do compile } > +// { dg-options "-O2 -fdump-tree-optimized > -fassume-sane-operators-new-delete=2" } > +// { dg-final { scan-tree-dump-not "j = 2;" "optimized" } } */ > +// { dg-final { scan-tree-dump-not "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 "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; > +} > --- gcc/testsuite/g++.dg/tree-ssa/pr110137-5.C.jj 2024-11-15 > 16:31:18.244919019 +0100 > +++ gcc/testsuite/g++.dg/tree-ssa/pr110137-5.C 2024-11-15 16:35:24.354446761 > +0100 > @@ -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; > +} > --- gcc/testsuite/g++.dg/tree-ssa/pr110137-6.C.jj 2024-11-15 > 16:31:18.244919019 +0100 > +++ gcc/testsuite/g++.dg/tree-ssa/pr110137-6.C 2024-11-15 16:35:52.152054593 > +0100 > @@ -0,0 +1,76 @@ > +// PR c++/110137 > +// { dg-do compile } > +// { dg-options "-O2 -fdump-tree-optimized > -fassume-sane-operators-new-delete=0" } > +// { 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; > +} > > Jakub >