VM model and aconst_init
Brian Goetz
brian.goetz at oracle.com
Tue Jan 25 14:57:17 UTC 2022
The motivation for this comes from erasure. Even if we specialize, we need to be able to use specialized generics in an erased context, because there will be erased clients. If we have
class Foo<T> {
T m();
T[] arr();
}
we can use also sorts of bridging/casting tricks to make Foo<int> work with the erasure of T to Object, but we’ve got much less latitude when we want to treat an int[] as an Object[]. Since arrays are mutable / identity objects, we can’t copy them even if we wanted to; we need an identity-preserving way to say “this int[] array, as if it were an Object[]”. Rejigging array covariance to work over the “extends” relation rather than the “subtype” relation seems the least damaging way to get there.
On Jan 25, 2022, at 9:40 AM, Dan Heidinga <heidinga at redhat.com<mailto:heidinga at redhat.com>> wrote:
Can you mix and match both modes in the same method?
Probably, since the interpreter doesn’t care about
multi-bytecode patterns. Dunno if this causes a testing
problem, and if so how to fix it. I think it’s probably
OK, especially if we require the two-way checkcast
(Q-Foo not a subtype of L-Foo in the verifier) so that
each mode stays “in its own lane”.
More explicitly, this is a set of use cases for using
Q-types in C_Class entries in the constant pool to switch
to Q-mode for bytecodes that refer to classes, including
withfield and aconst_init.
Let's talk a bit about having the L world and the Q world completely disjoint at least from the bytecode verifier POV.
It means that we need some checkcasts to move in both direction, from a Q-type to a L-type and vice-versa.
But at the same time, an array of L-type is a subtype of an array of Q-type ?
The result to a very uncommon/unconventional type system, and i'm not a big fan of surprises in that area.
I've been puzzling over this as well and echo your discomfort with it,
mostly on the array side. I haven't been able to identify what the
sharp edge here is though other than that it feels surprising.
After playing with bytecode sequences, the part I'm not clear on is
whether I can store an LFoo; into a [QFoo; directly, or do I need a
checkcast QFoo; before the aastore? If I need the checkcast (and I
think I do), then I'm starting to come around to the view that the
array side isn't actually any different from what we'd do for any
other subclass relationship. The checkcast for Q->L is still odd, but
less concerning as it deals with new value semantics rather than
changing the array covariance?
For reference, the bytecodes sequences I've been looking at are the following:
Convert with checkcast:
--------------------------------
aload_1
checkcast QFoo; // or LFoo;
and
Convert with Q->L array store/load:
---------------------------------------
anewarray //LFoo
astore_2
aload_2
iconst_0
invokestatic QFoo.<new>QFoo; // or any other way to get a Q
aastore
aload_2
iconst_0
aaload // use as an LFoo
Convert with L->Q array store/load:
---------------------------------------
anewarray //QFoo
astore_2
aload_2
iconst_0
invokestatic X.getFoo:()LFoo;
// Is a checkcast needed here first to downcast? I think so
aastore
aload_2
iconst_0
aaload // use as an QFoo
--Dan
Furthermore, i believe that subtyping is a key to avoid multiple bytecode verification of the generics code.
By example, with the TypeRestriction attribute [1], the restriction has to be subtype of the declared type/descriptor.
Rémi
[1] https://cr.openjdk.java.net/~jrose/values/parametric-vm.html#type-restricted-methods-and-fields-and-the-typerestriction-attribute
More information about the valhalla-spec-observers
mailing list