RFR (M): 8184334: Generalizing Atomic with templates

Andrew Haley aph at redhat.com
Wed Jul 26 13:22:37 UTC 2017


I've been sketching out an alternative way to handle this.  I've got
my flak jacket ready, so here goes...

My goals are:

  Simplicity; with this comes, hopefully, clarity.  Within reason, we
  should minimize the extra code this brings in.

  Type safety where possible.  I'm aware that we compile HotSpot with
  -fno-strict-aliasing or its equivalent, but I think we can do this
  job without relying on that.  The more information we expose to the
  compiler, the better code it can generate.

  Something that plays nicely with compiler builtins, where
  appropriate.  It must also be able to handle legacy
  assembly-language definitions.

[ Correctness, hopefully, goes without saying. ]

I've done all the work on cmpxchg, because that is the most complex of
the operations: I believe that if cmpxchg can work well, the rest will
be relatively simple.

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

We should expose the type information of the original operands all the
way to the back ends, and not cast pointer types if we can possibly
avoid it.  So, in this design every back end defines a template function
to match this declaration:

  template <typename T, typename U, typename V>
  inline static U cmpxchg(T exchange_value, volatile U* dest, V compare_value,
                          cmpxchg_memory_order order = memory_order_conservative);

[ Note: the types of the operand, the compare value, and the exchange
value are distinct.  This is a design decision I took from Erik's
patch, but I don't like it: it seems to me that we should insist on
the same types for the operands.  This requires a few changes in
HotSpot, but arguably these are bugs anyway.  I tried this, and had to
fix eight places, all trivial.  We should do it as a cleanup. ]

First, the simplest possible case.  In some modern GCC targets (e.g
Aarch64) we can trivially define our generic cmpxchg using a
compiler builtin, like so:

template <typename T, typename U, typename V>
inline U Atomic::cmpxchg(T exchange_value, volatile U* dest, V compare_value,
                         cmpxchg_memory_order order) {
  return __sync_val_compare_and_swap(dest, compare_value, exchange_value);
}

That's everything we need, although I'd do some optimization for
the memory_order.

With older targets and those where we need to support assembly
language, we'll have to use some template magic to select an
appropriate assembly fragment:

template <typename T, typename U, typename V>
inline U Atomic::cmpxchg(T exchange_value, volatile U* dest, V compare_value,
                         cmpxchg_memory_order order) {
  return CmpxchgHelper<T, U, V, sizeof (U)>()(exchange_value, dest,
                                              compare_value, order);
}

Here's one such fragment, for the 64-bit case:

template <typename T, typename U, typename V>
struct Atomic::CmpxchgHelper<T, U, V, 8> {
  U operator()(T exchange_value, volatile U* dest, V compare_value,
               cmpxchg_memory_order order) {
    U result;
    __asm__ __volatile__ ("lock cmpxchgq %1,(%3)"
                          : "=a" (result)
                          : "r" ((U)exchange_value), "a" ((U)compare_value), "r" (dest)
                          : "cc", "memory");
    return result;
  }
};

The other cases are very similar, and I won't describe them here.

Note that we're using plain casts to convert the compare and exchange
values: this allows trivial cases such as extending a plain integer
constant to a 64-bit type.

[ Note: The new Helper Classes are necessary because the antique
version of C++ we use to build HotSpot doesn't permit the partial
specialization of templates.  This is merely annoying syntax and
doesn't affect the generated code. ]

Where there are back-end functions that cannot be genericized in this
way (perhaps because they always take a void * or an intptr_t *) we
can do whatever casting is necessary in the back-end fragments.  There
doesn't need to be any casting in the shared code.

This proposal does not handle floating-point operands because none are
currently needed, but I believe that the correct way to do it involves
explicit specializations for the types double and float.  These can be
in the shared code in atomic.h if we ever need them.

[ Note: Supporting floating-point operands is problematic because it's
hard to get the types right.  If T, U, and V can all be different
types then we need to know exactly what is meant by cmpxchg() where
the object in memory is an integer type and the compare or exchange
values are of floating-point type.  IMO, we should reject nonsense
like that at compile time.  Forcing T, U, and V to be the same type
allows us to do that. ]

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

This solution doesn't require any new helper classes.  It does,
however, require changes to the back ends which convert methods to
helper classes.  This is additional work now, but the result IMO will
be simpler, easier to maintain, and more future proof.

Do you know of any operating systems, compilers, or targets where this
won't work?  Or of any particular difficulty doing this?  I know we'll
have to get buy-in from the back end maintainers, but right now in the
JDK 10 release cycle is the time to get things like this done.

-- 
Andrew Haley
Java Platform Lead Engineer
Red Hat UK Ltd. <https://www.redhat.com>
EAC8 43EB D3EF DB98 CC77 2FAD A5CD 6035 332F A671


More information about the hotspot-runtime-dev mailing list