Can indirect class parameters be noalias?

classic Classic list List threaded Threaded
18 messages Options
Reply | Threaded
Open this post in threaded view
|

Can indirect class parameters be noalias?

Hubert Tong via cfe-dev

Clang IRGen currently doesn’t mark indirect parameters as noalias. Considerations:

  • A lot of targets don’t pass struct arguments indirectly outside of C++, but some do, notably AArch64.

  • In a pure C world, we would always be able to mark such parameters noalias, because arguments are r-values and there’s no way to have a pointer to an r-value.

  • ObjC __weak references can have pointers to them from the ObjC runtime. You can’t pass a weak reference immediately as an argument because __weak is a qualifier and qualifiers are ignored in calls, but you can put one in a struct and pass that, and that struct has to be passed indirectly. Arguably such a parameter cannot be noalias because of the pointer from the runtime, but then again, ObjC code isn’t allowed to directly access the weak reference (it has to call the runtime), which means that no accesses that LLVM can actually see violate the noalias restriction.

  • C++ parameters of non-trivially-copyable class type cannot be marked noalias: it is absolutely permitted to escape a pointer to this within a constructor and to replace that pointer whenever the object is moved. This is both well-defined and sometimes useful.

  • It’s actually possible to escape a pointer to any C++ object within its constructor, and that pointer remains valid for the duration of the object’s lifetime. And you can do this with NRVO, too, so you don’t even need to have a type with non-trivial constructors, as long as the object isn’t copied. Note that this even messes up the C case, which is really unfortunate: arguably we need to pessimize C code because of the possibility it might interoperate with C++.

  • But I think there’s an escape hatch here. C++ has a rule which is intended to give implementation extra leeway with passing and returning trivial types, e.g. to pass them in registers. This rule is C++ [class.temporary]p3, which says that implementations can create an extra temporary object to pass an object of type X as long as “each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor”. This object is created by (trivially) copy/move-initializing from the argument/return object. Arguably we can consider any type that satisfies this condition to be formally copied into a new object as part of passing or returning it. We don’t need to actually do the copy, I think, we just need to consider a copy to have been done in order to formally disrupt any existing pointers to the object. (Although arguably you aren’t allowed to copy an object into a new object at the original object’s current address; it would be an unfortunate consequence of this wording if we had to either forgo optimization or do an unnecessary copy here.)

Thoughts?

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
On Wed, 29 Jul 2020 at 12:52, John McCall <[hidden email]> wrote:

Clang IRGen currently doesn’t mark indirect parameters as noalias. Considerations:

  • A lot of targets don’t pass struct arguments indirectly outside of C++, but some do, notably AArch64.

  • In a pure C world, we would always be able to mark such parameters noalias, because arguments are r-values and there’s no way to have a pointer to an r-value.

  • ObjC __weak references can have pointers to them from the ObjC runtime. You can’t pass a weak reference immediately as an argument because __weak is a qualifier and qualifiers are ignored in calls, but you can put one in a struct and pass that, and that struct has to be passed indirectly. Arguably such a parameter cannot be noalias because of the pointer from the runtime, but then again, ObjC code isn’t allowed to directly access the weak reference (it has to call the runtime), which means that no accesses that LLVM can actually see violate the noalias restriction.

  • C++ parameters of non-trivially-copyable class type cannot be marked noalias: it is absolutely permitted to escape a pointer to this within a constructor and to replace that pointer whenever the object is moved. This is both well-defined and sometimes useful.

  • It’s actually possible to escape a pointer to any C++ object within its constructor, and that pointer remains valid for the duration of the object’s lifetime. And you can do this with NRVO, too, so you don’t even need to have a type with non-trivial constructors, as long as the object isn’t copied. Note that this even messes up the C case, which is really unfortunate: arguably we need to pessimize C code because of the possibility it might interoperate with C++.

  • But I think there’s an escape hatch here. C++ has a rule which is intended to give implementation extra leeway with passing and returning trivial types, e.g. to pass them in registers. This rule is C++ [class.temporary]p3, which says that implementations can create an extra temporary object to pass an object of type X as long as “each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor”. This object is created by (trivially) copy/move-initializing from the argument/return object. Arguably we can consider any type that satisfies this condition to be formally copied into a new object as part of passing or returning it. We don’t need to actually do the copy, I think, we just need to consider a copy to have been done in order to formally disrupt any existing pointers to the object. (Although arguably you aren’t allowed to copy an object into a new object at the original object’s current address; it would be an unfortunate consequence of this wording if we had to either forgo optimization or do an unnecessary copy here.)

Thoughts?

From a high level: I think the C++ language semantics *should* permit us to assume that objects passed by value to functions, and objects returned by value from functions (in which category I include *this in a constructor), are noalias.

I think concretely, the escape hatch doesn't stop things from going wrong, because -- as you note -- even though we *could* have made a copy, it's observable whether or not we *did* make a copy. For example:

#include <stdio.h>

struct A {
    A(A **where) : data{"hello world"} { *where = this; }
    char data[65536];
};
A *p;

[[gnu::noinline]]
void f(A a) {
    for (int i = 0; i != sizeof(A::data) - 2; ++i)
        p->data[i+1] = a.data[i];
    puts(a.data);
}

// elsewhere, perhaps compiled by a smarter compiler that doesn't make a copy here
int main() { f({&p}); }

I think it's valid for this program to print "hello world" or for it to print "hhhhhhhhhhhhh...", but it's not valid to (eg) turn the copy loop into a memcpy with undefined behavior.

As it happens, we do actually make a redundant copy here when performing the call to `f`, which seems wasteful. And so do GCC and ICC, which means the 'noalias' would actually be correct here considering only the behavior of those compilers. So in principle we could address this in the ABI by saying that the copy is mandatory. But I don't think we should -- I think the above code should have undefined behavior because it accesses a function parameter through an access path not derived from the name of the function parameter.

We do have some wording in the standard that tries to give aliasing guarantees in some of these cases, but does so in a way that's not really useful. Specifically, [class.cdtor]p2: "During the construction of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor’s this pointer, the value of the object or subobject thus obtained is unspecified." (I mean, thanks for trying, but that's not all the cases, and "the value is unspecified" is not enough permission.)

Maybe we could mark such cases as 'noalias', behind a known-non-conforming flag. The question would then be whether we enable it by default or not.

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
On Wed, 29 Jul 2020 at 14:42, Richard Smith <[hidden email]> wrote:
On Wed, 29 Jul 2020 at 12:52, John McCall <[hidden email]> wrote:

