[cfe-dev] Can indirect class parameters be noalias?
Hal Finkel via cfe-dev
cfe-dev at lists.llvm.org
Fri Jul 31 16:50:01 PDT 2020
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).
-Hal
>
> On Fri, Jul 31, 2020 at 2:27 PM Hal Finkel via cfe-dev
> <cfe-dev at lists.llvm.org <mailto:cfe-dev at lists.llvm.org>> 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
>>> <rjmccall at apple.com> <mailto:rjmccall at apple.com> 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
>>> cfe-dev at lists.llvm.org <mailto:cfe-dev at lists.llvm.org>
>>> 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
> cfe-dev at lists.llvm.org <mailto:cfe-dev at lists.llvm.org>
> 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200731/770a55c3/attachment-0001.html>
More information about the cfe-dev
mailing list