[foreign-abi] RFR 8236004: Memory access var handles should support MemoryAddress carrier
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Dec 17 18:47:55 UTC 2019
On 17/12/2019 16:57, Paul Sandoz wrote:
>
>
>> On Dec 17, 2019, at 2:28 AM, Maurizio Cimadamore
>> <maurizio.cimadamore at oracle.com
>> <mailto:maurizio.cimadamore at oracle.com>> wrote:
>>
>>
>> On 17/12/2019 01:07, Paul Sandoz wrote:
>>> Looking good but sorry I read the code incorrectly and confused
>>> myself and lead you down the wrong path as a result. Still swapping
>>> back all this stuff :-)
>>>
>>> I originally thought the VarHandle impls were incorrect generating
>>> the accessModeTypeUncached methods returning method types with
>>> erased components.
>>>
>>> I think we need to revert back to Object in
>>> X-VarHandleMemoryAddressView.java.template
>>>
>>> @ForceInline
>>> - static MemoryAddressProxy checkAddress(Object obb, long offset,
>>> long length, boolean ro) {
>>> - MemoryAddressProxy oo =
>>> (MemoryAddressProxy)Objects.requireNonNull(obb);
>>> + static MemoryAddressProxy checkAddress(MemoryAddressProxy obb,
>>> long offset, long length, boolean ro) {
>>> + MemoryAddressProxy oo = Objects.requireNonNull(obb);
>>>
>>> oo.checkAccess(offset, length, ro);
>>> return oo;
>>> }
>>>
>>>
>>> @ForceInline
>>> - static $type$ getVolatile0(VarHandleMemoryAddressBase handle,
>>> Object obb, long base) {
>>> + static $type$ getVolatile0(VarHandleMemoryAddressBase handle,
>>> MemoryAddressProxy obb, long base) {
>>>
>>> MemoryAddressProxy bb = checkAddress(obb, base,
>>> handle.length, true);
>>> return convEndian(handle.be,
>>> UNSAFE.get$RawType$Volatile(
>>> bb.unsafeGetBase(),
>>> offset(bb, base, handle.alignment)));
>>> }
>>>
>>> Although it's fixed in your case the VarHandles linking machinery
>>> operates on erased signatures. Now I recall one major reason why:
>>> it's so that invocation behaves similar to MH.invoke rather than
>>> MH.invokeExtact, thus making it easier to pass subtypes without
>>> having to declare an explicit cast at the call site (which would
>>> have made it far more painful for adoption). As a consequence the
>>> VarHandle implementations must perform checks on ref types.
>>
>> I did a benchmark and it seems like everything is linked correctly
>> (through the guards) as before?
>>
>
> Yes, it works fine, until you pass an instance of something that is
> not of MemoryAddressProxy e.g. pass in an instance of String, which
> will get spoofed as an instance of MemoryAddressProxy. The guards
> will only check if the object is a reference type.
Ok, I see what you mean, the problem is not that linking occurs
abnormally, the problem is that the underlying linkage is not sound (in
the absence of Java casts).
I guess this means that same treatment should also be applied to the
MemoryAddressProxy parameter (e.g. of a VarHandle::set operation) right?
Maurizio
>
> For example:
>
> @ForceInline
> @LambdaForm.Compiled
> final static Object guard_L_L(VarHandle handle, Object arg0,
> VarHandle.AccessDescriptor ad) throws Throwable {
> if (handle.vform.methodType_table[ad.type] ==
> ad.symbolicMethodTypeErased) {
> Object r = MethodHandle.linkToStatic(handle, arg0,
> handle.vform.getMemberName(ad.mode));
> return ad.returnType.cast(r);
> }
> else {
> MethodHandle mh = handle.getMethodHandle(ad.mode);
> return mh.asType(ad.symbolicMethodTypeInvoker).invokeBasic(handle,
> arg0);
> }
> }
>
> The fast path checks ref equality of the erased signatures (the
> expected from the vform compared to that at the call site, then passes
> on the arguments to the internal linkToStatic which performs no type
> checking (see also documentation on MH.invokeBasic).
>
> This enables VH access usage in say ForkJoin code to pass instances
> ForkJoinTask or null without explicit casts i.e. MH.invoke semantics
> but without the cost of an asType transformation. The guard falls
> back to asType when the erased signatures differ e.g. primitive
> conversion.
>
> I think that now properly answers your question about how the heck
> does the MemoryAddressProxy trick work! The VHs consume instances of
> MemoryAddressProxy, users pass in instances of MemoryAddress, and
> MemoryAddressImpl is on the only implementation of both interfaces.
>
>
>> And, if we erase the receiver type to Object, then, shouldn't we do
>> the same for the VarHandle carrier (e.g. Object, not MemoryAddress?)
>>
>
> The VarHandle instance (the receiver) gets passed as the first
> argument to the method when being linked (in the up call to the
> MethodHandleNatives method). We know in that case it's always a
> VarHandle instance. Same for the appendix type passed as the last
> argument.
>
> Here us the snippet of code in
> MerthodHandleNatives.varHandleOperationLinkerMethod:
>
> // Get the guard method type for linking
> final Class<?>[] guardParams = new Class<?>[sigType.parameterCount() + 2];
> // VarHandle at start
> guardParams[0] = VarHandle.class;
> for (int i = 0; i < sigType.parameterCount(); i++) {
> guardParams[i + 1] = sigType.parameterType(i);
> }
> // Access descriptor at end
> guardParams[guardParams.length - 1] = VarHandle.AccessDescriptor.class;
> MethodType guardType = MethodType.makeImpl(guardReturnType,
> guardParams, true);
>
>
> Hth,
> Paul.
More information about the panama-dev
mailing list