RFR: 8233073: Make BitMap accessors more memory ordering friendly

Kim Barrett kim.barrett at oracle.com
Tue Oct 29 01:54:16 UTC 2019


Should this review be happening on hotspot-dev rather than
hotspot-gc-dev?  GC is not the only BitMap client; compiler uses them
too (and generally rather differently).

> On Oct 28, 2019, at 11:29 AM, Erik Österlund <erik.osterlund at oracle.com> wrote:
> 
> Hi,
> 
> I have need for accessors on BitMap being more explicitly memory ordering aware, to fix a bug in ZGC for AArch64 (https://bugs.openjdk.java.net/browse/JDK-8233061).
> In particular, I need failed bit sets to still have acquire semantics, and I need a getter with acquire semantics.
> 
> My intention is to solve the problem by making the relevant BitMap accessors accept explicitly passed in memory ordering parameters, and utilize them. I draw the line of conservativeness at supporting IRIW-consistent loads. Having spent a great deal of time finding a single algorithm that breaks due to IRIW-consistency violations, and knowing the complexity of algorithms that actually can break due to that, I would be *very* surprised if we got anywhere close to that. Therefore, acquiring loads are the most conservative loads I support. This is explicitly stated in the API, so that anyone that actually relies on IRIW consistency in the future can reconsider that, and add a mode that fences before loads on nMCA machines.
> 
> The main points of controversy with this patch, where I expect people to have wildly different opinions and hopefully get at least a little bit upset are the following:
> 
> 1) For the same reason that our implementation of Atomic::cmpxchg does not supply both one ordering for success and one for failed CAS, unlike the C++11 atomic counter part, I do not do so either in the par_set_bit API. In the Atomic API, this was very much intentional, because it is tricky to reason about the subtle effects of having relaxed failed CAS and conservative success. In fact, it's a bug of precisely that nature I am chasing. Therefore, I wish to transfer that same reasoning to the par_set_bit API, and not allow passing in a weaker failing memory ordering. A consequence of this is that I have made the uses of this API more conservative for failed bit flips than it was in the past. However, this new API allows relaxing the real pain point of the API: the success case (with it's bi-directional full fencing semantics). So I expect it can be applied to make RMO architectures happier where it really matters in the end. However, I will not attempt to prove that relaxing these calls is okay in various places with this patch: that is outside of my scope, I'm merely adding API hooks for allowing that.
> 
> 2) The default strength on the getter is memory_order_relaxed and not memory_order_conservative. After looking at uses, I realize it's used mostly in single threaded contexts by compiler code, and there is seemingly only a single use in the VM that cares about having acquire (ZGC, and it's broken today). While letting the frequency of uses decide what is the default rather than what is safest is not something I would normally do, it does feel like since the norm is so vastly in favour of the relaxed variant, I don't want to let the one ZGC use case clutter half the VM with explicitly relaxing the load. I am okay with reverting that decision if people want me to.
> 
> CR:
> http://cr.openjdk.java.net/~eosterlund/8233073/webrev.00/
> 
> Bug:
> https://bugs.openjdk.java.net/browse/JDK-8233073
> 
> Thanks,
> /Erik

------------------------------------------------------------------------------
src/hotspot/share/utilities/bitMap.hpp 
207   bool at(idx_t index, atomic_memory_order memory_order = memory_order_relaxed) const;

My initial reaction here is that I'd prefer adding par_at() rather
than giving at() a memory order argument.  This would also address the
question of what the default should be.  For at(), it's nonatomic.
For par_at() it's acquire.

That would also avoid imposing volatile ordering on at().

As you said, existing uses of at() are relaxed/nonatomic.  The code
rearrangement for MarkBitMap::is_marked() makes me wonder if any of
the calls should be acquire ordered, but obviously none are now...

------------------------------------------------------------------------------
src/hotspot/share/utilities/bitMap.hpp 
205   // The memory ordering goes up to memory_order_acquire, but not higher. It is
206   // assumed that users of the BitMap API will never rely on IRIW consistency.

I think what this means is that memory_order_seq_cst
(memory_order_conservative in HotSpot) isn't supported? So just as we
only have Atomic::load (relaxed) and Atomic::load_acquire (acquire).
That seems okay. But if we aren't going to support the stronger
semantics, I don't think this should permit the corresponding memory
order value.

C++11 specifies that atomic load operations cannot have a memory order
of memory_order_release or memory_order_acq_rel. (Similarly, store
operations cannot have a memory order of memory_order_consume,
memory_order_acquire, or memory_order_acq_rel. That isn't relevant for
this change, as all the modifying operations here are RMW.)

So I think we should just be explicit that only relaxed and acquire
are supported here.  (And actually make that true; see next comment.)

------------------------------------------------------------------------------
src/hotspot/share/utilities/bitMap.inline.hpp 
 55 inline bool BitMap::at(idx_t index, atomic_memory_order memory_order) const {
...
 58   return (load_word_ordered(addr, memory_order) & bit_mask(index)) != 0;

This is using the load_word_ordered helper, but the behavior of that
function is designed to support the RMW operations, and I think isn't
really right for at() (see previous comment). The simplest solution to
get what I'm suggesting would be to add an assert here that the
memory_order is either relaxed or acquire.

------------------------------------------------------------------------------
src/hotspot/share/gc/shared/markBitMap.inline.hpp
71   return _bm.at(addr_to_offset(addr), memory_order_relaxed);

The memory order argument isn't needed with the current default, and
wouldn't even be permitted with the above suggestion add par_at.

------------------------------------------------------------------------------

I'm not understanding part of the problem description though.  You say

  ... have made the uses of this API more conservative for failed bit
  flips than it was in the past. 

But the pre-existing unordered cases in the setting functions (e.g.
don't go through cmpxchg) are those where the bit is already set to
the desired value, so there's no failure to change the bit involved.
It seems reasonable to me that an acquire (at least) is usually
desirable on that path, for reasons similar to why one wants an
acquire on the outside-the-lock test when using the Double Checked
Locking pattern. But that's not what was said, so I'm not sure I'm
understanding the point.

------------------------------------------------------------------------------

The par_xxx_range operations are not being directly modified by this
change. When only 1 bit is actually involved, they'll delegate to the
conservative single-bit operations, so are changed to pick up the
acquire on the already set to the desired value path. Otherwise, they
always go through conservative cmpxchg as before.  That all seems fine.

------------------------------------------------------------------------------




More information about the hotspot-gc-dev mailing list