forked from OSchip/llvm-project
[GC docs] Add example IR, assembly, and stackmaps to Statepoint documentation
When I originally committed the statepoint docs, I left placeholders for example IR fragments. I'm finally getting around to filling those in. I also added IR fragments to illustrate the usage of the PlaceSafepoints pass while I was at it. llvm-svn: 230601
This commit is contained in:
parent
f609a1db58
commit
5017ab5d0e
|
@ -115,21 +115,37 @@ garbage collected objects.
|
|||
<statepoint-utilities>` described below.
|
||||
|
||||
This abstract function call is concretely represented by a sequence of
|
||||
intrinsic calls known as a 'statepoint sequence'.
|
||||
|
||||
intrinsic calls known collectively as a "statepoint relocation sequence".
|
||||
|
||||
Let's consider a simple call in LLVM IR:
|
||||
todo
|
||||
|
||||
Depending on our language we may need to allow a safepoint during the
|
||||
execution of the function called from this site. If so, we need to
|
||||
let the collector update local values in the current frame.
|
||||
.. code-block:: llvm
|
||||
|
||||
Let's say we need to relocate SSA values 'a', 'b', and 'c' at this
|
||||
safepoint. To represent this, we would generate the statepoint
|
||||
sequence:
|
||||
define i8 addrspace(1)* @test1(i8 addrspace(1)* %obj)
|
||||
gc "statepoint-example" {
|
||||
call void ()* @foo()
|
||||
ret i8 addrspace(1)* %obj
|
||||
}
|
||||
|
||||
todo
|
||||
Depending on our language we may need to allow a safepoint during the execution
|
||||
of ``foo``. If so, we need to let the collector update local values in the
|
||||
current frame. If we don't, we'll be accessing a potential invalid reference
|
||||
once we eventually return from the call.
|
||||
|
||||
In this example, we need to relocate the SSA value ``%obj``. Since we can't
|
||||
actually change the value in the SSA value ``%obj``, we need to introduce a new
|
||||
SSA value ``%obj.relocated`` which represents the potentially changed value of
|
||||
``%obj`` after the safepoint and update any following uses appropriately. The
|
||||
resulting relocation sequence is:
|
||||
|
||||
.. code-block:: llvm
|
||||
|
||||
define i8 addrspace(1)* @test1(i8 addrspace(1)* %obj)
|
||||
gc "statepoint-example" {
|
||||
%0 = call i32 (void ()*, i32, i32, ...)* @llvm.experimental.gc.statepoint.p0f_isVoidf(void ()* @foo, i32 0, i32 0, i32 5, i32 0, i32 -1, i32 0, i32 0, i32 0, i8 addrspace(1)* %obj)
|
||||
%obj.relocated = call coldcc i8 addrspace(1)* @llvm.experimental.gc.relocate.p1i8(i32 %0, i32 9, i32 9)
|
||||
ret i8 addrspace(1)* %obj.relocated
|
||||
}
|
||||
|
||||
Ideally, this sequence would have been represented as a M argument, N
|
||||
return value function (where M is the number of values being
|
||||
|
@ -140,14 +156,24 @@ representation.
|
|||
Instead, the statepoint intrinsic marks the actual site of the
|
||||
safepoint or statepoint. The statepoint returns a token value (which
|
||||
exists only at compile time). To get back the original return value
|
||||
of the call, we use the 'gc.result' intrinsic. To get the relocation
|
||||
of each pointer in turn, we use the 'gc.relocate' intrinsic with the
|
||||
appropriate index. Note that both the gc.relocate and gc.result are
|
||||
tied to the statepoint. The combination forms a "statepoint sequence"
|
||||
and represents the entitety of a parseable call or 'statepoint'.
|
||||
of the call, we use the ``gc.result`` intrinsic. To get the relocation
|
||||
of each pointer in turn, we use the ``gc.relocate`` intrinsic with the
|
||||
appropriate index. Note that both the ``gc.relocate`` and ``gc.result`` are
|
||||
tied to the statepoint. The combination forms a "statepoint relocation
|
||||
sequence" and represents the entitety of a parseable call or 'statepoint'.
|
||||
|
||||
When lowered, this example would generate the following x86 assembly::
|
||||
put assembly here
|
||||
When lowered, this example would generate the following x86 assembly:
|
||||
|
||||
.. code-block:: gas
|
||||
|
||||
.globl test1
|
||||
.align 16, 0x90
|
||||
pushq %rax
|
||||
callq foo
|
||||
.Ltmp1:
|
||||
movq (%rsp), %rax # This load is redundant (oops!)
|
||||
popq %rdx
|
||||
retq
|
||||
|
||||
Each of the potentially relocated values has been spilled to the
|
||||
stack, and a record of that location has been recorded to the
|
||||
|
@ -155,6 +181,35 @@ stack, and a record of that location has been recorded to the
|
|||
needs to update any of these pointers during the call, it knows
|
||||
exactly what to change.
|
||||
|
||||
The relevant parts of the StackMap section for our example are:
|
||||
|
||||
.. code-block:: gas
|
||||
|
||||
# This describes the call site
|
||||
# Stack Maps: callsite 2882400000
|
||||
.quad 2882400000
|
||||
.long .Ltmp1-test1
|
||||
.short 0
|
||||
# .. 8 entries skipped ..
|
||||
# This entry describes the spill slot which is directly addressable
|
||||
# off RSP with offset 0. Given the value was spilled with a pushq,
|
||||
# that makes sense.
|
||||
# Stack Maps: Loc 8: Direct RSP [encoding: .byte 2, .byte 8, .short 7, .int 0]
|
||||
.byte 2
|
||||
.byte 8
|
||||
.short 7
|
||||
.long 0
|
||||
|
||||
This example was taken from the tests for the :ref:`RewriteStatepointsForGC` utility pass. As such, it's full StackMap can be easily examined with the following command.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
opt -rewrite-statepoints-for-gc test/Transforms/RewriteStatepointsForGC/basics.ll -S | llc -debug-only=stackmaps
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Intrinsics
|
||||
===========
|
||||
|
||||
|
@ -459,6 +514,35 @@ and inserting safepoint polls sufficient to ensure running code checks for a
|
|||
safepoint request on a timely manner. This pass is expected to be run before
|
||||
RewriteStatepointsForGC and thus does not produce full relocation sequences.
|
||||
|
||||
As an example, given input IR of the following:
|
||||
|
||||
.. code-block:: llvm
|
||||
|
||||
define void @test() gc "statepoint-example" {
|
||||
call void @foo()
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @do_safepoint()
|
||||
define void @gc.safepoint_poll() {
|
||||
call void @do_safepoint()
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
This pass would produce the following IR:
|
||||
|
||||
.. code-block:: llvm
|
||||
|
||||
define void @test() gc "statepoint-example" {
|
||||
%safepoint_token = call i32 (void ()*, i32, i32, ...)* @llvm.experimental.gc.statepoint.p0f_isVoidf(void ()* @do_safepoint, i32 0, i32 0, i32 0)
|
||||
%safepoint_token1 = call i32 (void ()*, i32, i32, ...)* @llvm.experimental.gc.statepoint.p0f_isVoidf(void ()* @foo, i32 0, i32 0, i32 0)
|
||||
ret void
|
||||
}
|
||||
|
||||
In this case, we've added an (unconditional) entry safepoint poll and converted the call into a ``gc.statepoint``. Note that despite appearances, the entry poll is not necessarily redundant. We'd have to know that ``foo`` and ``test`` were not mutually recursive for the poll to be redundant. In practice, you'd probably want to your poll definition to contain a conditional branch of some form.
|
||||
|
||||
|
||||
At the moment, PlaceSafepoints can insert safepoint polls at method entry and
|
||||
loop backedges locations. Extending this to work with return polls would be
|
||||
straight forward if desired.
|
||||
|
|
Loading…
Reference in New Issue