Bug 110137 - implement clang -fassume-sane-operator-new
Summary: implement clang -fassume-sane-operator-new
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 14.0
: P3 enhancement
Target Milestone: 15.0
Assignee: Not yet assigned to anyone
URL:
Keywords: missed-optimization, patch
Depends on:
Blocks: 116651 96945 109442 116868
  Show dependency tree / graph
 
Reported: 2023-06-06 08:15 UTC by Richard Biener
Modified: 2025-01-29 20:19 UTC (History)
13 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2023-06-09 00:00:00


Attachments
Proposed implementation. (821 bytes, patch)
2024-05-27 03:07 UTC, user202729
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Richard Biener 2023-06-06 08:15:58 UTC
clang seems to have -fassume-sane-operator-new which allows to assert that
a call to the operator has no side-effects besides the allocation, in particular that it doesn't modify or inspect global memory.
Comment 1 Richard Biener 2023-06-06 08:17:25 UTC
Related is the __builtin_operator_new (sp?) feature which possibly has the
same effect and can be used in the standard library even when
-fno-assume-sane-operator-new is in effect.

It seems clang makes -fassume-sane-operator-new the default btw.
Comment 2 Jonathan Wakely 2023-06-09 12:22:18 UTC
This would be very nice, confirmed.
Comment 3 AK 2023-08-02 04:20:07 UTC
1. clang also has noalias on nothrow versions of operator new. will `-fassume-sane-operator-new` enable that as well?

2. as per: http://eel.is/c++draft/basic.stc.dynamic#allocation-2 

"""If the request succeeds, the value returned by a replaceable allocation function is a non-null pointer value ([basic.compound]) p0 different from any previously returned value p1, unless that value p1 was subsequently passed to a replaceable deallocation function."""

Does this mean that all successful new allocations can be assumed to be a noalias as long as the pointer wasn't passed to a deallocation function? In that case when possible, can the compiler `infer` from a bottom-up analysis that an allocation is a noalias?
Comment 4 Jonathan Wakely 2023-08-02 06:56:49 UTC
Why would that depend on this new option? It's a requirement of the standard, it has to be true always.
Comment 5 rguenther@suse.de 2023-08-02 08:24:02 UTC
On Wed, 2 Aug 2023, redi at gcc dot gnu.org wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110137
> 
> --- Comment #4 from Jonathan Wakely <redi at gcc dot gnu.org> ---
> Why would that depend on this new option? It's a requirement of the standard,
> it has to be true always.

And GCC already assumes this.  Note we don't make an exception for
pointers passed to deallocation - we're using the info mainly
for memory access disambiguation and rely on the deallocation call
itself to be a barrier for code motion here.
Comment 6 user202729 2024-05-27 03:07:38 UTC
Created attachment 58293 [details]
Proposed implementation.

I've implement it. (Attached patch) It would be helpful if someone can review it.

Some notes:

1. Clang's documentation says the effect of `-fassume-sane-operator-new` is to assume no-alias, but it is pointed out that Clang also assumes no-side-effect. So I explicitly state that in the documentation.
2. The original intent is to fix https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110035 .

