This is the mail archive of the
mailing list for the glibc project.
Re: [MTASCsft PATCH WIP5 01/33] Multi Thread, Async Signal and Async Cancel safety documentation: intro
- From: Alexandre Oliva <aoliva at redhat dot com>
- To: Torvald Riegel <triegel at redhat dot com>
- Cc: libc-alpha at sourceware dot org, carlos at redhat dot com, mtk dot manpages at gmail dot com
- Date: Sun, 01 Dec 2013 02:46:05 -0200
- Subject: Re: [MTASCsft PATCH WIP5 01/33] Multi Thread, Async Signal and Async Cancel safety documentation: intro
- Authentication-results: sourceware.org; auth=none
- References: <20131113081059 dot 3464 dot 51385 dot stgit at frit dot home> <20131113081132 dot 3464 dot 30409 dot stgit at frit dot home> <1384859432 dot 32326 dot 364 dot camel at triegel dot csb> <orsiurva0g dot fsf at livre dot home> <1384956325 dot 3152 dot 591 dot camel at triegel dot csb> <orhab6t6m8 dot fsf at livre dot home> <1385051174 dot 3152 dot 1637 dot camel at triegel dot csb> <orvbzksz47 dot fsf at livre dot home> <1385409288 dot 3152 dot 3539 dot camel at triegel dot csb> <or8uwaom88 dot fsf at livre dot home> <1385568632 dot 3152 dot 6871 dot camel at triegel dot csb> <orsiuhn0xl dot fsf at livre dot home> <1385656676 dot 3152 dot 9077 dot camel at triegel dot csb>
On Nov 28, 2013, Torvald Riegel <email@example.com> wrote:
>> I'm not familiar with any hw memory model in which an unsynchronized
>> single-word read could actually read garbage, rather than a value that
>> may be outdated.
> First, this is about compilers too, not just the HW. The only
> counter-example that comes to my mind right now is self-modifying code
> on x86.
You mean inconsistency between instruction and data caches? Regardless,
(and bearing in mind this is in no way related with the feof_unlocked
discussion at hand) I'm curious as to how a single word load could read
garbage rather than earlier or newer contents of the accessed memory
location. This claim just doesn't fit the hw memory models I'm familiar
>> > The compiler can reload from memory,
>> It can't. It's only one access.
> It can. Why could it not?
Because compilers don't go about making useless accesses to memory.
Given a function like:
extern int foo;
why would the compiler load more than once from the same memory
location? Even if it did, it's still taking only one bit out of it, and
that will necessarily be taken from only one of the loads, so any other
load is not only excessive, it's useless and irrelevant.
> If it can figure aliasing out and the assumed sequential code doesn't
> write to the memory location between the load and the reload, it can
> assume that nobody else modified it, so reload from memory.
So now you're talking of multiple calls of bar(). Each one *must*
reload from memory. That's expected, and correct. Nobody's expecting
the same value needs to be returned, even if the thread doesn't modify
foo itself. In the bit tests we're discussing, it does not matter.
>> > It could decide to split a 64b load into two 32b loads.
>> It can't, it's a single word load.
> Why not (unless volatile/asm)? The compiler only has to make sure that
> the outcome is equivalent to what the abstract machine would do. Sure
> it can do the split if that's supported
Even if it does the pointless split, it would *still* get the correct
answer, because it's only looking at one bit, and that one bit, even if
possibly loaded multiple times, will only be used once out of these
multiple times to compute the result of the bitmasking.
Even if another thread modifies the word, the absence of synchronization
makes both the previous and the modified values suitable, because
there's no predetermined happens-before relationship. If there was some
form of synchronization and exclusive access, then it would rule out one
of the otherwise-acceptable results.
>> > There were GCC bugs in this area that were revealed by fuzz testing, so
>> > I'm not making that up.
>> That would be a compiler bug. Compiler bugs can cause misbehavior.
> Those bugs were cases in which the compiler did stuff that's perfectly
> fine in sequential code.
Loading twice from a word accessed only once would still be a bug:
there's no excuse for the compiler to generate such obviously wasteful
>> >> sin() doesn't have synchronization either, and it's perfectly MT-Safe.
>> >> Why should it synchronize with any other threads?
>> > Your argument is backwards. You say that there can be MT-Safe functions
>> > that don't need shared-memory synchronization because they don't access
>> > shared memory. Fine. But how does that imply that functions that use
>> > shared memory don't need shared-memory synchronization?
>> It doesn't. A single counter-example is enough to disprove what
>> purports to be a general statement.
> Exactly, it doesn't. So *your* claim that generally, MT-Safe functions
> don't need to synchronize is wrong.
I didn't make such a claim.
I claimed there was no requirement in POSIX that all functions perform
synchronization. That much is true. If it weren't so, even functions
that did not access shared memory would have to synchronize. Such a
wasteful requirement is not there. The example I gave above is an
obvious example in which no synchronization whatsoever is mandated,
which is enough to disprove your implied claim that a function marked
MT-Safe has synchronization, which is what I was responding to when I
mentioned sin() above. Here's the earlier passage in the thread:
>> >> > how should a user expect that feof doesn't have synchronization
>> >> > [...]? It's marked MT-Safe.
>> What begs proving is whether POSIX mandates synchronization for any
>> case. It offers synchronization operations and mutexes as examples of
>> what an implementation might internally use to achieve safety, but the
>> only interfaces for which synchronization is mandatory are the ones used
>> for synchronization (mutexes, locks and condition variables)
> In the choices I outlined above, you can certainly make the choice that
> once more than one thread accesses the same virtually shared state, they
> essentially "fork" the shared state into per-thread state, and don't
> synchronize on it ever more.
Or fully synchronize on the next synchronization operation, or partially
at any point in time between the accesses and the full synchronization.
Anyway, if they ever diverged in any perceptible way, there must have
been a data race => undefined behavior. That's the POSIX memory model
in a nutshell for you.
I guess we could duplicate in our manual these facts that are so clearly
stated in POSIX. But should we? Should we duplicate from the C
Standard the chapter about the various other ways to trigger undefined
behavior? How about the C*11 memory models, or the memory models of the
various kinds of hardware we target?
>> You know what? Maybe you're an expert in concurrency and memory models,
>> and that twists your view of MT-Safe under the looks-like-a-nail
>> principle. This possibility appears to fit the obsevable behavior.
> You do know what memory models are built for, don't you? They define
> allowed behavior under multi-threaded executions. Why would MT-Safe not
> be a subset of this topic?
There's the nail again! :-)
Why would it be a *subset*, rather than a vaguely related property that
may be implemented/constructed on *top* of abstractions and
generalizations of multiple different memory models, and exposed to its
users as even simpler and perhaps even abstractions that are apparently
unrelated to memory modules?
What if it your view was upside-down, and the MT-Safe notion came first,
and memory models compatible with it were specified years, even decates
later? Oh, wait! :-)
Alexandre Oliva, freedom fighter http://FSFLA.org/~lxoliva/
You must be the change you wish to see in the world. -- Gandhi
Be Free! -- http://FSFLA.org/ FSF Latin America board member
Free Software Evangelist Red Hat Brazil Compiler Engineer