[cfe-dev] Can indirect class parameters be noalias?

John McCall via cfe-dev cfe-dev at lists.llvm.org
Wed Jul 29 19:00:41 PDT 2020


On 29 Jul 2020, at 17:42, Richard Smith wrote:
> On Wed, 29 Jul 2020 at 12:52, John McCall <rjmccall at apple.com> 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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200729/013054ba/attachment-0001.html>


More information about the cfe-dev mailing list