Discussion:
[ub] launder and aliasing
Robert Haberlach
2016-02-26 12:12:45 UTC
Permalink
I apologize in advance if this was discussed before; I didn't check the entire archive. Consider

static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }

As it stands, invocation of foo is undefined, as the argument to launder is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.

launder is designed to inhibit address propagation analysis, which is the only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any implementation. If so, can we make the above formally well-defined?
Being able to use launder in such scenarios would render circumlocutions via memcpy superfluous.

Robert
Jens Maurer
2016-02-26 12:45:28 UTC
Permalink
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check the entire archive. Consider
static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to launder is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which is the only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any implementation. If so, can we make the above formally well-defined?
Being able to use launder in such scenarios would render circumlocutions via memcpy superfluous.
That the suggested approach not enough to make this work; see 3.10p10.

Jens
Robert Haberlach
2016-02-26 12:58:15 UTC
Permalink
Post by Jens Maurer
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check the entire archive. Consider
static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to launder is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which is the only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any implementation. If so, can we make the above formally well-defined?
Being able to use launder in such scenarios would render circumlocutions via memcpy superfluous.
That the suggested approach not enough to make this work; see 3.10p10.
Sorry if I was unclear; I didn't (yet) mean to suggest any specific strategy to make this defined. Clearly, more than just the specification of
launder needs to be adjusted. My question is rather whether allowing the shown code itself is sensible.
Jens Maurer
2016-02-26 18:17:24 UTC
Permalink
Post by Robert Haberlach
Post by Jens Maurer
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check the entire archive. Consider
static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to launder is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which is the only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any implementation. If so, can we make the above formally well-defined?
Being able to use launder in such scenarios would render circumlocutions via memcpy superfluous.
That the suggested approach not enough to make this work; see 3.10p10.
Sorry if I was unclear; I didn't (yet) mean to suggest any specific strategy to make this defined. Clearly, more than just the specification of
launder needs to be adjusted. My question is rather whether allowing the shown code itself is sensible.
I don't think so. It seriously pessimizes type-based alias analysis
for the compiler.

Jens
Hubert Tong
2016-02-26 14:26:27 UTC
Permalink
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check the
entire archive. Consider
static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to launder
is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which is the
only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any
implementation. If so, can we make the above formally well-defined?
This is not fine. Type-based aliasing analysis (TBAA) is allowed to
determine that the write to "f" and the read from the dereference is not
related.
Consider the effects of inlining:
int main(void) { return !foo(0.5f); }

becomes (loosely):

int main(void) {
int __ret;
{
float __f = 0.5f;
__ret = *std::launder(reinterpret_cast<int *>(&__f));
}
return !__ret;
}

The implementation is allowed to observe that there are no TBAA-compatible
reads of the value of __f within its lifetime. The likely result is that
the initialization of __f would be optimized away.

-- HT

Being able to use launder in such scenarios would render circumlocutions
Post by Robert Haberlach
via memcpy superfluous.
Robert
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
Robert Haberlach
2016-02-26 18:03:52 UTC
Permalink
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check the entire archive. Consider
static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to launder is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which is the only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any implementation. If so, can we make the above formally well-defined?
This is not fine. Type-based aliasing analysis (TBAA) is allowed to determine that the write to "f" and the read from the dereference is not related.
int main(void) { return !foo(0.5f); }
int main(void) {
int __ret;
{
float __f = 0.5f;
__ret = *std::launder(reinterpret_cast<int *>(&__f));
}
return !__ret;
}
The implementation is allowed to observe that there are no TBAA-compatible reads of the value of __f within its lifetime.
Isn't the whole idea that launder's definition is, to some extent, intransparent to optimizers? We're passing a pointer to f into launder. As long as
the analyser is told to assume that calls to launder perform what not, it cannot elide the initialization, because launder could read.

