FTR: JVM Lang Summit presentation

John Rose john.r.rose at oracle.com
Fri Aug 8 22:48:39 UTC 2014


On Aug 8, 2014, at 2:34 PM, Remi Forax <forax at univ-mlv.fr> wrote:

> 
> On 08/08/2014 03:01 AM, John Rose wrote:
>> On Aug 7, 2014, at 4:25 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
>> 
>>>> slide 71:
>>>> I still think VarHandle is not a good idea. Having two way to represent roughly same thing, MethodHandle and VarHandle, is not a good idea.
>>> In a perfect world, yes.  MethodHandle tried to model fields, it was a good first attempt, but it didn’t cover all the cases.  It can’t model accesses with (or without) fences; it can’t model atomic updates.  We could of course try to add more MH types to cover this, but the combinatorial explosion is large.  So, having explored the “continue down the MH path”, we discovered that the VarHandle path is richer and more suited to the problem.  Live and learn.
> 
> or live and let die ...

(Dang; now I'm hearing a bad pop song from my youth.  "...in this ever-changing world in which we live in...")

> I see two serious issues to the VarHandle prototype.
> But first, I should say that I'm glad that we are moving away from a language feature to an API feature (even if the API require some compiler tweaks).
+1  Minimizing the language tweaks, to zero usually, is part of the game.

> First, VarHandle use signature polymorphism method and I fail to see why.
Main reason:  To provide a direct cut through the language to the JSR 292 code spinning mechanisms (LFs etc.).

In parallel, we can work on improving the cut (syntax etc.), while we try out the provisional notation (to replace many uses of Unsafe in J2SE code).

> To take an example the method compareAnSet takes 3 parameters, the first one is
> typed with the class that declare the field and the two other parameters are the type of the field. It should be typed using generics and the real issue is that generics are erased so what we need is a runtime check that guarantee that even if someone trick the java type system it will fail at runtime.
> Using a signature polymorphism has its own issues, null is not typed correctly (BTW, the compiler should raise a compile time error if the type of null is used to match a polymorphic signature instead of using Void, my bad on that) and subtype fail to work properly (it's valid to have the type of the expected value and the type of the future value to be different in a compareAndSet).

Yes.  All that is true.  It would be far better to have specialization now (including parameter bounds of 'any') to build the necessary types.
Using sig-poly methods like we are doing now does not play well with static typing.

(I disagree about the value of null:Void though I see your point.  The value sets of the two types are identical:  {null}, so one can be a safe proxy for the other, at least in these limited circumstances.)

> Then I fail to see why it can not be built on top of MethodHandle instead of on top of LambdaForm.
> 
>> It seems to me there are serious usability problems with using MHs to build a VH API.  Given this:
>>    VarHandle vh = ... reify ObjType.fld ...;
>>    SomeType v0 = ..., v1 = ...;
>> 
>> we have:
>>    boolean z = vh.compareAndSet(obj, v0, v1);
>> 
>> versus:
>>    boolean z;
>>    try { z = vh.compareAndSet().invokeExact(obj, v0, v1); }
>>    catch (Exception e) { throw new AssertionError(e); }
> 
> Technically the compareAndSet implemented using a method handle can fail at runtime if obj is null or if a lambda form can be allocated,
> so the code that use a method handle is even worst:
> 
>   boolean z;
>   try { z = vh.compareAndSet().invokeExact(obj, v0, v1); }
>   catch (Throwale e) {
>     if (e instanceof RuntimeException) throw (RuntimeException)e;
>     if (e instanceof Error) throw (Error)e;
>     throw new AssertionError(e);
>   }

(Cue our other discussion: https://bugs.openjdk.java.net/browse/JDK-8051294 )

> here, I agree that we need some sugar, but neither necessarily some syntactic sugar nor a new kind of beast,
> just a small wrapper on top of a method handle can be enough.
> Something along :
> 
>  private static MethodHandle compareAndSet(Lookup lookup, Class<?> declaringClass, String fieldName, Class<?> fieldType)
>        throws NoSuchFieldException, IllegalAccessException {
>    MethodHandle getter = lookup.findGetter(declaringClass, fieldName, fieldType);
>    MethodHandleInfo methodHandleInfo = lookup.revealDirect(getter);
>    Field field = methodHandleInfo.reflectAs(Field.class, lookup);
>    long offset = UNSAFE.objectFieldOffset(field);
>    MethodHandle mh = MethodHandles.insertArguments(COMPARE_AND_SWAP_OBJECT, 1, offset);
>    return mh.asType(MethodType.methodType(boolean.class, declaringClass, fieldType, fieldType));
>  }
> 
>  public class VarHandle<T, U> {
>    private final @Stable MethodHandle compareAndSet;
> 
>    VarHandle(MethodHandle compareAndSet) {
>        this.compareAndSet = compareAndSet;
>    }
> 
>    public boolean compareAndSet(T object, U expected, U value) {
>      try {
>        return (boolean)compareAndSet.invoke(object, expected, value);
>      } catch (Throwable e) {
>        if (e instanceof RuntimeException) throw (RuntimeException)e;
>        if (e instanceof Error) throw (Error)e;
>        throw new AssertionError(e);
>      }
>    }
>  }
> 
> Notice the asType at the end of the method compareAndSet and that I use invoke and not invokeExact when calling the method handle.

The impact of this is that the MH itself "knows" the preferred type of the call, which is something.  But the user of the API is required to box any primitives ('int' to 'Integer'), and then widen all arguments to 'Object' (erasure to bound of T, U).  The call of the compareAndSet using erased types is then followed by a call to MH.invoke of the same erased types.  Then the MH has a chance to undo the damage two stack frames away, issuing casts and unboxing operations.

It is far better (and this is what the VH prototype does right) if the user of the API issues exactly the right calls in the first place, omitting boxing and up-casts.  If there's a simple and elegant way to push unconverted types through layers of method calls, I don't see it yet.

Your suggestion below is to annotate normal methods to make the compiler push through unconverted types through method calls into indy; it is promising, but completing the idea requires pushing method layering information into indy, so that it can do the equivalent of method specialization.  That looks like it's far beyond a tweak.

> And obviously, things can be a little better if VarHandle is a value type.
> 
>> And if we expose an intermediate MH, we would have to ensure that the MH would optimize away cleanly.
> 
> yes and the code above is worst because it suppose that invoke is optimized cleanly.
> 
>> 
>> The VH prototype allows signature polymorphism to leak into some new types.  If we adopt the prototype as is, we will have to specify that leakage in the JLS and JVMS.  My biggest problem with the prototype is that I don't see a clean way to do that yet.
>> 
>> We can, of course, expose a MH at the bytecode level, under some suitable sugar.  And we can control its allocation cost if we are willing to use an invokedynamic instruction; then we would have a VH metafactory to produce a MH for each operation, for each signature.
>> 
>> One cost of using indy is that we would need a JLS change to define the sugary stuff that calls for the instruction.  That leads us back toward some variation of "obj.fld.volatile.compareAndSet(v0, v1)" or "vh.volatile.compareAndSet(obj, v0, v1)".
> 
> No, indy doesn't mean automatically syntactic sugar, you can have indy on methods on an API.

Here's where we start having fun.

> It's better to teach javac a new annotation, let say @Invokedynamic, that you put on methods. javac will generate an invokedynamic with a constant method handle to the implementation of the method if it exist (non abstract) as bootstrap argument instead of an invoke* when calling those methods.

We could do that.  It would allow the user of the API to issue unboxed, narrowed types to the call.  The constant method handle would still have the boxed, widened types, unfortunately.

To push the unboxed, narrowed types all the way into the invokeExact would require that javac somehow emit a recipe for specializing the implementation method to whatever types show up, at which point we are doing this:
  http://cr.openjdk.java.net/~briangoetz/valhalla/specialization.html

That urges the dependency question:  Should Unsafe removal wait until we have method specialization?
And/or:  Is there a small subset of full method specialization, compatible with the full design, that would support VarHandle APIs?

> Unlike @PolymoprhicSignature, @Invokedynamic relies on the classical typechecking so no need to cast the result value and workaround the issues I have explained above.
> Yet, because it use invokedynamic, you can do all the typecheck you want inside the bootstrap method and doesn't pay those cost for each calls at runtime.

What you are suggesting sounds like @specialized in Scala, except wired through BSMs.  It requires something like a compiler post-pass to be run at link time, to follow the recipe for creating special versions of the user-visible API methods.

I like the idea about using classical type checking.  Really, it amounts to a very tricky implementation of normal method calls, fully compatible with the current JLS, but with boxing and up-casting operations elided from static code and commuting past the API calls themselves.

We would need an operator or optimization powerful enough to bubble the boxing and up-casting all the way into the MH.invoke call, through the bytecodes of the intermediate 'compareAndSet' method.  Crucially, the call site "invokevirtual MH.invoke(Object,Object,Object)" would have to be rewritten as "invokevirtual MH.invoke(t,u,u)" for the t,u bubbling through the method, at the given indy point.  If that can be done, and if the MH has the right type "boolean(t,u,u)" then the JIT will fold up the code properly.

The BSM must somehow get hold of the bytes of 'compareAndSet' and rework them to reorder the type conversions into MH.invokeExact, where they would vanish.


Put it this way:  Using a tightly typed indy allows static code to avoid committing to argument type conversions, and punts them into the callee (to be negotiated by the BSM).  Then, the MH.invoke call (if also tightly typed) delays argument type conversions, and if the original types are correct, the conversions disappear.  If there are methods in between ('compareAndSet' or unspecialized 'guardWithTest' combinators) then the arguments have to be loosened to 'Object' references, unless those methods can be specialized or otherwise optimized.

Brian, this story uses Object-typed values for customized type variables.  It applies box/unbox/cast operations according to present day rules.  I suppose it's a point in favor of using 'Object' as an erasure of 'any', and it points towards a formalism which reorders box/unbox/cast operations as a main idea behind specialization.

> 
>> If we had fully polymorphic argument list abstraction, we could define vh.compareAndSet(...) as a method which called invokeExact internally.  That is a much bigger thing than ad hoc signature polymorphism, but possibly with a global payoff.  Hard to do.  Maybe method specialization would help, in a more modest way, to do similar things.
>> 
>> It feels to me that we might go back for some sugar in the end, as a way of minimizing deep spec. complexity by targeting indy.
> 
> again, we don't need @PolymorphicSignature + indy, just classical typechecking + indy, this is far more simple.

...Assuming the big missing pieces noted above are simple also.

> 
>> 
>> But for now we are learning the most, the fastest, by using the VarHandle prototype.
> 
> I beg to disagree because I don't think that playing with something that use @PolymorphicSignature is the right way to see the problem.

At least it's a great way to see the back end of the problem:  generating and managing type-safe customized code for unsafe operations.  We would have to do this anyway even if we had all the language-level tricks you are assuming.  While we argue language and bytecode stuff, the JIT compiler folks are already working on cleaning up and optimizing the back-end paths.

— John


More information about the valhalla-dev mailing list