Unsafe API and explicit VT allocation

John Rose john.r.rose at oracle.com
Thu Jul 19 21:20:57 UTC 2018


This is not critical for LW1, but I want to start the ball rolling
on discussion of explicit low-level buffering intrinsics for value
types.  Specifically, I'd like to talk about the C2 IR shapes
that support this.  (I'm prompted to this by looking at some
recent work by Tobias.)

So, we should talk about IR shapes for making private mutable
buffers for value types, for use (only) by the Unsafe API.

I think it involves an intrinsic to trigger an allocate regardless of
previous allocation state, which precedes the unsafe ops, and
(maybe) a subsequent intrinsic (or the same intrinsic) to suppress
the allocation state at the end of the Unsafe patching.

The Java code would be something like:

private static final Unsafe U = Unsafe.getUnsafe();
private static final long FO_counter = U.objectFieldOffset(MyVT.class.getField("counter"));
MyVT peekPoke(MyVT x) {
   Object xbuf = U.bufferValue(x, MyVT.class);
   int counter = U.getInt(xbuf, FO_count);
   ++counter;
   U.put(xbuf, FO_count, counter);
   return U.unbufferValue(xbuf, MyVT.class);
}

An alternative to that pattern would be to ask the Unsafe user
to assemble the value in a 1-array containing the value under
construction, *but* that fails to work unless the value array is
flattened, which is not guaranteed (though is likely).  So I think
we need an intrinsic or two here (in the Unsafe API) to control
buffering.

The bracketing of the Unsafe code which peeks and pokes the
buffer would probably involve not only explicit allocation ops,
but also CPU-order membars and/or Opaque masking of
either the value or the memory states accessed by Unsafe.

The IR would be something like:

MyVT peekPoke(MyVT x) {
   //Object xbuf = U.bufferValue(x, MyVT.class);
   checkTypeOrTrap(x, MyVT);
   xbuf = ValueTypePtrNode::forceAllocate(x);
   //int counter = U.getInt(xbuf, FO_count);
   addr = field_addressing(T_INT, xbuf, longcon(FO_count));
   counter = expand_unsafe_get(addr);
   //++counter;
   counter' = AddNode::make(counter, intcon(1));
   //U.put(xbuf, FO_count, counter);
   expand_unsafe_put(addr, counter);
   //return U.unbufferValue(xbuf, MyVT.class);
   return_value = ValueTypePtrNode::discardBuffer(xbuf);
}

If the Unsafe ops correspond correctly to normal field
accesses, as observed by the JIT, then the buffer can be
completely deleted, and the fieldwise computation released
from any residual barriers (which suggests CPU-order is
the wrong tool here).

The important thing in the IR is that everybody agrees that
xbuf is totally local (doesn't escape from the IR block).
The discardBuffer either removes the buffer node completely
or hides it securely behind some sort of barrier or Opaque node.

For extra points, the bufferValue and unbufferValue intrinsics could
also do interesting things with objects, such as a forced shallow
clone.  Such operations would be, well, "unsafe" for general objects,
but would be a useful tool for writing "withers" for value-based
classes (which approximate the characteristics of true value types).

In other words, like the rest of Unsafe, such intrinsics can be useful
(occasionally even indispensable) when used with the cooperation
of the class and proper respect for its invariants, though they are
utterly dangerous otherwise.

— John

P.S. Another message is coming about the various internal formats
we are using for value types…


More information about the valhalla-dev mailing list