RFR: 8189871: Refactor GC barriers to use declarative semantics

Erik Österlund erik.osterlund at oracle.com
Wed Nov 15 16:42:48 UTC 2017


Hi David,

Thank you for the review.

On 2017-11-15 08:47, David Holmes wrote:
> Hi Erik,
>
> I really like the level of abstraction and encapsulation this provides.

Glad to hear it!

> Can't comment on the GC specific details or the template mechanics 
> directly, of course. :)
>
> A couple of comments:
>
> src/hotspot/share/oops/klass.hpp
>
>  412   // Is an oop/narrowOop null or subtype of this Klass?
>  413   template <typename T>
>  414   bool is_covariant(T element);
>
> I find "is_covariant" a very obscure way to name this. It may be 
> academically accurate but it's really just asking if the element is of 
> a type that is a subclass of the current klass. The null handling 
> complicates it, but it seems to me that:
>
> template <typename T>
> bool Klass::is_instanceof_or_null(T element);
>
> would be more consistent with how we normally refer to things in the 
> VM (though the _or_null can be dropped from the name).

Hmm, I see your point. I have renamed covariant/contravariant 
accordingly to fit better into our current notions.

The ARRAYCOPY_CONTRAVARIANT decorator has been renamed ARRAYCOPY_CHECKCAST.
The is_covariant check has been renamed is_instanceof_or_null as you 
proposed.
The covariant_bound() method has been renamed to element_klass().

> ---
>
> src/hotspot/share/oops/objArrayOop.cpp
>
> Klass* objArrayOopDesc::covariant_bound()
>
> There's that word again. :) If you really think you need to use 
> covariance within these API's you really need to add some comments to 
> the method declarations to explain them. Most of us probably have a 
> minimal recollection of covariance and contravariance from discussing 
> type-safety for method parameters and return types. :)

Fixed as mentioned above.

>
> ---
>
> src/hotspot/share/prims/unsafe.cpp
>
> The changes from jobjects to oops made me uneasy, but I'm assuming the 
> places where MemoryAccess and GuardedMemoryAccess are used are 
> affectively all leave routines with no chance of hitting anything that 
> would respond to a safepoint request?

Yes, that is correct. There are no thread transitions in those paths.

Here is a new full webrev:
http://cr.openjdk.java.net/~eosterlund/8189871/webrev.01/

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

Thanks,
/Erik

> Thanks,
> David
> -----
>
> On 10/11/2017 3:00 AM, Erik Österlund wrote:
>> 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