Re: Evolving the wrapper classes
> On Jun 19, 2020, at 2:44 PM, fo...@univ-mlv.fr wrote: > > If the VM see I as a subtype of Object, there is no need for a > Ljava/lang/Integer$val; at all, it's better to have aload/astore/etc to work > on I directly. That's initially somewhat attractive, but think about the implications for things like slot size. Primitives and inline objects are really two very different things in the current JVM design.
Re: Evolving the wrapper classes
> On Jun 19, 2020, at 1:51 PM, fo...@univ-mlv.fr wrote: > > covariant return type > interface I { > int foo(); > } > interface J { > Object foo(); > } > class A implements I, J { >int foo(); > } > > with I.java compiled a long time ago. Nothing in this proposal changes how these classes are compiled. The following methods will exist: I.foo()I J.foo()Ljava/lang/Object; A.foo()I A.foo()Ljava/lang/Object; // bridge I think you may be mixed up thinking that we'll sometimes translate 'int' in a descriptor to 'Ljava/lang/Integer$val;', but that's not the case. 'I' is preferred wherever possible.
Re: Evolving the wrapper classes
- Mail original - > De: "daniel smith" > À: "Remi Forax" > Cc: "Brian Goetz" , "valhalla-spec-experts" > > Envoyé: Vendredi 19 Juin 2020 21:27:12 > Objet: Re: Evolving the wrapper classes >> On Jun 19, 2020, at 12:54 PM, Remi Forax wrote: >> >>> Note that [I and [QInteger$val have the exact same layout, so it is really a >>> matter of treating the two type names as referring to the same underlying >>> runtime type. >> >> yes, but at the same time descriptor are matched by name and you need to have >> the proper descriptor when overriding/implementing a method, >> so the strategy of blindly replacing every I by QInteger$val; doesn't really >> work. >> >> Usually the solution is to use bridges but bridges only work with subtyping >> relationship not equivalence relationship (because you can travel in both >> direction). >> I believe we need to bring the forward/bridge-o-matic at the same time we >> retrofit primitive to inline. > > In the VM this is mostly a verification problem: have a > '[Qjava/lang/Integer$val;', need a '[I'? You're good! ("Mostly", because there > is still the matter of ensuring there's a single encoding for both kinds of > objects, or that the instructions are capable of handling two different > encodings.) > > I'm not sure we'd get into any situations where a '([I)V' descriptor needs to > override a '([Qjava/lang/Integer$val)V' descriptor, or vice versa, until we > get > to specialization, covariant return type interface I { int foo(); } interface J { Object foo(); } class A implements I, J { int foo(); } with I.java compiled a long time ago. > and then I'm not sure this is any different than other forms > of bridging. All existing code will continue to use 'I' in its compiled > descriptors. if everything is compiled at the same time, there is no issue, otherwise you can create a loop. Rémi
Re: Evolving the wrapper classes
> On Jun 19, 2020, at 12:54 PM, Remi Forax wrote: > >> Note that [I and [QInteger$val have the exact same layout, so it is really a >> matter of treating the two type names as referring to the same underlying >> runtime type. > > yes, but at the same time descriptor are matched by name and you need to have > the proper descriptor when overriding/implementing a method, > so the strategy of blindly replacing every I by QInteger$val; doesn't really > work. > > Usually the solution is to use bridges but bridges only work with subtyping > relationship not equivalence relationship (because you can travel in both > direction). > I believe we need to bring the forward/bridge-o-matic at the same time we > retrofit primitive to inline. In the VM this is mostly a verification problem: have a '[Qjava/lang/Integer$val;', need a '[I'? You're good! ("Mostly", because there is still the matter of ensuring there's a single encoding for both kinds of objects, or that the instructions are capable of handling two different encodings.) I'm not sure we'd get into any situations where a '([I)V' descriptor needs to override a '([Qjava/lang/Integer$val)V' descriptor, or vice versa, until we get to specialization, and then I'm not sure this is any different than other forms of bridging. All existing code will continue to use 'I' in its compiled descriptors.
Re: Evolving the wrapper classes
Blindly is perhaps a word too strong, let say we have to come with a plan, a good plan, and i fail to see how it can work with only the current bridge mechanism we have. Rémi > De: "Brian Goetz" > À: "Remi Forax" > Cc: "Tobi Ajila" , "valhalla-spec-experts" > , "valhalla-spec-experts" > > Envoyé: Vendredi 19 Juin 2020 20:59:35 > Objet: Re: Evolving the wrapper classes >> yes, but at the same time descriptor are matched by name and you need to have >> the proper descriptor when overriding/implementing a method, >> so the strategy of blindly replacing every I by QInteger$val; doesn't really >> work. > Who said blindly?
Re: Evolving the wrapper classes
I hope to surprise you positively! On 6/19/2020 3:09 PM, fo...@univ-mlv.fr wrote: Blindly is perhaps a word too strong, let say we have to come with a plan, a good plan, and i fail to see how it can work with only the current bridge mechanism we have.
Re: Evolving the wrapper classes
yes, but at the same time descriptor are matched by name and you need to have the proper descriptor when overriding/implementing a method, so the strategy of blindly replacing every I by QInteger$val; doesn't really work. Who said blindly?
Re: Evolving the wrapper classes
> De: "Brian Goetz" > À: "Tobi Ajila" > Cc: "valhalla-spec-experts" , > "valhalla-spec-experts" > Envoyé: Vendredi 19 Juin 2020 20:18:09 > Objet: Re: Evolving the wrapper classes > Zooming out, what we've been trying to do is shake out the places where the > JVM > treats primitives and references differently, and aligning them, so that we > are > able to broaden the approach of "generics erase T to Object" to include > inlines > and primitives. The war cry might be: > Object is the new Any > L-World does much of this for inlines, but we don't want to leave primitives > out > in in the cold in the programming model; being able to get good behavior for > Foo but not the same for Foo would be a missed opportunity to > provide a uniform programming model. Much of this is either handled by > existing > L-World behavior (e.g., behavior of ==), but this seam is one that needs to be > covered. We can cover some in the static compiler (conversions between I and > Qint) but when it comes to arrays, the invariance of arrays would expose our > tricks, and we'd have to have awful restrictions like "you can't use arrays in > generics." > Note that [I and [QInteger$val have the exact same layout, so it is really a > matter of treating the two type names as referring to the same underlying > runtime type. yes, but at the same time descriptor are matched by name and you need to have the proper descriptor when overriding/implementing a method, so the strategy of blindly replacing every I by QInteger$val; doesn't really work. Usually the solution is to use bridges but bridges only work with subtyping relationship not equivalence relationship (because you can travel in both direction). I believe we need to bring the forward/bridge-o-matic at the same time we retrofit primitive to inline. Rémi > On 6/19/2020 1:07 PM, Tobi Ajila wrote: >>> Because arrays have identity (not to mention potentially large copying >>> costs), >>> there is simply no reasonable conversion we can define; any "conversion" >>> would >>> involve copying all the data, changing identity, or both. Just as with the >>> array subtyping requirements (Point[] <: Point.ref[] <: Object[]), these are >> > things only the VM can do for us. >> I suspected that this was likely due to the large cost of converting between >> `[I` and `[java/lang/Integer$val`. However, I am still a little unclear as to >> what the motivation is for this. Is this solely for specialized generics? >> In Dan's examples with `I` and `java/lang/Integer$val`, the only places where >> conversions are needed are when primitives are used as type parameters or to >> call instance methods on them, both of which can already be done with >> primitive >> arrays. So in the LW3 - LW20 timeframe would we have any need for these >> conversions? If so, could you provide some examples? >> In the case of specialized generics, is the intention that `[I` (and I >> suppose >> `I` as well) will appear in generic code?
Re: Evolving the wrapper classes
Zooming out, what we've been trying to do is shake out the places where the JVM treats primitives and references differently, and aligning them, so that we are able to broaden the approach of "generics erase T to Object" to include inlines and primitives. The war cry might be: Object is the new Any L-World does much of this for inlines, but we don't want to leave primitives out in in the cold in the programming model; being able to get good behavior for Foo but not the same for Foo would be a missed opportunity to provide a uniform programming model. Much of this is either handled by existing L-World behavior (e.g., behavior of ==), but this seam is one that needs to be covered. We can cover some in the static compiler (conversions between I and Qint) but when it comes to arrays, the invariance of arrays would expose our tricks, and we'd have to have awful restrictions like "you can't use arrays in generics." Note that [I and [QInteger$val have the exact same layout, so it is really a matter of treating the two type names as referring to the same underlying runtime type. On 6/19/2020 1:07 PM, Tobi Ajila wrote: > Because arrays have identity (not to mention potentially large copying costs), there is simply no reasonable conversion we can define; any "conversion" would involve copying all the data, changing identity, or both. Just as with the array subtyping requirements (Point[] <: Point.ref[] <: Object[]), these are things only the VM can do for us. I suspected that this was likely due to the large cost of converting between `[I` and `[java/lang/Integer$val`. However, I am still a little unclear as to what the motivation is for this. Is this solely for specialized generics? In Dan's examples with `I` and `java/lang/Integer$val`, the only places where conversions are needed are when primitives are used as type parameters or to call instance methods on them, both of which can already be done with primitive arrays. So in the LW3 - LW20 timeframe would we have any need for these conversions? If so, could you provide some examples? In the case of specialized generics, is the intention that `[I` (and I suppose `I` as well) will appear in generic code?
RE: Evolving the wrapper classes
Thanks for the example Dan, this "Object[] objs = arr; // just like Point[] <: Object[]" makes it very clear. Brian's response makes more sense to me now. > From: Dan Smith > To: Tobi Ajila > Cc: Brian Goetz , valhalla-spec-experts > , valhalla-spec-experts > > Date: 2020/06/19 01:32 PM > Subject: [EXTERNAL] Re: Evolving the wrapper classes > > > > On Jun 19, 2020, at 11:07 AM, Tobi Ajila wrote: > > > > I am still a little unclear as to what the motivation is for this. > Is this solely for specialized generics? > > > > In Dan's examples with `I` and `java/lang/Integer$val`, the only > places where conversions are needed are when primitives are used as > type parameters or to call instance methods on them, both of which > can already be done with primitive arrays. So in the LW3 - LW20 > timeframe would we have any need for these conversions? If so, could > you provide some examples? > > I think it comes down to specialization and subtyping. > > Pre-specialization, here's one example that uses subtyping: > > int[] arr = { 1 }; > Object[] objs = arr; // just like Point[] <: Object[] > Object obj = objs[0]; > Integer i = (Integer) obj; > > This would compile to something like: > > iconst_1 > newarray T_INT > dup > iconst_0 > iconst_1 > iastore > astore_0 > > aload_0 > astore_1 > > aload_1 > iconst_0 > aaload > astore_2 > > aload_2 > checkcast java/lang/Integer > astore_3 > > Going in the other direction—allocating a [Qjava/lang/Integer; and > then using iaload/iastore on it—may not be necessary unless/until > the language supports "new T[]" in specialized code, but it > tentatively makes sense to support now anyway, rather than having to > come back and fix it up later. > > > In the case of specialized generics, is the intention that `[I` > (and I suppose `I` as well) will appear in generic code? > > If you mean "can '[' be specialized to '[I'?", the answer is no. > The primitive types cannot act as type arguments.
Re: Evolving the wrapper classes
PSA: this thread has been polluted with the address: valhalla-spec-experts Which just generates admin notifications. Please delete that address from any replies. :-)
Re: Evolving the wrapper classes
> On Jun 19, 2020, at 11:07 AM, Tobi Ajila wrote: > > I am still a little unclear as to what the motivation is for this. Is this > solely for specialized generics? > > In Dan's examples with `I` and `java/lang/Integer$val`, the only places where > conversions are needed are when primitives are used as type parameters or to > call instance methods on them, both of which can already be done with > primitive arrays. So in the LW3 - LW20 timeframe would we have any need for > these conversions? If so, could you provide some examples? I think it comes down to specialization and subtyping. Pre-specialization, here's one example that uses subtyping: int[] arr = { 1 }; Object[] objs = arr; // just like Point[] <: Object[] Object obj = objs[0]; Integer i = (Integer) obj; This would compile to something like: iconst_1 newarray T_INT dup iconst_0 iconst_1 iastore astore_0 aload_0 astore_1 aload_1 iconst_0 aaload astore_2 aload_2 checkcast java/lang/Integer astore_3 Going in the other direction—allocating a [Qjava/lang/Integer; and then using iaload/iastore on it—may not be necessary unless/until the language supports "new T[]" in specialized code, but it tentatively makes sense to support now anyway, rather than having to come back and fix it up later. > In the case of specialized generics, is the intention that `[I` (and I > suppose `I` as well) will appear in generic code? If you mean "can '[' be specialized to '[I'?", the answer is no. The primitive types cannot act as type arguments.
RE: Evolving the wrapper classes
> Because arrays have identity (not to mention potentially large copying costs), there is simply no reasonable conversion we can define; any "conversion" would involve copying all the data, changing identity, or both. Just as with the array subtyping requirements (Point[] <: Point.ref [] <: Object[]), these are things only the VM can do for us. I suspected that this was likely due to the large cost of converting between `[I` and `[java/lang/Integer$val`. However, I am still a little unclear as to what the motivation is for this. Is this solely for specialized generics? In Dan's examples with `I` and `java/lang/Integer$val`, the only places where conversions are needed are when primitives are used as type parameters or to call instance methods on them, both of which can already be done with primitive arrays. So in the LW3 - LW20 timeframe would we have any need for these conversions? If so, could you provide some examples? In the case of specialized generics, is the intention that `[I` (and I suppose `I` as well) will appear in generic code?
Re: Evolving the wrapper classes
> Given your examples can we assume that the JVM will never need to do an > implicit `Qjava/lang/Integer$val;` to `I` conversion? And these will always > be explicit conversions performed by javac? > Correct. > > - The type [I is considered by the verifier to be equivalent to > > [java/lang/Integer$val. Array operations (aaload, iaload, etc.) support > > this. > > Could you please explain the motivation behind this? Specifically, in which > cases are iaload and aaload operations both performed on `[I` ? > > If `I` and `Qjava/lang/Integer$val;` will require explicit javac conversions, > shouldn't `[I` and `[java/lang/Integer$val` also? > Because arrays have identity (not to mention potentially large copying costs), there is simply no reasonable conversion we can define; any “conversion” would involve copying all the data, changing identity, or both. Just as with the array subtyping requirements (Point[] <: Point.ref[] <: Object[]), these are things only the VM can do for us.
RE: Evolving the wrapper classes
Hi Dan S. >>> - Where necessary (depending on the operations being performed), the compiler generates conversions between 'I' and 'java/lang/Integer$val'. 'I' is preferred wherever possible. >> >> We have to use QInteger$val whenever we use int as a type parameter, the rest of the time, we can use I. ... > (Note that none of these conversions are "boxing" or "unboxing". They're strictly compilation artifacts. It may be useful to come up with a new word for them.) Given your examples can we assume that the JVM will never need to do an implicit `Qjava/lang/Integer$val;` to `I` conversion? And these will always be explicit conversions performed by javac? > - The type [I is considered by the verifier to be equivalent to [java/lang/Integer$val. Array operations (aaload, iaload, etc.) support this. Could you please explain the motivation behind this? Specifically, in which cases are iaload and aaload operations both performed on `[I` ? If `I` and `Qjava/lang/Integer$val;` will require explicit javac conversions, shouldn't `[I` and `[java/lang/Integer$val` also? Regards, --Tobi
Re: Evolving the wrapper classes
> On Jun 17, 2020, at 4:30 PM, Kevin Bourrillion wrote: > >> - The type 'Integer.val' is equivalent to 'int'. Primitive types are inline >> types—they have members, support method invocation, etc. >> > This at least suggests that `42L.hashCode()` would begin to work just as > `"foo".hashCode()` does? Yep, that's what I mean. Member accesses are now allowed (assuming the member you're looking for exists) for all types. > Users can write `Integer.val` in their code, but would there ever be a good > reason to? I assume we would always prefer `int`. And this actually makes me > wonder if it's worth considering also allowing `int.ref` to be an alias for > `Integer` because it would allow users to drop the word `Integer` from their > code more completely, and therefore `int` would look more and more like it > was just an inline type like any other. It reminds you that the old > boxing/unboxing isn't in play anymore. And `int.ref` is more self-evidently > something you can't synchronize on, etc. But, what would remain weird is that > you don't actually find a val-default class called `int` sitting in an > `int.java` file. Agreed, I think it would be reasonable to consider both i) supporting 'int.ref' as type syntax, and ii) prohibiting 'Integer.val'. Although, redundancies aside, if you have some discomfort with bare 'Integer' being a reference type for an inline class, you may have similar discomfort with our "reference default" story in general (e.g., 'LocalDateTime' will have the same properties).
Re: Evolving the wrapper classes
Hmm, just maybe this will be less confusing than I was fearing. I'm seeing now that "Integer is the real class, int is alias for Integer.val" is a whole lot cleaner than "int becomes a val-default class and Integer is demoted to alias for int.ref", which for some reason was the way I was thinking of it. I'm sure that, sometime during the evolution, I probably said it the way you remember. Then Dan came along and cleaned it up :) Java language/compiler changes (when --enable-preview is set): - The class file reader knows how to find the special Integer.class and Integer$val.class. - The type 'Integer.val' is equivalent to 'int'. Primitive types are inline types—they have members, support method invocation, etc. This at least /suggests /that `42L.hashCode()` would begin to work just as `"foo".hashCode()` does? We certainly have that option. We could decide to not take it, on the theory that it scares the neighbors, but it does seem sensible to just say "0L is a long-valued expression" and "long implements XYZ interfaces", and let the neighbors be scared for a few minutes. Users /can/ write `Integer.val` in their code, but would there ever be a good reason to? I assume we would always prefer `int`. And this actually makes me wonder if it's worth considering also allowing `int.ref` to be an alias for `Integer` because it would allow users to drop the word `Integer` from their code more completely, and therefore `int` would look more and more like it was just an inline type like any other. It reminds you that the old boxing/unboxing isn't in play anymore. And `int.ref` is more self-evidently something you can't synchronize on, etc. But, what would remain weird is that you don't /actually /find a val-default class called `int` sitting in an `int.java` file. Right, there's a set of pros and cons here, none of which are technical. Being able to say `int.ref` makes it more clear that `int` and `Point` are the same thing, but on the other hand, it raises issues of "there are two ways to say the same thing, so let's have an endless debate about it."
Re: Evolving the wrapper classes
> On Jun 17, 2020, at 4:13 PM, Brian Goetz wrote: > >> - Where necessary (depending on the operations being performed), the >> compiler generates conversions between 'I' and 'java/lang/Integer$val'. 'I' >> is preferred wherever possible. > > We have to use QInteger$val whenever we use int as a type parameter, the rest > of the time, we can use I. Right. Specifically, if we support inline types as type arguments before we get to specialization, we'll use erasure, which looks like: new java/util/ArrayList; // dup, init astore 1 aload 1 iconst_0 invokestatic Qjava/lang/Integer$val;.(I)Qjava/lang/Integer$val; // <-- conversion invokevirtual java/util/ArrayList.add:(Ljava/lang/Object;)Z pop aload1 iconst_0 invokevirtual java/util/ArrayList.get(I)Ljava/lang/Object; checkcast Qjava/lang/Integer$val; invokevirtual Qjava/lang/Integer$val;.intValue()I // <-- conversion And then there's also instance method invocations: iconst_0 invokestatic Qjava/lang/Integer$val;.(I)Qjava/lang/Integer$val; // <-- conversion invokevirtual Qjava/lang/Integer$val;.floatValue()F (Note that none of these conversions are "boxing" or "unboxing". They're strictly compilation artifacts. It may be useful to come up with a new word for them.)
Re: Evolving the wrapper classes
This is pretty much what I was expecting. A few comments: Step 1: Warnings A dynamic warning, initially only activated by opt-in, when someone attempts to synchronize on an instance of a wrapper class. There's a changeset in review now for 16. - Where necessary (depending on the operations being performed), the compiler generates conversions between 'I' and 'java/lang/Integer$val'. 'I' is preferred wherever possible. We have to use QInteger$val whenever we use int as a type parameter, the rest of the time, we can use I. Library changes: - The constructors are removed, replaced with private constructors. This can happen earlier if we want; we can just remove them (after suitable DFR.) There are factories.
Evolving the wrapper classes
Here's a concrete proposal for how we'll evolve the wrapper classes (Byte, Short, Integer, Long, Float, Double, Character, and Boolean) to be inline classes whose ".val" representations are (in the Java language) the primitive types. This has the effect of replacing boxing conversions in the Java language with lighter-weight reference conversions (no identity is imposed), and will facilitate specialization in the JVM by wrapping primitive values in lightweight inline class instances. Important concepts in this approach: - The wrapper classes are reference-default classes—'Integer' is a '.ref' type. - In the language, 'int' is an alias for 'Integer.val'. These are the same type. - In the JVM, there are three distinct types: 'Ljava/lang/Integer;', 'Qjava/lang/Integer$val;', and 'I' The below outline feels pretty complete to me, as far as the core library/JVM/compiler components are concerned, and quite achievable. Please raise anything I'm overlooking (I'm sure there's something...). --- Step 1: Warnings In the near future, we implement a variety of warnings for clients of the wrapper classes who rely on features that will break when the wrapper classes are inline classes: Library changes: - The constructors, currently marked deprecated, are deprecated for removal. This should amplify warnings about their use. Java compiler changes: - Attempts to synchronize on or invoke wait/notify methods of expressions with wrapper class static types produce a new warning. - Possibly, uses of '==', 'identityHashCode', or 'clone' on these expressions produce a warning. - Possibly, any uses of 'getClass' that compare with '==' to wrapper class literals produce a warning. JVM changes: - Possibly, runtime warnings occur mimicking some of the compiler warnings, but using runtime types. (Note that all of these warnings may also apply to Value-based Classes. The wrapper classes happen to fall short of the value-based class requirements in their factories' guarantees about identity; these rules about factories and equality are probably unnecessary limitations, given the current deterministic behavior of acmp.) --- Step 2: Preview Feature When, or sometime after, we ship inline classes as a preview feature, we support treating the wrapper classes as inline. JVM changes (when --enable-preview is set): - References to java/lang/Integer and java/lang/Integer$val are hacked to load special class files corresponding to the .ref and .val types of inline class Integer. - The type [I is considered by the verifier to be equivalent to [java/lang/Integer$val. Array operations (aaload, iaload, etc.) support this. Java language/compiler changes (when --enable-preview is set): - The class file reader knows how to find the special Integer.class and Integer$val.class. - The type 'Integer.val' is equivalent to 'int'. Primitive types are inline types—they have members, support method invocation, etc. - Where necessary (depending on the operations being performed), the compiler generates conversions between 'I' and 'java/lang/Integer$val'. 'I' is preferred wherever possible. - Boxing can be specified with stronger guarantees about '=='. --- Step 3: Standard Feature When we're ready to leave preview, we'll need to raise the profile of "those things we warned about are going to blow up now!". Library changes: - The wrapper classes are declared in source as reference-default inline classes. - The constructors are removed, replaced with private constructors. JVM changes: - Wrapper classes are loaded using standard processes. Java language/compiler changes: - The wrapper classes have special permission to declare fields of their own type. - Wrapper classes are read using standard processes.