Perhaps launder isn't the right function to base such functionality on, anyway (I just had the impression it fitted the picture of an "opaque"
identity function well). Then my question would rather be if it is sensible to provide some function (or language construct), e.g. taking a glvalue
and returning one of a given type, s.t. aliasing via the returned glvalue can be well-defined. I don't think there would be great technical obstacles
in adjusting aliasing analysers to treat such calls accordingly, but it would allow for a concise way of disabling strict aliasing for an operation
while still benefiting from powerful optimization everywhere else.

Robert
Post by Robert Haberlach
The likely result is that
the initialization of __f would be optimized away.
-- HT
Being able to use launder in such scenarios would render circumlocutions via memcpy superfluous.
Robert
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
Richard Smith
2016-02-26 21:46:26 UTC
Permalink
Post by Robert Haberlach
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check the entire archive. Consider
static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to launder is not pointing to an object of type int (or similar) within its lifetime,
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which is the only concerning optimization. Moreover, as long as all usual conditions are
met (alignment, size & trap values), this should be fine on any implementation. If so, can we make the above formally well-defined?
This is not fine. Type-based aliasing analysis (TBAA) is allowed to determine that the write to "f" and the read from the dereference is not related.
int main(void) { return !foo(0.5f); }
int main(void) {
int __ret;
{
float __f = 0.5f;
__ret = *std::launder(reinterpret_cast<int *>(&__f));
}
return !__ret;
}
The implementation is allowed to observe that there are no TBAA-compatible reads of the value of __f within its lifetime.
Isn't the whole idea that launder's definition is, to some extent, intransparent to optimizers? We're passing a pointer to f into launder. As long as
the analyser is told to assume that calls to launder perform what not, it cannot elide the initialization, because launder could read.
launder is known to the optimizer, and is known to not read or write
any memory. If it could, that would significantly inhibit
optimizations in code that uses it.
Post by Robert Haberlach
Perhaps launder isn't the right function to base such functionality on, anyway (I just had the impression it fitted the picture of an "opaque"
identity function well). Then my question would rather be if it is sensible to provide some function (or language construct), e.g. taking a glvalue
and returning one of a given type, s.t. aliasing via the returned glvalue can be well-defined. I don't think there would be great technical obstacles
in adjusting aliasing analysers to treat such calls accordingly, but it would allow for a concise way of disabling strict aliasing for an operation
while still benefiting from powerful optimization everywhere else.
I don't think that is the right interface for such functionality. No
matter what you do with the pointer, if you create a situation where
an int* and a float* can simultaneously exist and both can be used to
load or store the same memory, you destroy TBAA.

Gaby had a paper that would guarantee that memcpy can be used to
reinterpret the bytes of an object of one type as a value of another
type; that would seem to fit the bill here. And you can use that to
build higher-level operations, such as this:

template<typename T, typename U> T *change_object_type(U *p) {
static_assert(sizeof(T) == sizeof(U));
static_assert(is_trivially_copyable_v<T> && is_trivially_copyable_v<U>);
char buffer[sizeof(T)];
memcpy(buffer, p, sizeof(T));
p->~U();
T *result = new (p) T;
memcpy(result, buffer, sizeof(T));
return result;
}

int foo(float f) {
int *i = change_object_type<int>(&f); // float is dead, long live the int!
return *i;
}

Your compiler ought to be able to be able to optimize that down to
essentially nothing (maybe a round-trip through memory to convert a
floating-point register to an integer register, or maybe just a mov
from one register to another, depending on CPU architecture, ABI,
etc.).
Post by Robert Haberlach
Robert
Post by Robert Haberlach
The likely result is that
the initialization of __f would be optimized away.
-- HT
Being able to use launder in such scenarios would render circumlocutions via memcpy superfluous.
Robert
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
Gabriel Dos Reis
2016-02-26 21:55:49 UTC
Permalink
| I don't think that is the right interface for such functionality. No matter what you
| do with the pointer, if you create a situation where an int* and a float* can
| simultaneously exist and both can be used to load or store the same memory,
| you destroy TBAA.