Clang IRGen currently doesn’t mark indirect parameters as noalias. Considerations:

  • A lot of targets don’t pass struct arguments indirectly outside of C++, but some do, notably AArch64.

  • In a pure C world, we would always be able to mark such parameters noalias, because arguments are r-values and there’s no way to have a pointer to an r-value.

  • ObjC __weak references can have pointers to them from the ObjC runtime. You can’t pass a weak reference immediately as an argument because __weak is a qualifier and qualifiers are ignored in calls, but you can put one in a struct and pass that, and that struct has to be passed indirectly. Arguably such a parameter cannot be noalias because of the pointer from the runtime, but then again, ObjC code isn’t allowed to directly access the weak reference (it has to call the runtime), which means that no accesses that LLVM can actually see violate the noalias restriction.

  • C++ parameters of non-trivially-copyable class type cannot be marked noalias: it is absolutely permitted to escape a pointer to this within a constructor and to replace that pointer whenever the object is moved. This is both well-defined and sometimes useful.

  • It’s actually possible to escape a pointer to any C++ object within its constructor, and that pointer remains valid for the duration of the object’s lifetime. And you can do this with NRVO, too, so you don’t even need to have a type with non-trivial constructors, as long as the object isn’t copied. Note that this even messes up the C case, which is really unfortunate: arguably we need to pessimize C code because of the possibility it might interoperate with C++.

  • But I think there’s an escape hatch here. C++ has a rule which is intended to give implementation extra leeway with passing and returning trivial types, e.g. to pass them in registers. This rule is C++ [class.temporary]p3, which says that implementations can create an extra temporary object to pass an object of type X as long as “each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor”. This object is created by (trivially) copy/move-initializing from the argument/return object. Arguably we can consider any type that satisfies this condition to be formally copied into a new object as part of passing or returning it. We don’t need to actually do the copy, I think, we just need to consider a copy to have been done in order to formally disrupt any existing pointers to the object. (Although arguably you aren’t allowed to copy an object into a new object at the original object’s current address; it would be an unfortunate consequence of this wording if we had to either forgo optimization or do an unnecessary copy here.)

Thoughts?

From a high level: I think the C++ language semantics *should* permit us to assume that objects passed by value to functions, and objects returned by value from functions (in which category I include *this in a constructor), are noalias.

... specifically in the case where they're trivially copyable and the implementation was permitted to make a copy. In the case of non-trivial copy operations, I think we probably should be forced to assume that the address of the object may have escaped.
 
I think concretely, the escape hatch doesn't stop things from going wrong, because -- as you note -- even though we *could* have made a copy, it's observable whether or not we *did* make a copy. For example:

#include <stdio.h>

struct A {
    A(A **where) : data{"hello world"} { *where = this; }
    char data[65536];
};
A *p;

[[gnu::noinline]]
void f(A a) {
    for (int i = 0; i != sizeof(A::data) - 2; ++i)
        p->data[i+1] = a.data[i];
    puts(a.data);
}

// elsewhere, perhaps compiled by a smarter compiler that doesn't make a copy here
int main() { f({&p}); }

I think it's valid for this program to print "hello world" or for it to print "hhhhhhhhhhhhh...", but it's not valid to (eg) turn the copy loop into a memcpy with undefined behavior.

As it happens, we do actually make a redundant copy here when performing the call to `f`, which seems wasteful. And so do GCC and ICC, which means the 'noalias' would actually be correct here considering only the behavior of those compilers. So in principle we could address this in the ABI by saying that the copy is mandatory. But I don't think we should -- I think the above code should have undefined behavior because it accesses a function parameter through an access path not derived from the name of the function parameter.

We do have some wording in the standard that tries to give aliasing guarantees in some of these cases, but does so in a way that's not really useful. Specifically, [class.cdtor]p2: "During the construction of an object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor’s this pointer, the value of the object or subobject thus obtained is unspecified." (I mean, thanks for trying, but that's not all the cases, and "the value is unspecified" is not enough permission.)

Maybe we could mark such cases as 'noalias', behind a known-non-conforming flag. The question would then be whether we enable it by default or not.

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
In reply to this post by Hubert Tong via cfe-dev

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall <[hidden email]> wrote:

Clang IRGen currently doesn’t mark indirect parameters as noalias.
Considerations:

-

A lot of targets don’t pass struct arguments indirectly outside of
C++, but some do, notably AArch64.
-

In a pure C world, we would always be able to mark such parameters
noalias, because arguments are r-values and there’s no way to have a
pointer to an r-value.
-

ObjC __weak references can have pointers to them from the ObjC
runtime. You can’t pass a weak reference immediately as an argument because
__weak is a qualifier and qualifiers are ignored in calls, but you can
put one in a struct and pass that, and that struct has to be passed
indirectly. Arguably such a parameter cannot be noalias because of the
pointer from the runtime, but then again, ObjC code isn’t allowed to
directly access the weak reference (it has to call the runtime), which
means that no accesses that LLVM can actually see violate the noalias
restriction.
-

C++ parameters of non-trivially-copyable class type cannot be marked
noalias: it is absolutely permitted to escape a pointer to this within
a constructor and to replace that pointer whenever the object is moved.
This is both well-defined and sometimes useful.
-

It’s actually possible to escape a pointer to *any* C++ object within
its constructor, and that pointer remains valid for the duration of the
object’s lifetime. And you can do this with NRVO, too, so you don’t even
need to have a type with non-trivial constructors, as long as the object
isn’t copied. Note that this even messes up the C case, which is really
unfortunate: arguably we need to pessimize C code because of the
possibility it might interoperate with C++.
-

But I think there’s an escape hatch here. C++ has a rule which is
intended to give implementation extra leeway with passing and returning
trivial types, e.g. to pass them in registers. This rule is C++
[class.temporary]p3, which says that implementations can create an extra
temporary object to pass an object of type X as long as “each copy
constructor, move constructor, and destructor of X is either trivial or
deleted, and X has at least one non-deleted copy or move constructor”. This
object is created by (trivially) copy/move-initializing from the
argument/return object. Arguably we can consider any type that satisfies
this condition to be *formally* copied into a new object as part of
passing or returning it. We don’t need to *actually* do the copy, I
think, we just need to consider a copy to have been done in order to
formally disrupt any existing pointers to the object. (Although arguably
you aren’t allowed to copy an object into a new object at the original
object’s current address; it would be an unfortunate consequence of this
wording if we had to either forgo optimization or do an unnecessary copy
here.)

Thoughts?

From a high level: I think the C++ language semantics *should* permit us to
assume that objects passed by value to functions, and objects returned by
value from functions (in which category I include *this in a constructor),
are noalias.

I agree that this should be the goal for trivial types. If nothing
else, it seems unfortunate that whether something is UB would depend
on a type’s ABI treatment in a specific situation. Guarantees should
be based on well-defined type properties, and implementations should
have to conform.

I think concretely, the escape hatch doesn't stop things from going wrong,
because -- as you note -- even though we *could* have made a copy, it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t have to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates an object
at the same address, but it’s not the same object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

As it happens, we do actually make a redundant copy here when performing
the call to `f`, which seems wasteful.

You’re probably looking at x86_64 code generation. The x86_64 ABI passes
this argument on the stack, which means that LLVM forces Clang to use an
IR pattern (byval) that it’s hard for LLVM to reliably optimize.

I agree that, because in practice we only elide the copy of a byval
argument in the caller in very specific situations, it is currently safe
to mark byval arguments as noalias in Clang. But arguably we shouldn’t
start expressing that assumption when it wouldn’t be true in the general
case because it would break if we made the compiler smarter.

And so do GCC and ICC, which means
the 'noalias' would actually be correct here considering only the behavior
of those compilers. So in principle we could address this in the ABI by
saying that the copy is mandatory. But I don't think we should -- I think
the above code should have undefined behavior because it accesses a
function parameter through an access path not derived from the name of the
function parameter.

