[PATCH] Statepoint infrastructure for garbage collection
Philip Reames
listmail at philipreames.com
Wed Oct 15 14:52:15 PDT 2014
Kevin,
Let me try to answer the point you're getting at. In doing so, I want
to explicitly separate the statepoint intrinsics which are currently up
for review, and the future late safepoint placement. The statepoint
intrinsics have value separate from the late safepoint placement
approach, and I want to justify them on their own merits.
The basic problem we're trying to solve with these intrinsics is
supporting fully relocating collectors. By definition, such a collector
needs to be precise w.r.t. root tracking. Even worse, we need to ensure
that *all copies* of a pointer are updated. It is not acceptable to
make two copies of a pointer, update one of them, then use the other for
a memory access.
If the compiler is allowed to introduce derived pointers (i.e. pointer
valued temporaries created by the compiler which point somewhere within
an object, or outside it, but associated with it), we also need to track
which *object* each *pointer* to be updated is associated with. This is
required to safely update the pointers.
For the sake of argument, let's say our frontend does safepoint insertion.
There's a couple of approaches which seem like they might work, let's
explore each in turn:
- We could use patchpoints to record all the values needed for the GC
stack map. This mostly works, but requires that the patchpoint not be
marked readonly or readnone (to prevent illegal reorderings). That
could be a usage convention. The real problem is that the compiler is
still free to introduce multiple *copies* of an SSA value over the
patchpoint. (This is completely legal under SSA semantics.) When it
does so, it creates a situation where the gc could fail to update a
pointer which will then be dereferenced. That's a bug. Worth stating
explicitly, I believe the patchpoint scheme would be sufficient *if you
do not every relocate a root*.
- We could use the gc.root. gc.root defines the allocs, but does not
define the call format, or any of the mechanisms to ensure proper
relocation. As such, it *by itself* is not viable. Also, gc.root
inherently assumes every value will have a stack slot. Without *heavy*
reengineering, there's no way to have a gc pointer in a callee saved
register over a call site. This is an unfortunate limitation. Any call
representation without explicit relocation suffers from the same bug as
the patchpoint scheme.
- We could combine gc.root allocas and patchpoints. This essentially
combines the flaws (no gc pointers in callee saved registers over calls,
and missed copies), with no benefit.
The statepoint intrinsics are basically the patchpoint option above, but
with relocation made explicit in the IR. While it's still legal for the
optimizer to create a copy of the value feeding a statepoint, that's now
okay. By construction, there can be no use of the original SSA value
(and thus the copy) after the statepoint. Instead, the explicitly
relocated value is used.
To summarize: We need (something like) statepoints for correctness of
fully relocating collectors.
(The points I'm making here are somewhat subtle. If it would help to
have IR examples here, ask. I'm deferring writing them because it's
time consuming.)
Other advantages of the statepoint approach:
The gc.relocate intrinsics (part of the statepoint proposal) also makes
it explicit in the IR what the base object of each pointer to be
relocated is. This isn't *required* (you could encode the same
information in the arguments of the statepoint), but making it explicit
is much cleaner.
The explicit relocation notation has the potential to be extended in to
the backend. With some register allocator changes (not part of this
patch!), we could support gc pointers in callee saved registers. This
is possible with the (incorrect) patchpoint scheme. It is possible, but
*hard*, with the gc.root scheme.
The posted patch includes a couple of small optimizations (i.e. null
forwarding) that help performance, but could (probably) be implemented
on top of another scheme. We have a number of planned optimizations on
the statepoint mechanism.
Now, let me finally bring up late safepoint placement. The only real
impact on this patch is that, to date, we have only focused on the
*correctness* of a statepoint passing through the optimizer. We have
not attempted to teach the optimizer about how to leverage one or
perform optimizations over one. There's room for improvement here (i.e.
not completely blocking inlining), but we prefer to approach this
problem by simply inserting them late. You could instead choose to
insert them at generation time, and teach the optimizer about their
semantics. That *strategy choice* is independent of the representation
choosen provided that representation is *correct*.
Yours,
Philip
On 10/14/2014 07:01 PM, Kevin Modzelewski wrote:
> I think a change like this might be more compelling if you could give more detail on how it would actually help (I can't find the detail I'm looking for in your blog posts). It seems like the value of this patch is that it will work with late safepoint placement, but it'd be nice to see some examples of cases where late safepoint placement gives you something that early safepoint placement (ie by the frontend) doesn't. It kind of feels like either approach will work well with only non-gc values, and neither approach will be able to do much optimization when you do function calls. I'm not trying to claim that that's necessarily true, but it'd be easier to understand your point if there was some example IR.
>
> http://reviews.llvm.org/D5683
>
>
More information about the llvm-commits
mailing list