C++ Logo

SG12

Advanced search

Subject: Re: [ub] launder and aliasing
From: Richard Smith (richardsmith_at_[hidden])
Date: 2016-02-26 15:46:26


On 26 February 2016 at 10:03, Robert Haberlach <rh633_at_[hidden]> wrote:
> On 02/26/2016 02:26 PM, Hubert Tong wrote:
>> On Fri, Feb 26, 2016 at 7:12 AM, Robert Haberlach <rh633_at_[hidden] <mailto:rh633_at_[hidden]>> wrote:
>>
>> 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.
>
> 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.

> 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.).

> Robert
>
>> 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
>> ub_at_[hidden] <mailto:ub_at_[hidden]>
>> http://www.open-std.org/mailman/listinfo/ub
>>
>>
>>
>>
>> _______________________________________________
>> ub mailing list
>> ub_at_[hidden]
>> http://www.open-std.org/mailman/listinfo/ub
>>
> _______________________________________________
> ub mailing list
> ub_at_[hidden]
> http://www.open-std.org/mailman/listinfo/ub


SG12 list run by sg12-owner@lists.isocpp.org