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
>

Reply via email to