
I suspect it's not by design, because there's certainly plans to make them inline primops, and the reordering issue of the cmm optimizer hasn't come up in the design discussion previously. (And I should add those notes to the associated tickets) On Tuesday, December 31, 2013, Edward Z. Yang wrote:
I was thinking about my response, and realized there was one major misleading thing in my description. The load reordering I described applies to load instructions in C-- proper, i.e. things that show up in the C-- dup as:
W_ x = I64[...addr...]
Reads to IORefs and reads to vectors get compiled inline (as they eventually translate into inline primops), so my admonitions are applicable.
However, the story with *foreign primops* (which is how loadLoadBarrier in atomic-primops is defined, how you might imagine defining a custom read function as a primop) is a little different. First, what does a call to an foreign primop compile into? It is *not* inlined, so it will eventually get compiled into a jump (this could be a problem if you're really trying to squeeze out performance!) Second, the optimizer is a bit more conservative when it comes to primop calls (internally referred to as "unsafe foreign calls"); at the moment, the optimizer assumes these foreign calls clobber heap memory, so we *automatically* will not push loads/stores beyond this boundary. (NB: We reserve the right to change this in the future!)
This is probably why atomic-primops, as it is written today, seems to work OK, even in the presence of the optimizer. But I also have a hard time believing it gives the speedups you want, due to the current design. (CC'd Ryan Newton, because I would love to be wrong here, and maybe he can correct me on this note.)
Cheers, Edward
P.S. loadLoadBarrier compiles to a no-op on x86 architectures, but because it's not inlined I think you will still end up with a jump (LLVM might be able to eliminate it).
Hi Edward,
Thanks very much for this reply, it answers a lot of questions I'd had. I'd hoped that ordering would be preserved through C--, but c'est la vie. Optimizing compilers are ever the bane of concurrent algorithms!
stg/SMP.h does define a loadLoadBarrier, which is exposed in Ryan Newton's atomic-primops package. From the docs, I think that's a general read barrier, and should do what I want. Assuming it works properly, of course. If I'm lucky it might even be optimized out.
Thanks, John
On Mon, Dec 30, 2013 at 6:04 AM, Edward Z. Yang
wrote: Hello John,
Here are some prior discussions (which I will attempt to summarize below):
http://www.haskell.org/pipermail/haskell-cafe/2011-May/091878.html
http://www.haskell.org/pipermail/haskell-prime/2006-April/001237.html
http://www.haskell.org/pipermail/haskell-prime/2006-March/001079.html
The guarantees that Haskell and GHC give in this area are hand-wavy at best; at the moment, I don't think Haskell or GHC have a formal memory model—this seems to be an open research problem. (Unfortunately, AFAICT all the researchers working on relaxed memory models have their hands full with things like C++ :-)
If you want to go ahead and build something that /just/ works for a /specific version/ of GHC, you will need to answer this question separately for every phase of the compiler. For Core and STG, monads will preserve ordering, so there is no trouble. However, for C--, we will almost certainly apply optimizations which reorder reads (look at CmmSink.hs). To properly support your algorithm, you will have to add some new read barrier mach-ops, and teach the optimizer to respect
(This could be fiendishly subtle; it might be better to give C-- a memory model first.) These mach-ops would then translate into appropriate arch-specific assembly or LLVM instructions, preserving the guarantees further.
This is not related to your original question, but the situation is a bit better with regards to reordering stores: we have a WriteBarrier MachOp, which in principle, prevents store reordering. In practice, we don't seem to actually have any C-- optimizations that reorder stores. So, at least you can assume these will work OK!
Hope this helps (and is not too inaccurate), Edward
Excerpts from John Lato's message of 2013-12-20 09:36:11 +0800:
Hello,
I'm working on a lock-free algorithm that's meant to be used in a concurrent setting, and I've run into a possible issue.
The crux of the matter is that a particular function needs to
following:
x <- MVector.read vec ix position <- readIORef posRef
and the algorithm is only safe if these two reads are not reordered (both the vector and IORef are written to by other threads).
My concern is, according to standard Haskell semantics this should be safe, as IO sequencing should guarantee that the reads happen in-order. Of course this also relies upon the architecture's memory model, but x86 also guarantees that reads happen in order. However doubts remain; I do not have confidence that the code generator will handle this properly. In particular, LLVM may freely re-order loads of NotAtomic and Unordered values.
The one hope I have is that ghc will preserve IO semantics through
Excerpts from John Lato's message of 2013-12-31 03:01:58 +0800: them. perform the the
entire pipeline. This see_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org javascript:; http://www.haskell.org/mailman/listinfo/glasgow-haskell-users