While this patch alone is insufficient to fix that (as explained in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110035#c17 ), it would be sufficient to improve the performance of the following (to only load from global memory once):

```
int a;

// Prevent optimization
void sink(void *m) {
    asm volatile("" : : "g"(m) : "memory");
}

int f(){
	int x=a;
	float* z=new float;
	int y=a;
	int result=x+y;
	sink(z);
	return result;
}
```

3. I'm not sure which additional optimizations can be done if we know something is pure, but this would improve that one case. Other cases can be added later on.
Comment 7 Jonathan Wakely 2024-05-27 07:47:34 UTC
Thanks for the patch, but patch review happens on the mailing list, not in bugzilla. Please repost to gcc-patches as documented in the submission guidelines, thanks.
Comment 8 user202729 2024-06-04 10:16:54 UTC
(In reply to Jonathan Wakely from comment #7)
> Thanks for the patch, but patch review happens on the mailing list, not in
> bugzilla. Please repost to gcc-patches as documented in the submission
> guidelines, thanks.

Thanks for letting me know, I didn't notice.

It would be helpful if you can take a look at the patch: https://gcc.gnu.org/pipermail/gcc-patches/2024-May/652977.html and let me know if it's correctly formatted.

Thank you.
Comment 9 Jan Hubicka 2024-06-04 10:39:40 UTC
Doing global flag has a problem since with LTO or using optimize
attribute user may mix code compiled with and without sane operator new.  
When function with insane operator new gets inlined to a function with
sane, we will assume sanity for calls that technically may not be sane.

So I think we do need to tag the declaration or call instead of using
flag_sane_operator_new in middle-end...
Comment 10 Jonathan Wakely 2024-06-04 11:31:25 UTC
But replacing operator new is a global property of the program. It seems to me that any translation unit claiming that operator new is sane must imply that it's sane globally.

It doesn't make sense for a function assuming A is true to be inlined into a function assuming A is false, when A will actually be the same for both.
Comment 11 Jonathan Wakely 2024-06-04 11:37:33 UTC
If a program really does need to ensure a particular TU assumes new can modify global memory (e.g. in the TU defining operator new, which makes use of some data structure) then that TU should probably be firewalled from the rest of the program, and not use LTO.
Comment 12 Jakub Jelinek 2024-06-04 11:45:35 UTC
Is the option supposed to be only about the standard global scope operator new/delete (_Znam etc.) or also user operator new/delete class methods?  If the former, then I agree it is a global property (or at least a per shared library/binary property, one can arrange stuff with symbol visibility etc.).
Otherwise it is a property of whatever operator new/delete you call.
I think DECL_IS_OPERATOR_{NEW,DELETE} is set on both of these, the standard ones have
also DECL_IS_REPLACEABLE_OPERATOR flag on them.
Anyway, handling this just in IRA doesn't seem to be enough, surely it should be also handled during alias analysis etc.
Comment 13 Jan Hubicka 2024-06-04 11:57:08 UTC
> Is the option supposed to be only about the standard global scope operator
> new/delete (_Znam etc.) or also user operator new/delete class methods?  If the
> former, then I agree it is a global property (or at least a per shared
> library/binary property, one can arrange stuff with symbol visibility etc.).
> Otherwise it is a property of whatever operator new/delete you call.
> I think DECL_IS_OPERATOR_{NEW,DELETE} is set on both of these, the standard
> ones have
> also DECL_IS_REPLACEABLE_OPERATOR flag on them.
> Anyway, handling this just in IRA doesn't seem to be enough, surely it should
> be also handled during alias analysis etc.

I can add the TBAA and IPA bits (actually have WIP patch for that
already).  But there is also __builtion_operator_new and
__builtin_operator_delete that tags sanity of new/delete with per-call
sensitivity (since calls originating from std library are more opaque
then direct calls done by users).  So we need per-call sensitivity
anyway which can be done by altenrative decl or flag in gimple_call.

If user is crazy enough to do fancy tricks in new/delete I think it may
be controlled by program state or so (so for some code new/delete may be
sane while for other code it does not need to be0

So having this working well with LTO is IMO reasonable thing to do from
QOI point of view...
Comment 14 user202729 2024-06-05 08:23:27 UTC
Regarding alias analysis. The current implementaion is such that:

compiler      | flag             | can alias?| can modify global?|
gcc           | sane             | no        | no                | << NEW
              | no-sane [default]| no        | yes               |
clang         | sane [default]   | no        | no                |
              | no-sane          | yes       | yes               |
[the standard]|                  | no        | yes               |

As pointed out in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110035#c13 , gcc already assume operator new's retuned pointer cannot alias any existing pointer. So no change is needed there.
Comment 15 Jan Hubicka 2024-06-05 13:00:25 UTC
> As pointed out in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110035#c13 , gcc
> already assume operator new's retuned pointer cannot alias any existing
> pointer. So no change is needed there.
Seems you are right, -fsane-operator-new is weaker than I assumed.
In addition to the assumption about modifying globals, one missed
optimization is removal of unused vectors (we have separate PR for that)

#include <vector>
void foo (int *);
int test()
{
        std::vector <int> a(1);
        return 0;
}
int test2(int size)
{
        int *a = new int;
        *a=0;
        delete a;
        return 0;
}
int test3(int size)
{
        int *a = (int *)::operator new (sizeof (int));
        *a=0;
        ::operator delete (a);
        return 0;
}
At -O2 Clang optimizes test and test2 "return 0", while we optimize only
test2.  libstdc++ uses clang's __builtin_operator_new that enables this
optimization based on claim that user's possibly insane new/delete can
not rely on internals of std::vector implementation and thus can not
reliably use memory being deleted.

I expected that -fassume-sane-operator-new should enable clang or GCC to
do the optimization for all three variants, but it does not (for clang).
I think it would be useful to have such a flag. There are number of
transformations we may want to do including
 - removal of dead stores to memory being deleted
 - removal of unnecesary reallocations i.e. when user creates empty
   vector and does sequence of push_backs.
 - avoiding new/delete for small vectors that fits to stack.
I believe those are sane for builtin_operator_new/delete. However since
clang does not define it this way, perhaps we will need
-fassume-really-sane-operator-new-delete for that?

It would be also nice to be able to determine functions that does paired
new/delete but no other side effects as pure/const etc.

BTW clang also does not optimize out load from global here:
int test3(int size)
{
        global = 0;
        int *a = (int *)::operator new (sizeof (int));
        *a=0;
        return global;
}
I would expect -fassume-sane-operator-new to make it possible?
Comment 16 user202729 2024-06-23 18:14:39 UTC
Hi,

I tried to implement patches that fix the two issues mentioned. Comments would be appreciated.

1. The operator new flag with many of the mentioned issues fixed: https://gcc.gnu.org/pipermail/gcc-patches/2024-June/654145.html

   Although I'm not entirely sure what to do with the link-time optimization part.

2. The optimization to move the definition to right before the use: https://gcc.gnu.org/pipermail/gcc-patches/2024-June/654700.html .

   As explained, I'm not sure if implementing it in the RTL phase; nevertheless, reviews and suggestions to improve the code, or explain how to implement it in GIMPLE phase would be appreciated.

------

Regarding the example code in the previous comment, it appears that the current analyzer is smart enough to do the optimization when the call to operator new is replaced by a pure function (curiously enough, it does not need to assume the returned value does not alias because both the values are 0). So, yes it should be possible (although I haven't test it with the proposed patch).
Comment 17 Sam James 2024-09-09 14:33:13 UTC
(In reply to user202729 from comment #16)
> Hi,
> 
> I tried to implement patches that fix the two issues mentioned. Comments
> would be appreciated.

I would ping it on the ML. You'll need Honza's attention for these at least, I think, and he's busy for the next two weeks or so, so maybe wait until after that.
Comment 18 Jakub Jelinek 2024-10-31 12:23:36 UTC
So, I think the first step should be document the new option and explain what extra guarantees it provides over what the C++ standard already guarantees.
I think the C++ standard should already guarantee that the return doesn't alias anything, I mean our current declarations already say that with malloc, and alloc_size (and sometimes alloc_align attributes).
So, do I understand it right that -fassume-sane-operator-new mainly just says that ::operator new/::operator delete don't have other side-effects besides allocation like Richi wrote in #c1, something we assume for malloc?
Basically assume that the implementation of those operators is a black box, not LTO optimized together with the rest?

I must say I'm a little bit confused about the __attribute__s used in <new>,
some ::operator new versions just have externally_visible attribute, others have also alloc_size + malloc, others have alloc_size + alloc_align + malloc, but I don't see it applied consistently, e.g. alloc_align whenever there is align_val_t argument.

Is malloc attribute all that guarantees it doesn't modify the global state?
Then I'd be afraid if a program replaces those global replaceable definition with his own it can violate that.  Or alloc_size?

Given the __builtin_operator_{new,delete} calls clang has, should we flag the assume-sane-operator-new property on CALL_EXPRs/GIMPLE_CALLs/CALL_INSNs and making that the property of those calls?

If so, I think we first want to implement -fassume-sane-operator-new by setting those flags on CALL_EXPRs to DECL_IS_REPLACEABLE_OPERATOR functions according to the option (and perhaps make it default to on), and then implement those 2 builtins as BUILT_IN_FRONTEND varargs builtins which lower in the FE to ::operator new/delete calls with the same arguments (error if such operator isn't declared) and only if it turns out calls to the DECL_IS_REPLACEABLE_OPERATOR functions, set the new flag on CALL_EXPR even when -fno-assume-sane-operator-new.
Comment 19 Jason Merrill 2024-10-31 13:07:20 UTC
As I was suggesting on IRC, I think CALL_FROM_NEW_OR_DELETE_P already has the semantics we want, we could also set it on direct calls when assuming sane op new/delete.  And maybe rename it to something like CALL_REMOVABLE_NEW_OR_DELETE_P?
Comment 20 Richard Biener 2024-10-31 13:11:03 UTC
Whether a call clobbers or uses memory is controlled by fnspec these days
and a malloc attribute does _not_ indicate this.  It works for malloc/free
because we make it so via the builtins in builtin_fnspec where the C++
new/delete expressions are handled in gimple_call_fnspec (but not indicating
not using/clobbering global memory).

Note we do treat C++ delete as not escaping the pointer and only writing to
it and not reading.  It still uses/clobbers global memory though.
Comment 21 Jason Merrill 2024-10-31 13:27:50 UTC
(In reply to Richard Biener from comment #20)
> It still uses/clobbers global memory though.

Hmm, I guess that's a separate issue from being able to elide the calls entirely, which is what CALL_FROM_NEW_OR_DELETE_P (and apparently __builtin_operator_new) means.

If the calls aren't elided, they could still mess with global memory, and that's what -fassume-sane-operator-new is for?  The testcase in comment 15 suggests that -fassume-sane-operator-new doesn't also imply elidability.

So I suppose using the same flag won't work after all, they seem to be different properties.
Comment 22 Jason Merrill 2024-10-31 13:40:27 UTC
But as Jonathan says in comment 10, the replaceable operator new is shared by the whole program, so I don't see why we would need a per-call flag for the global memory aliasing property; either the function touches global memory or it doesn't.
Comment 23 Jakub Jelinek 2024-10-31 13:41:54 UTC
I've just tried:
int i, j, k;

void *
foo ()
{
  i = 1;
  j = 2;
  void *p = __builtin_operator_new (32);
  j = 3;
  k = i;
  return p;
}

void *
bar ()
{
  i = 1;
  j = 2;
  void *p = __builtin_malloc (32);
  j = 3;
  k = i;
  return p;
}

void *
baz ()
{
  i = 1;
  j = 2;
  void *p = ::operator new (32);
  j = 3;
  k = i;
  return p;
}
and it seems that both g++ and clang++ for the malloc case optimize j = 2; away and k = i; into k = 1;, i.e. malloc is assumed not to read nor write global state visible to the program except the allocation itself.
While __builtin_operator_new results just in k = i; to k = 1; optimization and not the removal of j = 2; (i.e. __builtin_operator_new is assumed not to modify global state but is assumed to read it).
And ::operator new isn't optimized either way, no matter whether with -fassume-sane-operator-new or -fno-assume-sane-operator-new (those flags don't change foo behavior either).
So I wonder if we really should follow what clang++ does because it doesn't make much sense, lacks consistency and design.
Comment 24 Jakub Jelinek 2024-10-31 13:48:38 UTC
(In reply to Jason Merrill from comment #22)
> But as Jonathan says in comment 10, the replaceable operator new is shared
> by the whole program, so I don't see why we would need a per-call flag for
> the global memory aliasing property; either the function touches global
> memory or it doesn't.

Well, to be precise, it would need to be a per-call property if we want to allow people to redefine those and don't use those call flags within the TUs where they define it but want to use it in other TUs where the state it modifies is just an implementation detail.
But if we explain it well (and I think we need to do the same for malloc etc. already) that if one overrides those (whether malloc or ::operator new or ::operator delete) that the implementation should be compiled without -flto and for the latter two with -fno-sane-operator-new, it could be a global flag, because for everything else it is really a property of those calls, if it is sane (doesn't modify nor read global state from POV of other TUs) for a single call, then it should be sane for all other calls (except the implementation).
Comment 25 Jason Merrill 2024-10-31 13:57:46 UTC
(In reply to Jakub Jelinek from comment #24)
> Well, to be precise, it would need to be a per-call property if we want to
> allow people to redefine those and don't use those call flags within the TUs
> where they define it but want to use it in other TUs where the state it
> modifies is just an implementation detail.

Does LTO support per-TU flags?
Comment 26 rguenther@suse.de 2024-10-31 13:59:10 UTC
On Thu, 31 Oct 2024, jason at gcc dot gnu.org wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110137
> 
> --- Comment #25 from Jason Merrill <jason at gcc dot gnu.org> ---
> (In reply to Jakub Jelinek from comment #24)
> > Well, to be precise, it would need to be a per-call property if we want to
> > allow people to redefine those and don't use those call flags within the TUs
> > where they define it but want to use it in other TUs where the state it
> > modifies is just an implementation detail.
> 
> Does LTO support per-TU flags?

LTO supports per function flags (those marked 'Optimization').
Comment 27 Jan Hubicka 2024-10-31 14:02:49 UTC
This is somewhat weird testcase which makes two allocations which compiler can track as non-escaping and tests its ability to disambiguate them:

int test3(int *data)
{
        int *val = new int;
        *val = 12345;
#ifdef NEW
        int *a = new int [1000];
#else
        int *a = (int *)::operator new (1000 * sizeof (int));
#endif
        for (int i = 0; i < 1000; i++)
                data[i]=0;
        for (int i = 0; i < 1000; i++)
                a[data[i]]=1;
        int sum = 0;
        for (int i = 0; i < 1000; i++)
                sum += a[i];
#ifdef NEW
        delete[] a;
#else
        ::operator delete ((void *)val);
#endif
        return *val + sum;
}


If I use the operator new I get:
jan@padlo:/tmp> clang -O2 tt2.C -fassume-sane-operator-new tt2.C -S -DNEW ; grep 12345 tt2.s
        addl    $12345, %ebx                    # imm = 0x3039
jan@padlo:/tmp> clang -O2 tt2.C -fno-assume-sane-operator-new tt2.C -S -DNEW ; grep 12345 tt2.s
        movl    $12345, (%rax)                  # imm = 0x3039
jan@padlo:/tmp> clang -O2 tt2.C  tt2.C -S -DNEW ; grep 12345 tt2.s
        addl    $12345, %ebx                    # imm = 0x3039

so clang assumes sanity by default and needs it to determine that there is no aliasing between the two returned blocks. As discussed in Comment #14 without sane operator new, clang is even more conservative then we do about aliasing of return value.

jan@padlo:/tmp> clang -O2 tt2.C -fassume-sane-operator-new tt2.C -S  ; grep 12345 tt2.s
        movl    $12345, (%rax)                  # imm = 0x3039
jan@padlo:/tmp> clang -O2 tt2.C -fno-assume-sane-operator-new tt2.C -S  ; grep 12345 tt2.s
        movl    $12345, (%rax)                  # imm = 0x3039
jan@padlo:/tmp> clang -O2 tt2.C  tt2.C -S  ; grep 12345 tt2.s
        movl    $12345, (%rax)                  # imm = 0x3039

If functions are called directly, flag has no effect.

jan@padlo:/tmp> gcc -O2 tt2.C  tt2.C -S -DNEW ; grep 12345 tt2.s
        leal    12345(%rbx), %eax
jan@padlo:/tmp> gcc -O2 tt2.C  tt2.C -S  ; grep 12345 tt2.s
        movl    $12345, (%rax)

GCC also disambiguates only with sane operator new. I can see we can not optimize out the store but it is not quite clear to me why we do not propagate the constant.
Comment 28 Jan Hubicka 2024-10-31 14:04:37 UTC
We could make -fassume-sane-operator-new optimization attribute, but then we would need to prevent inlining functions with insane operators to functions with sane operators, or drop the sanity on caller.
Comment 29 Jan Hubicka 2024-10-31 14:12:30 UTC
For completeness note that we can also stick info into FUNCTION_DECLs (i.e. by an attribute) to avoid flags pollution. Then it could be TU sensitive since lto-symtab knows that in certain cases it should not merge the declaration and keeps multiple decls for single symbol.
But this does not play well with the observation that at least clang's version seem to make difference between operator use and direct calls.
Comment 30 Jakub Jelinek 2024-11-15 11:16:59 UTC
I had further look and I must say I really don't know what if anything is clang++'s -fassume-sane-operator-new vs. -fno-assume-sane-operator-new option doing.

Initially when that option has been added the option added implicitly malloc attribute to the replaceable global operator new/new[].
The current trunk adds noalias attribute, whatever that is, instead.
Now, we already do have malloc attribute on all the replaceable global operator news, and obviously that is not it.

I've tried following testcase with clang++ trunk with both -fassume-sane-operator-new
and -fno-assume-sane-operator-new and it emits the same assembly.  Plus gcc trunk.
In all 3 cases, foo calls mylloc (so it isn't DCEd), but optimizes it to return 0 (so malloc attribute implies non-aliasing).
bar is not optimized by any compiler.
baz is optimized in all 3 cases to just return 0, so malloc call is DCEd and non-aliasing is implied.
qux is fully optimized in all 3 cases (j = 2; store DSEd and k = i; optimized into k = 1;).
corge is like foo, non-aliasing implied (returns 0), the call is kept.
garply is not optimized at all in all 3 cases.
freddy is optimized into just return 0 like baz in all 3 cases.
And the only difference between the 2 compilers is in the last function,
clang optimizes in waldo k = i; into k = 1; but doesn't DSE j = 2; store, while
gcc doesn't optimize either.

void *mylloc (__SIZE_TYPE__) __attribute__((malloc));
int i, j, k;

int
foo (void)
{
  char a;
  return mylloc (42) == &a;
}

void *
bar (void)
{
  i = 1;
  j = 2;
  void *p = mylloc (32);
  j = 3;
  k = i;
  return p;
}

int
baz (void)
{
  char a;
  return __builtin_malloc (42) == &a;
}

void *
qux (void)
{
  i = 1;
  j = 2;
  void *p = __builtin_malloc (32);
  j = 3;
  k = i;
  return p;
}

int
corge (void)
{
  char a;
  return ::operator new (42) == &a;
}

void *
garply (void)
{
  i = 1;
  j = 2;
  void *p = ::operator new (32);
  j = 3;
  k = i;
  return p;
}

int
fredy (void)
{
  char a;
  return __builtin_operator_new (42) == &a;
}

void *
waldo (void)
{
  i = 1;
  j = 2;
  void *p = __builtin_operator_new (32);
  j = 3;
  k = i;
  return p;
}

So, I think we shouldn't implement -fassume-sane-operator-new because it isn't really useful option.  If we want some global flag which says that replaceable global ::operator new/new[]/delete/delete[] doesn't use or modify global state visible to the callers, I think it should be a differently named option.
And I wonder what makes clang++ believe that replaceable global operator new can't modify global state visible to the compiler when called from within new expression (or __builtin_operator_new, both work the same).  Sure, the C++ standard says that one can omit the calls or replace them with stack allocations or merge them, so relying on particular side-effects is complicated.  On the other side, when one actually overrides the replaceable global operator new and implements it with something non-trivial in the current compilation unit, then it certainly will modify the global state etc.

Thus, shall we follow clang++ and add the implication that gimple_call_from_new_or_delete which is DECL_IS_REPLACEABLE_OPERATOR_NEW_P (or just
DECL_IS_REPLACEABLE_OPERATOR?) implies no modification of global state (and do it without actually giving users a way to stop assuming that)?
Or add a new switch which implies it globally regardless of gimple_call_from_new_or_delete?  And if the latter, what would be the default?
Comment 31 Jakub Jelinek 2024-11-15 12:47:27 UTC
I believe to match clang++ behavior the needed change would be:
--- gcc/gimple.cc.jj	2024-11-14 18:26:16.877459015 +0100
+++ gcc/gimple.cc	2024-11-15 13:40:43.484711373 +0100
@@ -1605,7 +1605,7 @@ gimple_call_fnspec (const gcall *stmt)
   if (fndecl
       && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
       && gimple_call_from_new_or_delete (stmt))
-    return "m ";
+    return "mP";
   return "";
 }
 
Clearly ::operator delete, even when called from delete expression, isn't assumed not to change global state nor read it by clang++.
Or of course if we want to go even further than that, we could return "mC" and let it behave like __builtin_malloc.
Though, while perhaps one can somehow derive from the fact that the call can be omitted the undesirability to visibly change global state (because the compiler could decide not to call it and thus the global state change wouldn't occur), I don't see what rationale we'd use for not reading global state, other than that when using malloc under the hood the operator doesn't do that (visibly).
Comment 32 Richard Biener 2024-11-15 13:05:31 UTC
(In reply to Jakub Jelinek from comment #31)
> I believe to match clang++ behavior the needed change would be:
> --- gcc/gimple.cc.jj	2024-11-14 18:26:16.877459015 +0100
> +++ gcc/gimple.cc	2024-11-15 13:40:43.484711373 +0100
> @@ -1605,7 +1605,7 @@ gimple_call_fnspec (const gcall *stmt)
>    if (fndecl
>        && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
>        && gimple_call_from_new_or_delete (stmt))
> -    return "m ";
> +    return "mP";
>    return "";
>  }
>  
> Clearly ::operator delete, even when called from delete expression, isn't
> assumed not to change global state nor read it by clang++.
> Or of course if we want to go even further than that, we could return "mC"
> and let it behave like __builtin_malloc.
> Though, while perhaps one can somehow derive from the fact that the call can
> be omitted the undesirability to visibly change global state (because the
> compiler could decide not to call it and thus the global state change
> wouldn't occur), I don't see what rationale we'd use for not reading global
> state, other than that when using malloc under the hood the operator doesn't
> do that (visibly).

See PR101480 for a reason why to not do this.
Comment 33 Jakub Jelinek 2024-11-15 13:43:41 UTC
(In reply to Richard Biener from comment #32)
> See PR101480 for a reason why to not do this.

That testcase would be fine with "mP" rather than "mC".
Sure, that PR is a reason not to do the stronger (C) case unless some flag is used.
Maybe we can have both, use "mP" by default and have some (uniquely named) non-default flag which turns that into "mC" and the delete case into ".Co ", and perhaps does that even when not from new/delete.
Comment 34 rguenther@suse.de 2024-11-15 14:03:05 UTC
On Fri, 15 Nov 2024, jakub at gcc dot gnu.org wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110137
> 
> --- Comment #33 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
> (In reply to Richard Biener from comment #32)
> > See PR101480 for a reason why to not do this.
> 
> That testcase would be fine with "mP" rather than "mC".
> Sure, that PR is a reason not to do the stronger (C) case unless some flag is
> used.
> Maybe we can have both, use "mP" by default and have some (uniquely named)
> non-default flag which turns that into "mC" and the delete case into ".Co ",
> and perhaps does that even when not from new/delete.

I don't think the testcase is a good argument for some intermediate
"mP", it seems artifically reduced and soon you'll see one with
modification shown.  That said, P vs. C seems arbitrary to me.
Comment 35 GCC Commits 2024-11-22 18:56:01 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:27778979c9a1e32a6ca74e5b5f377385225449b1

commit r15-5595-g27778979c9a1e32a6ca74e5b5f377385225449b1
Author: Jakub Jelinek <jakub@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  <jakub@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.
Comment 36 Sam James 2025-01-29 20:19:48 UTC
Fixed in 15, or are we keeping it open for something?