https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122483

--- Comment #20 from Richard Sandiford <rsandifo at gcc dot gnu.org> ---
(In reply to H.J. Lu from comment #17)
> > Code like:
> > 
> >   void bar(void);
> >   void (*foo)(void) [[arm::streaming_compatible]] = bar;
> > 
> > is a hard error [https://godbolt.org/z/b7GTo6nPx], so it doesn't IMO make
> > sense for:
> > 
> >   void bar(void);
> > 
> > to be treated as a redeclaration of:
> > 
> >   void bar(void) [[arm::streaming_compatible]];
> > [...]
> 
> For x86, I got
> 
> [hjl@gnu-tgl-3 gcc]$ cat x.c 
> extern void bar(void);
> void (*foo)(void) __attribute__ ((preserve_none)) = bar;
> [hjl@gnu-tgl-3 gcc]$ ./xgcc -B./ -S -O2 x.c 
> x.c:2:53: error: initialization of ‘void (__attribute__((preserve_none))
> *)(void)’ from incompatible pointer type ‘void (*)(void)’
> [-Wincompatible-pointer-types]
>     2 | void (*foo)(void) __attribute__ ((preserve_none)) = bar;
>       |                                                     ^~~
> x.c:1:13: note: ‘bar’ declared here
>     1 | extern void bar(void);
>       |             ^~~
> [hjl@gnu-tgl-3 gcc]$ 
> 
> since ix86_comp_type_attributes returns 0:
> 
>   if (ix86_type_no_callee_saved_registers_p (type1)
>       != ix86_type_no_callee_saved_registers_p (type2))
>     return 0;
> 
> Does aarch64_comp_type_attributes check [[arm::streaming_compatible]]?
Yeah, it does.  That was my point :)  We do still get the required error for
the assignment (see the link above).  But that means that the types are
distinct, so it seems wrong to allow the same function to be declared with two
different types.

In response to the later comments: I'm not saying that anything else has to
behave like the SVE ACLE attributes.  I'm just asking that we don't regress the
SVE ACLE attributes as part of other changes.

I suppose the rationale for merging:

void sc_a () [[arm::streaming_compatible]];
void sc_a ();

is that the absence of an attribute is the absence of a choice.  But that isn't
true for SVE ACLE function types.  Every function type is classified as either
"normal", "streaming", or "streaming compatible".  For obvious reasons, we
didn't retrospectively require all "normal" functions to be annotated in a
special way.  That would have been completely impractical.  Instead, the
absence of an annotation indicates a normal function.

But the point is that a choice is always being made.  The choice is silent for
normal functions and noisy for streaming and streaming-compatible functions. 
But there's a choice nonetheless.  There is no concept of an "uncertain",
"non-commital" or "deferential" type.

I suppose another way of viewing it is that, in an SVE context:

  void f(void);

should be read as equivalent to:

  void f(void) [[arm::nonstreaming]];

for something hypothetical attribute [[arm::nonstreaming]].  The error is then
complaining about the conflict between "arm::nonstreaming" and
arm::streaming_compatible.

Another argument for why it's important is that the two types can be
distinguished by _Generic and by C++ overloading.  Consider:

-----------------------------------------------------------------------------
extern void foo(void) [[arm::streaming_compatible]];
extern void bar(void);

#define CLASSIFY(X) (_Generic(X, void (*)(void) [[arm::streaming_compatible]]:
1, void (*)(void): 2, default: 3 ))

int foo_class = CLASSIFY(foo);
int bar_class = CLASSIFY(bar);
-----------------------------------------------------------------------------

This (correctly) sets foo_class to 1 and bar_class to 2
[https://godbolt.org/z/qGn3PveT9].  Similarly, in C++:

-----------------------------------------------------------------------------
void foo(void) [[arm::streaming_compatible]];
void bar(void);

constexpr int classify(void (*)(void) [[arm::streaming_compatible]]) { return
1; }
constexpr int classify(void (*)(void)) { return 2; }

int foo_class = classify(foo);
int bar_class = classify(bar);
-----------------------------------------------------------------------------

[https://godbolt.org/z/P6YTqWEWb].  Templates instantiated with one type are
distinct from templates instantiated with the other.  (Note that G++ doesn't
yet implement the mangling correctly AFAIK, but Clang does.)

So this isn't just something that affects the formal type system (the errors
above) and implementation details (the ABI).  It affects the abstract machine
as well.

As far as Clang comparisons go, Clang gets the SVE ACLE behaviour right in both
C [https://godbolt.org/z/h4YnseMbP] and C++ [https://godbolt.org/z/4sz3e8K5W]. 
The code in both cases is:

  void foo(void) __arm_streaming_compatible;
  void foo(void);

As can be seem from this, the ACLE syntax is actually:

  __arm_streaming_compatible

rather than:

  [[arm::streaming_compatible]]

to cope with concerns about using [[...]] attributes for things that affect
behaviour/correctness.  In GCC, the former is simply a macro that expands to
the latter.

Reply via email to