specializing generic methods

Dan Smith daniel.smith at oracle.com
Fri Aug 29 22:11:34 UTC 2014


On Aug 29, 2014, at 2:42 PM, Maurizio Cimadamore <maurizio.cimadamore at oracle.com> wrote:

> Hi,
> as you can see from recent commits, we are starting to tackle the problem of specialized generic method calls; Brian and I were thinking about the following corner-case:
> 
> class Box<any T> {
>     T t;
> 
>     static <any U> Box<U> make(U u) { return null; }
> }
> 
> 
> class Client {
> 
>     <any Z> void testAny(Z z) {
>         Z z2 = Box.make(z).t;
>     }
> 
>     <Z> void testNonAny(Z z) {
>         Z z2 = Box.make(z).t;
>     }
> }
> 

> 3) Consider the case where the first method is called as follows:
> 
> testAny(1)
> 
> How is the specializer supposed to specialize the body of the generic 'testAny' method? Does this involve replaying inference where Z=int?

testAny is compiled to a template.  Pseudocode:

<*> void testAny(*) {
  load* z
  invokestatic Box.make:(*)Box<*>
  getfield Box<*>.t:*
}

The specializer substitutes asterisks for the appropriate type/instruction, and we're good.

On the role of inference: the purpose of inference is to determine that the type being invoked is "Box.make(*)Box".  That inference result is embedded in the bytecode.  At runtime, all that happens is the type argument of testAny is "applied" as a type argument to Box.make (and the mechanism for doing this is specialization).

> 3) Our current thinking is that a simple type substitution all we need - i.e. we see that the inferred return type is Box<Z> and we replace Z for int, obtaining Box<int> - which is then mangled into Box${0=I}. Can you think of cases where this approach would be insufficient - i.e. where replaying inference would lead to a substantially different result w.r.t. the one obtained through substitution?

"Replaying inference would lead to a substantially different result" is not a problem.

I'll observe that inference only does two things with an 'any' type variable (I think): i) leave it as a placeholder in a reference type that appears in a bound, and ii) equate it with an ivar.  With those constraints, I don't think there's anything you could do to manually specialize a well-typed invocation so that inference would make different choices.  Not absolutely sure, but I can't think of anything.

(Restricting myself to non-overloaded methods, anyway.  Start overloading, and of course you can trigger different overload results with specialization.)

Expand the abilities of inference to start dealing with things like numeric conversions, and you could start to see differences:

<any Y> Y m(Y arg1, Y arg2);
<numeric X> void test1(X x, float f) { m(x, f); } // infers Y = double
void test2(int x, float f) { m(x, f); } // infers Y = float

But, back to my first sentence: who cares?  If the bytecode says that test1 invokes 'm(double)double', then the specialized code _should not_ mimic test2.  It should continue to invoke 'm(double)double' instead.

Java is designed so that information decided at compile time is embedded into the IR.  This kind of thing happens everywhere in the value arguments/parameters world (paralleling type arguments/parameters).  As argument application happens and complex expressions reduce to simpler expressions, we don't re-compute compile-time information.

E.g.

class Parent { private int x; }
class Child extends Parent { private String x; static Child instance = new Child(); }

int consume(Parent arg) {}
String consume(Child arg) {}

Parent getVal() { return Child.instance; }

int i = getVal().x; // ok
int i = Child.instance.x; // error
int i = consume(getVal()); // ok
int i = consume(Child.instance); // error

Moral: we should not expect to be able to throw away compile-time information, recompute it, and get the same answer after the program has been partially evaluated.  ("Partially evaluated" includes both value argument and type argument application.)

—Dan


More information about the valhalla-dev mailing list