Valhalla EG notes Aug 29, 2018
Karen Kinnear
karen.kinnear at oracle.com
Fri Aug 31 15:24:52 UTC 2018
Attendees: Remi, Tobi, John, Dan Smith, Frederic, David Simms, Victor Rudometov, Karen
AI: Karen: send out ways in which value types can become null
AI: Dan: send out summaries/tradeoffs of nullability strategies
corrections/clarifications welcome
I. Value Type upcoming plans?
Remi would like to see a release without waiting for reified generics.
We are all in agreement about phasing:
proposing:
step 1: EA binaries that support erased generics
- need agreement on how to handle nullability
with nullable value types - to get feedback/validation on this approach
step 2: Preview with language support with erased generics
farther future: support for reified generics/generic specialization.
II. Nullability - the key open design issue for our next step
Assumptions we now all agree on: based in part from user feedback from LW1 - many thanks!
1. To support erased generics we need nullable value types
2. From a JVM perspective we need the default for value types to be the same as for Object, i.e. nullable,
i.e. LPoint; in a vm descriptor is nullable if Point is a value type.
Tradeoffs from the JVM level:
backward compatibility with erased generics: which requires nullable value types
JIT optimizations: need null-free value types
There are additional language-level tradeoffs such as exposure to syntactic complexity.
It is a language design choice what the nullability default is for value types at the source level.
Karen clarified that for LW1: javac rejected nulls for any value types, the jvm implementation decoupled value
type consistency from the nullability check and has runtime null checks. So we need to move beyond this
mismatch.
Remi:
Is nullability of value types declaration-site or use-site?
John:
Qualitative difference between local type checking and multiple compilation units. e.g. if nullability can not be constrained locally, it may require a dynamic check.
LW1 taught us:
1. fields and array elements: flattenable or not - decision needs to proceed regardless of the static knowledge of callers
2. Analogously, method calls, implementation artifacts such as vtables, need design constraint similar to heap storage,
i.e. need to make decisions regardless of static knowledge of callers
Generics breaks assumption that Value Types attribute can be used to assume that all value types are non-nullable.
Consider concept of Constraints on Descriptors.
We don’t want new descriptors with new rules.
We want LDescriptors with a rider which only added constraints, no expansions
LW1 showed us that runtime checks can cover many value type nullability checks. The ValueTypes attribute
does not cover generics or value-based-class migration.
Optimizations unlocked for value types (ed. note - that are not null)
- flattenable
- method calling convention
Both are global contracts
Need side channel information such as ValueTypes attribute or additional information on descriptor.
We need finer-grained nullability information to reduce the requirement on cross-compilation consistency.
“find the primitive” exercise:
How do we describe a reference to a value type as nullable or non-nullable
To reduce friction with generics and erasure - changed idea - need to have the default for new types be nullable.
There are also some wormholes in our value type consistency checking, e.g. MethodHandles and we need to reify
a global contract.
Propose that we reify a first class descriptor
- which is more future proof than QTypes
vision: Add constraints as riders to explicit types. This is extensible
This provides a predictable requirement and can be also used by specialized generics, for language design and
migration design.
Example for specialized generics: List<ComplexDouble> != List<ComplexFloat>
What if we continued to use List as an interface, and used a descriptor to add constraints
See Type Operators JEP (ed. note: JDK-8204937 )
We might want to phase this in
Frederic is also exploring a proposal to add suffices to type descriptors, e.g. for nullability
Karen: Note that a key benefit of using descriptors is the granularity they offer, not at the class file level,
that allows for JIT optimization and backward compatibility in the same file
Remi: would prefer a side table
Karen: need descriptor for field and method declaration and access
note: array creation will need enhancing
Frederic: anewarray today only has Class_info for the element, so needs extending
One approach is to extend a small number of bytecodes to operate either on a Class_info or on a reference
John: proposing extending Class_info to support descriptors
Remi: type annotation on code
e.g. want method attribute for bytecode
John: channel null-rejecting/null-accepting in
1) descriptor vs.
2) ValueTypes attribute
Descriptor is more localized
note: no one wants to use the type annotations approach
Descriptor approach:
extensible mechanism
Type with a tail: constraint only, restricts the value space, not extend
Verifier can check value space restrictions, and may allow some restricted->unrestricted conversions
Karen: let’s start with explicit conversions with exact matches for the verifier and evolve from there
John: generics did that, in future the verifier could handle no-op conversions
Remi: how would you express this in the java language?
John: this is a vm mechanism, that is a language choice
Remi: Would javac track nullability
John: A source processor that did not understand type constraints could rely on dynamic checks
Remi: concerns about leakage if stored in a local variable
John: Var x = myMap.get() // result is unconstrained
Dan: tentatively the type system may provide a way to express nullability
John: already expresses unconstrained
Remi: javac - objects are always nullable
John: language level take
- want javac to assume restrict nulls for value types unless evidence to the contrary, e.g. calls to erased generics that
semantically return null or explicit source decorations
- not clear what a local variable will denote
- might want inverted defaults for value type nullability at the source level
Remi: concern: erased generic returns null -> this could spread
checkcast could prevent null from propagating
John:
Clojure/Scala - have their own descriptor constraints
could decouple with indy on checkcast
Remi: value type and nullability as separate concerns
what about a reference type that is non-nullable from the vm point of view
John: nonnullable and value type might be the 1st use. We need an extensible mechanism
Remi: why not value type attribute
John: could explore value type as a descriptor extension
Remi: what if we solved this at the java level, if java wants a nullable VT, use erasure and checkcast?
John: Dan is exploring possible models
Getting user back pressure - don’t take away nulls
Liked the trick of erasing to object
lost the battle due to language requirements
Remi: if erase to Object/Interfaces, this is not backward compatible
Not forward compatible either with expectations of overloading in java language
Have overloading today because we don’t have Object as root for primitives and value types
What if we got a value from generics already erased and let java handle this?
Karen: we need to handle both erased generics and value-based-class migration
Remi: Create folks want value types, and want to retrofit existing classes - they don’t want to lose
sync and mutability.
ed. note: You can only pass-by-value if you have no identity and are immutable.
Karen: Descriptor contraints extensibility could provide additional use cases for optimization, such
as “frozen” or just identity less. We chose value types as a sweet spot that solves a number of use
cases and can be optimized.
Additional discussion on overloading
John: one goal is to migrate List<valuetype> to a reified generic. We could do dynamic checking and speculating,
it is more performant to do reified generics
The nullability contract of a container impacts the implementation of the value space
Remi: If map.get() were to return a value type, we don’t see the null until we publish or invoke a method
John: with the more complex descriptor model, getfield has fewer runtime checks
Karen: goals is to have information for javac, verifier and runtime so that javac can have the same contract for
nullability and NPEs that we have today for ClassCastExceptions - i.e. if your code compiles without warnings you will not get a runtime CCE should apply to NPEs (or ArrayStoreException)
Remi: concern about heap pollution
Guava has an unsafe cast to take a public static method()Object and return anything
John: we should distinguish between heap pollution and the far riskier other levels of unsafe
Remi: what if javac could handle erased generics without descriptors? And require a null check before a null
escapes
Karen: we would lose the value-based-class migration
John: If Remi is describing: if we only had the ValueTypes attribute and one descriptor and the language folks handled any null erased to Object, then we have to check for wormholes, such as MethodHandles or other cases in which the ValueTypes attributes differ.
Karen: I don’t see a language solution for value-based-class migration. Note also that there are multiple ways to get null, not just through bytecodes.
Remi: What if we had a small jdk-only list of corner cases for migration?
Karen: Dan has pointed out and I agree with him that many users are going to want the benefits of migrating to value types, and even in the JDK, there are already experiments beyond the small list of existing VBCs.
If we have a way to avoid corner cases that would be a much more maintainable design.
Remi: But if sync and == don’t work there is no way to migrate most classes. And if value-based-class properties are not enforced, there is no guarantee they are followed.
Karen: recognize that - we will be offering a flag to check identity - one for javac lint and one for runtime.
Karen: note also that for erased generics we have two null issues
1. returning null
2. passing array in and writing null
goal is a javac warning before a runtime NPE
Remi: Can we find a way to solve this at the language level, javac can give a warning but the vm doesn’t need to know
Kotlin has 2 types: nullable and nonnullable with compiler enforcement
Frederic: clarification Kotlin has references that are nullable or not, not types
John: Had been exploring using erasure for the nullable value type case and the ValueType attribute implying null.
Dan is exploring possible alternative translation strategies with different approaches to distinguish a non-nullable value type from an old erased value type
Remi: concern is that if we solve nullability here for value types, we may miss something for the bigger nullability picture.
.net got this wrong and requires too much complexity with little user benefit.
Longer-term: want non-nullable
Exploring how to solve value types and reified generics without involving the vm.
John: would be happy to defer a nullability vm feature until reified generics or after
Remi: wants longer-term non-nullability, e.g. for Strings, with vm optimizations
.net added too much source level line noise for little benefit
Karen: we need to handle nullability for value types for more than just erased generics. We have mentioned value-based-class migration. There are 8-9 different ways in which value types can become null at runtime.
AI: Karen - send nullability cases
Dan: Will send out a description of current options we are exploring to handle requirement for null-accepting and null-rejecting value types.
More information about the valhalla-spec-experts
mailing list