We do have some wording in the standard that tries to give aliasing
guarantees in some of these cases, but does so in a way that's not really
useful. Specifically, [class.cdtor]p2: "During the construction of an
object, if the value of the object or any of its subobjects is accessed
through a glvalue that is not obtained, directly or indirectly, from the
constructor’s this pointer, the value of the object or subobject thus
obtained is unspecified." (I mean, thanks for trying, but that's not all
the cases, and "the value is unspecified" is not enough permission.)

Yeah, I think this rule is fixable if it’s updated to use the
same object model that the standard is now using elsewhere.
Basically, things like pointers to unconstructed objects should
only get “forwarded” to become pointers to the object when
construction is complete. But I don’t think you can run this
rule in reverse to restrict how you can use pointers derived
from this.

(I also don’t know how to make this work with the ubiquitous
placement-new pattern. It feels like the language wants the
rule to be that you have to use the result of the new
expression, but (1) approximately nobody writes code that
complies with that and (2) doing so would be a significant
regression for a lot of code that would suddenly have to pass
around a pointer to the constructed object just to satisfy
a formal model when it already knows exactly where it’s stored.)

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev


On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall [hidden email] wrote:

...

I think concretely, the escape hatch doesn't stop things from going wrong,
because -- as you note -- even though we *could* have made a copy, it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t have to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates an object
at the same address, but it’s not the same object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.


I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal


...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall <[hidden email]> wrote:

...

I think concretely, the escape hatch doesn't stop things from
going wrong,
because -- as you note -- even though we *could* have made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t /have/ to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates /an/ object
at the same address, but it’s not the /same/ object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like bar(DefaultX) obviously cannot just pass the address of DefaultX as a by-value argument without first proving a lot of stuff about how foo uses both its parameter and DefaultX. I think noalias is actually a subset of what would have to be proven there.

In general, the standard is clear that you cannot rely on escaping a pointer to/into a trivially-copyable pr-value argument prior to the call and then rely on that pointer pointing into the corresponding parameter object. Implementations are allowed to introduce copies. But it does seem like the current wording would allow you to rely on that pointer pointing into some valid object, at least until the end of the caller’s full-expression. That means that, if we don’t guarantee to do an actual copy of the argument, we cannot make it UB to access the parameter variable through pointers to the argument temporary, which is what marking the parameter as noalias would do.

So I guess the remaining questions are:

  • Is this something we can reasonably change in the standard?
  • Are we comfortable setting noalias in C if the only place that would break is with a C++ caller?

John.

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall [hidden email] wrote:

...

I think concretely, the escape hatch doesn't stop things from
going wrong,
because -- as you note -- even though we *could* have made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t /have/ to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates /an/ object
at the same address, but it’s not the /same/ object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like bar(DefaultX) obviously cannot just pass the address of DefaultX as a by-value argument without first proving a lot of stuff about how foo uses both its parameter and DefaultX. I think noalias is actually a subset of what would have to be proven there.


Yes, I apologize. You're right: my pseudo-code missed the point. So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.


In general, the standard is clear that you cannot rely on escaping a pointer to/into a trivially-copyable pr-value argument prior to the call and then rely on that pointer pointing into the corresponding parameter object. Implementations are allowed to introduce copies. But it does seem like the current wording would allow you to rely on that pointer pointing into some valid object, at least until the end of the caller’s full-expression. That means that, if we don’t guarantee to do an actual copy of the argument, we cannot make it UB to access the parameter variable through pointers to the argument temporary, which is what marking the parameter as noalias would do.

So I guess the remaining questions are:

  • Is this something we can reasonably change in the standard?


This is the part that I'm unclear about. What change would we make?



  • Are we comfortable setting noalias in C if the only place that would break is with a C++ caller?


Out of curiosity, if you take C in combination with our statement-expression extension implementation (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), and notwithstanding the statement in the GCC manual about returns by value (i.e., the part just before where it says, "Therefore the this pointer observed by Foo is not the address of a."), is there any relationship to this topic?

Thanks again,

Hal


John.

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev


On 7/31/20 1:24 PM, Hal Finkel wrote:
On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall [hidden email] wrote:

...

I think concretely, the escape hatch doesn't stop things from
going wrong,
because -- as you note -- even though we *could* have made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t /have/ to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates /an/ object
at the same address, but it’s not the /same/ object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like bar(DefaultX) obviously cannot just pass the address of DefaultX as a by-value argument without first proving a lot of stuff about how foo uses both its parameter and DefaultX. I think noalias is actually a subset of what would have to be proven there.


Yes, I apologize. You're right: my pseudo-code missed the point. So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.


In general, the standard is clear that you cannot rely on escaping a pointer to/into a trivially-copyable pr-value argument prior to the call and then rely on that pointer pointing into the corresponding parameter object. Implementations are allowed to introduce copies. But it does seem like the current wording would allow you to rely on that pointer pointing into some valid object, at least until the end of the caller’s full-expression. That means that, if we don’t guarantee to do an actual copy of the argument, we cannot make it UB to access the parameter variable through pointers to the argument temporary, which is what marking the parameter as noalias would do.

So I guess the remaining questions are:

  • Is this something we can reasonably change in the standard?


This is the part that I'm unclear about. What change would we make?



Also, maybe some extended use of the no_unique_address attribute would help?

 -Hal



  • Are we comfortable setting noalias in C if the only place that would break is with a C++ caller?


Out of curiosity, if you take C in combination with our statement-expression extension implementation (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), and notwithstanding the statement in the GCC manual about returns by value (i.e., the part just before where it says, "Therefore the this pointer observed by Foo is not the address of a."), is there any relationship to this topic?

Thanks again,

Hal


John.

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.


#include <assert.h>

struct Foo {
    int x;
    // assert fails if you comment out these unused fields!
    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}


On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email]> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:
On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall [hidden email] wrote:

...

I think concretely, the escape hatch doesn't stop things from
going wrong,
because -- as you note -- even though we *could* have made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t /have/ to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates /an/ object
at the same address, but it’s not the /same/ object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like bar(DefaultX) obviously cannot just pass the address of DefaultX as a by-value argument without first proving a lot of stuff about how foo uses both its parameter and DefaultX. I think noalias is actually a subset of what would have to be proven there.


Yes, I apologize. You're right: my pseudo-code missed the point. So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.


In general, the standard is clear that you cannot rely on escaping a pointer to/into a trivially-copyable pr-value argument prior to the call and then rely on that pointer pointing into the corresponding parameter object. Implementations are allowed to introduce copies. But it does seem like the current wording would allow you to rely on that pointer pointing into some valid object, at least until the end of the caller’s full-expression. That means that, if we don’t guarantee to do an actual copy of the argument, we cannot make it UB to access the parameter variable through pointers to the argument temporary, which is what marking the parameter as noalias would do.

So I guess the remaining questions are:

  • Is this something we can reasonably change in the standard?


This is the part that I'm unclear about. What change would we make?



Also, maybe some extended use of the no_unique_address attribute would help?

 -Hal



  • Are we comfortable setting noalias in C if the only place that would break is with a C++ caller?


Out of curiosity, if you take C in combination with our statement-expression extension implementation (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), and notwithstanding the statement in the GCC manual about returns by value (i.e., the part just before where it says, "Therefore the this pointer observed by Foo is not the address of a."), is there any relationship to this topic?

Thanks again,

Hal


John.

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev


On 7/31/20 5:59 PM, James Y Knight wrote:
This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.


#include <assert.h>

