[foreign-memaccess] creating memory access handles
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Wed Jun 19 12:28:44 UTC 2019
Hi,
Last week we had a great patch from Jorn which implements a new, more
basic way to get at the memory access handles; the basic idea is to
start from simple carriers (e.g. an 'int' accesor) and _combine_ them
together. There are basically two ways in which you can combine: (i) add
offset to the base address (useful for accessing struct elements) or
(ii) add an extra access dimension (useful for array indexing). There's
also an extra combinator which can be used to force a certain alignment,
more on that later.
When a memory access VarHandle is built, it will have three important
properties:
* the displacement
* the number of 'free variables' in it (and their 'scales' in bytes)
* the alignment (either derived from the carrier, or forced with the
combinator method)
* carrier type (e.g. int.class)
All these info is used in order to compute a single offset which is
applied on top of the MemoryAddress that is passed as argument to the
VH; the formula would look something like this:
offset = c_0 + (x_1 * s_1 + c_1) + (x_2 * s_2 + c_2) + ... + (x_n * s_n
+ c_n)
That is, it's a sum of many components, a constant one (c_0) and some
indexed one, where x_1, x_2 ... x_n are free variables that are bound by
the VH call.
With this formula it is easy to see that:
* VH.displacement = c_0 + c_1 + c_2 + ... + c_n
* VH.scales = { s_1, s_2 ... s_n }
This is all good. But I found myself asking: what are the conditions
under which a VH combinator call is well-formed? Is it possible to
construct stuff with the combinator API which doesn't make sense?
I think that is currently the case - that is, the combinator API is
'less safe' than its LayoutPath-based cousin. While some of that is
unavoidable (LayoutPath works on Layout, so it has more info), some of
that is also purely accidental. I've identified two category where I
found the combinator API too weak: array VH creation and alignment
enforcing.
*** array indexing ***
Let's start with arrays. In general, with the combinator API, you start
off with a simple accessor - e.g. something for:
i32
and then you build up from there - e.g. we can add some displacement:
[x64 i32]
And then we can wrap it all into an array indexing:
[5 : [x64 i32]]
And we can add even more indexing:
[10 : [5 : [x64 i32]]]
Now, each array indexing is done with this API call:
VarHandle elementHandle(VarHandle handle, long scale)
the 'scale' here is, essentially, the size of the element type of the
array being considered.
I think this imposes a requirement on which 'scale' numbers we can use -
that is, if 'handle' is a VH whose carrier is 4 bytes and displacement
is 10 - then the scale we use must be greater/equal than 10 + 4. A
failure to meet this requirement will mean that indexing the VH with an
index > 0 will possibly still point to a location inside the array.
While this restriction doesn't completely remove this possibility (there
could always be 'stuff' after the 'i32' we want to access), I think it
might be sensible to try and enforce this.
This also means that, going back to our formula, all the scales are
'sorted' that is:
s_1 >= s_2 >= ... >= s_n
This corresponds to the principle that the first index dimensions in the
VarHandle should correspond to the 'outermost' sequence in the layout.
So, concluding, when calling the above combinator method
(elementHandle), we have to make sure that:
scale >= handle.displacement + sizeof(handle.carrier)
*** alignment enforcing ***
When combining together VH, we must make sure that we respect alignment
constraints that might appear on these VH. So, if we start from a simple
VH which access something like this:
i32
the constraint is easily resolved - after all, i32 has a natural
alignment (4 bytes), so the VH is well-formed (this of course doesn't
mean we're 100% safe - at runtime we should still check that the address
passed to the VH is compatible with that alignment, but that's a
_dynamic_ requirement, not a _static_ one).
Now, suppose we want to add some displacement:
x64 i32
Is this still good? The resulting VH will have these properties:
* displacement = 8
* scales = {}
* carrier = int.class
* alignment = 4
here we have to check that (8 + 4) % 4 = 0. It can be seen that this is
always the case, and, in particular, the alignment constraints are
satisfied as long as the offset we pass to the combinator is a multiple
of the alignment constraint. That is, when we call:
VarHandle offsetHandle(VarHandle handle, long offset)
This has to hold:
offset % handle.alignment = 0
Ok, but what if I create an array VH ? How do I enforce alignment
constraints in that case?
[ 5 : [ x64 i32 ] ]
So, things are more tricky here - and it is helpful to appeal to our
mathematical formulation; we can model the above as:
offset = 12 * x_i + 8
and, of course we want this offset to be aligned, so:
(12 * x_i + 8) % handle.alignment = 0
Here we can note that 'x_i' is an integral constant, and we also now
that the displacement must already be a multiple of the alignment (see
above).
So, for this formula to hold, we need to make sure that the scale (12
here) is a multiple of the alignment (in this case 4, so ok). In fact we
can show that, when this is the case, the static alignment constraints
are _always_ guaranteed:
((scale * x_i + handle.displacement) % handle.alignment) = 0
but, if scale is a multiple of handle.alignment, then we have:
((N * handle.alignment * x_i + handle.displacement) % handle.alignment) = 0
But wait, handle.displacement is also a multiple of the alignment (as
per above):
((N * handle.alignment * x_i + (M * handle.alignment)) %
handle.alignment) = 0
So we can factor:
(handle.alignment * ((N * x_i) + M)) % handle.alignment = 0
which is trivially true.
So, concluding, I think that we should do the following:
1) MemoryAccessVarHandles::elementHandle(handle, displacement) must
check that displacement >= handle.displacement + sizeof(handle.carrier)
2) MemoryAccessVarHandles::offsetHandle(handle, offset) must check that:
offset % handle.alignment == 0
3) MemoryAccessVarHandles::elementHandle(handle, scale) must check that:
scale % handle.alignment == 0
As for MemoryAccessVarHandles::alignAccess - I see two options:
1) We remove it, and enforce alignment to be specified when you create
the leaf VH (preferred option)
2) We keep it, but then we must re-validate existing scales/displacement
against the new alignment constraint
Maurizio
More information about the panama-dev
mailing list