RFR: 8189871: Refactor GC barriers to use declarative semantics
Erik Österlund
erik.osterlund at oracle.com
Wed Nov 15 16:55:38 UTC 2017
Hi Coleen,
On 2017-11-14 02:34, coleen.phillimore at oracle.com wrote:
>
> http://cr.openjdk.java.net/~eosterlund/8189871/webrev.00/src/hotspot/share/classfile/javaClasses.cpp.udiff.html
>
>
> + assert(!is_reference ||
> InstanceKlass::cast(obj->klass())->is_subclass_of(SystemDictionary::Reference_klass()),
> "sanity");
>
>
> Can you do something like this instead of all the InstanceKlass::cast.
>
> + InstanceKlass* k = InstanceKlass::cast(obj->klass());
> + bool is_reference = k->reference_type() != REF_NONE;
> + assert(!is_reference ||
> k->is_subclass_of(SystemDictionary::Reference_klass()), "sanity");
> + return is_reference;
> +}
>
Yes, sure. Fixed in latest webrev in this thread
(http://cr.openjdk.java.net/~eosterlund/8189871/webrev.00_01/)
> And do you know that this is an instance rather than array instance?
> InstanceKlass::cast() has an assert that is_instance_klass().
The code previously assumed it is_instance_klass(), and I did not
question its correctness. But now that you mention it, it does seem
safer to explicitly check. So I added an explicit check for that.
>
> http://cr.openjdk.java.net/~eosterlund/8189871/webrev.00/src/hotspot/share/oops/klass.cpp.udiff.html
>
>
> Revert file with only one line removed.
>
Fixed.
>
> http://cr.openjdk.java.net/~eosterlund/8189871/webrev.00/src/hotspot/share/runtime/access.hpp.html
>
>
> 240 template <typename T>
> 241 struct OopOrNarrowOopInternal: AllStatic {
> 242 typedef oop type;
> 243 };
> 244
> 245 template <>
> 246 struct OopOrNarrowOopInternal<narrowOop>: AllStatic {
> 247 typedef narrowOop type;
> 248 };
> 249
>
> Kim and I agree that we should not have the default template
> definition for oop and have two specializations instead. Mostly I
> agree because this is confusing.
>
> 240 template <typename T>
> 241 struct OopOrNarrowOopInternal;
>
> 240 template <>
> 241 struct OopOrNarrowOopInternal<oop>: AllStatic {
> 242 typedef oop type;
> 243 };
> 244
> 245 template <>
> 246 struct OopOrNarrowOopInternal<narrowOop>: AllStatic {
> 247 typedef narrowOop type;
> 248 };
> 249
>
This choice is actually deliberate. The reason is that we pass in things
that is-an oop but is not exactly oop, like for example arrayOop or
oopDesc* and the whole subclass tree if oop. An earlier incarnation of
the Access API used a new IsDerived<X, Y> metafunction to determine
whether a type is-an oop.
However, in order to keep the solution simpler and introduce less type
dependencies, I instead use this OopOrNarrowOop thing to pass oop-like
things through it and narrow it down to oop or narrowOop. If a narrowOop
is passed in, then it becomes narrowOop, if you pass in oopDesc*,
arrayOop, oop, instanceOop, or whatever, then it results in an oop, and
tries to implicitly convert whatever was passed in to oop. That will act
as if the overload was "oop" in the way that you can pass in anything
that is implicitly convertible to exactly oop or narrowOop, and once you
make it past that layer, it will be treated only as oop or narrowOop.
Sorry for any resulting confusion.
>
> We were also trying to figure out how the runtime would know whether
> to use IN_CONCURRENT_ROOT vs IN_ROOT decorator, since it probably
> varies for GCs. And requires runtime code to know whether the root is
> scanned concurrently or not, which we don't know.
>
The Access API needs to know whether this is scanned concurrently or not
(because we *have* to perform different barriers then). So whatever data
structure the access is performed on, needs to make sure this is known.
> This is all I have for now but I'm going to download the patch and
> have more of a look tomorrow.
Thank you for the review and thank you for taking the time to look at this.
Thanks,
/Erik
> Thanks,
> Coleen
>
>
> On 11/9/17 12:00 PM, 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