+1.
See my comment about blowing off the entire planet.
Patrick, a few months ago, made a comment about how scary std::launder is -- he is right :-)

| Gaby had a paper that would guarantee that memcpy can be used to reinterpret
| the bytes of an object of one type as a value of another type; that would seem
| to fit the bill here. And you can use that to build higher-level operations, such as
| this:

I was meaning to update that paper for Jacksonville, because it has to go in C++17, but life happened.
This that paper is the consensus of SG12 (as we reviewed it in Kona), I will make sure it gets honorable mention in Jacksonville and we have a full wording in the post-Jacksonville mailing for consideration for C++17 in Oulu.

|
| template<typename T, typename U> T *change_object_type(U *p) {
| static_assert(sizeof(T) == sizeof(U));
| static_assert(is_trivially_copyable_v<T> && is_trivially_copyable_v<U>);
| char buffer[sizeof(T)];
| memcpy(buffer, p, sizeof(T));
| p->~U();
| T *result = new (p) T;
| memcpy(result, buffer, sizeof(T));
| return result;
| }
|
| int foo(float f) {
| int *i = change_object_type<int>(&f); // float is dead, long live the int!
| return *i;
| }
|
| Your compiler ought to be able to be able to optimize that down to essentially
| nothing (maybe a round-trip through memory to convert a floating-point
| register to an integer register, or maybe just a mov from one register to
| another, depending on CPU architecture, ABI, etc.).

-- Gaby
Jeffrey Yasskin
2016-02-26 22:44:51 UTC
Permalink
Post by Gabriel Dos Reis
| Gaby had a paper that would guarantee that memcpy can be used to reinterpret
| the bytes of an object of one type as a value of another type; that would seem
| to fit the bill here. And you can use that to build higher-level operations, such as
I was meaning to update that paper for Jacksonville, because it has to go
in C++17, but life happened.
This that paper is the consensus of SG12 (as we reviewed it in Kona), I
will make sure it gets honorable mention in Jacksonville and we have a full
wording in the post-Jacksonville mailing for consideration for C++17 in
Oulu.
Argh. Jacksonville is the last meeting to get things through EWG if they're
going into C++17. Is it at all possible for you to get the paper into a
state that EWG could approve, and get it onto Ville's agenda? If the
existing paper if SG12's consensus, maybe that's enough. Post-Jacksonville
should be fine for the full wording, since CWG will still be processing
C++17 topics in Oulu.

Jeffrey
Gabriel Dos Reis
2016-02-26 23:29:27 UTC
Permalink
Will do.

From: ub-***@open-std.org [mailto:ub-***@open-std.org] On Behalf Of Jeffrey Yasskin
Sent: Friday, February 26, 2016 2:45 PM
To: WG21 UB study group <***@open-std.org>
Subject: Re: [ub] launder and aliasing

On Fri, Feb 26, 2016 at 1:55 PM, Gabriel Dos Reis <***@microsoft.com<mailto:***@microsoft.com>> wrote:
| Gaby had a paper that would guarantee that memcpy can be used to reinterpret
| the bytes of an object of one type as a value of another type; that would seem
| to fit the bill here. And you can use that to build higher-level operations, such as
| this:

I was meaning to update that paper for Jacksonville, because it has to go in C++17, but life happened.
This that paper is the consensus of SG12 (as we reviewed it in Kona), I will make sure it gets honorable mention in Jacksonville and we have a full wording in the post-Jacksonville mailing for consideration for C++17 in Oulu.

Argh. Jacksonville is the last meeting to get things through EWG if they're going into C++17. Is it at all possible for you to get the paper into a state that EWG could approve, and get it onto Ville's agenda? If the existing paper if SG12's consensus, maybe that's enough. Post-Jacksonville should be fine for the full wording, since CWG will still be processing C++17 topics in Oulu.

