Q-types are dead, long live non-null side attributes !
Remi Forax
forax at univ-mlv.fr
Wed Jan 11 13:23:45 UTC 2023
Hi everybody, i would like to propose a slightly different semantics for Valhalla which is more backward compatible than the current one and that follows the vision of Brian that a non null zero-default value type is equivalent to what we currently what we call the .val mirror of a primitive class.
Why do we need Q-types ?
(1) to indicate that a field can be flattened,
(2) to indicate that a method parameter can be called "by value",
(3) to indicate that an array can be flattened,
(4) to indicate the type argument of a universal generics.
What is the problem with a Q-type ?
Sadly we do not have all these benefits without paying a price.
- a Q-type encodes compile time information not runtime information, so it does not support separate compilation
(especially if Q-type is not compatible with its equivalent L-type),
- a Q-type can not be erased. Erasure helps a lot adoptions when both use-site and declaration-site need to be updated to follow a new protocol.
I propose to encode the same information as a Q-type using a side attribute instead to avoid the problems raised by the Q-type notation.
A Q-type is use site notation equivalent to a L-type (a class name) + a non-null bit. I propose to store the non-null bit into a separate attribute associated to a field or a method declaration.
For cover the use-cases (1) and (2) but not (3) and (4).
In fact (3) and (4) are the same use-case, we need to create a specialized generics or an array specialized by a non-null type. In both case, the specialised array/generics is a runtime construction so we do not need a descriptor for it but a way to inject a runtime class of a non-null type (the secondary class as this is actually called in the prototype) as a descriptor. The parametric VM design proposed by John already propose such mechanism.
Using a side attribute (on methods and fields) to record the nullability information greatly simplify the classfile verifier, because the verifier does not need to be aware of the side attribute. A worst the VM will throw a NPE at runtime.
It makes the VM implementation slightly more complex because,
- when calling a method, the VM (the interpreter) needs to checks the side attribute and emits a NPE accordingly
- when storing a value inside a field, the VM needs to check the side attribute and if the field type is a zero-default value type at runtime and emits a NPE in that case (the VM already does the same thing for arrays).
The JIT has the same information as before,
- a field can be flattened if the type is zero-default value type at runtime and the non-null bit is set,
- an argument can be passed by value if the type is a zero-default value type at runtime and the non-null bit is set,
- a local variable of a zero-default value type at runtime + a nullcheck is scalarizable.
I've implemented a prototype (John asks for it) of this semantics on top of the LW5 prototype,
https://github.com/forax/civilizer
It uses annotations, both at declaration site (@Value and @ZeroDefault) and use site (@NonNull and @Nullable) + bytecode rewriting because it's easier than modifying the compiler and the VM.
- The nullchecks of the parameter is done by adding calls to Objects.requireNonNull() at the beginning of the methods,
- The nullcheck of the field is done by declaring it with a Q-type + rewriting the access to the field using invokedynamic, so the access are done using L-types.
Q-types are dead, long live non-null side attributes !
regards,
Rémi
More information about the valhalla-spec-experts
mailing list