GC interface update
Erik Österlund
erik.osterlund at oracle.com
Tue Apr 25 15:35:57 UTC 2017
Hi everyone,
I'm glad to see that we all want to go towards modularizing the GC
implementations in hotspot more. Thank you Roman for starting this
thread. I have wanted a better GC interface since I first set foot in
hotspot.
As mentioned, I have been cooking up a GC barrier interface prototype
based on ideas mentioned earlier in this thread. I will provide a
preview of where it is headed in this email before we start to diverge
too much.
I have a long patch queue with many individual changes, but to get an
overview for the discussion here, I will start by posting a combined
webrev for preview of the whole thing as a pre-review. Once the real
detailed review starts, I will start sending out the smaller incremental
changes that are easier to grasp in reviews.
The webrev is based on the latest jdk10-hs repo.
Full webrev: http://cr.openjdk.java.net/~eosterlund/gc_interface/webrev.00/
== High Level Design Philosophy ==
The overall design idea has been to remove all explicit calls to barrier
code in the VM. The barrier code is often required for conforming to
some kind of semantics. So rather than having these explicit barrier
calls in the shared code, the new API is instead to perform a memory
access that specifies the intended semantic properties instead. These
semantic properties are not only GC related but could be any property
that is important for performing a memory access with the right
semantics. These semantic properties are called decorators in my API.
So for example, a decorator for a load on an oop could be ACCESS_ON_HEAP
to denote that this access is performed in the Java heap, and MO_ACQUIRE
that the access should have acquire memory ordering semantics and
ACCESS_ON_WEAK to denote that the access is performed on a weakly
reachable reference. This would in the end need to boil down to the
following barriers:
1) Compressed oops to decode narrow oops
2) Potentially an acquire membar on e.g. ARM machines
3) Potentially a SATB enqueue barrier for SATB-type GCs
So rather than treating these differently and having runtime-resolved
explicit barriers built bottom up, the approach is to build accesses top
down instead. The GC may override the whole access to to anything, but
will probably want to reuse things like decoding and encoding compressed
oops, and the pre/post write pattern and memory ordering. Therefore
barrier sets may ask their super class to fill in such details. This
allows arbitrary level of control without introducing code duplication.
Each BarrierSet has 4 barrier-related components with a similar design.
1) An AccessBarrier class responsible for performing accesses requested
by the runtime system through a new Access API (more later)
2) A BarrierSetCodeGen class responsible for generating accesses in
platform-specific assembly code (stub generators and interpreter)
3) A C1BarrierSetCodeGen class responsible for generating accesses for
the C1 compiler
4) A C2BarrierSetCodeGen class responsible for generating accesses for
the C2 compiler
So there is one class for each part of hotspot (runtime,
platform-specific, c1, c2), and they all follow a class hierarchy
mirroring their respective BarrierSet hierarchy to reuse more general
functionality like memory ordering and compressed oops.
== Runtime: Access API ==
The runtime part of the API goes through a new class called Access. All
decorated accesses should go through this interface. It makes heavy use
of templates to perform the right accesses and barriers in the most
optimal way, by connecting the intended Access semantics to the
appropriately decorated AccessBarrier of the current BarrierSet. It
combines different decorators in a pipeline that are resolved at
different times in the JVM life cycle, but handled in the same way. Some
decorators are resolved at build-time, like for example whether the
build needs to support barriers on primitives. If Shenandoah is built
for example, this decorator will be set, and if Shenandoah is not built,
it will not be set. Other decorators are resolved statically at the call
site, such as what strength a reference has. Yet some decorators are
resolved at runtime, such as whether compressed oops are used or not and
which garbage collector was selected.
When there exists runtime dependencies for resolving a barrier, the
Access system will generate function pointers for the access. The
function pointers initially point to a resolver function that checks the
selected runtime properties, and then patches the function pointer to
point to a statically generated function with those properties set, so
that the next time the function is called, it will call straight into
the appropriate barrier. This means that where we would previously have
multiple virtual calls for pre- and post-write barriers, followed by if
checks for compressed oops, all of that boils down to a single function
pointer call that then has inlined everything that needs to be done for
that set of runtime parameters.
The goal has been to separate out GC-specific code to GC-specific
directories as far as barriers are concerned. To glue this together,
there is a barrierSetConfig.hpp and barrierSetConfig.inline.hpp. The
barrierSetConfig.hpp configures what barrier sets there are and produces
a macro allowing you to do something for each barrier set. This is used
by barrier resolution at runtime. The barrierSetConfig.inline.hpp
basically just includes in the GC-specific inline headers to allow
inlining the GC barriers all the way. So anyone making a new GC should
put their GC in there. I added a Shenandoah GC therere as an example so
you can see what I mean.
The Access API goes through a template pipeline. First the Access class
bridges the API to functions in the AccessInternal namespace. This
involves using temporary proxy objects to artificially infer the return
types of loads. Then in the AccessInternal namespace the types are first
decayed, meaning that CV-qualifiers and references are stripped. Then
types of addresses and values are joined, at which times certain
decorators are infered like the use of compressed oops when e.g. loading
an oop from a narrowOop*. Other implicit decorarors are also inferred
then, such as a default memory ordering if none is specified, and other
rules related to memory ordering such as sequential consistent stores
implicitly also being releasing stores etc. Then buildtime decorators
are added and a pre-runtime stage is reached where the mechanism tries
to bind accesses statically if possible, and otherwise producing a
runtime-dispatch point that statically generates all possible runtime
variants of the access and a self-patching function pointer that
resolves the correct variant at runtime. These statically generated
barriers are resolved through the BarrierSet AccessBarrier that gives
the GC full control for generating an appropriate access. It can use the
DecoratorTest class to check for different decorators specifying
semantics that add barriers altering the access. Eventually, a super
class of the AccessBarrier called BasicAccessBarrier that handles
compressed oops and it calls RawAccessBarrier that inspects the decayed
times and forwards to appropriate calls to Atomic, OrderAccess or
performs volatile or raw accesses depending on selected memory ordering.
I have then applied the Access API to many weird accesses performed in
the runtime system where we check if we are using G1 and then
subsequently doing some weird ad-hoc SATB enqueue barrier. Examples
include the string table, ciMetadata and jvmtiTagMap, unsafe get,
reference get, jweak resolve etc. These accesses now use decorated
accesses through Access instead.
== C1 ==
The shared C1 barrier code has been moved into the C1BarrierSetCodeGen
class for each specific barrier set. It generates decorated accesses,
and decorates it with GC barriers as required by the specified
semantics. The slowpath stubs have been refactored. The code sutbs have
moved into the C1BarrierSetCodeGen class and it assembles the machine
code with the platform specific BarrierSetCodeGen assembler. The
runtime1 code stubs have been changed to not be generated in switch
statements, but instead with a code generation closure that calls into
the C1BarrierSetCodeGen that calls assembles the runtime1 stub with the
platform specific BarrierSetCodeGen.
The design of accesses going through C1BarrierSetCodeGen is consistent
with the rest of the Access API: the accesses are built top down and
allows overriding the whole operation. The C1BarrierSetCodeGen class
mirrors the class hierarchy of the BarrierSets.
== C2 ==
Similar to the C1BarrierSetCodeGen, the C2BarrierSetCodeGen helps the
GraphKit generate decorated accesses top-down. The class hierarchy of
the C2BarrierSetCodeGen class mirrors the class hierarchy of the
BarrierSets. Since C2 expands GC barriers rather early and then pulls
the barriers through optimizations, there are some additional calls to
be able to distinguish barrier-related nodes from non-barrier nodes.
== Graal ==
For now I only try not to break the Graal port used for AoT in the
hotspot repository. Ideally, graal would follow the same pattern, but
initially this is out of scope for me.
== GC: BarrierSet consolidation ==
The hierarchy of our barrier sets seem unnecessarily deep - partially
because the card table itself is part of the card table barrier sets. I
have split the card table hierarchy and separated it from the barrier
set hierarchy. A CardTableModRefBarrier *has* a CardTable. As a result
the hierarchy could be simplified a lot to contains only BarrierSet,
ModRefBarrierSet, CardTableBarrierSet and G1BarrierSet. G1BarrierSet and
CardTableBarrierSet are the only leaves, and ModRefBarrierSet is only a
small helper class.
== Colaboration ==
I hope you like the direction this is going and hope it will suit
Shenandoah as well. I have not yet applied the Access API for all
primitives yet because I thought that you probably have a better idea
where they are since your GC uses such barriers a lot more. But the
framework should be able to support that without much trouble. So I hope
we can work together a bit on this. If there are any shortcomings, I
hope we can work it out together.
Also, as you can see, I have only provided x86 and SPARC ports so far.
The architecture specific code mostly involves the stub generators, the
interpreter, and the G1 C1 slow path stuff. I was hoping to eventually
get some help from other port maintainers to port this to their
respective platforms. If you feel compelled to help porting this to ARM,
I would be very happy. ;)
And perhaps somebody would like to help getting PPC and S390 on board
too. I thought I would at least start the discussion now.
So yeah, hope everyone likes this direction. If there are any questions,
I will happily answer them. Any feedback is very welcome.
Thanks,
/Erik
On 2017-04-25 14:05, Per Liden wrote:
> On 2017-04-24 15:46, Roman Kennke wrote:
>> Am 24.04.2017 um 08:37 schrieb Per Liden:
>>> On 04/20/2017 02:29 PM, Roman Kennke wrote:
>>>> Am 20.04.2017 um 14:01 schrieb Per Liden:
>>>>> On 2017-04-20 12:05, Aleksey Shipilev wrote:
>>>>>> On 04/20/2017 09:37 AM, Kirk Pepperdine wrote:
>>>>>>>> Good stuff. However, one thing I'm not quite comfortable with
>>>>>>>> is the
>>>>>>>> introduction of the GC class (and its sub classes). I don't quite
>>>>>>>> see the
>>>>>>>> purpose of this interface split-up between GC and CollectedHeap. I
>>>>>>>> view
>>>>>>>> CollectedHeap as _the_ interface (but yes, it needs some love),
>>>>>>>> and
>>>>>>>> as a
>>>>>>>> result I think the the functions you've exposed in the GC class
>>>>>>>> actually
>>>>>>>> belongs in CollectedHeap.
>>>>>>>
>>>>>>> I thought the name CollectedHeap implied the state of the heap
>>>>>>> after the
>>>>>>> collector has completed. What is the intent of CollectedHeap?
>>>>>>
>>>>>> No, CollectedHeap is the actual current GC interface. This is the
>>>>>> entry point to
>>>>>> GC as far as the rest of runtime is concerned, see e.g.
>>>>>> CollectedHeap*
>>>>>> Universe::create_heap(), etc. Implementing CollectedHeap,
>>>>>> CollectorPolicy, and
>>>>>> BarrierSet are the bare minimum required for GC implementation
>>>>>> today. [1]
>>>>>
>>>>> Yep, and I'd like us to move towards tightening down the GC
>>>>> interface to
>>>>> basically be cleaned up versions of CollectedHeap and BarrierSet.
>>>>>
>>>>> CollectorPolicy and some other things that class drags along, like
>>>>> AdaptiveSizePolicy, are way too collector specific and I don't think
>>>>> that should be exposed to the rest of the VM.
>>>>
>>>> Right, I totally agree with this.
>>>>
>>>> BTW, another reason for making a new GC interface class instead of
>>>> further bloating CollectedHeap as the central interface was that there
>>>> is way too much implementation stuff in CollectedHeap. Ideally, I'd
>>>> like
>>>> to have a true interface with no or only trivial implementations
>>>> for the
>>>> declared methods, and most importantly nothing that's only ever needed
>>>> by the GC itself (and never called by the runtime). But as I said, I'm
>>>> not against a serious refactoring and tightening-up of CollectedHeap
>>>> instead.
>>>
>>> Yes, I'd like to keep CollectedHeap as the main interface, but I
>>> completely agree that CollectedHeap currently contains too much
>>> implementation stuff that we probably want to move out.
>>
>> Ok, I will revert that part of the change to use CollectedHeap as main
>> interface then. It's no big deal, so far I only had one additional
>> method for servicability support in the GC interface class anyway.
>
> Ok, sounds good.
>
> And regarding BarrierSet. As you know, Erik Österlund is working on
> overhauling BarrierSet and how barriers are used across the VM. He'll
> be sending out his current proposal later today.
>
>>
>> Would you also prefer keep 'management' of the heap in Universe too?
>> I.e. Universe::create_heap() and Universe::heap() etc? Or do you see a
>> benefit in moving it out like I did with gc_factory.cpp? The idea being
>> that there's only one smallish place that knows about all the existing
>> GC impls?
>
> I'd like to keep Universe::heap() and create_heap(), but I'd like to
> move away from our current if-else if-else.. and instead have a more
> declarative way of saying which GC's are available. create_heap()
> would then just walk the list of available GC and ask if it's enabled
> and if so create an instance. I think we'd want to do something
> similar to (or even combine this with) what Erik is doing in his
> BarrierSet patch.
>
> In general, to make it easier to review/test/integrate all these
> changes it would be good if we can have incremental patches, each
> addressing some specific/contained area.
>
> cheers,
> Per
More information about the hotspot-gc-dev
mailing list