From john.r.rose at oracle.com Wed Dec 4 18:10:55 2019 From: john.r.rose at oracle.com (John Rose) Date: Wed, 4 Dec 2019 10:10:55 -0800 Subject: Valhalla EG 20191204 Message-ID: <1B684A78-F719-445C-B616-C13B138330BF@oracle.com> Present: John R., Tobi A., Dan H., Remi F., Fred P. (Permission slip for Simms, who had a school meeting.) (Brian is off working on his eclair document?) agenda: discussion of eclairs, invoke modes (virtual vs. interface) ref-object vs. val-object (top types for inlines and refs) NOT REACHED: templates, java.lang.Class vs. ?crass" Remi: auto-unboxing is the essential feature of eclairs => interface can be empty, except for supertypes Dan: enforce sealing in VM? John: just a translation strategy hack, maybe (Fred) VMAC can't have a sealed super, since the VMAC can't be named! migration of java.util.Optional: auto-bridging? invokevirtual -> interface? Dan: what rules/restrictions? Remi: see if it can be done with all interfaces Dan: one CP entry needs to potentially support all invocation modes (even errors) lots of corner cases in state transitions of resolution and selection John: seems to require every methodref CP entry to support all invocation insns Remi: can have a list of migrated interfaces and special-case those? (Dan: ugly) if you have both invokeinterface and invokespecial you need three words! John: MH-based linkage to handle invokevirtual -> invokeinterface John: wrap a Method* metadata pointer around a MethodHandle managed pointer? Dan: J9 allocates method wrappers contiguously, but maybe doable more bang for the buck to do autobridging! Dan: we had a list of use cases, still up to date? AI: float a loose proposal (Dan: looking at replacing with J9 MH impl Lambda Forms; dual impls. make it harder to do decompilation for deoptimization and debug) refobj vs. valobj? Remi: you only need one; Dan: hard to do generics over negative types can have compile-time ref-object type which erases to Object John: java.lang.Record for inlines? Remi: Record should be interface, with special permisison to implement toString default methods cannot abstract Object methods, and cannot define finals example: final toString method on lambda example: JUnit5 can write, parameter of test is a factory, factory uses lambdas, => printed report has stupid names for lambdas ? ouch maybe "fat" serializable lambda should have a useful toString method? (?fatten? the interface with toString?) From brian.goetz at oracle.com Fri Dec 6 17:01:21 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 12:01:21 -0500 Subject: Long-awaited State of the Values docs Message-ID: <7dfcff03-9070-c008-68a7-a0e009a5ddd7@oracle.com> [ right mailing list this time ] I have posted the first two sections of "State of Valhalla" at: http://cr.openjdk.java.net/~briangoetz/valhalla/sov/01-background.html http://cr.openjdk.java.net/~briangoetz/valhalla/sov/02-object-model.html This reflects numerous simplifications of the language and VM model, following the discussions that we started at JVMLS this year.? We have been able to eliminate the multiple painful dualities (LV vs QV, zero-default vs null-default, T vs T?) by replacing them with a much simpler relationship between an inline class and a companion interface. The other major AHA buried in this document is the realization that the language and VM models need not align 100%. LWorld is the right VM model, but we can use it as a _translation target_ rather than our language model.? This allows the primitives to blossom into full inline classes.? In the new world, we have inline classes and identity classes, primitives are mostly just inline classes, and the relationship between inlines and identity classes is isomorphic to the current relationship between primitives and classes. There's a lot to absorb here, so I suggest you read it a few times and then bring your questions. From brian.goetz at oracle.com Fri Dec 6 18:52:48 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 13:52:48 -0500 Subject: Laying the groundwork Message-ID: <78D7487B-CE9E-43E9-8521-C6D10A0D2A64@oracle.com> With a viable-looking language and VM model on the table, I think its time to look at what we can do to _prepare_ the world for Valhalla. The Valhalla story will create some disturbance in the force; better to lay the groundwork early. The most disruptive changes outlined in the recent document will be: - Migrating primitive wrappers to interfaces - Migrating away from `new Object()` We can prepare the ground for these early, as follows: - Add new interface IdentityObject (JDK) - Add new factories in Object: IdentityObject newIdentity() (JDK) - Warn when user says ?new Object()?, perhaps translate to factory invocation (Language) - Inject IdentityObject into classes as a super interface (JVM) - Deprecate for removal wrapper constructors (JDK) - Deprecate for ?removal? Object constructor (JDK) - Warnings when synchronizing on an instance of a wrapper (Language) - Support T.default notation (Language) - Detect synchronization on wrapper instances and throw (JVM) Most of these, with the exception of the last, range from simple to trivial. From brian.goetz at oracle.com Fri Dec 6 19:56:24 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 14:56:24 -0500 Subject: Lifting operations from inline classes to reference projections Message-ID: <493597E4-5BEE-4CA9-B110-52CADDA88F97@oracle.com> If we have an inline class: inline class V { public void m() { } } then, at least for the public members of V (if not more), we would like to lift these onto the reference projection: V.ref vr = ? vr.m() // succeeds One way to get there is to simply have the compiler desugar these members onto the reference projection. This is simple for the VM, in that these become ordinary interface calls and no additional VM help is needed, but on the other hand, we are now duplicating members between V and V.ref, which VM folks don?t like. The VM folks would prefer that V.ref be an ?empty? interface. Another would be to appeal to casting; desugar `vr.m()` as `((V) vr).m()` in the static compiler. This works, until we want to make V private (and the combination of public reference projection and private implementation is expected to be a common one.) If we do this, we?ll get a linkage error trying to resolve the cast, since it names a class not accessible to the client. A third way is to soften up the linkage requirements for `invokeinterface` when in the presence of a reference projection. Here, when called upon to link an invocation, if the method is not present in the interface, and the interface is a reference projection for V, we can try to link to the corresponding method on V, _ignoring access control for V itself but still doing access control for the method_. (This also has the benefit of working for non-public members too.) From forax at univ-mlv.fr Fri Dec 6 20:41:28 2019 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 6 Dec 2019 21:41:28 +0100 (CET) Subject: IdentityObject and InlineObject Message-ID: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> Hi Brian, really nice write-up. I've not a lot to say apart about IdentityObject and InlineObject because I fully agree on the major points (and i've promised to not tak about == but wait and see instead). As we briefly talk last Wednesday, i believe there is a less disruptive way to add IdentityObject in the language. Let see if we can do better, there are three reasons to have IdentityObject (and InlineObject) - documentation (support for javadoc) - bound for generic type parameter - class at runtime for instanceof The main drawbacks of Brian's proposal are: - IdentityObject needs to be injected dynamically by the VM. - replacing occurrences of Object by IdentityObject (in IdentityHashMap by example) is not a binary backward compatible change. - new Object() needs to be deprecated (for removal?) and replaced by IdentityObject.newIdentityObject. The only way to solve the point 2 is to have a different view at compile time and at runtime. For that, - the compiler should see IdentityObject as a valid super type of every identity classes. - the compiler erase IdentityObject to java.lang.Object (in particular, IdentityObject.newIdentityObject() return type is erased to Object). - synchronized emit a warning if the the argument is not typed as an IdentityObject. If IdentityObject is erased to Object, - IdentityObject.java exists as support for documentation. - it doesn't need to be injected dynamically by the VM anymore - because IdentityObject is still present in the generic signature, it can be used as bound of generic type parameter - replacing some occurrences of Object by IdentityObject is backward compatible - IdentityObject can not be used in instanceof or cast, but the compiler can suggest to use if (!(o instanceof InlineObject)) instead. I believe this solution is less disruptive and provide a better way to introduce IdentityObject and InlineObject in a backward compatible way. regards, R?mi From brian.goetz at oracle.com Fri Dec 6 21:04:08 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 16:04:08 -0500 Subject: IdentityObject and InlineObject In-Reply-To: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> Message-ID: > As we briefly talk last Wednesday, i believe there is a less disruptive way to add IdentityObject in the language. > > Let see if we can do better, there are three reasons to have IdentityObject (and InlineObject) > - documentation (support for javadoc) > - bound for generic type parameter (or type for regular parameter) > The only way to solve the point 2 is to have a different view at compile time and at runtime. > For that, > - the compiler should see IdentityObject as a valid super type of every identity classes. > - the compiler erase IdentityObject to java.lang.Object (in particular, IdentityObject.newIdentityObject() return type is erased to Object). It is tempting to erase IdentityObject to Object, but then: Object o = new Point(0,0); if (o instanceof IdentityObject) sync(o) { ? } If we erase IdentityObject to Object, then we cannot do the runtime test accurately. I suspect that 99+% of the cases where someone is locking on an inline class (or something to be migrated to an inline class) is doing it behind Object or behind a type variable (which is the same thing.) > If IdentityObject is erased to Object, ? and we lose the ability to do runtime checks. Here?s another place where this erasure game is painful: void m(Object o) { ? } void m(IdentityObject o) { ? } So, reframing: - IF we are willing to give up runtime checks and overloading, THEN we can erase InlineObject to Object, because the remaining users are merely about things like type bounds, which can be checked at compile time and erased. So you are basically asking: how important are these things, and are they worth injecting the extra interface? From forax at univ-mlv.fr Fri Dec 6 21:28:14 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 6 Dec 2019 22:28:14 +0100 (CET) Subject: IdentityObject and InlineObject In-Reply-To: References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> Message-ID: <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoy?: Vendredi 6 D?cembre 2019 22:04:08 > Objet: Re: IdentityObject and InlineObject >> As we briefly talk last Wednesday, i believe there is a less disruptive way to >> add IdentityObject in the language. >> >> Let see if we can do better, there are three reasons to have IdentityObject (and >> InlineObject) >> - documentation (support for javadoc) >> - bound for generic type parameter > > (or type for regular parameter) > >> The only way to solve the point 2 is to have a different view at compile time >> and at runtime. >> For that, >> - the compiler should see IdentityObject as a valid super type of every identity >> classes. >> - the compiler erase IdentityObject to java.lang.Object (in particular, >> IdentityObject.newIdentityObject() return type is erased to Object). > > It is tempting to erase IdentityObject to Object, but then: > > Object o = new Point(0,0); > if (o instanceof IdentityObject) > sync(o) { ? } > > If we erase IdentityObject to Object, then we cannot do the runtime test > accurately. you can: Object o = new Point(0,0); if (!(o instanceof InlineObject)) sync((IdentityObject)o) { ? } > > Here?s another place where this erasure game is painful: > > void m(Object o) { ? } > void m(IdentityObject o) { ? } You see it as painful, i see it as a feature, it means that i can replace Object by IdentityObject and still be binary backward compatible. And this example is not a good one because introducing overloads that have a subtyping relationship is hard to get right. You may not have the same behavior depending if something is typed as Object or as IdentityObject even if it's the same class at runtime. Not the kind of code you want to use. > > So, reframing: > - IF we are willing to give up runtime checks and overloading, THEN we can erase > InlineObject to Object, because the remaining users are merely about things > like type bounds, which can be checked at compile time and erased. No, if you willing to remain binary backward compatible with existing code when you introduce IdentityObject, then it should be erased. > > So you are basically asking: how important are these things, and are they worth > injecting the extra interface? No, injecting is fun but I value binary backward compatibility more. R?mi From brian.goetz at oracle.com Fri Dec 6 21:37:35 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 16:37:35 -0500 Subject: IdentityObject and InlineObject In-Reply-To: <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> Message-ID: > You see it as painful, i see it as a feature, it means that i can replace Object by IdentityObject and still be binary backward compatible. OK, now we're getting to the actual problem you're concerned about -- that you think that there will be a lot of new binary compatibility issues as a result of this.? I am less worried about that, so perhaps you could outline where you think we're creating a problem, and we can evaluate whether that is bad?? Seems to me we may be in solution-to-not-really-a-problem territory. From forax at univ-mlv.fr Fri Dec 6 21:48:09 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 6 Dec 2019 22:48:09 +0100 (CET) Subject: IdentityObject and InlineObject In-Reply-To: References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> Message-ID: <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoy?: Vendredi 6 D?cembre 2019 22:37:35 > Objet: Re: IdentityObject and InlineObject >> You see it as painful, i see it as a feature, it means that i can replace Object >> by IdentityObject and still be binary backward compatible. > > OK, now we're getting to the actual problem you're concerned about -- > that you think that there will be a lot of new binary compatibility > issues as a result of this.? I am less worried about that, so perhaps > you could outline where you think we're creating a problem, and we can > evaluate whether that is bad?? Seems to me we may be in > solution-to-not-really-a-problem territory. Currently something typed Object represent either any or ref, we decide that most of the Object represents any, so we still have to change some of the Object that represent ref. We can not change Object at declaration site if it's not binary backward compatible. Generics in Java are binary backward compatible for that reason. R?mi From brian.goetz at oracle.com Fri Dec 6 21:59:07 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 16:59:07 -0500 Subject: IdentityObject and InlineObject In-Reply-To: <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> Message-ID: <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> > Currently something typed Object represent either any or ref, we decide that most of the Object represents any, so we still have to change some of the Object that represent ref. Keep pulling on this string .. exactly WHAT will have to change? Let's have some specific examples, and then we can talk about how common they will be, and whether there are not other mitigating options.? I worry you may be assuming something that isn't there. From forax at univ-mlv.fr Fri Dec 6 22:16:14 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 6 Dec 2019 23:16:14 +0100 (CET) Subject: IdentityObject and InlineObject In-Reply-To: <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> Message-ID: <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoy?: Vendredi 6 D?cembre 2019 22:59:07 > Objet: Re: IdentityObject and InlineObject >> Currently something typed Object represent either any or ref, we decide that >> most of the Object represents any, so we still have to change some of the >> Object that represent ref. > > Keep pulling on this string .. exactly WHAT will have to change? Let's > have some specific examples, and then we can talk about how common they > will be, and whether there are not other mitigating options.? I worry > you may be assuming something that isn't there. package java.util; public class IdentityHashMap { ... } package java.io; public class IOReader { protected IdentityObject lock; ... } package java.awt; public class Component { public IdentityObject getTreeLock() { ... } ... } and so on. R?mi From brian.goetz at oracle.com Fri Dec 6 22:28:55 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 6 Dec 2019 17:28:55 -0500 Subject: IdentityObject and InlineObject In-Reply-To: <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> Message-ID: <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> > package java.util; > public class IdentityHashMap { ... } This one has an easy out: ??? public class IdentityHashMap { ... } I was hoping your example would be type bounds, since they're easily amenable to this trick (either explicitly, or with compiler help.) Got more? From john.r.rose at oracle.com Sat Dec 7 01:31:48 2019 From: john.r.rose at oracle.com (John Rose) Date: Fri, 6 Dec 2019 17:31:48 -0800 Subject: IdentityObject and InlineObject In-Reply-To: <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> Message-ID: <1848BBC3-7CDF-47C1-BCA0-08FAEA8ACCE3@oracle.com> On Dec 6, 2019, at 2:28 PM, Brian Goetz wrote: > > public class IdentityHashMap { ... } (There?s your erasure trick, Remi, used against you. Brian has no mercy!) From forax at univ-mlv.fr Sat Dec 7 19:48:04 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 7 Dec 2019 20:48:04 +0100 (CET) Subject: IdentityObject and InlineObject In-Reply-To: <1848BBC3-7CDF-47C1-BCA0-08FAEA8ACCE3@oracle.com> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> <1848BBC3-7CDF-47C1-BCA0-08FAEA8ACCE3@oracle.com> Message-ID: <242186193.70707.1575748084308.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Brian Goetz" > Cc: "Remi Forax" , "valhalla-spec-experts" > > Envoy?: Samedi 7 D?cembre 2019 02:31:48 > Objet: Re: IdentityObject and InlineObject > On Dec 6, 2019, at 2:28 PM, Brian Goetz < [ mailto:brian.goetz at oracle.com | > brian.goetz at oracle.com ] > wrote: >> public class IdentityHashMap { ... } > (There?s your erasure trick, Remi, used against you. Brian has no mercy!) No, it's actually a good question ! First, it only works for one of the 3 examples I have given. Then i've found that using this trick useful on generic static method and far less useful on generic classes because those can have subclasses. By example, in the case of IdentityHashMap, you only delay the issue because any classes that inherits from IdentityHashMap (and IdentityHashMap with the (first) type argument specified) is not binary backward compatible. R?mi From maurizio.cimadamore at oracle.com Mon Dec 9 14:45:19 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 9 Dec 2019 14:45:19 +0000 Subject: IdentityObject and InlineObject In-Reply-To: <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> Message-ID: <5930f0ba-bd76-80ee-10d7-ec6bc5c1110a@oracle.com> On 06/12/2019 22:28, Brian Goetz wrote: > >> package java.util; >> public class IdentityHashMap { ... } > > This one has an easy out: > > ??? public class IdentityHashMap { ... } > > I was hoping your example would be type bounds, since they're easily > amenable to this trick (either explicitly, or with compiler help.) Got > more? > > Uhm - doing this change is certainly binary compatible (the extra bound is erased) - but it's not source compatible - every client using IdentityHashMap would fail to compile now, right? E.g. imagine code like this: public class Foo { public Map cache = new IdentityHashMap<>(); } With the proposed bound change, two things will happen: 1) the inferred type of the 'new' expression will change 2) the inferred type as of (1) will be incompatible with the type in the LHS -> error To fix the issue, you have to change the LHS type - if this is a public API, this will then trigger more downstream issues (again, not about BC, but about source compatibility). Maurizio From forax at univ-mlv.fr Mon Dec 9 21:59:47 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 9 Dec 2019 22:59:47 +0100 (CET) Subject: IdentityObject and InlineObject In-Reply-To: <5930f0ba-bd76-80ee-10d7-ec6bc5c1110a@oracle.com> References: <145022679.12232.1575664888343.JavaMail.zimbra@u-pem.fr> <26289123.14140.1575667694305.JavaMail.zimbra@u-pem.fr> <405816651.15621.1575668889446.JavaMail.zimbra@u-pem.fr> <9aece22f-bba5-da40-ae7d-51f97eeea403@oracle.com> <785666926.16844.1575670574901.JavaMail.zimbra@u-pem.fr> <2a98e4de-f556-466a-dee3-e6aafc1da2ab@oracle.com> <5930f0ba-bd76-80ee-10d7-ec6bc5c1110a@oracle.com> Message-ID: <772487990.527819.1575928787049.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Maurizio Cimadamore" > ?: "Brian Goetz" , "Remi Forax" > Cc: "valhalla-spec-experts" > Envoy?: Lundi 9 D?cembre 2019 15:45:19 > Objet: Re: IdentityObject and InlineObject > On 06/12/2019 22:28, Brian Goetz wrote: >> >>> package java.util; >>> public class IdentityHashMap { ... } >> >> This one has an easy out: >> >> ??? public class IdentityHashMap { ... } >> >> I was hoping your example would be type bounds, since they're easily >> amenable to this trick (either explicitly, or with compiler help.) Got >> more? >> >> > Uhm - doing this change is certainly binary compatible (the extra bound > is erased) - but it's not source compatible - every client using > IdentityHashMap would fail to compile now, right? > > E.g. imagine code like this: > > public class Foo { > > public Map cache = new IdentityHashMap<>(); > > } > > With the proposed bound change, two things will happen: > > 1) the inferred type of the 'new' expression will change > 2) the inferred type as of (1) will be incompatible with the type in the > LHS -> error > > To fix the issue, you have to change the LHS type - if this is a public > API, this will then trigger more downstream issues (again, not about BC, > but about source compatibility). yes, changing a type to use a subtype, Object to IdentityObject is only source compatible if the type is a return type (covariant). And it has nothing to do with IdentityObject being erased or not, we have the same issue in both cases. We have choosen to see Object as any (L-word) because there will be few places were Object really means ref instead of any. But for those locations, changing Object to IdentityObject is not a source compatible change. So we can, - not change them, in that case a runtime error will occurs if someone do by example a synchronize on Object. - introduce IdentityObject and the error will be caught at compile time instead of at runtime, but it's not a source compatible change. We can try to ease the migration by using the same kind of tricks used we the code was generified but it's less easy because we are moving from an already generified world to another generified world, but trying to couple the migration Object -> IdentityObject with the migration erased generics -> any generics, may offer this kind of opportunity again. > > Maurizio R?mi From maurizio.cimadamore at oracle.com Mon Dec 16 14:14:38 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 16 Dec 2019 14:14:38 +0000 Subject: Lifting operations from inline classes to reference projections In-Reply-To: <493597E4-5BEE-4CA9-B110-52CADDA88F97@oracle.com> References: <493597E4-5BEE-4CA9-B110-52CADDA88F97@oracle.com> Message-ID: <2b79f1a4-ca69-e6a7-51f0-1d12a544f7e2@oracle.com> On 06/12/2019 19:56, Brian Goetz wrote: > If we have an inline class: > > inline class V { > public void m() { } > } > > then, at least for the public members of V (if not more), we would like to lift these onto the reference projection: > > V.ref vr = ? > vr.m() // succeeds > > One way to get there is to simply have the compiler desugar these members onto the reference projection. This is simple for the VM, in that these become ordinary interface calls and no additional VM help is needed, but on the other hand, we are now duplicating members between V and V.ref, which VM folks don?t like. The VM folks would prefer that V.ref be an ?empty? interface. Is there a way to do this by appealing to delegation? E.g. rather than completely duplicating the V member in V.ref, can we use the cast trick below to add a "bridge" from V.ref to V? I'm not too worried about having a V.ref with its own members - after all, V.ref should be a language fiction (for the VM, the only real thing is V). So, in my mental model, it will be javac, not the VM, to do most of the lifting here. Am I wrong in my assumption? Maurizio > > Another would be to appeal to casting; desugar `vr.m()` as `((V) vr).m()` in the static compiler. This works, until we want to make V private (and the combination of public reference projection and private implementation is expected to be a common one.) If we do this, we?ll get a linkage error trying to resolve the cast, since it names a class not accessible to the client. > > A third way is to soften up the linkage requirements for `invokeinterface` when in the presence of a reference projection. Here, when called upon to link an invocation, if the method is not present in the interface, and the interface is a reference projection for V, we can try to link to the corresponding method on V, _ignoring access control for V itself but still doing access control for the method_. (This also has the benefit of working for non-public members too.) > > > > From brian.goetz at oracle.com Mon Dec 16 14:27:59 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 16 Dec 2019 09:27:59 -0500 Subject: Lifting operations from inline classes to reference projections In-Reply-To: <2b79f1a4-ca69-e6a7-51f0-1d12a544f7e2@oracle.com> References: <493597E4-5BEE-4CA9-B110-52CADDA88F97@oracle.com> <2b79f1a4-ca69-e6a7-51f0-1d12a544f7e2@oracle.com> Message-ID: > > I'm not too worried about having a V.ref with its own members - after > all, V.ref should be a language fiction (for the VM, the only real > thing is V). So, in my mental model, it will be javac, not the VM, to > do most of the lifting here. Am I wrong in my assumption? The VM team has expressed a preference that these members not be duplicated, so we're exploring alternatives.? One alternative that has been suggested is: if X is a reference projection of V, then `invokeinterface X.m()`, be adjusted to look for members on V. (Some adjustments would be made to the resolution for the case of private implementations, where the access check would be relaxed on the implementation _class_ but not the _member_.? This sidesteps the problem with the obvious solution (interface methods on V.ref) having to do with non-public methods.) From forax at univ-mlv.fr Mon Dec 16 15:00:08 2019 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 16 Dec 2019 16:00:08 +0100 (CET) Subject: Lifting operations from inline classes to reference projections In-Reply-To: References: <493597E4-5BEE-4CA9-B110-52CADDA88F97@oracle.com> <2b79f1a4-ca69-e6a7-51f0-1d12a544f7e2@oracle.com> Message-ID: <812269400.487559.1576508408864.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Maurizio Cimadamore" , "valhalla-spec-experts" > > Envoy?: Lundi 16 D?cembre 2019 15:27:59 > Objet: Re: Lifting operations from inline classes to reference projections >> >> I'm not too worried about having a V.ref with its own members - after >> all, V.ref should be a language fiction (for the VM, the only real >> thing is V). So, in my mental model, it will be javac, not the VM, to >> do most of the lifting here. Am I wrong in my assumption? > > The VM team has expressed a preference that these members not be > duplicated, so we're exploring alternatives.? One alternative that has > been suggested is: if X is a reference projection of V, then > `invokeinterface X.m()`, be adjusted to look for members on V. (Some > adjustments would be made to the resolution for the case of private > implementations, where the access check would be relaxed on the > implementation _class_ but not the _member_.? This sidesteps the problem > with the obvious solution (interface methods on V.ref) having to do with > non-public methods.) I don't think that private implementation is a real issue. Let say when have an inline type HashEntry, public class HashMap { private static inline class HashEntry implements Map.Entry { private K key; private V value; public K getKey() { return key; } ... } ... } now if we introduce a public HashEntry.ref, we have public class HashMap { private static inline class HashEntry implements Map.Entry, HashEntry.ref { private K key; private V value; public K getKey() { return key; } ... } public interface HashEntry.ref extends Map.Entry { // empty } ... } >From outside HashMap, HashEntry.ref.key is not valid, but that's expected, HashEntry.ref.getKey() is accessible because a ref has the same set of interfaces as the corresponding inline type and this is how public methods are getting exposed. R?mi From daniel.smith at oracle.com Wed Dec 18 23:57:40 2019 From: daniel.smith at oracle.com (Dan Smith) Date: Wed, 18 Dec 2019 16:57:40 -0700 Subject: Superclasses for inline classes Message-ID: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> [Expanding on and summarizing discussion about abstract superclasses from today's meeting.] ----- Motivation There are some strong incentives for us to support inline classes that have superclasses other than Object. Briefly, these include: - Identity -> inline migration candidates (notably java.lang.Integer) often extend abstract classes - A common refactoring may be to extend an existing class with a (possibly private) inline implementation - Abstract classes are more expressive than interfaces - If we compile Foo.ref to an abstract class, we can better represent the full API of an inline class using an abstract class To be clear, much of this has to do with migration, and I subscribe to a fairly expansive view of how much we should permit and encourage migration. I think most every project in the world has at least a few opportunities to use inline classes. Our design should limit the friction necessary (e.g., disruptive redesigns of type hierarchies) to integrate inline classes with the existing body of code. We've considered, as an alternative, supporting transparent migration of existing classes to interfaces. But this raises many difficult issues surrounding source, binary, and behavioral compatibility. It would be nice not to have to tackle those issues, nor introduce a lot of caveats into the class -> interface migration story. ----- Constraints Inline class instantiation is is fundamentally different from identity class instantiation. While the language seeks to smooth over these differences, under the hood all inline objects come from 'defaultvalue' and 'withfield' invocations. There is no opportunity in these bytecodes for a superclass to execute initialization code. (Could we redesign the construction model to properly delegate to a superclass? Sure, but that's a huge new feature that probably isn't justified by the use cases.) As a result, constructors, instance initializers, and instance fields in a superclass are unusable to inline class instances. In fact, their existence would be a vulnerability, since authors typically make assumptions about initialization having occurred. Fortunately, 'Object' doesn't require any initialization and so can safely be extended. Our goal is to expand the set of safe-to-extend classes. ----- Language model An inline class may extend another class, as long as the superclass has the following properties: - It has no instance fields - It has no constructors - It has no instance initializers - It is abstract* or Object - It extends another class with these properties Subtype polymorphism works the same for superclasses as it does for superinterfaces. (*Remi points out that we could drop the 'abstract' restriction, meaning there may be identity instances of the superclass. Given the restriction on fields, though, I'm struggling to envision a use case; the consensus is that 'new Object()' is probably something we want to *stop* supporting.) Call a class that satisfies these constraints an "initialization-free class" (bikeshedding on this term is welcome!). Like an interface, its value set may include references to both inline class instances and identity class instances. We *do *not* want the initialization-free property to be expressed as a class modifier?this feature is too obscure to deserve that much prominence, encouraging every class author to consider one more degree of freedom; and we don't want every class to have to manually opt in. But we *do* need the initialization-free property to be part of the public information about the class. For example, the javadoc should say something like "this is an initialization-free class". Otherwise, it's impossible to tell the difference between, e.g., a class with no fields and a class with private fields. In the past, a Java class declaration that lacks a constructor always got a default constructor. In this model, however, an initialization-free class has no constructor at all. A 'super()' call directed at such a class is a no-op. ----- Compilation & JVM support There are two alternative compilation strategies: 1) An initialization-free class is compiled like always, including an '' method of the form 'aload_0; invokespecial; return;'. Some metadata (flag or attribute) indicates that the class is initialization-free. The initialization-free flag is partially validated at class load time: it's an error to claim to be initialization-free and be non-abstract (and non-Object), declare instance fields, or extend a non-initialization-free superclass. We don't validate method contents. If someone chooses to generate an "initialization-free" class file that contains code, they accept the risk that the code won't run. On loading, an inline class must extend an initialization-free class. 2) An initialization-free class is compiled without an '' method. For binary compatibility, existing references to 'Foo.' must successfully resolve, with invocation being a no-op. (This is something new?resolution to fake declarations?and potentially concerning.) At class load time, an inline class must extend a chain of superclasses that are abstract (or Object), lack '' methods, and lack instance field declarations. Existing classes that meet these requirements may act as inline class superclasses (probably surprisingly, since currently a class without an method can't be initialized at all). (My thoughts on (1) vs. (2): both are plausible, and I like the lack of metadata overhead in (2), but otherwise (1) seems much cleaner.) In either case, the big new feature here is that an inline class may have a superclass other than Object. This may violate some existing assumptions in the implementation, although it sounds like we can hope nothing new really needs to be done to support it. From john.r.rose at oracle.com Thu Dec 19 01:46:42 2019 From: john.r.rose at oracle.com (John Rose) Date: Wed, 18 Dec 2019 17:46:42 -0800 Subject: atomicity for value types Message-ID: <55A8853E-AFF4-4CDB-A963-3C6E73D0859A@oracle.com> In a nutshell, here?s a proposal for value type atomicity: - Define a contextual keyword ?alwaysatomic" (working title ?__AlwaysAtomic?). - It can only occur with ?inline? in class declaration. - All instances of the given inline class are protected against races. - Protection is ?as if? each field were volatile, ?and the same? for array elements. - In the class file the ACC_VOLATILE bit is used (0x0040) to record the keyword. - This bit does not necessarily disable flattening; if the JVM can get away with flattening it should. - The JVM can get away with flattening such values on stack, in registers, and perhaps in final heap variables. - The JVM can get away with flattening such values if they are ?naturally atomic?, meaning they can be wholly loaded or stored in one (atomic) hardware instruction. More details to follow. Here?s a backgrounder I wrote a while ago: http://cr.openjdk.java.net/~jrose/oblog/value-tearing.html ? John From forax at univ-mlv.fr Thu Dec 19 09:51:18 2019 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 19 Dec 2019 10:51:18 +0100 (CET) Subject: Superclasses for inline classes In-Reply-To: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> Message-ID: <1878946597.160512.1576749078349.JavaMail.zimbra@u-pem.fr> It occurs to me that we may have jump into the conclusion that we should use inheritance/implementation a little to fast. At least, i think it worth exploring another complementary option. In Java, you have several ways to have subtyping, saying a FooInline is a subclass FooInline.ref is one way, but using wildcard is another one, if Foo can be a template classes with Foo the inline. It will not solve the retrofitting of java.lang.Number so it's a complementary approach but it can solve the retrofitting of java.lang.Integer in a better way than to use subclasses. Let say java.lang.Integer is a template class, but not a template class over a type like a specialized generics are but a template class over the inline bit. In that case java.lang.Integer can have two specializations, one is an indirect type, the result of new Integer(8), and one is an inline type the result of Integer.valueOf(8). The nice properties of this organization are: - object.getClass() will return Integer.class for both specializations (it's not the same species but it's the same class). - java.lang.Integer can still be final (because final means no subclass but not no subtype). R?mi From daniel.smith at oracle.com Thu Dec 19 18:38:17 2019 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 19 Dec 2019 11:38:17 -0700 Subject: Superclasses for inline classes In-Reply-To: <1878946597.160512.1576749078349.JavaMail.zimbra@u-pem.fr> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> <1878946597.160512.1576749078349.JavaMail.zimbra@u-pem.fr> Message-ID: <5C414FA5-C142-42FA-A884-EE667BF0958B@oracle.com> Yes, it is totally on the table that the language model doesn't bother to introduce an implicit class or interface above the inline class, and instead we're talking about two types derived from a single class declaration. No matter what choice we make there, there's a separate question of whether inline classes can extend other classes, and I'm arguing that there are some good reasons that they should. (There's also the question of how a 'ref' type gets compiled, nevermind what the language model says, and whether an abstract class would be a useful tool for that purpose.) > On Dec 19, 2019, at 2:51 AM, Remi Forax wrote: > > It occurs to me that we may have jump into the conclusion that we should use inheritance/implementation a little to fast. > At least, i think it worth exploring another complementary option. > > In Java, you have several ways to have subtyping, > saying a FooInline is a subclass FooInline.ref is one way, > but using wildcard is another one, if Foo can be a template classes with Foo the inline. > > It will not solve the retrofitting of java.lang.Number so it's a complementary approach but it can solve the retrofitting of java.lang.Integer in a better way than to use subclasses. > > Let say java.lang.Integer is a template class, but not a template class over a type like a specialized generics are but a template class over the inline bit. > In that case java.lang.Integer can have two specializations, one is an indirect type, the result of new Integer(8), and one is an inline type the result of Integer.valueOf(8). > > The nice properties of this organization are: > - object.getClass() will return Integer.class for both specializations (it's not the same species but it's the same class). > - java.lang.Integer can still be final (because final means no subclass but not no subtype). > > R?mi > > > > From daniel.smith at oracle.com Thu Dec 19 21:15:17 2019 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 19 Dec 2019 14:15:17 -0700 Subject: Reference-default style Message-ID: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> As we flesh out the migration story for inline classes, I've found it useful to identify two different styles that programmers will want to follow as they make use of inline classes. I want to introduce some terminology to talk about these styles, which will hopefully help us think about what use cases we're designing for. ----- Inline-default In this style, most clients of an inline class will want to treat it like another primitive type. They'll use the type directly, and maybe allocate flat arrays (or, eventually, other specialized data structures). Operations that make use of null or erased generics will be uncommon. Examples: - Numeric types that are effectively variations on primitives - Typed wrappers for single values (e.g., measurements, pointers) - Low-level flat building blocks for data structures - Multiple-return structures (like cursors) Most of our design is tailored to this style. In the language model, 'Point' is an inline type, and you need to modify it?'Point.ref'?to access the equivalent reference type (if you must?we expect people to utter 'Point.ref' about as frequently as they currently utter 'Integer'). ---- Reference-default In this style, most clients of an inline class will interact with it through a reference type. They'll use nulls and erased generics the same way they always have. Clients may not even realize that there is an inline class under the hood. Flattening is not a priority, and may even be unwanted (because of cycles, tearing, etc.). Examples: - Published classes that are already committed to a reference view (Optional, LocalDateTime) - Components in a system that makes heavy use of null or generics - General-purpose records (e.g., a POJO view of a database) - Nodes in recursive data structures - Behavior abstractions (e.g., functions) - APIs that don't want their clients to have to think about inline types - Classes without a natural default value - APIs that want to limit access to default values "Why even bother with an inline class?" is a fair question for these use cases. Some answers: - Principle of least privilege: if you don't need identity, don't claim it - Potential for GC improvements* and opportunistic JIT flattening - A subset of users need flattening, but few enough that it doesn't deserve "default" treatment - A migration strategy?new code should work with inline types, but old code is written for reference types (sorry, new code) (*On GC: do we have good numbers on this? Personally, my choices about adding class abstractions often come down to "is this abstraction worth the allocation and GC pressure costs associated with lots of new objects?" My performance model here is horrible, so who knows if this is a smart question to ask, but it would be nice if we could say broadly "use an inline class and stop worrying about it.") Brian proposes an approach to supporting the reference-default style in "State of Valhalla", but I'm not sure it's ideal?this design space seems fairly unexplored to me still. Briefly, here are some ways the language might support it: 1) As a design pattern We tell programmers to produce two declarations, an inline class and an interface (or abstract class, per another thread). The interface gets the "good name" and exposes the intended API. The inline class may be exposed with an alternate name, for clients who need it, or hidden as private. The language is still 100% inline-default?the 'Foo.ref' type still exists, but it's redundant and nobody needs to use it. If we did nothing (and honestly, that's an attractive feature roadmap! super cheap!), I think we'd see this design pattern developing naturally in the community. 2) As an "advanced" feature of inline classes This is the State of Valhalla strategy: inline classes are designed to be inline-default, but as a special-case feature, you can also declare the 'Foo.ref' interface, give it a name, and wire it up to the inline class declaration. In reference-default style, the programmer gives the "good name" to the reference projection, and either gives an alternate name to the inline class or is able to elide it entirely (in that case, clients use 'Foo.inline'). Ways this is different than (1): - The 'Foo.inline' type operator - Implicit conversions (although sealed types can get us there in (1)) - There are two types, not three (and two JVM classes, not three) - Opportunities for "boilerplate reduction" in the two declarations 3) As an equal partner with inline-default An inline class declaration introduces two types, an inline type and a reference type. But a modifier on the declaration determines whether the "good name" goes to the inline type or the reference type. The other type can be derived using an operator ('Foo.ref' or 'Foo.inline'). There's never a need for an alternate name. In this case, the language isn't biased to one style or the other; each declaration picks one. The trade-off is that clients need to keep track of one more bit when thinking about the inline class ("Is this a *foo* inline class or a *bar* inline class?" Actual terminology to be bikeshedded...) 4) As the only supported style An inline class declaration always gives the "good name" to the reference type, and you always use an operator to get to the inline type ('Foo.inline'?but we're gonna need better syntax.) This one would represent a significant shift in the design center of the feature. If you want flattening everywhere, you're going to need to make liberal use of the '.inline' operator. But if you just want to declare that a bunch of your classes don't have identity, and hopefully get a cheap performance boost as a result, it's simple. The burden of learning something new is shifted to "advanced" users and APIs to whom flattening is important. 5) As a use-site contextual option There's a single inline class declaration with two corresponding types. At the use site, the programmer provides context that picks one or the other for the "good name" (perhaps as a property of the 'import' statement, or some new compiler direction in a source file header, package/module declaration, command line flag, ...). This probably makes the most sense paired with (4): the *default* default is the reference type, but the language lets you switch to the inline type if you want. Then, unless the client opts in to inline types, they get familiar reference type behavior from the class. Conclusion: I'm not ready to completely dismiss any of these designs, but my preferences at the moment are (1) and (3). Options (4) and (5) are more ambitious, discarding some of our assumptions and taking things in a different direction. Like many design patterns, (1) suffers from boilerplate overhead ((2) too, without some language help). It also risks some missed opportunities for optimization or language convenience, because the relationship between the inline and reference type is incidental. (I'd like to get a clearer picture of whether this really matters or not.) (2), (3), and (5) suffer from added language complexity. (2) tries to manage it by pushing the feature off into "advanced" territory. But, ultimately, you can't understand the language without understanding those advanced features?the first time you encounter reference-default style, you'll have to rethink your understanding of how inline classes work. (5) feels like something fundamentally new in Java, although if you squint it's "just" a variation on name resolution. What originally prompted this idea was seeing a similar approach in attempts to introduce nullability type operators?legacy code has the "wrong" default, so you need some lightweight way to pick a different default. (4) is a simple and consistent story, but probably not the feature we're building. It hinges on how important we think the inline-default use cases are, and how painful we think the 'inline' operator (spelling TBD) would be to use in those cases. Since (1) is already done (it's the "do nothing" option), it makes sense to use it as a baseline, and then ask whether any of the alternatives are a significant enough improvement that they're worth developing. This will be informed by our understanding of the use cases for the two styles, and some real world experience would probably help that understanding. From maurizio.cimadamore at oracle.com Fri Dec 20 00:10:14 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 20 Dec 2019 00:10:14 +0000 Subject: Reference-default style In-Reply-To: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> References: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> Message-ID: <62c6ee44-5659-5718-0542-3ef144b55109@oracle.com> On 19/12/2019 21:15, Dan Smith wrote: > Like many design patterns, (1) suffers from boilerplate overhead ((2) too, without some language help). It also risks some missed opportunities for optimization or language convenience, because the relationship between the inline and reference type is incidental. (I'd like to get a clearer picture of whether this really matters or not.) At the last post JVMLS meeting I was a string advocate of this position. This is effectively the pattern used in the Panama memory access API, where we have public (in future sealed) interfaces backed up by inline-ready implementation classes. While I still think that there will be a lot of cases like these - Panama also needs something which is more akin to the 'programmable primitive'-half of the Valhalla glass. That is, we might want to introduce a int128 type or float16, which might be required to interop with certain system ABIs. When you do that, you would like to have these types (e.g. int128) the *public* ones, the ones with the good names. You want users to create (flat) arrays of them, rather than oops arrays. So, as much as I like (1) I don't think we can fully get away with that? Maurizio From john.r.rose at oracle.com Fri Dec 20 01:05:10 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 19 Dec 2019 17:05:10 -0800 Subject: Superclasses for inline classes In-Reply-To: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> Message-ID: On Dec 18, 2019, at 3:57 PM, Dan Smith wrote: > > abstract superclasses A quick observation: The Panama Vector API uses interfaces to abstract from specific vector types, and those specific vector types must be made inline to enable full optimization. For example: Float128Vector <: FloatVector <: Vector <: Vector In this scheme we either need to seal FloatVector to Float128Vector and its siblings, or else we need to do something with abstract classes. There may be incremental benefits to making FloatVector be an abstract class, such as being able to define and document toString, equals, and hashCode. If in addition we could define FloatVector as an abstract *inline* class, we might benefit from the effect of FloatVector being non-nullable. The top-level interface Vector would still be nullable. The plan of record is to make every vector type be either an interface or an inline, and then move to specialized interfaces and inlines when templates become available. But Vector is one place where abstract classes are worth a serious look. ? John From john.r.rose at oracle.com Fri Dec 20 02:12:43 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 19 Dec 2019 18:12:43 -0800 Subject: Superclasses for inline classes In-Reply-To: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> Message-ID: <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> On Dec 18, 2019, at 3:57 PM, Dan Smith wrote: > > [Expanding on and summarizing discussion about abstract superclasses from today's meeting.] > > ----- > Motivation > > There are some strong incentives for us to support inline classes that have superclasses other than Object. Briefly, these include: > > - Identity -> inline migration candidates (notably java.lang.Integer) often extend abstract classes > - A common refactoring may be to extend an existing class with a (possibly private) inline implementation > - Abstract classes are more expressive than interfaces > - If we compile Foo.ref to an abstract class, we can better represent the full API of an inline class using an abstract class I?m glad we are cracking open this can of worms; I?ve always thought that interfaces as inline supers were good enough but not necessarily the whole story. (At the risk of instilling more terror, I?ll say that I think that an abstract super to an inline could contribute non-static fields, in a way that is meaningful, useful, and efficient. The initialization of such inherited fields would of course use withfield and would require special rules to allow the initialization to occur in the subclass constructor/factory. I suppose this is a huge feature, as Dan says later. A similar effect will be available from templates, with less special pleading.) (Does it make sense to allow an abstract class to *also* be inline? Maybe, although there is a serious question about its default value. If a type is abstract its default value is probably required to be null.) A useful organizing concept for abstract supers, relative to inlines, is a pair of bits, not both true, ?always-inlined? and ?never-inlined?. Object and interfaces have neither mark by default. The super of an identity class cannot be ?always-inlined" and the super of an inline class cannot be ?never-inlined?. Or, an identity (resp. inline) class has the ?always-inlined? (resp. ?never-inlined?) bit set. And for every T <: U in the class hierarchy, if T is always-inlined, then U must not be never-inlined, and vice versa. Thus if U is marked then every T <: U is forbidden to have the opposite mark. Or, even more simply, both bits are deemed to inherit down to all subtypes, and no type may contain both marks. I don?t know how to derive those bits from surface syntax. A marker interface for each is a first cut: AlwaysInlined, NeverInlined. Marker interfaces are kind of smelly. These particular ones work a little better than their complements (InlineFriendly, IdentityFriendly) because they exclude options rather than include them. (Could a *non-abstract* inline be a super of another inline? No, I?d like to draw the line there, because that leads to paradoxes with flattening, or else makes the super non-flattenable in most uses, or violates a substitutability rule.) > To be clear, much of this has to do with migration, and I subscribe to a fairly expansive view of how much we should permit and encourage migration. I think most every project in the world has at least a few opportunities to use inline classes. Our design should limit the friction necessary (e.g., disruptive redesigns of type hierarchies) to integrate inline classes with the existing body of code. You have a point. Migration is not a task but a way of life? > We've considered, as an alternative, supporting transparent migration of existing classes to interfaces. But this raises many difficult issues surrounding source, binary, and behavioral compatibility. It would be nice not to have to tackle those issues, nor introduce a lot of caveats into the class -> interface migration story. As I said earlier, for value types we have this recurring need to bend interfaces to be more like abstract classes, or else allow abstract classes to become more like interfaces. > ----- > Constraints > > Inline class instantiation is is fundamentally different from identity class instantiation. While the language seeks to smooth over these differences, under the hood all inline objects come from 'defaultvalue' and 'withfield' invocations. There is no opportunity in these bytecodes for a superclass to execute initialization code. In the case we are discussing, the interface-like trick that abstract classes need to learn is to have (declaratively) empty constructors. I think that if a class (abstract or not) has a non-empty constructor, it must also be given the ?never-inline? mark. (This is one reason that mark isn?t simply a marker interface.) In this way (or some equivalent) a class with a non-empty constructor will never attempt to be the super of an inline. > (Could we redesign the construction model to properly delegate to a superclass? Sure, but that's a huge new feature that probably isn't justified by the use cases.) Probably not. Unless folks demand to factor fields as well as behaviors into abstract supers of inlines. > As a result, constructors, instance initializers, and instance fields in a superclass are unusable to inline class instances. In fact, their existence would be a vulnerability, since authors typically make assumptions about initialization having occurred. If constructors are declaratively empty, it follows that subclasses must be given both responsibility and authority to initialize all fields of supers with empty constructors. The easiest way to handle this is to forbid such fields. Another way is to treat field initialization as a protected activity (may occur in a subclass constructor). Currently it is arguably a private activity (must occur in the same class constructor). So: abstract class S { __DeclarativelyEmpty S(); final int x; } final class C extends S { final int y; C(int a, int b) { super.x = a; this.y = b; } } My take is that it?s doable, and its worth is unproven at present. > Fortunately, 'Object' doesn't require any initialization and so can safely be extended. Our goal is to expand the set of safe-to-extend classes. > > ----- > Language model > > An inline class may extend another class, as long as the superclass has the following properties: > - It has no instance fields > - It has no constructors > - It has no instance initializers > - It is abstract* or Object > - It extends another class with these properties It all comes down to this I think: - The class has a declaratively empty constructor. (Perhaps it may have others!) With the following axioms and inferences: - Object has a declaratively empty constructor. (And no others, in fact.) - All interfaces have a declaratively empty constructor. (And no others, in fact.) - A class (other than Object) with a declaratively empty constructor must have a super with declaratively empty constructor. - A class with a declaratively empty constructor must only static initializers. - Checking of blank final initialization is performed as if a declaratively empty constructor in fact has an empty body. Therefore, a class with a declaratively empty constructor must not declare final fields, or else we must extend DA/DU rules for blank finals to allow ?protected initialization?, as sketched above. I think it?s better to tease the conditions apart in this way rather than lump them all together. > Subtype polymorphism works the same for superclasses as it does for superinterfaces. > > (*Remi points out that we could drop the 'abstract' restriction, meaning there may be identity instances of the superclass. Given the restriction on fields, though, I'm struggling to envision a use case; the consensus is that 'new Object()' is probably something we want to *stop* supporting.) Pushing the other way, and given that the restriction on fields could be lifted, there are good use cases like Integer. The inline subtypes of Integer would use the declaratively empty constructor, while the identity instances would use a different constructor. And Integer would be marked neither ?always-inline? nor ?never-inline?, so the subtype ?int? would work OK as an inline. Its constructor would do a ?withfield? to initialize Integer.value, as a protected blank final. If we conclude that there are no use cases for such abstract-final fields, fine. But claiming that such a use case cannot exist because there are no such fields is a mere circularity. > Call a class that satisfies these constraints an "initialization-free class" (bikeshedding on this term is welcome!). See above. And note that a class might have *both* initialization free modes and regular constructors, *at the same time*. The key property is that there is a constructor which is declaratively empty. That provides us the API surface we need to fit things together properly in the subclass. > Like an interface, its value set may include references to both inline class instances and identity class instances. OK. > We *do *not* want the initialization-free property to be expressed as a class modifier?this feature is too obscure to deserve that much prominence, encouraging every class author to consider one more degree of freedom; and we don't want every class to have to manually opt in. I agree. It really a modifier on a constructor, isn?t it? I suppose it always goes on the default (nullary) constructor. > But we *do* need the initialization-free property to be part of the public information about the class. For example, the javadoc should say something like "this is an initialization-free class". Otherwise, it's impossible to tell the difference between, e.g., a class with no fields and a class with private fields. > > In the past, a Java class declaration that lacks a constructor always got a default constructor. In this model, however, an initialization-free class has no constructor at all. A 'super()' call directed at such a class is a no-op. Yep. And that?s (one reason) why declarative emptiness differs from textual emptiness and must be contagious upward. > > ----- > Compilation & JVM support > > There are two alternative compilation strategies: > > 1) An initialization-free class is compiled like always, including an '' method of the form 'aload_0; invokespecial; return;'. Some metadata (flag or attribute) indicates that the class is initialization-free. Yep, maybe. The property is verifiable by inspecting bytecodes. But if we are going to verify that property, since we are changing the verifier, we could make the constructor unambiguously empty. I suggest marking it ACC_ABSTRACT, which is currently a disallowed marking, but it carries the correct connotations (body is disallowed, not merely trivial). A class with an ACC_ABSTRACT constructor is forbidden to extend a class without a corresponding constructor. Perhaps Object is a special case, or perhaps its sole constructor is explicitly marked __DeclarativelyEmpty. Perhaps the spelling of __DeclarativelyEmpty is ?abstract?. That wouldn?t make me sad. > The initialization-free flag is partially validated at class load time: it's an error to claim to be initialization-free and be non-abstract (and non-Object), declare instance fields, or extend a non-initialization-free superclass. > > We don't validate method contents. If someone chooses to generate an "initialization-free" class file that contains code, they accept the risk that the code won't run. Ugh. We don?t because we can?t reliably. But we should. I think the ACC_ABSTRACT option is better for that reason. > On loading, an inline class must extend an initialization-free class. > > 2) An initialization-free class is compiled without an '' method. > > For binary compatibility, existing references to 'Foo.' must successfully resolve, with invocation being a no-op. (This is something new?resolution to fake declarations?and potentially concerning.) > > At class load time, an inline class must extend a chain of superclasses that are abstract (or Object), lack '' methods, and lack instance field declarations. That?s relatively magical, reasoning from the absence of something to the presence of a special contract. Yuck. Also it make it impossible for a class to have other kinds of constructors (Integer). Maybe that?s OK in the end, but it seems to cut off some natural moves. > Existing classes that meet these requirements may act as inline class superclasses (probably surprisingly, since currently a class without an method can't be initialized at all). > > (My thoughts on (1) vs. (2): both are plausible, and I like the lack of metadata overhead in (2), but otherwise (1) seems much cleaner.) > > In either case, the big new feature here is that an inline class may have a superclass other than Object. This may violate some existing assumptions in the implementation, although it sounds like we can hope nothing new really needs to be done to support it. So, (3) allow the constructor with no arguments to be declared ?abstract? with no body. Amend the JVM rules to allow this, and to check upward to the super for the same condition. During static checking of source, treat such a constructor as empty, *and* as forbidding non-static initializers. I think that gets what we need in a much clearer manner. Then, all supers of an inline are required to have either no constructors (interfaces) or a nullary abstract constructor. Done. I like (3) better than (1) or (2). :-) ? John From brian.goetz at oracle.com Fri Dec 20 15:59:57 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 20 Dec 2019 10:59:57 -0500 Subject: Superclasses for inline classes In-Reply-To: <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> Message-ID: <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> Stepping back, the thing that frightened us away from this was the combination of (a) not wanting to have a modifier on abstract classes to indicate inline-friendly, and (b) worrying that it was a lot of (brittle) work to structurally detect inline-friendly abstract classes.? Dan has cut this knot by tying it to the presence of a declaratively-empty constructor, which changes the story a lot.? (I remain unconvinced that instance fields in inline-friendly abstract classes could possibly be in balance, cost-benefit-wise, and super-unconvinced about inlines extending non-abstract classes.) As John says, there are three potential states for an abstract type: always-identity (true of traditional abstract classes), always-inline, and identity-agnostic.? (A possibly way to capture these is by leaning on our new friends IdentityObject and InlineObject; an always-inline abstract type implements InlineObject, and always-identity abstract type implements IdentityObject, and an agnostic one implements neither.)? It is also possible we might prune away the always-inline flavor of abstract types, leaving us with two: inline-friendly or identity-locked. From a JLS perspective, we could say an abstract type is inline-friendly iff: ?- its supertypes are all inline-friendly ?- it has a declaratively empty constructor ?- it has no synchronized methods ?- it has no instance fields ?- it has no instance initializers Object, and all interfaces, would be inline-friendly (we can adjust the declaration of Object to meet this requirement); the compiler would structurally recognize abstract classes as inline-friendly and set the bits in the classfile. In migrating Integer and friends to be inline-friendly (or inline-locked) abstract classes, all we do is push the fields and instance method implementations down into the primitive classes, which is fine because these classes are final.? (Making Integer an abstract class vs an interface means that int inherits the stupid statics on Integer, like the terminally confusing getInteger(String).? Maybe some further deprecation of static inheritance is warranted here.) If we say "instance fields is a can of worms" (which I think we should), we get a further simplification: we don't need to deal with the combination of both declaratively-empty constructors and traditional constructors; either you have real constructors (identity-locked) or empty ones (inline-locked/friendly), and in the case of an inline-friendly being extended by an identity class, the super constructor call is seen as a no-op. > So, (3) allow the constructor with no arguments to be declared ?abstract? > with no body. Amend the JVM rules to allow this, and to check upward > to the super for the same condition. During static checking of source, > treat such a constructor as empty,*and* as forbidding non-static initializers. > I think that gets what we need in a much clearer manner. The remaining bikeshed this leaves us is how to mark the constructor (abstract seems a good strawman), and whether to pull on the {Inline,Identity}Object levers. On 12/19/2019 9:12 PM, John Rose wrote: > On Dec 18, 2019, at 3:57 PM, Dan Smith wrote: >> [Expanding on and summarizing discussion about abstract superclasses from today's meeting.] >> >> ----- >> Motivation >> >> There are some strong incentives for us to support inline classes that have superclasses other than Object. Briefly, these include: >> >> - Identity -> inline migration candidates (notably java.lang.Integer) often extend abstract classes >> - A common refactoring may be to extend an existing class with a (possibly private) inline implementation >> - Abstract classes are more expressive than interfaces >> - If we compile Foo.ref to an abstract class, we can better represent the full API of an inline class using an abstract class > I?m glad we are cracking open this can of worms; I?ve always thought > that interfaces as inline supers were good enough but not necessarily > the whole story. > > (At the risk of instilling more terror, I?ll say that I think that an abstract > super to an inline could contribute non-static fields, in a way that is > meaningful, useful, and efficient. The initialization of such inherited > fields would of course use withfield and would require special rules > to allow the initialization to occur in the subclass constructor/factory. > I suppose this is a huge feature, as Dan says later. A similar effect will > be available from templates, with less special pleading.) > > (Does it make sense to allow an abstract class to *also* be inline? > Maybe, although there is a serious question about its default value. > If a type is abstract its default value is probably required to be null.) > > A useful organizing concept for abstract supers, relative to inlines, > is a pair of bits, not both true, ?always-inlined? and ?never-inlined?. > Object and interfaces have neither mark by default. The super of > an identity class cannot be ?always-inlined" and the super of an > inline class cannot be ?never-inlined?. Or, an identity (resp. inline) > class has the ?always-inlined? (resp. ?never-inlined?) bit set. And > for every T <: U in the class hierarchy, if T is always-inlined, then > U must not be never-inlined, and vice versa. Thus if U is marked > then every T <: U is forbidden to have the opposite mark. Or, > even more simply, both bits are deemed to inherit down to all > subtypes, and no type may contain both marks. > > I don?t know how to derive those bits from surface syntax. A marker > interface for each is a first cut: AlwaysInlined, NeverInlined. Marker > interfaces are kind of smelly. These particular ones work a little better > than their complements (InlineFriendly, IdentityFriendly) because > they exclude options rather than include them. > > (Could a *non-abstract* inline be a super of another inline? No, I?d > like to draw the line there, because that leads to paradoxes with flattening, > or else makes the super non-flattenable in most uses, or violates a > substitutability rule.) > >> To be clear, much of this has to do with migration, and I subscribe to a fairly expansive view of how much we should permit and encourage migration. I think most every project in the world has at least a few opportunities to use inline classes. Our design should limit the friction necessary (e.g., disruptive redesigns of type hierarchies) to integrate inline classes with the existing body of code. > You have a point. Migration is not a task but a way of life? > >> We've considered, as an alternative, supporting transparent migration of existing classes to interfaces. But this raises many difficult issues surrounding source, binary, and behavioral compatibility. It would be nice not to have to tackle those issues, nor introduce a lot of caveats into the class -> interface migration story. > As I said earlier, for value types we have this recurring need to bend > interfaces to be more like abstract classes, or else allow abstract classes > to become more like interfaces. > >> ----- >> Constraints >> >> Inline class instantiation is is fundamentally different from identity class instantiation. While the language seeks to smooth over these differences, under the hood all inline objects come from 'defaultvalue' and 'withfield' invocations. There is no opportunity in these bytecodes for a superclass to execute initialization code. > In the case we are discussing, the interface-like trick that abstract > classes need to learn is to have (declaratively) empty constructors. > > I think that if a class (abstract or not) has a non-empty constructor, > it must also be given the ?never-inline? mark. (This is one reason > that mark isn?t simply a marker interface.) In this way (or some > equivalent) a class with a non-empty constructor will never attempt > to be the super of an inline. > >> (Could we redesign the construction model to properly delegate to a superclass? Sure, but that's a huge new feature that probably isn't justified by the use cases.) > Probably not. Unless folks demand to factor fields as well as behaviors > into abstract supers of inlines. > >> As a result, constructors, instance initializers, and instance fields in a superclass are unusable to inline class instances. In fact, their existence would be a vulnerability, since authors typically make assumptions about initialization having occurred. > If constructors are declaratively empty, it follows that subclasses must be > given both responsibility and authority to initialize all fields of supers > with empty constructors. The easiest way to handle this is to forbid > such fields. Another way is to treat field initialization as a protected > activity (may occur in a subclass constructor). Currently it is arguably > a private activity (must occur in the same class constructor). > > So: > > abstract class S { > __DeclarativelyEmpty S(); > final int x; > } > final class C extends S { > final int y; > C(int a, int b) { > super.x = a; > this.y = b; > } > } > > My take is that it?s doable, and its worth is unproven at present. > >> Fortunately, 'Object' doesn't require any initialization and so can safely be extended. Our goal is to expand the set of safe-to-extend classes. >> >> ----- >> Language model >> >> An inline class may extend another class, as long as the superclass has the following properties: >> - It has no instance fields >> - It has no constructors >> - It has no instance initializers >> - It is abstract* or Object >> - It extends another class with these properties > It all comes down to this I think: > > - The class has a declaratively empty constructor. (Perhaps it may have others!) > > With the following axioms and inferences: > > - Object has a declaratively empty constructor. (And no others, in fact.) > - All interfaces have a declaratively empty constructor. (And no others, in fact.) > - A class (other than Object) with a declaratively empty constructor must have a super with declaratively empty constructor. > - A class with a declaratively empty constructor must only static initializers. > - Checking of blank final initialization is performed as if a declaratively empty constructor in fact has an empty body. > > Therefore, a class with a declaratively empty constructor must not declare final fields, or else we must extend DA/DU rules for blank finals to allow ?protected initialization?, as sketched above. > > I think it?s better to tease the conditions apart in this way rather than lump them all together. > >> Subtype polymorphism works the same for superclasses as it does for superinterfaces. >> >> (*Remi points out that we could drop the 'abstract' restriction, meaning there may be identity instances of the superclass. Given the restriction on fields, though, I'm struggling to envision a use case; the consensus is that 'new Object()' is probably something we want to *stop* supporting.) > Pushing the other way, and given that the restriction on fields could be lifted, there are good use cases like Integer. The inline subtypes of Integer would use the declaratively empty constructor, while the identity instances would use a different constructor. > > And Integer would be marked neither ?always-inline? nor ?never-inline?, so the subtype ?int? would work OK as an inline. Its constructor would do a ?withfield? to initialize Integer.value, as a protected blank final. > > If we conclude that there are no use cases for such abstract-final fields, fine. > But claiming that such a use case cannot exist because there are no such fields > is a mere circularity. > >> Call a class that satisfies these constraints an "initialization-free class" (bikeshedding on this term is welcome!). > See above. And note that a class might have *both* initialization free modes > and regular constructors, *at the same time*. The key property is that there > is a constructor which is declaratively empty. That provides us the API > surface we need to fit things together properly in the subclass. > >> Like an interface, its value set may include references to both inline class instances and identity class instances. > OK. > >> We *do *not* want the initialization-free property to be expressed as a class modifier?this feature is too obscure to deserve that much prominence, encouraging every class author to consider one more degree of freedom; and we don't want every class to have to manually opt in. > I agree. It really a modifier on a constructor, isn?t it? I suppose it always > goes on the default (nullary) constructor. > >> But we *do* need the initialization-free property to be part of the public information about the class. For example, the javadoc should say something like "this is an initialization-free class". Otherwise, it's impossible to tell the difference between, e.g., a class with no fields and a class with private fields. >> >> In the past, a Java class declaration that lacks a constructor always got a default constructor. In this model, however, an initialization-free class has no constructor at all. A 'super()' call directed at such a class is a no-op. > Yep. And that?s (one reason) why declarative emptiness differs from textual > emptiness and must be contagious upward. > >> ----- >> Compilation & JVM support >> >> There are two alternative compilation strategies: >> >> 1) An initialization-free class is compiled like always, including an '' method of the form 'aload_0; invokespecial; return;'. Some metadata (flag or attribute) indicates that the class is initialization-free. > Yep, maybe. The property is verifiable by inspecting bytecodes. > But if we are going to verify that property, since we are changing > the verifier, we could make the constructor unambiguously empty. > I suggest marking it ACC_ABSTRACT, which is currently a disallowed > marking, but it carries the correct connotations (body is disallowed, > not merely trivial). A class with an ACC_ABSTRACT constructor > is forbidden to extend a class without a corresponding constructor. > Perhaps Object is a special case, or perhaps its sole constructor is > explicitly marked __DeclarativelyEmpty. Perhaps the spelling of > __DeclarativelyEmpty is ?abstract?. That wouldn?t make me sad. > >> The initialization-free flag is partially validated at class load time: it's an error to claim to be initialization-free and be non-abstract (and non-Object), declare instance fields, or extend a non-initialization-free superclass. >> >> We don't validate method contents. If someone chooses to generate an "initialization-free" class file that contains code, they accept the risk that the code won't run. > Ugh. We don?t because we can?t reliably. But we should. I think > the ACC_ABSTRACT option is better for that reason. > >> On loading, an inline class must extend an initialization-free class. >> >> 2) An initialization-free class is compiled without an '' method. >> >> For binary compatibility, existing references to 'Foo.' must successfully resolve, with invocation being a no-op. (This is something new?resolution to fake declarations?and potentially concerning.) >> >> At class load time, an inline class must extend a chain of superclasses that are abstract (or Object), lack '' methods, and lack instance field declarations. > That?s relatively magical, reasoning from the absence of something to > the presence of a special contract. Yuck. Also it make it impossible > for a class to have other kinds of constructors (Integer). Maybe that?s > OK in the end, but it seems to cut off some natural moves. > >> Existing classes that meet these requirements may act as inline class superclasses (probably surprisingly, since currently a class without an method can't be initialized at all). >> >> (My thoughts on (1) vs. (2): both are plausible, and I like the lack of metadata overhead in (2), but otherwise (1) seems much cleaner.) >> >> In either case, the big new feature here is that an inline class may have a superclass other than Object. This may violate some existing assumptions in the implementation, although it sounds like we can hope nothing new really needs to be done to support it. > So, (3) allow the constructor with no arguments to be declared ?abstract? > with no body. Amend the JVM rules to allow this, and to check upward > to the super for the same condition. During static checking of source, > treat such a constructor as empty, *and* as forbidding non-static initializers. > I think that gets what we need in a much clearer manner. > > Then, all supers of an inline are required to have either no constructors > (interfaces) or a nullary abstract constructor. Done. > > I like (3) better than (1) or (2). :-) > > ? John > > > From john.r.rose at oracle.com Fri Dec 20 19:00:03 2019 From: john.r.rose at oracle.com (John Rose) Date: Fri, 20 Dec 2019 11:00:03 -0800 Subject: stronger deprecation In-Reply-To: <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> Message-ID: <24DB7EED-F346-4187-98A3-099C0059A9F9@oracle.com> (Splitting out a mini-topic.) On Dec 20, 2019, at 7:59 AM, Brian Goetz wrote: > > ? the terminally confusing getInteger(String). Maybe some further deprecation of static inheritance is warranted here The translation strategy and JVM have a mechanism for totally submerging such methods, so that they are no longer visible to the source code; it?s ACC_SYNTHETIC. A synthetic method occupies a descriptor and is linkable and reflectable but cannot be used from source code. There is no syntax for defining such things in source code; the compiler back end spits them out into class files. But if these noxious methods were to be deprecated to the point of unusability *in source code*, yet still needed to be present as linkage points for old classfiles, we could create a marking, and a user model, for keeping them around. We could define a modifier with the appropriate properties and slap it on offenders like getInteger. Here?s a PoC design FTR: https://bugs.openjdk.java.net/browse/JDK-8236444 (Something like this might also be appropriate for non-deprecated ?back doors? like deserialization API points.) ? John From brian.goetz at oracle.com Fri Dec 20 20:04:32 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 20 Dec 2019 15:04:32 -0500 Subject: Reference-default style In-Reply-To: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> References: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> Message-ID: <5bc689b8-08a0-9b59-a17e-01b8940e3940@oracle.com> > 1) As a design pattern This was the strawman starting point, shortly after the JVMLS meeting, which kicked off the "eclair" notion.? While this one seems like "the simplest thing that could work", it strikes me as too simple. When some version of this approach was floated much earlier, Stephen commented "I'm not looking forward to making up new names for the inline flavor of LocalDateTime and friends."? I share this concern, but 100x so on behalf of the clients -- I don't want to force clients to have to keep a mental database of "what is the inline flavor of this called."? So I think its basically a forced move that there is some mechanical way to say "the other flavor of T". Several folks have come out vocally in favor of the Foo / foo naming convention, which could conceivably satisfy this requirement.? But, I see this as a move we will likely come to regret.? (Among other things, there goes our source of conditional keywords, forever.? On its own, that's a lot of damage to the future evolution of the language.) The "mechanical way to describe the reference companion/projection/pair/whatever" becomes even stronger when we get to specialized generics, as we'll need to be able to say `T.ref` for a type variable `T` (this is, for example, the return type of `Map::get`.)? The other direction is plausible too (when `T extends InlineObject`), though I don't have compelling examples of this in mind right now, so its possible that this is only a one-way requirement. > 2) As an "advanced" feature of inline classes > > This is the State of Valhalla strategy: inline classes are designed to be inline-default, but as a special-case feature, you can also declare the 'Foo.ref' interface, give it a name, and wire it up to the inline class declaration. > > In reference-default style, the programmer gives the "good name" to the reference projection, and either gives an alternate name to the inline class or is able to elide it entirely (in that case, clients use 'Foo.inline'). > > Ways this is different than (1): > - The 'Foo.inline' type operator > - Implicit conversions (although sealed types can get us there in (1)) > - There are two types, not three (and two JVM classes, not three) > - Opportunities for "boilerplate reduction" in the two declarations Much of the generality of (2) comes from the goals of migrating primitives to just be declared classes, while retaining the spelling `Integer` for the ref projection, and not having _two_ box types. If we're willing to special-case the primitives, then we may be able to do better here. > 3) As an equal partner with inline-default > > An inline class declaration introduces two types, an inline type and a reference type. But a modifier on the declaration determines whether the "good name" goes to the inline type or the reference type. The other type can be derived using an operator ('Foo.ref' or 'Foo.inline'). There's never a need for an alternate name. > > In this case, the language isn't biased to one style or the other; each declaration picks one. The trade-off is that clients need to keep track of one more bit when thinking about the inline class ("Is this a *foo* inline class or a *bar* inline class?" Actual terminology to be bikeshedded...) In a previous iteration, we had an LV/QV duality at the VM level, which corresponded to a null-default/zero-default duality at the language level.? We hated both of these (too much complexity for too little gain), so we ditched them.? What you're proposing is to reintroduce a new duality, `ref-default` vs `inline-default`, which would arbitrate custody of "the good name". What I like about this is that _both_ `Foo.ref` and `Foo.inline` become true projections from the class declaration Foo; there's no "write a bunch of classes and wire up their relationship".? (Though some degree of special pleading and auto-wiring would be needed for primitives, which seems like it is probably acceptable.)? It is a more principled position, and not actually all that different in practice from (2), in that the default is still inline. What I don't like is that (a) the author has to pick a polarity at development time (and therefore can pick wrong), and (b) to the extent ref-default is common, the client now has to maintain a mental database of the polarity of every inline class, and (c) if the polarity is not effectively a forced move (as in (2), where we only use it for migration), switching polarities will (at least) not be binary compatible.? So the early choice (made with the least information) is permanent. From a user perspective, we are introducing _two_ new kinds of top level abstractions; in (2), we are introducing one, and leaning on interfaces/abstract classes for the other.? On the other other hand, having more ref-default classes than the migrated ones will make `.inline` stick out less. Do we want to step back away from the experiment that is `inline`, and go back to `Foo.ref` and `Foo.val`?? If we're looking to level the playing field, giving them equally fussy/unfussy names is a leveler... > 4) As the only supported style > > An inline class declaration always gives the "good name" to the reference type, and you always use an operator to get to the inline type ('Foo.inline'?but we're gonna need better syntax.) > > This one would represent a significant shift in the design center of the feature. If you want flattening everywhere, you're going to need to make liberal use of the '.inline' operator. But if you just want to declare that a bunch of your classes don't have identity, and hopefully get a cheap performance boost as a result, it's simple. The burden of learning something new is shifted to "advanced" users and APIs to whom flattening is important. I can't really see this being a winner. > Conclusion: > > I'm not ready to completely dismiss any of these designs, but my preferences at the moment are (1) and (3). Options (4) and (5) are more ambitious, discarding some of our assumptions and taking things in a different direction. > > Like many design patterns, (1) suffers from boilerplate overhead ((2) too, without some language help). It also risks some missed opportunities for optimization or language convenience, because the relationship between the inline and reference type is incidental. (I'd like to get a clearer picture of whether this really matters or not.) The main knock on (1) is that it leans on an ad-hoc convention, and to the extent this convention is not universally adhered to, user confusion abounds.? (Think about how many brain cycles you've spent being even mildly miffed that the box for `long` is `Long` but the box for `char` is `Character`.? If it's more than zero, that's a waste of cycles.) I really have a hard time seeing (1) as leading where we want. > (5) feels like something fundamentally new in Java, although if you squint it's "just" a variation on name resolution. What originally prompted this idea was seeing a similar approach in attempts to introduce nullability type operators?legacy code has the "wrong" default, so you need some lightweight way to pick a different default. (5) could be achieved with another long-standing requests, aliased imports: ??? import Foo.inline as Foo; Not saying that makes it better, but a lot of people sort of want import to work this way anyway. From brian.goetz at oracle.com Fri Dec 20 20:33:43 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 20 Dec 2019 15:33:43 -0500 Subject: Fwd: Re: Reference-default style In-Reply-To: <1942540089.223334.1576836459644@email.ionos.co.uk> References: <1942540089.223334.1576836459644@email.ionos.co.uk> Message-ID: <1b04c865-b9db-5351-a748-18ef46a43a4b@oracle.com> Received on the -comments list. Summary: "I like the Foo/foo naming convention." -------- Forwarded Message -------- Subject: Re: Reference-default style Date: Fri, 20 Dec 2019 10:07:39 +0000 (GMT) From: Elias Vasylenko Reply-To: Elias Vasylenko To: Maurizio Cimadamore , Dan Smith , valhalla-spec-comments at openjdk.java.net I realise this has been said before, but it does seem particularly pertinent to this discussion so I hope it's not a bad time for a gentle reminder. There is an assumption baked into all these approaches that there is only reliably one "good name" for any given class, but arguably that's not the case. It could be convention that the inline version of a type takes the same name but with a lowercase spelling, e.g. the inline version of `Optional` is `optional`. With this convention, pretty much all inlineable types can reliably have a good name to refer to both inline and reference variants. As an added bonus, users can easily distinguish between them at the use site at a glance (assuming adherence to the convention can be relied upon). This naming convention also happens to be convenient for migrating existing classes such as `Optional` and `DateTime`; we just introduce inline versions called `optional` and `dateTime` (or `datetime`?). This also works out neatly for primitives and their wrappers. This doesn't obviate the need for the `.inline` and `.reference` type operators for two reasons. One, it would be icky to mechanically derive the alternate spelling, and we might not always want to force users to write both names down. Two, for type parameters. (For that matter why no `T.erased` operator? But that's not relevant.) So perhaps there are a couple of other options. 6a) A single inline class declaration introduces two types, either one or both of which can be named. If only one name is provided it goes to the inline type. // create an inline class `point` with an unnamed reference projection public inline class point { /*...*/ } // create an inline class `optional` with a reference projection `Optional` public inline class optional reference Optional { /*...*/ } 6b) As above, except if only one name is provided it goes to the reference type. // create an unnamed inline class with a reference projection `Node` public inline class Node { /*...*/ } // create an inline class `optional` with a reference projection `Optional` public inline optional class Optional { /*...*/ } The bikeshedding on how they'd be declared is just for the purposes of illustration and not a serious suggestion. The point is, I think if the barrier for declaring *two* "good names" is low enough then worrying about "inline default" vs "reference default" becomes redundant. > On 20 December 2019 at 00:10 Maurizio Cimadamore > wrote: > > > On 19/12/2019 21:15, Dan Smith wrote:> Like many design patterns, (1) > suffers from boilerplate overhead ((2) too, without some language > help). It also risks some missed opportunities for optimization or > language convenience, because the relationship between the inline and > reference type is incidental. (I'd like to get a clearer picture of > whether this really matters or not.)At the last post JVMLS meeting I > was a string advocate of this position.This is effectively the pattern > used in the Panama memory access API,where we have public (in future > sealed) interfaces backed up byinline-ready implementation classes. > While I still think that there will be a lot of cases like these > -Panama also needs something which is more akin to the > 'programmableprimitive'-half of the Valhalla glass. That is, we might > want tointroduce a int128 type or float16, which might be required to > interopwith certain system ABIs. > When you do that, you would like to have these types (e.g. int128) > the*public* ones, the ones with the good names. You want users to > create(flat) arrays of them, rather than oops arrays. > So, as much as I like (1) I don't think we can fully get away with that? > Maurizio From john.r.rose at oracle.com Fri Dec 20 21:11:59 2019 From: john.r.rose at oracle.com (John Rose) Date: Fri, 20 Dec 2019 13:11:59 -0800 (PST) Subject: Superclasses for inline classes In-Reply-To: <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> Message-ID: On Dec 20, 2019, at 7:59 AM, Brian Goetz wrote: > > Stepping back, the thing that frightened us away from this was the combination of (a) not wanting to have a modifier on abstract classes to indicate inline-friendly, Perhaps that?s made better by inverting the sense of the ?bit? (perhaps modifier), so that inline-hostile classes have the burden of marking themselves, and inheritance can help with that (but only if the bit is inverted). But inline-friendly types still need to contrive declaratively-empty constructors, so it looks like there?s a lot of recompilation in our future. > and (b) worrying that it was a lot of (brittle) work to structurally detect inline-friendly abstract classes. Dan has cut this knot by tying it to the presence of a declaratively-empty constructor, which changes the story a lot. This is a specific case of making abstract classes more like interfaces. If you look carefully at interfaces, you can see that their flexibility derives, in large part, from the lack of constructors. (This in turn implies the lack of instance fields, but in a secondary way: You can?t control such fields fully without a constructor.) Having declaratively empty constructors in classes (abstract or not!) opens for them some of the same paths that interfaces enjoy. This reasoning can go the other way, too, to make interfaces more like abstract classes. As I?ve pointed out before, interfaces could be given explicit declaratively-empty constructors, which in turn could be given less-public access than the interfaces themselves. This would provide the same level of subclass control for interfaces as for abstract classes, with an effect close to ?sealed interfaces?, from a class-like primitive (access control on constructors). In particular an interface could be sealed to its nest mates by marking its declaratively-empty constructor as private (and so on). I?m not pointing this out to say we should done sealing differently; I love the way sealing turned out. But it?s important to be aware of some underlying ?class physics? at play here with both interfaces and abstracts. As a VM guy I tend to see the ?physics" this way as logical design constraints that bubble up from the VM, instead of starting with a desired psychology and working down through the chemistry (if you get my drift). Over time I see interfaces becoming more like abstract classes (notably with default methods), and abstract classes returning the favor by growing declaratively-empty constructors. This is not an accident. I?m convinced that as we continue to pay attention to the ?physics?, we will be better informed in our treatment of other aspects of types, including instance fields and identity. > (I remain unconvinced that instance fields in inline-friendly abstract classes could possibly be in balance, cost-benefit-wise, and super-unconvinced about inlines extending non-abstract classes.) My concern here is to point out the logical possibility of such things, not to advocate for them now. Treating fields and non-abstract supers as corollaries, rather than than axioms, makes me more certain we have grasped the physical essentials of the problem. This is a valuable design heuristic: We know that if we can say ?no? to features while still understanding how they could fit in the future, we have arrived at a more factored, more desirable design. This is why I?m talking now, hypothetically, about fields and concretes. (Which is more important, physics or psychology? Neither and both, I suppose, but physics has this privilege: If you build on an inconsistent or gratuitously complex logical foundation, your user experience will never ever be as smooth as it could be.) > As John says, there are three potential states for an abstract type: always-identity (true of traditional abstract classes), always-inline, and identity-agnostic. (A possibly way to capture these is by leaning on our new friends IdentityObject and InlineObject; an always-inline abstract type implements InlineObject, and always-identity abstract type implements IdentityObject, and an agnostic one implements neither.) It is also possible we might prune away the always-inline flavor of abstract types, leaving us with two: inline-friendly or identity-locked. I think the always-inline flavor of abstract *classes* can be replaced, for most use cases, with interfaces (with sealing + default methods - toString), and later on with templates (as long as the polymorphism is parametric). Many uses of an always-inline flavor of *interfaces* can be replaced by a sealed interface, where all the permits are inlines. Dan and Remi have pointed out some places where we might be confronted with a demand for more, which means we should keep our eyes open. I?m very happy to bid less, for now, and perhaps forever. > From a JLS perspective, we could say an abstract type is inline-friendly iff: > - its supertypes are all inline-friendly > - it has a declaratively empty constructor (Yes and yes!) > - it has no synchronized methods I think the natural way to phrase this is that the type itself does not admit synchronization, either via a synchronized method or a synchronized statement. Then it?s a type system property rather than a structural property of methods. > - it has no instance fields > - it has no instance initializers Yes, although the latter condition might be stated more concisely that all initializers must be static. The language partially obscures, but fully obeys, the ?physical? fact that all non-static initializers contribute effects into every constructor. If there is just one declaratively-empty constructor, that forces a contradiction. This contradiction can be documented in terms of the practical effects you list. (Defining a non-final non-static field does not directly contradict a declaratively-empty constructor, but it motivates forbidding such fields until and unless there is some new mechanism or workaround for encapsulating them properly. Templates provide a workaround, and protected init for blank finals could provide another, if we decided it was worth doing, at some point in the future.) > Object, and all interfaces, would be inline-friendly (we can adjust the declaration of Object to meet this requirement); the compiler would structurally recognize abstract classes as inline-friendly and set the bits in the classfile. I like this, as long as ?structurally? includes some explicit signal either in the source code or (at least) in superclass (from which a new default would be silently inferred). IMO there?s it?s hard to see a case for ?promoting? apparently-empty constructors (like C(){}) into declaratively-empty ones. They would become invisibly-non-empty via action at a distance in supers and in field definitions. What I?m suggesting is that a class C can have either or neither of these two declarations: C() { ? } abstract C(); //__DeclarativelyEmpty But not both. And if neither is present, the rule applies for supplying a default constructor: If there is no constructor at all, a nullary default is supplied if possible. And *that* rule is modified to make the default constructor be declaratively empty, if and only if the nullary super-class constructor is already in fact declaratively empty. (Did I miss anything?) > In migrating Integer and friends to be inline-friendly (or inline-locked) abstract classes, all we do is push the fields and instance method implementations down into the primitive classes, which is fine because these classes are final. Yes, that?s probably OK. Good! Maybe there?s a role for templates to play also. > (Making Integer an abstract class vs an interface means that int inherits the stupid statics on Integer, like the terminally confusing getInteger(String). Maybe some further deprecation of static inheritance is warranted here.) (Yep, see other message.) > If we say "instance fields is a can of worms" (which I think we should), (for now, maybe forever) > we get a further simplification: we don't need to deal with the combination of both declaratively-empty constructors and traditional constructors; This is an interesting point, and I think it?s true under the assumption that we don?t open that can of worms, yet or maybe ever. The converse is also true: If there were some utility to have the *same class* have *both inline and non-inline instances*, then it would need *separate constructors*. That would incur the cost of shepherding the worms as they crawl out of the can. I don?t have a proposal here, though, at least not one that passes the red-face test. > either you have real constructors (identity-locked) or empty ones (inline-locked/friendly), and in the case of an inline-friendly being extended by an identity class, the super constructor call is seen as a no-op. Yes! >> So, (3) allow the constructor with no arguments to be declared ?abstract? >> with no body. Amend the JVM rules to allow this, and to check upward >> to the super for the same condition. During static checking of source, >> treat such a constructor as empty,*and* as forbidding non-static initializers. >> I think that gets what we need in a much clearer manner. > > The remaining bikeshed this leaves us is how to mark the constructor (abstract seems a good strawman), and whether to pull on the {Inline,Identity}Object levers. +1 for abstract; I think it?s at least as good an analogy with abstract methods and classes as ?final?. Note that ?abstract? means ?somebody else must supply my concrete bits?, and that?s *exactly* the case here, where the only guy that supply the constructor of an inline class is the inline class itself. It?s feeling like progress here. (Thanks, Dan!) ? John From john.r.rose at oracle.com Fri Dec 20 21:46:06 2019 From: john.r.rose at oracle.com (John Rose) Date: Fri, 20 Dec 2019 13:46:06 -0800 Subject: Reference-default style In-Reply-To: <5bc689b8-08a0-9b59-a17e-01b8940e3940@oracle.com> References: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> <5bc689b8-08a0-9b59-a17e-01b8940e3940@oracle.com> Message-ID: On Dec 20, 2019, at 12:04 PM, Brian Goetz wrote: > > The other direction is plausible too (when `T extends InlineObject`), though I don't have compelling examples of this in mind right now, so its possible that this is only a one-way requirement. You?ve already looked at this, but to spell out more details FTR: Reversing arrows, as T.ref is useful as a return type, so T.inline is useful an argument type. T.ref as a return value means ?might produce nulls?. And conversely T.inline as an argument means ?won?t accept nulls?. Both are cases where the usual application of Postel?s Law needs an explicit variance. It?s really about nulls more than inlines per se. That?s all very nice, but an erased generic which applies only to inlines seems like a relatively useless generic. (Could be failure of imagination, though.) And applying such a generic (with ?T.inline? arguments) to identity and reference types creates cognitive dissonance in the user model. I guess T.notnull would be more honest, as far as generics are concerned. But adding that (hello T! T?) is a new job, with lots of knock-on complexity. So, no compelling examples (reasonable cost, good benefit), as you said. ? John From daniel.smith at oracle.com Fri Dec 20 23:30:46 2019 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 20 Dec 2019 16:30:46 -0700 Subject: Superclasses for inline classes In-Reply-To: References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> Message-ID: <185F8D7A-DFD2-4A98-BF6C-9BD95DAAC3EB@oracle.com> A few of points in this thread emphasize differences between *declared* inline-friendly abstract classes and *implicitly* inline-friendly abstract classes. > On Dec 20, 2019, at 2:11 PM, John Rose wrote: > >> - it has no synchronized methods > > I think the natural way to phrase this is that the type itself does not > admit synchronization, either via a synchronized method or a > synchronized statement. Then it?s a type system property rather > than a structural property of methods. If the class is declared inline-friendly, then sure, this is a check we can make. If not, we can either: - Infer from the use of 'synchronized' that this is *not* an inline-friendly class - Call the class inline-friendly anyway, and let it blow up at run time A compile-time error is not an option. I lean towards the runtime error, because inferring a class property based on a random method modifier is too subtle. Put another way, if you want strong checking for 'synchronized' (and I think we can safely call this an option, not a necessity), you also probably want some sort of explicit opt-in to inline friendliness. >> Object, and all interfaces, would be inline-friendly (we can adjust the declaration of Object to meet this requirement); the compiler would structurally recognize abstract classes as inline-friendly and set the bits in the classfile. > > I like this, as long as ?structurally? includes some explicit signal either in the > source code or (at least) in superclass (from which a new default would be > silently inferred). IMO there?s it?s hard to see a case for ?promoting? > apparently-empty constructors (like C(){}) into declaratively-empty ones. > They would become invisibly-non-empty via action at a distance in supers > and in field definitions. Hmm, action at a distance is inevitable in the "just infer it" model. Somebody decides to add an innocent-seeming private field, and they've broken binary compatibility with their subclasses. Are we being too clever? We need the class to give us a permanent guarantee about the API; but at the same time, we're hoping the author doesn't have to notice that they're making this promise. That may not be workable. I kind of like your idea of a new flavor of constructor. If we drop the inference piece, it forces us to surface something in the language, but maybe it's subtle enough that, unlike a new flavor of class, it doesn't trip the "must include in Java 101" wire? From brian.goetz at oracle.com Fri Dec 20 23:48:03 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 20 Dec 2019 18:48:03 -0500 Subject: Superclasses for inline classes In-Reply-To: <185F8D7A-DFD2-4A98-BF6C-9BD95DAAC3EB@oracle.com> References: <145A4BBE-4972-4CEB-A149-95C4D6E5C519@oracle.com> <2BB0F367-306E-4516-800C-367FD48FABBE@oracle.com> <266e7f65-0ab1-f4c8-d336-9e0c1fc7cdd5@oracle.com> <185F8D7A-DFD2-4A98-BF6C-9BD95DAAC3EB@oracle.com> Message-ID: <993e6971-5ac4-6526-756f-d51021a4fb1b@oracle.com> > I kind of like your idea of a new flavor of constructor. If we drop the inference piece, it forces us to surface something in the language, but maybe it's subtle enough that, unlike a new flavor of class, it doesn't trip the "must include in Java 101" wire? While I have little faith in textbook authors to correctly make this call (based on having reviewed dozens of textbooks), yes, this seems a reasonable balance of the concerns. From daniel.smith at oracle.com Sat Dec 21 00:03:21 2019 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 20 Dec 2019 17:03:21 -0700 Subject: Reference-default style In-Reply-To: <62c6ee44-5659-5718-0542-3ef144b55109@oracle.com> References: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> <62c6ee44-5659-5718-0542-3ef144b55109@oracle.com> Message-ID: <46059DB6-7697-404C-9158-49EAE0B66110@oracle.com> > On Dec 19, 2019, at 5:10 PM, Maurizio Cimadamore wrote: > > On 19/12/2019 21:15, Dan Smith wrote: >> Like many design patterns, (1) suffers from boilerplate overhead ((2) too, without some language help). It also risks some missed opportunities for optimization or language convenience, because the relationship between the inline and reference type is incidental. (I'd like to get a clearer picture of whether this really matters or not.) > > At the last post JVMLS meeting I was a string advocate of this position. This is effectively the pattern used in the Panama memory access API, where we have public (in future sealed) interfaces backed up by inline-ready implementation classes. To rephrase this the way I would say it: Panama has use cases that for a reference-default style. > While I still think that there will be a lot of cases like these - Panama also needs something which is more akin to the 'programmable primitive'-half of the Valhalla glass. That is, we might want to introduce a int128 type or float16, which might be required to interop with certain system ABIs. > > When you do that, you would like to have these types (e.g. int128) the *public* ones, the ones with the good names. You want users to create (flat) arrays of them, rather than oops arrays. And: Panama has use cases for an inline-default style. > So, as much as I like (1) I don't think we can fully get away with that? I think you're misunderstanding (1). The (1) story is: the language gives you inline-default inline classes; if you want to use a reference-default style, you get there via a design pattern (public superinterfaces of possibly-private inline classes). From daniel.smith at oracle.com Sat Dec 21 00:23:09 2019 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 20 Dec 2019 17:23:09 -0700 Subject: Reference-default style In-Reply-To: <5bc689b8-08a0-9b59-a17e-01b8940e3940@oracle.com> References: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> <5bc689b8-08a0-9b59-a17e-01b8940e3940@oracle.com> Message-ID: > On Dec 20, 2019, at 1:04 PM, Brian Goetz wrote: > > I don't want to force clients to have to keep a mental database of "what is the inline flavor of this called." > to the extent ref-default is common, the client now has to maintain a mental database of the polarity of every inline class > From a user perspective, we are introducing _two_ new kinds of top level abstractions; in (2), we are introducing one, and leaning on interfaces/abstract classes for the other. > (Think about how many brain cycles you've spent being even mildly miffed that the box for `long` is `Long` but the box for `char` is `Character`. If it's more than zero, that's a waste of cycles.) I think you're trying to have it both ways. Is the story for (2) that there are two declarations, an interface and an inline class, that clients will think of as distinct entities? Or is the story that there is one entity, the pair of types, with one name for both of them? Once your mental database is keyed on a single name, associated with two different types, it feels like you're doing the latter. But if that's the case, then you need a "polarity" column in the database. I guess I'm saying I'm not sure you can get away with only one name while expecting clients to buy that it's "just" an interface. "Interface that has a matching inline class" *is* a new kind of abstraction. > if the polarity is not effectively a forced move (as in (2), where we only use it for migration) > On the other other hand, having more ref-default classes than the migrated ones will make `.inline` stick out less. One of the points I'm trying to make in this mail is that reference-default use cases are going to exist everywhere, no matter what language choices we make. It's something useful people will do, whatever tools we give them. I suppose options (3)-(5) will encourage the style more, so there will be more of them, but (1) and (2) aren't going to be able to contain it to "only for migration". >> (5) feels like something fundamentally new in Java, although if you squint it's "just" a variation on name resolution. What originally prompted this idea was seeing a similar approach in attempts to introduce nullability type operators?legacy code has the "wrong" default, so you need some lightweight way to pick a different default. > (5) could be achieved with another long-standing requests, aliased imports: > > import Foo.inline as Foo; > > Not saying that makes it better, but a lot of people sort of want import to work this way anyway. Yep. And I think something like this may be in our future, but something we have to decide is whether we're comfortable with two different compilation units interacting with each other but spelling the same type two different ways. From maurizio.cimadamore at oracle.com Sun Dec 22 21:16:32 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Sun, 22 Dec 2019 21:16:32 +0000 Subject: Reference-default style In-Reply-To: <46059DB6-7697-404C-9158-49EAE0B66110@oracle.com> References: <9041A550-BE7D-4D53-9DE5-FF307DD08BD1@oracle.com> <62c6ee44-5659-5718-0542-3ef144b55109@oracle.com> <46059DB6-7697-404C-9158-49EAE0B66110@oracle.com> Message-ID: <96f7324d-0c71-d623-230e-60001cfb5fb0@oracle.com> On 21/12/2019 00:03, Dan Smith wrote: >> On Dec 19, 2019, at 5:10 PM, Maurizio Cimadamore wrote: >> >> On 19/12/2019 21:15, Dan Smith wrote: >>> Like many design patterns, (1) suffers from boilerplate overhead ((2) too, without some language help). It also risks some missed opportunities for optimization or language convenience, because the relationship between the inline and reference type is incidental. (I'd like to get a clearer picture of whether this really matters or not.) >> At the last post JVMLS meeting I was a string advocate of this position. This is effectively the pattern used in the Panama memory access API, where we have public (in future sealed) interfaces backed up by inline-ready implementation classes. > To rephrase this the way I would say it: Panama has use cases that for a reference-default style. > >> While I still think that there will be a lot of cases like these - Panama also needs something which is more akin to the 'programmable primitive'-half of the Valhalla glass. That is, we might want to introduce a int128 type or float16, which might be required to interop with certain system ABIs. >> >> When you do that, you would like to have these types (e.g. int128) the *public* ones, the ones with the good names. You want users to create (flat) arrays of them, rather than oops arrays. > And: Panama has use cases for an inline-default style. > >> So, as much as I like (1) I don't think we can fully get away with that? > I think you're misunderstanding (1). > > The (1) story is: the language gives you inline-default inline classes; if you want to use a reference-default style, you get there via a design pattern (public superinterfaces of possibly-private inline classes). Gotcha - thanks for the clarification Maurizio >