Jeffrey
Ville Voutilainen
2016-02-26 22:45:19 UTC
Permalink
Post by Gabriel Dos Reis
| Gaby had a paper that would guarantee that memcpy can be used to reinterpret
| the bytes of an object of one type as a value of another type; that would seem
| to fit the bill here. And you can use that to build higher-level operations, such as
I was meaning to update that paper for Jacksonville, because it has to go in C++17, but life happened.
This that paper is the consensus of SG12 (as we reviewed it in Kona), I will make sure it gets honorable mention in Jacksonville and we have a full wording in the post-Jacksonville mailing for consideration for C++17 in Oulu.
What are you waiting for? Write that paper and come to EWG with it on
Tuesday morning. :)
Gabriel Dos Reis
2016-02-26 23:29:12 UTC
Permalink
| -----Original Message-----
| From: ub-***@open-std.org [mailto:ub-***@open-std.org] On
| Behalf Of Ville Voutilainen
| Sent: Friday, February 26, 2016 2:45 PM
| To: WG21 UB study group <***@open-std.org>
| Subject: Re: [ub] launder and aliasing
|
| On 26 February 2016 at 23:55, Gabriel Dos Reis <***@microsoft.com> wrote:
| > | Gaby had a paper that would guarantee that memcpy can be used to
| > | reinterpret the bytes of an object of one type as a value of another
| > | type; that would seem to fit the bill here. And you can use that to
| > | build higher-level operations, such as
| > | this:
| > I was meaning to update that paper for Jacksonville, because it has to go in
| C++17, but life happened.
| > This that paper is the consensus of SG12 (as we reviewed it in Kona), I will
| make sure it gets honorable mention in Jacksonville and we have a full
| wording in the post-Jacksonville mailing for consideration for C++17 in Oulu.
|
| What are you waiting for? Write that paper and come to EWG with it on
| Tuesday morning. :)

Yes, sir!

-- Gaby
Howard Hinnant
2016-02-26 22:06:41 UTC
Permalink
Post by Richard Smith
Gaby had a paper that would guarantee that memcpy can be used to
reinterpret the bytes of an object of one type as a value of another
type; that would seem to fit the bill here. And you can use that to
template<typename T, typename U> T *change_object_type(U *p) {
static_assert(sizeof(T) == sizeof(U));
static_assert(is_trivially_copyable_v<T> && is_trivially_copyable_v<U>);
char buffer[sizeof(T)];
memcpy(buffer, p, sizeof(T));
p->~U();
T *result = new (p) T;
memcpy(result, buffer, sizeof(T));
return result;
}
int foo(float f) {
int *i = change_object_type<int>(&f); // float is dead, long live the int!
return *i;
}
Adding this might be another nice check:

static_assert(alignof(T) <= alignof(U));

Howard
Robert Haberlach
2016-02-28 11:18:26 UTC
Permalink
I don't think that is the right interface for such functionality. No matter what you do with the pointer, if you create a situation where an int*
and a float* can simultaneously exist and both can be used to load or store the same memory, you destroy TBAA.
I see.

I take it that the placement new in your example is necessary to apply [basic.life]/7, as we wouldn't otherwise be able to obtain a pointer actually
pointing to the last created object of type T (or more fundamentally, a memcpy does not eo ipso create an object of a different type in the target
memory location). However, this requires default constructability. Is there a way to "inject" the new type without necessitating a default constructor
call?

IIRC there was a paper suggesting a construct that performs such vacuous "initialization" (demonstrated using __cookie__ as an initializer), but I
can't find it at the moment.

