Post by Robert HaberlachPost by Robert HaberlachI 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 HaberlachPerhaps 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 HaberlachRobert
Post by Robert HaberlachThe 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