hg: valhalla/valhalla/langtools: Add experimental support for generic class literals

John Rose john.r.rose at oracle.com
Mon Aug 18 23:06:50 UTC 2014


On Aug 18, 2014, at 2:54 PM, John Rose <john.r.rose at oracle.com> wrote:

> Perhaps there's a lightweight way to hook into invokedynamic from a Java API in a way that would allow the BSM to fish through the call site and glean the instantiation information somehow at link time.

Here are a few more details which might make this line of thought clearer.

Suppose we have a stream-like API which provides instantiating (making, configuring) and execution.

class CoolAlgorithms {
  static <T> CoolAlgorithm<T> makeInstance(Supplier<T> p1);
}
interface CoolAlgorithm<T> {
  CoolAlgorithm<U> configureMore(Function<T,U> p2);
  Collection<T> execute(Collection<T> p3);
}

A use would be:
  makeInstance(p1).configureMore(p2).execute(p3);

It is hard to tell by inspection which of the p1/p2/p3 should drive generation of customized code.  In general, they are sub-objects which contain both types of information.  (A closure's code part is often profitable to customize on but its data part is often not, unless the data part is a further closure.  And a stream pipeline acts like a closure in this respect, in its whole basic structure, but the initial data source should not be customized, unless it is a one-off and monstrous in size.)

Such a call structure, even a nested one, could be hoisted into an invokedynamic call site.  It would be processed by a bootstrap method.  At that point it would be profitable to have some way to signal which parts were static and which were dynamic, and (if the call were nested) so on recursively.

The invokedynamic version of such a thing can be introduced in stages which correspond to increasingly complex BSMs.

The simplest is almost a no-op.  Suppose we have a one-method API.  (Later we can expand to ones like the first API above.)

class MyFactory {
  @Indy(StandardJavaInvocation.class)
  static MyWidget<T,U> makeMe(T p1, U p2);
}

use: makeMe(p1, p2);
//not: invokestatic MyFactory.makeMe(Object,Object)
//but:
invokedynamic makeMe(TYPEOF(p1),TYPEOF(p2))
with bsm: StandardJavaInvocation.metafactory
with args: { MethodHandle(MyFactory#makeMe) }
where:
  static ConstantCallSite StandardJavaInvocation.metafactory(Lookup ignore1, String ignore2, MethodType type, MethodHandle target) {
    return new ConstantCallSite(target.asType(type));
  }

The BSM would simply wire up the given method handle to the call site, and call it done.  Note that javac would retain full control over the call site, and the behavior would be indistinguishable from the standard (annotation-free) behavior.  The BSM doesn't do much, except affirm the type of the call site.

The only customization would be narrowing the target method ("makeMe") to the actual types ("TYPEOF...") that were reported by javac.  (If javac reports "(Object, Object) -> MyWidget" as the types, then the asType call would have no effect.)  It is interesting to think of the possible customization effects just from the "asType" call, especially if the types are rich (like classdynamic types).

Further steps would involve attempts to move more information about the arguments into the BSM.  For example:

class MyFactory {
  @Indy(CustomizingJavaInvocation.class)
  static MyWidget<T,U> makeMe(@Customize T p1, U p2);
}

use: makeMe("p1string$SD", p2);
invokedynamic makeMe(TYPEOF(p2))
with bsm: CustomizingJavaInvocation.metafactory
with args: { MethodHandle(MyFactory#makeMe, "p1String") }

The metafactory would somehow determine that p1 was a static argument, and p2 a dynamic one.  (The "$SD" hints at this.)  The static argument would be inserted (using bindTo or insertArguments) and the call site created.  Here there would be more scope for customized compilation of the MH that is specific to the call site.

Or:

use: makeMe((x)->(x+p1a), p2);
invokedynamic makeMe$ED(TYPEOF(p1a),TYPEOF(p2))
with bsm: NestedCustomizingJavaInvocation.metafactory
with args: { MethodHandle(MyFactory#makeMe, Caller#$p1Expander) }

Here, the nested expression would be handled at link time by some side method ($p1Expander) generated appropriately by javac.  Note that the dynamic parts of the expression could be passed directly to the invokedynamic as a flattened argument list, as long as the BSM was given enough information about parcelling out the arguments to the appropriate subexpressions.  There are lots of ways to vary this theme; the examples here are just enough to suggest the possibilities.

All of this presumes some sort of link-time customize operation which would "burn in" the static arguments before any dynamic arguments were processed.

Note that this kind of stuff can be useful even if the constraint is maintained that the user's program must execute the same as if it did not have the annotations (though the annotations may affect execution speed).

In some cases, the BSM may need to "crack" the direct method handles handed to it, to determine things like parameter annotations.  A very aggressive BSM may even want the internal bytecodes for some reason; we don't have an API for that yet.

The details are necessarily complicated, amounting to a two-phase programming model like templates.  But the above sketches show that we could make a surface notation for such a thing that would amount to a small, high-leverage, initially conservative cut into Java method call semantics, and no cuts at all in syntax or name resolution.

You can file this under "ways we might use indy to edit call sites to annotated methods", and under "when worrying about customization, concentrate on link-time, and let the syntax take care of itself."  Also under "possible use for reflection on per-method bytecodes".

Cheers,
— John


More information about the valhalla-dev mailing list