struct Foo {
    int x;
    // assert fails if you comment out these unused fields!
    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}


Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

 -Hal



On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email]> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:
On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall [hidden email] wrote:

...

I think concretely, the escape hatch doesn't stop things from
going wrong,
because -- as you note -- even though we *could* have made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t /have/ to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates /an/ object
at the same address, but it’s not the /same/ object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like bar(DefaultX) obviously cannot just pass the address of DefaultX as a by-value argument without first proving a lot of stuff about how foo uses both its parameter and DefaultX. I think noalias is actually a subset of what would have to be proven there.


Yes, I apologize. You're right: my pseudo-code missed the point. So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.


In general, the standard is clear that you cannot rely on escaping a pointer to/into a trivially-copyable pr-value argument prior to the call and then rely on that pointer pointing into the corresponding parameter object. Implementations are allowed to introduce copies. But it does seem like the current wording would allow you to rely on that pointer pointing into some valid object, at least until the end of the caller’s full-expression. That means that, if we don’t guarantee to do an actual copy of the argument, we cannot make it UB to access the parameter variable through pointers to the argument temporary, which is what marking the parameter as noalias would do.

So I guess the remaining questions are:

  • Is this something we can reasonably change in the standard?


This is the part that I'm unclear about. What change would we make?



Also, maybe some extended use of the no_unique_address attribute would help?

 -Hal



  • Are we comfortable setting noalias in C if the only place that would break is with a C++ caller?


Out of curiosity, if you take C in combination with our statement-expression extension implementation (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), and notwithstanding the statement in the GCC manual about returns by value (i.e., the part just before where it says, "Therefore the this pointer observed by Foo is not the address of a."), is there any relationship to this topic?

Thanks again,

Hal


John.

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
# Regarding what change to the standard should be proposed:

I think what John is after is to make it undefined behavior to escape a pointer in the constructor of a trivially-copyable class.

Or, maybe it can be a bit more limited: you are allowed to escape the pointer, and e.g. use it to detect whether a copy was made — I don’t think *observing* whether a copy was made is necessarily problematic — but *dereferencing* the escaped pointer, i.e. peeking at the data it points to, should be undefined behavior.  

I.e. the key thing is to make Richard’s example undefined behavior — correct me if I’m wrong.

If we had this guarantee, we could mark any trivially-copyable-class-type’d parameters "noalias" and reap the optimization benefits in C and C++ alike.

But it would *also* be good, as I believe Hal suggests, to be able to specify attributes on parameters to e.g. guarantee a copy is made.  That would provide a very useful side benefit: whenever a user manually forces a copy in this way, they can be assured that any escaped pointer usage is back to being well-defined/supported (since a trivial copy is always guaranteed to be noalias).  

In other words, whenever you want to still be allowed to escape a pointer in a constructor for class `A` and remain standard-compliant, all you would need to do is change any `f(A a)` to something like `f([[forcecopy]] A a)`.  

We could also provide an alternative [[maybealias]] attribute which does not force a copy, but does force IRGen to omit the noalias marking, i.e. to be pessimistic.

Bottom line any weird cases can still be allowed to be standard compliant, but they would have to address the weirdness of the behavior they desire by explicitly telling the compiler what it can and cannot do, as I believe Hal suggested.  

This would definitely be a good thing, by a) making weird cases more visible to the eye while b) allowing better optimization of more typical cases.

--

# Regarding James’ example: 

This implicates aliasing as well, without ever dealing in escaped pointers — `foo_` is a potential alias of `f` in `CreateFoo()` whenever it is constructed via `foo_(CreateFoo())`.  While I don’t think this can affect the noalias status of function parameter types, which John is mainly concerned with, it seems like it should addressed in any solution to the parameter issue since it is so similar.

I am not an expert on the particulars of noalias, NRVO, or what the standard currently has to say about this, but the issue seems to be that whenever we modify object data using the returned value of a method like `CreateFoo()`, and allow it to use NRVO to modify the object data indirectly, at its ultimate destination, rather than working on a copy first, there will be potential aliasing problems due to the visibility of this and the sub-object pointers within the method.

However I think these issues are apart from the matter of whether the assert should always hold — I think it’s valid for the `assert(foo_.x == 55);` to sometimes hold and sometimes not, as it could be used to detect whether a copy was made, which could be of interest to the user.

So, it seems to me that the compiler needs to either 
  1. assume the variable to be “returned” (`Foo f;` in the body of `CreateFoo()`) in such a method is *not* noalias, or 
  2. disable NRVO, i.e. force a copy, whenever writing the “returned” value of a class method to data within that class. 
I.e. you can do one optimization or the other, but not both.

--

# For any struggling to follow the discussion, here is my (limited) understanding of the problem John initially raised:

Given a call `f(A(...))` to a function `f(A a)`, in constructing argument `a` the compiler implementation will always begin by building a temporary of the argument object using the constructor call `A(…)`.  

But then, the C++ standard allows some leeway: If `A` is trivially copyable, the implementation may build zero or more copies of that object before settling on the one to which &a will point.  (If `A` is not trivially copyable, it cannot make any such copies, so there is no such leeway.)

So, in some implementations `&a` may point to that initial temporary; in others it may point to a copy of that temporary.

This flexibility only becomes a problem if the pointer to the initial constructed temporary is allowed to "escape" from its constructor; i.e. if the constructor reveals the object’s `this` pointer, or one if its sub-object pointers, to the outside world, such that other code can depend on that pointer.

The standard apparently currently supports using an escaped pointer to a temporary however the user wishes.

Unfortunately this support creates a barrier to optimization: should an implementation decide to make *no* copies of the temporary argument object initially constructed via `A(...)`, such that `&a` = the address of the initial temporary, i.e. `&a` = the address which might have been escaped during construction of the temporary, in a perfectly standard-compliant way, then the compiler is forced to account for the possibility that the user has obtained `&a` before the function call and might be referring into `a`’s data in the body of `f` under some "alias" (e.g. `p` in Richard’s example).

To account for this possibility CodeGen must tread carefully and pessimistically, foregoing optimizations which could be used if only we could guarantee either that:
  1. `a` was "noalias" or 
  2. any path by which an alias of `a` arose is not standard-compliant and thus okay to disregard.

In practice, this means we want to either: 
  1. Force to compiler to *always* make a copy when constructing `a`, solely for the purpose of giving `a` a fresh address which nothing else could have possibly seen (though if optimization is the goal, forcing ourselves to make a needless copy seems a step in the wrong direction), or 
  2. Guarantee that any path which exposes the address of the temporary (or, at least, which dereferences such an address) is non-standard-compliant, i.e. declare that any "alias" of `a` is by its nature invalid, so that we can safely mark `a` as noalias.  

Option 1 is what clang and other compilers seem to currently be doing, as Richard suggests (and disapproves of), but Option 2 (which I believe is what John is after) seems the most reasonable, since I cannot think of a good reason anyone should need to use the actual data content of that initial temporary.  

But as suggested above, even if a user *does* have a good reason, and really does want to dereference an escaped pointer while remaining standard compliant, we could just require them to add something like a [[forcecopy]] or [[maybealias]] attribute to the parameter.  

So, hard to see how anyone would lose with this proposed change.

- Dave


On Jul 31, 2020, at 7:50 PM, Hal Finkel via cfe-dev <[hidden email]> wrote:


