Specialization statics
The problem
In the old world, there are two possible member placements: static and instance. Static members have traditionally been described as “members on the class”, and we all assumed we understood what “class” meant. However, “class” is a heavily overloaded term, and it’s starting to crack, e.g. with generic specialization deriving multiple runtime types from a single class file.
The meaning of static may take on one of the following meanings in the new world:
- Per type – All specializations get their own static members.
- Per class – All specializations share their static members.
There are use cases for both 1 and 2.
Use cases
1. Type variable related statics
In the old world, we knew that the erasure of a type variable was always its bound. This allowed us to create static members that would always be compatible with type variables, without referring to the type variable itself. For example:
class Foo<T> {
static Object someT;
}
In this example, Object is used instead of T, because T is not in the static scope. However, this is not possible when we introduce specialization, as we no longer have a common type for representing T.
This pattern is seen in use cases like Collections.emptyList().
2. Statics independent from type variables
There are also cases where the static is not related to the type variable information. One example is an instance counter, which the user may want to treat as common for all specializations of a class.
Solutions
Given that there are use case for both per-type and per-class static members, it appears that we need to support both member placements. Logically, there will be three places to put members:
- Class (all List)
- Runtime type (all List<int>)
- Instance (this List<int>)
This can be implemented in different ways with varying levels of support from the Java compiler, the JVM, and the JDK.
1. Library solution
We can place the statics on the class, and implement specialization statics using a pattern similar to ClassValue. The disadvantages are that it may have lower performance, it introduces more code changes when migrating to any-generics, and it is more complex to use in other JVM languages.
2. New specialization static placement
In this solution, we assume that specialization statics are not related to traditional class-wide statics. We fully embrace the new field placement, and support it both in the Java language and in the JVM. Specialization statics exist as its own set of members in the runtime type, and traditional statics continue to be class-wide. This will make the distinction between the different static types very clear, and it appears to be an acceptable solution. It does require an access flag on both fields and methods, but both have available bits.
The disadvantage is that it requires non-trivial JVM changes that would only be used by specialized types. The class structures in the JVM would have to represent the new placement as an additional set of fields. Getting, setting, and invoking these members requires additional bytecodes, which are a scarce resource.
3. Repurpose existing statics
In this solution, we assume that statics were in fact specialization statics all along; we just didn’t have specializations yet. The static placement will be used for specialization statics, and we specify a run-time type that will contain the static members we want to be common to all the specializations of a class. Currently, static members are always accessed on the raw type LFoo;. Given that the raw type will be interpreted to mean the erased type, the erased type seems like the natural type to hold the class-wide static members.
Accessing a class-wide static member will look like this:
class Foo<any T> {
static int i;
ACC_SPECIALIZED static int j;
}
Read i:
getstatic Foo.i:I
getstatic ParamType[Foo, erased].i:I
getstatic Foo.i:I
getstatic ParamType[Foo, erased].i:I
Read j:
getstatic ParamType[Foo, T].j:I
getstatic ParamType[Foo, T].j:I
The third placement still exists at the language level, and the compiler can use either conditional members or a new access flag to indicate when a static member is class wide. At the VM level, the access flag (or condition) is used by the specialization process to determine whether to include a static member in the current specialization.
Conclusion
Describing static members as “members on the class” was a result of the fact there was no distinction between classes and run-time types. This lack of distinction also means that we can change the description of static members to “members on the type” without breaking the old world.
In order to support the old static pattern (i.e. class-wide) on new any-generic classes, the language will support a mechanism that moves per-class statics to the erased type.
Bjørn Vårdal
IBM Runtimes
IBM Runtimes
