RFR: 8189871: Refactor GC barriers to use declarative semantics

Erik Österlund erik.osterlund at oracle.com
Thu Nov 9 17:00:03 UTC 2017


Hi,

In an effort to remove explicit calls to GC barriers (and other 
orthogonal forms of barriers, like encoding/decoding oops for compressed 
oops and fencing for memory ordering), I have built an API that I call 
"Access". Its purpose is to perform accesses with declarative semantics, 
to handle multiple orthogonal concerns that affect how an access is 
performed, including memory ordering, compressed oops, GC barriers for 
marking, reference strength, etc, and as a result making GCs more 
modular, and as a result allow new concurrently compacting GC schemes 
utilizing load barriers to live in harmony in hotspot without everyone 
going crazy manually inserting barriers if UseBlahGC is enabled.

CR:
https://bugs.openjdk.java.net/browse/JDK-8189871

Webrev:
http://cr.openjdk.java.net/~eosterlund/8189871/webrev.00/

So there are three views of this I suppose:

1) The frontend: how this is actually used in shared code
2) The backends: how anyone writing a GC sticks their required barriers 
in there
3) The internals: how accesses find their way from the frontend to the 
corresponding backend

== Frontend ==

Let's start with the frontend. I hope I made this fairly simple! You can 
find it in runtime/access.hpp
Each access annotates its declarative semantics with a set of 
"decorators", which is the name of the attributes/properties affecting 
how an access is performed.
There is an Access<decorator> API that makes the declarative semantics 
possible.

For example, if I want to perform a load acquire of an oop in the heap 
that has "weak" strength, I would do something like:
oop result = Access<MO_ACQUIRE | IN_HEAP | 
ON_WEAK_OOP_REF>::oop_load_at(obj, offset);

The Access API would then send the access through some GC backend, that 
overrides the whole access and tells it to perform a "raw" load acquire, 
and then possibly keep it alive if necessary (G1 SATB enqueue barriers).

To make life easier, there are some helpers for the most common access 
patterns that merely add some default decorator for the involved type of 
access. For example, there is a RawAccess for performing AS_RAW accesses 
(that bypasses runtime checks and GC barriers), HeapAccess sets the 
IN_HEAP decorator and RootAccess sets the IN_ROOT decorator for 
accessing root oops. So for the previous call, I could simply do:

oop result = HeapAccess<MO_ACQUIRE | ON_WEAK_OOP_REF>::oop_load_at(obj, 
offset);

The access.hpp file introduces each decorator (belonging to some 
category) with an explanation what it is for. It also introduces all 
operations you can make with access (loads, stores, cmpxchg, xchg, 
arraycopy and clone).

This changeset mostly introduces the Access API but is not complete in 
annotating the code more than where it gets very awkward if I don't.

== Backend ==

For a GC maintainer, the BarrierSet::AccessBarrier is the top level 
backend that provides basic accesses that may be overridden. By default, 
it just performs raw accesses without any GC barriers, that handle 
things like compressed oops and memory ordering only. The ModRef barrier 
set introduces the notion of pre/post write barriers, that can be 
overridden for each GC. The CardTableModRef barrier set overrides the 
post write barrier to mark cards, and G1 overrides it to mark cards 
slightly differently and do some SATB enqueueing. G1 also overrides 
loads to see if we need to perform SATB enqueue on weak references.

The raw accesses go to the RawAccessBarrier (living in 
accessBackend.hpp) that performs the actual accesses. It connects to 
Atomic and OrderAccess for accesses that require that.

== Internals ==

Internally, the accesses go through a number of stages in 
access.inline.hpp as documented at the top.

1) set default decorators and get rid of CV qualifiers etc. Sanity 
checking also happens here: we check that the decorators make sense for 
the access being performed, and that the passed in types are not bogus.
2) reduce types so if we have a different type of the address and value, 
then either it is not allowed or it implies we use compressed oops and 
remember that we know something about whether compressed oops are used 
or not, before erasing address type
3) pre-runtime dispatch: figure out if all runtime checks can be 
bypassed into a raw access
4) runtime dispatch: send the access through a function pointer that 
upon the first invocation resolves the intended GC AccessBarrier 
accessor on the BarrierSet that handles this access, as well as figures 
out whether we are using compressed oops or not while we are at it, and 
then calls it through the post-runtime dispatch
5) post-runtime dispatch: fix some erased types that were not known at 
compile time such as whether the address is a narrowOop* or oop* 
depending on whether compressed oops was selected at runtime or not, and 
call the resolved BarrierSet::AccessBarrier accessor (load/store/etc) 
with all the call-site build-time and run-time resolved decorators and 
type information that describes the access.

Testing: mach5 tier1-5

Thanks,
/Erik


More information about the hotspot-dev mailing list