On 7/31/20 5:59 PM, James Y Knight wrote:
This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.


#include <assert.h>

struct Foo {
    int x;
    // assert fails if you comment out these unused fields!
    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}


Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

 -Hal



On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email]> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:
On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall [hidden email] wrote:

...

I think concretely, the escape hatch doesn't stop things from
going wrong,
because -- as you note -- even though we *could* have made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter variable has
the same address as the argument. That doesn’t /have/ to be the same
question as whether a copy was performed: we could consider there to be
a formal copy (or series of copies) that ultimately creates /an/ object
at the same address, but it’s not the /same/ object and so pointers
to the old object no longer validly pointer to it. But I guess that
would probably violate the lifetime rules, because it would make accesses
through old pointers UB when in fact they should at worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but unfortunately, I think that the point that you raise here is a key issue. Whether or not the copy is performed is visible in the model, and so we can't simply act as though there was a copy when optimizing. Someone could easily have code that looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like bar(DefaultX) obviously cannot just pass the address of DefaultX as a by-value argument without first proving a lot of stuff about how foo uses both its parameter and DefaultX. I think noalias is actually a subset of what would have to be proven there.


Yes, I apologize. You're right: my pseudo-code missed the point. So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.


In general, the standard is clear that you cannot rely on escaping a pointer to/into a trivially-copyable pr-value argument prior to the call and then rely on that pointer pointing into the corresponding parameter object. Implementations are allowed to introduce copies. But it does seem like the current wording would allow you to rely on that pointer pointing into some valid object, at least until the end of the caller’s full-expression. That means that, if we don’t guarantee to do an actual copy of the argument, we cannot make it UB to access the parameter variable through pointers to the argument temporary, which is what marking the parameter as noalias would do.

So I guess the remaining questions are:

  • Is this something we can reasonably change in the standard?


This is the part that I'm unclear about. What change would we make?



Also, maybe some extended use of the no_unique_address attribute would help?

 -Hal




  • Are we comfortable setting noalias in C if the only place that would break is with a C++ caller?


Out of curiosity, if you take C in combination with our statement-expression extension implementation (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), and notwithstanding the statement in the GCC manual about returns by value (i.e., the part just before where it says, "Therefore the this pointer observed by Foo is not the address of a."), is there any relationship to this topic?

Thanks again,

Hal


John.

As Richard's example shows, the code doesn't need to explicitly compare the addresses to detect the copy either. Any code that reads/writes to the objects can do it. A perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 != A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add an attribute like 'can_copy' indicating that the optimizer can make a formal copy of the argument in the callee and use that instead of the original pointer if that seems useful. I can certainly imagine a transformation such as LICM making use of such a thing (although the cost modeling would probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
In reply to this post by Hubert Tong via cfe-dev

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.

 -Hal

On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email] <[hidden email]>> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:

On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall
<[hidden email]> <[hidden email]> wrote:

...

I think concretely, the escape hatch doesn't stop things
from
going wrong,
because -- as you note -- even though we *could* have
made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter
variable has
the same address as the argument. That doesn’t /have/ to
be the same
question as whether a copy was performed: we could
consider there to be
a formal copy (or series of copies) that ultimately
creates /an/ object
at the same address, but it’s not the /same/ object and
so pointers
to the old object no longer validly pointer to it. But I
guess that
would probably violate the lifetime rules, because it
would make accesses
through old pointers UB when in fact they should at
worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but
unfortunately, I think that the point that you raise here is
a key issue. Whether or not the copy is performed is visible
in the model, and so we can't simply act as though there was
a copy when optimizing. Someone could easily have code that
looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like |bar(DefaultX)|
obviously cannot just pass the address of |DefaultX| as a
by-value argument without first proving a lot of stuff about how
|foo| uses both its parameter and |DefaultX|. I think |noalias|
is actually a subset of what would have to be proven there.

Yes, I apologize. You're right: my pseudo-code missed the point.
So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.

In general, the standard is clear that you cannot rely on
escaping a pointer to/into a trivially-copyable pr-value
argument prior to the call and then rely on that pointer
pointing into the corresponding parameter object.
Implementations are /allowed/ to introduce copies. But it does
seem like the current wording would allow you to rely on that
pointer pointing into /some/ valid object, at least until the
end of the caller’s full-expression. That means that, if we
don’t guarantee to do an actual copy of the argument, we cannot
make it UB to access the parameter variable through pointers to
the argument temporary, which is what marking the parameter as
|noalias| would do.

So I guess the remaining questions are:

* Is this something we can reasonably change in the standard?

This is the part that I'm unclear about. What change would we make?

Also, maybe some extended use of the no_unique_address attribute
would help?

 -Hal

* Are we comfortable setting |noalias| in C if the only place
that would break is with a C++ caller?