Also, I don't see the point of p->~U(). Isn't U::~U trivial by assumption?
Hubert Tong
2016-02-27 22:13:26 UTC
Permalink
Post by Robert Haberlach
Post by Robert Haberlach
I apologize in advance if this was discussed before; I didn't check
the entire archive. Consider
Post by Robert Haberlach
static_assert(alignof(float) >= alignof(int) && sizeof(float) >=
sizeof(int));
Post by Robert Haberlach
int foo(float f) {
return *std::launder(reinterpret_cast<int*>(&f)); }
As it stands, invocation of foo is undefined, as the argument to
launder is not pointing to an object of type int (or similar) within its
lifetime,
Post by Robert Haberlach
violating launder's requirement.
launder is designed to inhibit address propagation analysis, which
is the only concerning optimization. Moreover, as long as all usual
conditions are
Post by Robert Haberlach
met (alignment, size & trap values), this should be fine on any
implementation. If so, can we make the above formally well-defined?
Post by Robert Haberlach
This is not fine. Type-based aliasing analysis (TBAA) is allowed to
determine that the write to "f" and the read from the dereference is not
related.
Post by Robert Haberlach
int main(void) { return !foo(0.5f); }
int main(void) {
int __ret;
{
float __f = 0.5f;
__ret = *std::launder(reinterpret_cast<int *>(&__f));
}
return !__ret;
}
The implementation is allowed to observe that there are no
TBAA-compatible reads of the value of __f within its lifetime.
Isn't the whole idea that launder's definition is, to some extent,
intransparent to optimizers? We're passing a pointer to f into launder. As
long as
the analyser is told to assume that calls to launder perform what not, it
cannot elide the initialization, because launder could read.
The definition of launder is *not* opaque to optimizers. It is intended
that launder is well-known to not have side-effects.
Post by Robert Haberlach
Perhaps launder isn't the right function to base such functionality on,
anyway (I just had the impression it fitted the picture of an "opaque"
identity function well). Then my question would rather be if it is
sensible to provide some function (or language construct), e.g. taking a
glvalue
and returning one of a given type, s.t. aliasing via the returned glvalue
can be well-defined. I don't think there would be great technical obstacles
in adjusting aliasing analysers to treat such calls accordingly, but it
would allow for a concise way of disabling strict aliasing for an operation
while still benefiting from powerful optimization everywhere else.
Assuming you mean to use that glvalue directly (as opposed to forming a
pointer or reference binding) to perform said access, then such a construct
makes some sense to me.
Post by Robert Haberlach
Robert
Post by Robert Haberlach
The likely result is that
the initialization of __f would be optimized away.
-- HT
Being able to use launder in such scenarios would render
circumlocutions via memcpy superfluous.
Post by Robert Haberlach
Robert
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
_______________________________________________
ub mailing list
http://www.open-std.org/mailman/listinfo/ub
Gabriel Dos Reis
2016-02-26 17:01:29 UTC
Permalink
| -----Original Message-----
| From: ub-***@open-std.org [mailto:ub-***@open-std.org] On Behalf
| Of Robert Haberlach
| Sent: Friday, February 26, 2016 4:13 AM
| To: ***@open-std.org
| Subject: [ub] launder and aliasing
|
| I apologize in advance if this was discussed before; I didn't check the entire
| archive. Consider
|
| static_assert(alignof(float) >= alignof(int) && sizeof(float) >= sizeof(int));
| int foo(float f) {
| return *std::launder(reinterpret_cast<int*>(&f)); }
|
| As it stands, invocation of foo is undefined, as the argument to launder is not
| pointing to an object of type int (or similar) within its lifetime, violating launder's
| requirement.
|
| launder is designed to inhibit address propagation analysis, which is the only
| concerning optimization. Moreover, as long as all usual conditions are met
| (alignment, size & trap values), this should be fine on any implementation. If so,
| can we make the above formally well-defined?
| Being able to use launder in such scenarios would render circumlocutions via
| memcpy superfluous.
|
| Robert

No, that is not good idea. We should have simple and safe primitives to access and set the object representation of a variable, but std::launder isn't it.
It is tempting to kill two birds with one stone, this one will blow off the entire planet.

-- Gaby
Loading...