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

Hal Finkel via cfe-dev cfe-dev at lists.llvm.org
Mon Aug 3 20:28:38 PDT 2020


On 8/3/20 8:45 PM, John McCall wrote:
>
> On 31 Jul 2020, at 19:50, Hal Finkel wrote:
>
>     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>
>         <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).
>
> It does seem hard to argue that this is invalid under the 
> specification. To me it seems like it clearly /ought/ to be invalid, 
> though. Note that Clang has always emitted return address arguments as 
> |noalias|, so this has immediate significance.
>
> If I were writing the specification, I would rewrite the restriction 
> in |[class.cdtor]p2| to say that pointers derived by naming a 
> returned/constructed object do not formally point to the object until 
> the function actually returns, even if the copy is elided. That would 
> make James’s example undefined behavior.
>
> John.
>

I agree. It seems like we should be able to make a sanitizer detect this 
kind of mistake as well (although the general case will require some 
msan-like propagation scheme).

  -Hal


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

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200803/59863508/attachment-0001.html>


More information about the cfe-dev mailing list