Out of curiosity, if you take C in combination with our
statement-expression extension implementation
(https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
<https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html>), and
notwithstanding the statement in the GCC manual about returns by
value (i.e., the part just before where it says, "Therefore the
this pointer observed by Foo is not the address of a."), is there
any relationship to this topic?

Thanks again,

Hal

John.

As Richard's example shows, the code doesn't need to
explicitly compare the addresses to detect the copy either.
Any code that reads/writes to the objects can do it. A
perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 !=
A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add
an attribute like 'can_copy' indicating that the optimizer
can make a formal copy of the argument in the callee and use
that instead of the original pointer if that seems useful. I
can certainly imagine a transformation such as LICM making
use of such a thing (although the cost modeling would
probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev


On 8/3/20 8:45 PM, John McCall wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.


I agree. It seems like we should be able to make a sanitizer detect this kind of mistake as well (although the general case will require some msan-like propagation scheme).

 -Hal


 -Hal

On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email] <[hidden email]>> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:

On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall
[hidden email] <[hidden email]> wrote:

...

I think concretely, the escape hatch doesn't stop things
from
going wrong,
because -- as you note -- even though we *could* have
made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter
variable has
the same address as the argument. That doesn’t /have/ to
be the same
question as whether a copy was performed: we could
consider there to be
a formal copy (or series of copies) that ultimately
creates /an/ object
at the same address, but it’s not the /same/ object and
so pointers
to the old object no longer validly pointer to it. But I
guess that
would probably violate the lifetime rules, because it
would make accesses
through old pointers UB when in fact they should at
worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but
unfortunately, I think that the point that you raise here is
a key issue. Whether or not the copy is performed is visible
in the model, and so we can't simply act as though there was
a copy when optimizing. Someone could easily have code that
looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like |bar(DefaultX)|
obviously cannot just pass the address of |DefaultX| as a
by-value argument without first proving a lot of stuff about how
|foo| uses both its parameter and |DefaultX|. I think |noalias|
is actually a subset of what would have to be proven there.

Yes, I apologize. You're right: my pseudo-code missed the point.
So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.

In general, the standard is clear that you cannot rely on
escaping a pointer to/into a trivially-copyable pr-value
argument prior to the call and then rely on that pointer
pointing into the corresponding parameter object.
Implementations are /allowed/ to introduce copies. But it does
seem like the current wording would allow you to rely on that
pointer pointing into /some/ valid object, at least until the
end of the caller’s full-expression. That means that, if we
don’t guarantee to do an actual copy of the argument, we cannot
make it UB to access the parameter variable through pointers to
the argument temporary, which is what marking the parameter as
|noalias| would do.

So I guess the remaining questions are:

* Is this something we can reasonably change in the standard?

This is the part that I'm unclear about. What change would we make?

Also, maybe some extended use of the no_unique_address attribute
would help?

 -Hal

* Are we comfortable setting |noalias| in C if the only place
that would break is with a C++ caller?

Out of curiosity, if you take C in combination with our
statement-expression extension implementation
(https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
<https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html>), and
notwithstanding the statement in the GCC manual about returns by
value (i.e., the part just before where it says, "Therefore the
this pointer observed by Foo is not the address of a."), is there
any relationship to this topic?

Thanks again,

Hal

John.

As Richard's example shows, the code doesn't need to
explicitly compare the addresses to detect the copy either.
Any code that reads/writes to the objects can do it. A
perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 !=
A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add
an attribute like 'can_copy' indicating that the optimizer
can make a formal copy of the argument in the callee and use
that instead of the original pointer if that seems useful. I
can certainly imagine a transformation such as LICM making
use of such a thing (although the cost modeling would
probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev


On Aug 3, 2020, at 11:28 PM, Hal Finkel via cfe-dev <[hidden email]> wrote:


On 8/3/20 8:45 PM, John McCall wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.


I agree. It seems like we should be able to make a sanitizer detect this kind of mistake as well (although the general case will require some msan-like propagation scheme).

 -Hal


On second thought I agree as well — i.e. allow both the NRVO and the noalias optimizations to be applied as they currently are, and adjust the standard so that any code which would break due to those optimizations should be considered undefined.  

That would be consistent with the proposal of making escaping pointers within a constructor of a trivially-copyable class undefined behavior.

And more generally: if there are any other instances in which the compiler is currently forced to be pessimistic, just to handle a few weird usages which are technically allowed, I think by all means we should ask the standard to explicitly address those, via the same approach: the typical, more-optimizable cases should be allowed to fully optimize, by making the rare, less-optimizable cases undefined behavior.  

But in tandem, the standard should also introduce attributes that allow advanced users to explicitly opt out of those optimizations (e.g. "forcecopy" or "maybealias" or whatever) to allow their weird cases to remain standard compliant.  Nobody loses.

People write C and C++ because they want the fastest possible compiled code — the standard should get out of the way wherever it is obstructing optimization.

Dave


 -Hal

On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email] <[hidden email]>> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:

On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall
[hidden email] <[hidden email]> wrote:

...

I think concretely, the escape hatch doesn't stop things
from
going wrong,
because -- as you note -- even though we *could* have
made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter
variable has
the same address as the argument. That doesn’t /have/ to
be the same
question as whether a copy was performed: we could
consider there to be
a formal copy (or series of copies) that ultimately
creates /an/ object
at the same address, but it’s not the /same/ object and
so pointers
to the old object no longer validly pointer to it. But I
guess that
would probably violate the lifetime rules, because it
would make accesses
through old pointers UB when in fact they should at
worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but
unfortunately, I think that the point that you raise here is
a key issue. Whether or not the copy is performed is visible
in the model, and so we can't simply act as though there was
a copy when optimizing. Someone could easily have code that
looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like |bar(DefaultX)|
obviously cannot just pass the address of |DefaultX| as a
by-value argument without first proving a lot of stuff about how
|foo| uses both its parameter and |DefaultX|. I think |noalias|
is actually a subset of what would have to be proven there.

Yes, I apologize. You're right: my pseudo-code missed the point.
So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.

In general, the standard is clear that you cannot rely on
escaping a pointer to/into a trivially-copyable pr-value
argument prior to the call and then rely on that pointer
pointing into the corresponding parameter object.
Implementations are /allowed/ to introduce copies. But it does
seem like the current wording would allow you to rely on that
pointer pointing into /some/ valid object, at least until the
end of the caller’s full-expression. That means that, if we
don’t guarantee to do an actual copy of the argument, we cannot
make it UB to access the parameter variable through pointers to
the argument temporary, which is what marking the parameter as
|noalias| would do.

So I guess the remaining questions are:

* Is this something we can reasonably change in the standard?

This is the part that I'm unclear about. What change would we make?

Also, maybe some extended use of the no_unique_address attribute
would help?

 -Hal

* Are we comfortable setting |noalias| in C if the only place
that would break is with a C++ caller?

Out of curiosity, if you take C in combination with our
statement-expression extension implementation
(https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
<https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html>), and
notwithstanding the statement in the GCC manual about returns by
value (i.e., the part just before where it says, "Therefore the
this pointer observed by Foo is not the address of a."), is there
any relationship to this topic?

Thanks again,

Hal

John.

As Richard's example shows, the code doesn't need to
explicitly compare the addresses to detect the copy either.
Any code that reads/writes to the objects can do it. A
perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 !=
A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add
an attribute like 'can_copy' indicating that the optimizer
can make a formal copy of the argument in the callee and use
that instead of the original pointer if that seems useful. I
can certainly imagine a transformation such as LICM making
use of such a thing (although the cost modeling would
probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
In reply to this post by Hubert Tong via cfe-dev
Thanks for kicking off this discussion and for all the interesting responses.

Marking indirect parameters as noalias enables more aggressive memory optimization with respect to the indirect arguments on certain targets. On ARM64 this leads to 1%-2% in code size reductions in some cases.

While we work towards adjusting the C++ standard, I would like to provide a way for users to opt-in to the new behavior (e.g. for code-bases that exclusively use C or guarantee that no addresses can escape before the argument is passed): https://reviews.llvm.org/D85473

What do you think?

Cheers,
Florian

On Aug 4, 2020, at 04:28, Hal Finkel via cfe-dev <[hidden email]> wrote:


On 8/3/20 8:45 PM, John McCall wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.


I agree. It seems like we should be able to make a sanitizer detect this kind of mistake as well (although the general case will require some msan-like propagation scheme).

 -Hal


 -Hal

On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email] <[hidden email]>> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:

On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall
[hidden email] <[hidden email]> wrote:

...

I think concretely, the escape hatch doesn't stop things
from
going wrong,
because -- as you note -- even though we *could* have
made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter
variable has
the same address as the argument. That doesn’t /have/ to
be the same
question as whether a copy was performed: we could
consider there to be
a formal copy (or series of copies) that ultimately
creates /an/ object
at the same address, but it’s not the /same/ object and
so pointers
to the old object no longer validly pointer to it. But I
guess that
would probably violate the lifetime rules, because it
would make accesses
through old pointers UB when in fact they should at
worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but
unfortunately, I think that the point that you raise here is
a key issue. Whether or not the copy is performed is visible
in the model, and so we can't simply act as though there was
a copy when optimizing. Someone could easily have code that
looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like |bar(DefaultX)|
obviously cannot just pass the address of |DefaultX| as a
by-value argument without first proving a lot of stuff about how
|foo| uses both its parameter and |DefaultX|. I think |noalias|
is actually a subset of what would have to be proven there.

Yes, I apologize. You're right: my pseudo-code missed the point.
So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.

In general, the standard is clear that you cannot rely on
escaping a pointer to/into a trivially-copyable pr-value
argument prior to the call and then rely on that pointer
pointing into the corresponding parameter object.
Implementations are /allowed/ to introduce copies. But it does
seem like the current wording would allow you to rely on that
pointer pointing into /some/ valid object, at least until the
end of the caller’s full-expression. That means that, if we
don’t guarantee to do an actual copy of the argument, we cannot
make it UB to access the parameter variable through pointers to
the argument temporary, which is what marking the parameter as
|noalias| would do.

So I guess the remaining questions are:

* Is this something we can reasonably change in the standard?

This is the part that I'm unclear about. What change would we make?

Also, maybe some extended use of the no_unique_address attribute
would help?

 -Hal

* Are we comfortable setting |noalias| in C if the only place
that would break is with a C++ caller?

Out of curiosity, if you take C in combination with our
statement-expression extension implementation
(https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
<https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html>), and
notwithstanding the statement in the GCC manual about returns by
value (i.e., the part just before where it says, "Therefore the
this pointer observed by Foo is not the address of a."), is there
any relationship to this topic?

Thanks again,

Hal

John.

As Richard's example shows, the code doesn't need to
explicitly compare the addresses to detect the copy either.
Any code that reads/writes to the objects can do it. A
perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 !=
A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add
an attribute like 'can_copy' indicating that the optimizer
can make a formal copy of the argument in the callee and use
that instead of the original pointer if that seems useful. I
can certainly imagine a transformation such as LICM making
use of such a thing (although the cost modeling would
probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
Quick suggestion, then I’ll bow out/leave the discussion to those with greater expertise:

Perhaps link the noalias status of NRVO parameters to this option as well (e.g. `Foo f;` in James’s `CreateFoo()` example), since that is basically the same issue in reverse: whereas in Richard’s example the issue is the address of the object escaping and becoming an alias which is problematic after its construction, in James’s example the issue is the address of the object being visible, under an alias, during its construction.

And perhaps worthwhile to make this option the default, so you can observe if anyone notices/files bugs and thereby better determine if it should be made standard.

Good luck, interested to see how this is resolved,

Dave

On Aug 6, 2020, at 4:36 PM, Florian Hahn via cfe-dev <[hidden email]> wrote:

Thanks for kicking off this discussion and for all the interesting responses.

Marking indirect parameters as noalias enables more aggressive memory optimization with respect to the indirect arguments on certain targets. On ARM64 this leads to 1%-2% in code size reductions in some cases.

While we work towards adjusting the C++ standard, I would like to provide a way for users to opt-in to the new behavior (e.g. for code-bases that exclusively use C or guarantee that no addresses can escape before the argument is passed): https://reviews.llvm.org/D85473

What do you think?

Cheers,
Florian

On Aug 4, 2020, at 04:28, Hal Finkel via cfe-dev <[hidden email]> wrote:


On 8/3/20 8:45 PM, John McCall wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.


I agree. It seems like we should be able to make a sanitizer detect this kind of mistake as well (although the general case will require some msan-like propagation scheme).

 -Hal


 -Hal

On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev <[hidden email] <[hidden email]>> wrote:


On 7/31/20 1:24 PM, Hal Finkel wrote:

On 7/31/20 12:43 PM, John McCall wrote:

n 31 Jul 2020, at 7:35, Hal Finkel wrote:

On 7/29/20 9:00 PM, John McCall via cfe-dev wrote:

On 29 Jul 2020, at 17:42, Richard Smith wrote:

On Wed, 29 Jul 2020 at 12:52, John McCall
[hidden email] <[hidden email]> wrote:

...

I think concretely, the escape hatch doesn't stop things
from
going wrong,
because -- as you note -- even though we *could* have
made a copy,
it's
observable whether or not we *did* make a copy. For example:

I would say that it’s observable whether the parameter
variable has
the same address as the argument. That doesn’t /have/ to
be the same
question as whether a copy was performed: we could
consider there to be
a formal copy (or series of copies) that ultimately
creates /an/ object
at the same address, but it’s not the /same/ object and
so pointers
to the old object no longer validly pointer to it. But I
guess that
would probably violate the lifetime rules, because it
would make accesses
through old pointers UB when in fact they should at
worst access a valid
object that’s just unrelated to the parameter object.

I think that it would be great to be able to do this, but
unfortunately, I think that the point that you raise here is
a key issue. Whether or not the copy is performed is visible
in the model, and so we can't simply act as though there was
a copy when optimizing. Someone could easily have code that
looks like:

Foo DefaultX;

...

void something(Foo &A, Foo &B) {

  if (&A == &B) { ... }

}

void bar(Foo X) { something(X, DefaultX); }

This example isn’t really on point; a call like |bar(DefaultX)|
obviously cannot just pass the address of |DefaultX| as a
by-value argument without first proving a lot of stuff about how
|foo| uses both its parameter and |DefaultX|. I think |noalias|
is actually a subset of what would have to be proven there.

Yes, I apologize. You're right: my pseudo-code missed the point.
So the record is clear, let me rephrase:

Foo *DefaultX = nullptr;
...
Foo::Foo() { if (!DefaultX) DefaultX = this; }
...
void bar(Foo X) { something(X, *DefaultX); }
...
bar(Foo{});

I think that's closer to what we're talking about.

In general, the standard is clear that you cannot rely on
escaping a pointer to/into a trivially-copyable pr-value
argument prior to the call and then rely on that pointer
pointing into the corresponding parameter object.
Implementations are /allowed/ to introduce copies. But it does
seem like the current wording would allow you to rely on that
pointer pointing into /some/ valid object, at least until the
end of the caller’s full-expression. That means that, if we
don’t guarantee to do an actual copy of the argument, we cannot
make it UB to access the parameter variable through pointers to
the argument temporary, which is what marking the parameter as
|noalias| would do.

So I guess the remaining questions are:

* Is this something we can reasonably change in the standard?

This is the part that I'm unclear about. What change would we make?

Also, maybe some extended use of the no_unique_address attribute
would help?

 -Hal

* Are we comfortable setting |noalias| in C if the only place
that would break is with a C++ caller?

Out of curiosity, if you take C in combination with our
statement-expression extension implementation
(https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
<https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html>), and
notwithstanding the statement in the GCC manual about returns by
value (i.e., the part just before where it says, "Therefore the
this pointer observed by Foo is not the address of a."), is there
any relationship to this topic?

Thanks again,

Hal

John.

As Richard's example shows, the code doesn't need to
explicitly compare the addresses to detect the copy either.
Any code that reads/writes to the objects can do it. A
perhaps-more-realistic example might be:

  int Cnt = A.RefCnt; ++A.RefCnt; ++B.RefCnt; if (Cnt + 1 !=
A.RefCnt) { /* same object case */ }

The best suggestion that I have so far is that we could add
an attribute like 'can_copy' indicating that the optimizer
can make a formal copy of the argument in the callee and use
that instead of the original pointer if that seems useful. I
can certainly imagine a transformation such as LICM making
use of such a thing (although the cost modeling would
probably need to be fairly conservative).

 -Hal

...

John.


_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

_______________________________________________
cfe-dev mailing list
[hidden email] <[hidden email]>
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
<https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>

--
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev
In reply to this post by Hubert Tong via cfe-dev
Hi,

On Aug 4, 2020, at 18:32, David Rector via cfe-dev <[hidden email]> wrote:



On Aug 3, 2020, at 11:28 PM, Hal Finkel via cfe-dev <[hidden email]> wrote:


On 8/3/20 8:45 PM, John McCall wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.


I agree. It seems like we should be able to make a sanitizer detect this kind of mistake as well (although the general case will require some msan-like propagation scheme).

 -Hal


On second thought I agree as well — i.e. allow both the NRVO and the noalias optimizations to be applied as they currently are, and adjust the standard so that any code which would break due to those optimizations should be considered undefined.  

That would be consistent with the proposal of making escaping pointers within a constructor of a trivially-copyable class undefined behavior.

And more generally: if there are any other instances in which the compiler is currently forced to be pessimistic, just to handle a few weird usages which are technically allowed, I think by all means we should ask the standard to explicitly address those, via the same approach: the typical, more-optimizable cases should be allowed to fully optimize, by making the rare, less-optimizable cases undefined behavior.  


It would be great to make some progress on the issue, but unfortunately I am unclear what the next steps should be?

As mentioned earlier, I added a patch to add noalias under a flag (https://reviews.llvm.org/D85473). Are there any concerns/objections on this as a first step?

Whether to enable it by default can then be discussed separately. IIUC from the discussion, we would need an adjustment to the standard along to lines of John’s proposal to making escaping pointers within a constructor of a trivially-copyable class undefined behavior. Are there any pointers/suggestions on how to best go about that?

But in tandem, the standard should also introduce attributes that allow advanced users to explicitly opt out of those optimizations (e.g. "forcecopy" or "maybealias" or whatever) to allow their weird cases to remain standard compliant.  Nobody loses.

People write C and C++ because they want the fastest possible compiled code — the standard should get out of the way wherever it is obstructing optimization.


Agreed, although this change seems to have the potential to break a very small subset of code out there in a very subtle manner, without an easy way to detect that. But maybe we can detect and warn about the most common/obvious violations at least and that is enough?

Cheers,
Florian

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Can indirect class parameters be noalias?

Hubert Tong via cfe-dev


On Aug 26, 2020, at 12:45 PM, Florian Hahn <[hidden email]> wrote:

Hi,

On Aug 4, 2020, at 18:32, David Rector via cfe-dev <[hidden email]> wrote:



On Aug 3, 2020, at 11:28 PM, Hal Finkel via cfe-dev <[hidden email]> wrote:


On 8/3/20 8:45 PM, John McCall wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.


I agree. It seems like we should be able to make a sanitizer detect this kind of mistake as well (although the general case will require some msan-like propagation scheme).

 -Hal


On second thought I agree as well — i.e. allow both the NRVO and the noalias optimizations to be applied as they currently are, and adjust the standard so that any code which would break due to those optimizations should be considered undefined.  

That would be consistent with the proposal of making escaping pointers within a constructor of a trivially-copyable class undefined behavior.

And more generally: if there are any other instances in which the compiler is currently forced to be pessimistic, just to handle a few weird usages which are technically allowed, I think by all means we should ask the standard to explicitly address those, via the same approach: the typical, more-optimizable cases should be allowed to fully optimize, by making the rare, less-optimizable cases undefined behavior.  


It would be great to make some progress on the issue, but unfortunately I am unclear what the next steps should be?

As mentioned earlier, I added a patch to add noalias under a flag (https://reviews.llvm.org/D85473). Are there any concerns/objections on this as a first step?

Whether to enable it by default can then be discussed separately. IIUC from the discussion, we would need an adjustment to the standard along to lines of John’s proposal to making escaping pointers within a constructor of a trivially-copyable class undefined behavior. Are there any pointers/suggestions on how to best go about that?

I earlier inferred this was John’s proposal without him saying as much, and in fact now I think it wouldn’t go far enough, given the fact that NRVO basically lets certain functions act as a glorified constructors, giving further opportunities to escape a pointer.  Consider this addition to Richard’s example:

```
#include <stdio.h>

struct A {
    A(A **where) : data{"hello world 1"} { 
      *where = this; //Escaped pointer 1 (proposed UB?)
    }

    A() : data{"hello world 2"} {}

   

    char data[65536];
};
A *p;


[[gnu::noinline]]
void f(A a) {
    for (int i = 0; i != sizeof(A::data) - 2; ++i)
        p->data[i+1] = a.data[i];
    puts(a.data);
}

A CreateA(A **where) {
  A justlikethis;
  *where = &justlikethis; //Escaped pointer 2 (should also be UB, then)
  return justlikethis;
}

// elsewhere, perhaps compiled by a smarter compiler that doesn't make a copy here
int main() { 
  f({&p});        // 1
  f(CreateA(&p)); // 2
```

I think John was on to the right path in addressing James’s NRVO example; that example and John’s response reproduced here:

On Aug 3, 2020, at 9:45 PM, John McCall via cfe-dev <[hidden email]> wrote:

On 31 Jul 2020, at 19:50, Hal Finkel wrote:

On 7/31/20 5:59 PM, James Y Knight wrote:

This discussion reminds me of an example I ran into a couple weeks ago, where the execution of the program is dependent precisely upon whether the ABI calls for the object to be passed indirectly, or in a register

In the case where NVRO is triggered, the class member foo_ is fully-constructed on the first line of CreateFoo (despite appearing as if that's only constructing a local variable). In the case where the struct is small enough to fit in a register, NVRO does not apply, and in that case, foo_ isn't constructed until after CreateFoo returns.

Therefore, I believe it's implementation-defined whether the following program has undefined behavior.

https://godbolt.org/z/YT9zsz <https://godbolt.org/z/YT9zsz>

#include <assert.h>

struct Foo {
    int x;
*    // assert fails if you comment out these unused fields!
*    int dummy[4];
};

struct Bar {
    Bar() : foo_(CreateFoo()) {}

    Foo CreateFoo() {
        Foo f;
        f.x = 55;
        assert(foo_.x == 55);
        return f;
    }
    Foo foo_;
};

int main() {
    Bar b;
}

Looks that way to me too. The example in 11.10.5p2 sort of makes this point as well (by pointing out that you can directly initialize a global this way).

It does seem hard to argue that this is invalid under the specification. To me it seems like it clearly ought to be invalid, though. Note that Clang has always emitted return address arguments as noalias, so this has immediate significance.

If I were writing the specification, I would rewrite the restriction in [class.cdtor]p2 to say that pointers derived by naming a returned/constructed object do not formally point to the object until the function actually returns, even if the copy is elided. That would make James’s example undefined behavior.

John.

I think the goal should be to start with the above wording and add wording which also prevents escaping any pointer to the returned/constructed object until the function returns/construction finishes, for trivially copyable classes anyway...or something like that.  

I have wandered way out of my depth, so I’ll leave it to you guys from here!  Thanks Florian for doing the legwork/testing, so helpful!

Dave



But in tandem, the standard should also introduce attributes that allow advanced users to explicitly opt out of those optimizations (e.g. "forcecopy" or "maybealias" or whatever) to allow their weird cases to remain standard compliant.  Nobody loses.

People write C and C++ because they want the fastest possible compiled code — the standard should get out of the way wherever it is obstructing optimization.


Agreed, although this change seems to have the potential to break a very small subset of code out there in a very subtle manner, without an easy way to detect that. But maybe we can detect and warn about the most common/obvious violations at least and that is enough?

Cheers,
Florian


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev