On 3/7/20 6:04 AM, Chris Hall wrote:
On 06/03/2020 17:45, Jonathan Wakely wrote:
On Fri, 6 Mar 2020 at 16:17, Chris Hall <gcc@xxxxxxx> wrote:
Sure. But the Standard atomic_fetch_add() takes an _Atomic(xxx)* (as
the first parameter), and for the reasons given, I understand that
uint64_t* is not compatible with _Atomic(uint64_t)*.
I don't think GCC is treating them as though they are compatible. It
just accepts a broader range of types than only _Atomic ones, and
does the right thing for them all (apart from pointers where the
arithmetic is wrong).
FWIW: clang gets this right, and where the Standard says a parameter
must be an _Atomic(foo_t)* [for a standard atomic_xxx()], clang rejects
foo_t* arguments.
It's not clear to me that C actually requires it to be rejected, or if
it's just undefined (in which case GCC's decision to accept it and do
the obvious thing is OK).
The original outcome when atomics were first introduced in C as pure
library types (i.e., opaque structs -- see N1284) would have been
that these incompatibilities would have unavoidably been rejected.
But since then atomics morphed into first class language constructs
as have the atomic library APIs (see N1485). They now require
compiler support in the form of what are essentially function templates
that accepts arguments of unlimited (if not unconstrained) types. In
view of that, one could argue that an implementation is conforming (and
useful) that accepts any types as arguments to the atomic APIs. I don't
know if the choice of making the atomic APIs unconstrained in GCC was
deliberate but going back on that decision now, either in GCC, or by
trying to enforce it in the standard, after they've been out there for
a decade, seems risky. This is a bit of a mess, in large part because
the integration of the specification for first-class atomic types into
C11 was less than perfect.
Some of this background is discussed in my article
https://developers.redhat.com/blog/2016/01/14/toward-a-better-use-of-c11-atomics-part-1/
The subtlety of the distinction between "only defined to accept" and
"defined to only accept" had escaped me for a moment :-(
Of course, for all the machines I know well enough I can say that there
is no difference between, inter alia, uint64_t and _Atomic(uint64_t). So
what I have observed GCC doing is, arguably, helpful.
For all I know, GCC may be equally helpful on machines where uint64_t
and _Atomic(uint64_t) are not the same -- and issue some form of
diagnostic. Hopefully an error, rather than a
-Wincompatible-pointer-types warning, since a warning would (probably?)
not prevent the resulting code from crashing and burning.
If I write:
uint64_t px(uint64_t* p) { return *p ; }
char m[] = "There's glory for you!" ;
printf("%lu", px(m+9)) ;
GCC chips in a -Wincompatible-pointer-types (if suitably prompted...
-Wall ?).
Now, if I make a conscious decision to ignore all machines which may
crash or otherwise misbehave for unaligned uint64_t reads, I can cast
the m+9 -- and anything I then get is my fault.
I wonder if, by extension, the atomic_xxx() should (at least) warn when
the _Atomic() parameters are passed not-_Atomic() arguments ?
I think it would be useful to expose a command-line option in GCC
to request a diagnostic for uses of the APIs with non-atomic types.
Making it an error would probably not be a great solution because
of the risk of breaking working code that has come to rely on it.
Martin
As I have said elsewhere, I think the real problem is that the Standard
fails to define vital things as Implementation Defined, leaving the
programmer guessing as to what their code is going to do when compiled
for some machine/system they are not familiar with.
I note
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4013.html --
courtesy of Andrew Haley -- suggests new facilities to check at compile
time specifically for this case. IMO there should also be facilities to
check for "wait-free" -- unless "lock-free" already does that job... but
that's another story.
Chris