From john.r.rose at oracle.com Fri Mar 1 00:11:05 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 28 Feb 2019 16:11:05 -0800 Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <609925803.719671.1551348001599.JavaMail.zimbra@u-pem.fr> Message-ID: On Feb 28, 2019, at 7:54 AM, Brian Goetz wrote: > >> I think that trying to come that will encompass lazy instance field and lazy static field is a trap, because in the lazy instance case there is no constant pool to store the value. > > I tend to agree with Remi on this one. Condy may be a ?mere? implementation tactic, but it?s a darn good one, it has the semantics we want, and the use case of lazy statics is far more important than lazy instance vars. (We we would be happy if we only had lazy statics and not lazy instances.) See previous message. I agree with what you are saying, with one reservation, that "far more important" is a time-dependent assertion; it can change. There was a time when inner classes were "far more important" than lambdas. Internally in the JDK we make *heavy use* of lazy non-statics, in the form of @Stable fields. This genie is going to come out of its bottle sooner or later. And, while agreeing with you (with that one reservation), I still say it's smart to exclude the T.default value for a lazy value. If we don't do that, we *block* ourselves from removing "static" as a constraint. Or we put off the irregularity, because we will have to say "lazy statics can have T.default but lazy non-statics cannot." In a nutshell: We should make *both* moves, exclude T.default *and* align with condy. The limitation against T.default can be spun, to many users, as a common sense error check, against uninitialized variables. To more curious users, it can be spun as a conscious reservation for efficient implementations. Scala went the other way with their lazies and they have struggled (in vain I think) with the consequences. You need transactional memory to do race-free initialization of 64-bit lazies, if you don't reserve a neutral value. Let's learn from their difficulty. We have enough foresight here to accept the irregularity now, when it will do the least damage. > Note too that the DynamicConstantValue story is a tradeoff to enable a binary compatible migration from non-lazy to lazy; I think this is a sort of corner case, as the vast majority of field accesses are within the same class. If we didn?t care about binary-compatible migrations for public static final fields, there?s a translation-based story that is way simpler ? cross-class field access desguars to invocations of a synthetic accessor. Desugaring a field to a method is a simple move, but it's not a free move. Iit has the usual problems of virtualizing something the user thinks is concrete. I think it's at least as disruptive than making a limitation on the value space of lazies. Also, this assertion is certainly time-dependent: > the vast majority of field accesses are within the same class That's true because the existing options for exporting fields as API points are so limited. Adding features like lazy finals or sealed (semi-public) fields will increase the applicable use cases. If and when it becomes safer to expose a field outside the capsule, more folks will build fields into their APIs. ? John From brian.goetz at oracle.com Fri Mar 1 00:54:00 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 28 Feb 2019 19:54:00 -0500 Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <609925803.719671.1551348001599.JavaMail.zimbra@u-pem.fr> Message-ID: > The limitation against T.default can be spun, to many users, as a common sense > error check, against uninitialized variables. To more curious users, it can be spun > as a conscious reservation for efficient implementations. This is justifiable for (most) ref types, but what about numeric types??? I can imagine many potentially-expensive computations that could reasonably sum to zero.? (And as you point out, the smaller the range, the more silly this gets, ending with boolean.)? So if we're going to have this restriction, it should be restricted to class types, and the recommended workaround be some sort of Optional or box.? And given that not all values will be null-default, it rules out any value types for which the zero answer is a reasonable answer.? I think this edge is sharper than it looks. From john.r.rose at oracle.com Fri Mar 1 01:03:23 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 28 Feb 2019 17:03:23 -0800 Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <609925803.719671.1551348001599.JavaMail.zimbra@u-pem.fr> Message-ID: On Feb 28, 2019, at 4:54 PM, Brian Goetz wrote: > > >> The limitation against T.default can be spun, to many users, as a common sense >> error check, against uninitialized variables. To more curious users, it can be spun >> as a conscious reservation for efficient implementations. > > This is justifiable for (most) ref types, but what about numeric types? I can imagine many potentially-expensive computations that could reasonably sum to zero. (And as you point out, the smaller the range, the more silly this gets, ending with boolean.) So if we're going to have this restriction, it should be restricted to class types, and the recommended workaround be some sort of Optional or box. And given that not all values will be null-default, it rules out any value types for which the zero answer is a reasonable answer. I think this edge is sharper than it looks. What I want here can be obtained by forbidding nulls, specifically, for nullable types, and then doing something different for non-nullable types. A translation strategy could deal with non-nullable types (like boolean) by using condy for statics and some other mechanism for non-statics, such as desugaring the type of the field to a reified Optional. Would that have fewer sharp edges? Wrapping an Optional around every nullable non-static reference is conceivable but that seems to be add a disruptive number of indirections, and also Optional itself excludes nulls. We've made the no-null policy decisions for Optional; let's benefit from it for lazies. ? John From maurizio.cimadamore at oracle.com Fri Mar 1 01:30:36 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 1 Mar 2019 01:30:36 +0000 Subject: lazy statics design notes In-Reply-To: <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> Message-ID: Question: in which category of badness does this belong to? class Foo { ?? static int I = Foo.J; ?? static int J = 2; ?? public static void main(String[] args) { ????? System.out.println(I); //prints 0 ????? System.out.println(J); //prints 2 ?? } } The language allows forward references to static fields, assuming you use a _qualified_ name (don't ask me why :-)). But I guess this is similar to having a static init calling a static method which returns the value of a non-yet initialized static. In any case, it feels that, if we condify these, we would change semantics, as suddenly "I" will be initialized to 2 too? Also, beware of cycles! class Foo { ?? static int I = Foo.J; ?? static int J = I; ?? public static void main(String[] args) { ????? System.out.println(I); //prints 0 ????? System.out.println(J); //prints 0 ?? } } I think a condified translation would throw or run into an endless loop? To me, the crux of the issue is to draw a crisp line between things that can be condified and things that cannot (and have to fallback to ). But, the more I think, the less I'm convinced that such a line exist, or at least that a 'meaningful' one exists. Since all method calls are potentially cycle-inducing (or forward-reference-inducing), lazy statics treatment cannot apply to an initializer that has a method call? And, you can have forward references or cycles through field access too (as above)... so, that seems to leave just simple constants, which doesn't seem to offer a lot of bang for the bucks? Am I missing something? Sidebar: as a VM machinery, I'd love to see something like DynamicValue - in fact I was talking to Sundar the other day that, if we had it, we could solve some thorny issues we have on Panama/jextract bytecode generation. But as a language construct, this feels shaky, but maybe that's just me? Maurizio On 27/02/2019 20:33, John Rose wrote: > On Feb 27, 2019, at 7:30 AM, Karen Kinnear wrote: >> Subject: Valhalla EG notes Feb 13, 2019 >> To: valhalla-spec-experts >> ... >> III. [Remi Forax] DynamicValue attribute >> Another project Remi will lead and create JEP >> language level: static lazy final >> improve startup by allowing init with Condy at first access of individual static >> >> Drawbacks: opt-in at source >> change in semantics >> in static block - there is a lock >> condy BSM can execute multiple times > I was just talking with Vladimir Ivanov about lazy > statics. He is working on yet another performance > pothole with , generated by Clojure this time. > (It's not their fault; the system had to clean up a problem > with correct initialization order, and execution > is over-constrained already, so the JIT has to generate > more conservative code now.) > > I believe lazy statics would allow programmers > (and even more, language implementors) to > use much smaller s, or none at all, > in favor of granular lazy statics. > > So, here's a brain dump, fresh from my recent > lunch with Vladimir: > > Big problem #1: If you touch one static, you buy > them all. Big problem #2: If any one static > misbehaves (blocking, bad bootstrap), all statics > misbehave. Big problem #3: If hasn't > run yet, you need initialization barriers on all > use points of statics; result is that itself, > and anything it calls, is uniquely non-optimizable. > Big problem #4: After touching one static, the > program cannot make progress until the mutex > on the whole Class object is released. Big problem > #5: Setting up multiple statics is not transactional; > you can observe erroneous intermediate states during > the run of the . Big problem #6: Statics > are really, really hard to process in an AOT engine, > because nearly every pre-compiled code path must > assume that the static might not be booted up yet, > and if boot-up happens (just once per execution) > it invalidates many of the assumptions the AOT > engine wants to make about nearby code. > > Solutions from lazy statics: Solution #1: If you touch > one that's the one you buy (plus what's in the vestigial > if there is one at all). Solution #2: Misbehaving > statics don't misbehave until they are used (yes, bug > masking, boo hoo). Solution #3: Initialization barriers > are trivial: Just detect the T.default value of the variable. > Solution #4: There is no mutex, just a CAS at the end > of the BSM for the lazy static; no critical section. > Solution #5: The CAS at the end of the BSM is inherently > transactional. Solution #6: AOT engines can generate > somewhat simpler fast-path code by just testing for > T.default; the slow-path code is still hard to optimize, > but the limits are from the complexity of the BSM > that initializes the lazy static, not the total complexity > of the code. > > Objection: What if you *want* a mutex? I didn't like > the JVM blocking everything in but I don't > want a million racing threads computing the same > BSM value either. Ans: Fine, but make that an opt-in > mechanism, by folding some kind of flow control > into the relevant BSM, for your particular use case. > The JVM doesn't have to know about it. > > Objection: What if I want several statics to initialize in > one event, with or without mutex or transactions? > Ans: Easy, just have the BSM for each touch the others, > or run a common BSM that sets everything up (and then > returns the same value). (Note: At the cost of an > idempotency requirement during lazy init.) In the > most demanding cases, define a private static nested > class to serialize everything, which is today's workaround. > > Objection: Those aren't real statics, because you can't > set them to their T.default values! Ans: They are as > real as you are going to get without creating lots of > side metadata to track the N+1st variable state, which > is a cost nobody wants to pay. > > Objection: But I do want to opt into the overhead and > you aren't giving me my T.default; I need the full range > of values for my special use case. Ans: Then add an > indirection for your use case, to a wrapped copy of your > desired value; the null wrapper value is the T.default in > this case. It's at least as cheap as anything the JVM would > have done intrinsically. > > Objection: You disrespect 'boolean'. It only has one > state left after you filch 'false' to denote non-initialization. > My VM hack can do much better than that. Ans: Let me > introduce you to java.lang.Boolean. It has three states. > > Objection: What if someone uses bytecode to monkey > with the state of my lazy static? Your design is broken! > Ans: This is the sort of corner case that needs extra > VM support. In this case, it is sufficient to privatize > write access to a variable, even though it may be public, > to its declaring class. You can trust the declaring class > not to compile subverting assignments into itself, > because javac won't let it. > > Objection: I can't imagine the language design for this; > surely there are difficulties you haven't foreseen. Ans: > Neither can I, and there certainly are. The sooner we > start trying out prototypes the sooner we'll shake out > the issues. There are several things to try: > > http://openjdk.java.net/jeps/8209964 > http://cr.openjdk.java.net/~jrose/draft/lazy-final.html > > Bonus: The T.default hack scales to non-static > fields as well. So laziness is a separable tool > from the decision to make things static or not; > it survives more refactorings. The technique > is abundantly optimizable (both static and > non-static versions) as proven by the good > track record of @Stable inside the JDK. We > should share this gem outside the JDK, > which requires language and (more) VM > support. Language design issue: It's easier > to do the lazy static with an attribute than > doing the lazy non-static; you need an > instance-specific callback for the latter. TBD. > > The nice thing about this is that the OpenJDK JITs > have been making good use of @Stable annotations > for a long time. So the main problem here is finding > a language and VM framework that legitimizes this > sort of pattern (including safety checks and rule > enforcement on state changes). When that is done, > the JITs should make use of it with little extra effort. > > ? John From john.r.rose at oracle.com Fri Mar 1 02:14:11 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 28 Feb 2019 18:14:11 -0800 Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> Message-ID: <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> On Feb 28, 2019, at 5:30 PM, Maurizio Cimadamore wrote: > > Question: in which category of badness does this belong to? > > class Foo { > static int I = Foo.J; > static int J = 2; > > public static void main(String[] args) { > System.out.println(I); //prints 0 > System.out.println(J); //prints 2 > } > } > > The language allows forward references to static fields, assuming you use a _qualified_ name (don't ask me why :-)). I remember convincing myself long ago that this was semi-justified. > But I guess this is similar to having a static init calling a static method which returns the value of a non-yet initialized static. In any case, it feels that, if we condify these, we would change semantics, as suddenly "I" will be initialized to 2 too? Yes, that seems likely. A naive account of the semantics of lazy statics would be that each lazy static is "really" obtained via a static method which serves as its accessor. This static method contains something like this: public static @Synthetic int get$I() { if (!I$ready) I = (?init expr here?); return I; } The JMM makes extra demands here, of course, which only the JVM (or var handles) can properly satisfy. > Also, beware of cycles! > > class Foo { > static int I = Foo.J; > static int J = I; > > public static void main(String[] args) { > System.out.println(I); //prints 0 > System.out.println(J); //prints 0 > } > } > > I think a condified translation would throw or run into an endless loop? Condy has a "no cycle" clause in its contract which we can just reuse. You get something like a SOE. The naive semantics can model this by tracking states more carefully on the I$ready variable. > To me, the crux of the issue is to draw a crisp line between things that can be condified and things that cannot (and have to fallback to ). But, the more I think, the less I'm convinced that such a line exist, or at least that a 'meaningful' one exists. Every class gets one phase-point which is executed with mutual exclusion relative to any other access to the class. Programmers use this phase-point in a zillion ways. Surely there is no crisp characterization of all its uses. What we *can* do is tell programmers that, if they don't need a phase-point (or weren't even conscious that they were executing one), they can use lazies and get faster startup. > Since all method calls are potentially cycle-inducing (or forward-reference-inducing), lazy statics treatment cannot apply to an initializer that has a method call? And, you can have forward references or cycles through field access too (as above)... so, that seems to leave just simple constants, which doesn't seem to offer a lot of bang for the bucks? Am I missing something? Any Turing-capable programming language is "potentially cycle-inducing" and finding which programs cycle is undecidable. We live with it. I don't see how your reasoning applies here in a special way to lazies. This reminds me why the static variables have the restriction that one can't refer to the other if it's later in the file. It's not because there is no possible use for this (hence the Foo.J escape) but because most code benefits from a gentle static error check that helps the programmer prove that the uninitialized values of variables are not being used. (For local variables, the DU/DA rules perform the same check, more strictly.) Lazy statics will benefit from the same gentle checks. There will be some legitimate occasions to use the Foo.J escape to create a lexical loop that you (the human programmer) know won't turn into a dynamic loop. Better yes, if you don't use the Foo.J escape, then you know your lazies won't loop. So I think these features hang together. > Sidebar: as a VM machinery, I'd love to see something like DynamicValue - in fact I was talking to Sundar the other day that, if we had it, we could solve some thorny issues we have on Panama/jextract bytecode generation. But as a language construct, this feels shaky, but maybe that's just me? Yes, I want DynamicValue sooner rather than later. The potential looping doesn't bother me. The syntax paradigm of stuffing everything into a field initializer is annoyingly restrictive, but will be good enough to start with. We can relax it later, I think, about the same time we do the non-static version of the feature. What I mean is that some use cases for lazies has a use-site formulation, where (within the class at least) each use of a lazy potentially comes with a proposed value; there need not be a centralized point (the def-site of the lazy) where the lazy's value is defined. This is true less for statics and more for non-statics. Arguably, a use-site lazy mechanism is a wholly separate language feature, but I think they should be lumped if possible. And I think it's possible; that a use-site lazy generalizes a def-site lazy in the sense that the central def-site value (if any) is a default to be overridden in context by the proposed use-site value (if any). ? John P.S. I suppose there is rare legitimate code you might write where the lexical dependency of statics has a loop, which the dynamic logic of the program breaks. (Note that programs have loops of all sorts, and we trust them to break the loops dynamically even when we can't prove statically that they terminate.) Here's a silly example: static final int I = (J_FIRST ? (computeIFromJ(Foo.J = computeJ()) : computeI()) ; static int J = (J_FIRST ? Foo.J : computeJ()); This is what "static { }" blocks and blank finals are for. The initial version of the Java language, which specified the reference checks on statics in static initializers, also supported the "Foo.J" oddity, and it *didn't* have blank finals and their associated definite assignment rules. Those extra rules augment the initialization checks by allowing a static final to omit its initializer but requiring an eventual initialization somewhere. From maurizio.cimadamore at oracle.com Fri Mar 1 09:47:28 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 1 Mar 2019 09:47:28 +0000 Subject: lazy statics design notes In-Reply-To: <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> Message-ID: <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> Let me step back a bit. From a technical perspective I don't think there's any issue. You get condy semantics, which, as you say, is well defined w.r.t. cycles and laziness etc. From a language perspective I see an issue if we expect, as we said, that most people will just jump in and replace 'static' with 'lazy-static'. That is gonna have all sorts of behavioral incompatibilities. So, I was looking for a story for - when is it safe to replace an existing 'static' with 'lazy-static' ? And the answer seems messy. Think of it from an IDE perspective: when do I offer the refactoring to 'lazy-static' to the user? If the answer ends up being along the lines of "when the initializer is a literal", I think most people won't even bother, and the argument that, since everyone will opt-in into 'lazy' thus making disappear won't, IMHO, hold very much. Maurizio On 01/03/2019 02:14, John Rose wrote: > On Feb 28, 2019, at 5:30 PM, Maurizio Cimadamore wrote: >> Question: in which category of badness does this belong to? >> >> class Foo { >> static int I = Foo.J; >> static int J = 2; >> >> public static void main(String[] args) { >> System.out.println(I); //prints 0 >> System.out.println(J); //prints 2 >> } >> } >> >> The language allows forward references to static fields, assuming you use a _qualified_ name (don't ask me why :-)). > I remember convincing myself long ago that this was semi-justified. > >> But I guess this is similar to having a static init calling a static method which returns the value of a non-yet initialized static. In any case, it feels that, if we condify these, we would change semantics, as suddenly "I" will be initialized to 2 too? > Yes, that seems likely. A naive account of the semantics > of lazy statics would be that each lazy static is "really" > obtained via a static method which serves as its accessor. > This static method contains something like this: > > public static @Synthetic int get$I() { > if (!I$ready) > I = (?init expr here?); > return I; > } > > The JMM makes extra demands here, of course, which only > the JVM (or var handles) can properly satisfy. > >> Also, beware of cycles! >> >> class Foo { >> static int I = Foo.J; >> static int J = I; >> >> public static void main(String[] args) { >> System.out.println(I); //prints 0 >> System.out.println(J); //prints 0 >> } >> } >> >> I think a condified translation would throw or run into an endless loop? > Condy has a "no cycle" clause in its contract which we > can just reuse. You get something like a SOE. > > The naive semantics can model this by tracking > states more carefully on the I$ready variable. > >> To me, the crux of the issue is to draw a crisp line between things that can be condified and things that cannot (and have to fallback to ). But, the more I think, the less I'm convinced that such a line exist, or at least that a 'meaningful' one exists. > Every class gets one phase-point which is executed with > mutual exclusion relative to any other access to the class. > Programmers use this phase-point in a zillion ways. > Surely there is no crisp characterization of all its uses. > > What we *can* do is tell programmers that, if they > don't need a phase-point (or weren't even conscious > that they were executing one), they can use lazies > and get faster startup. > >> Since all method calls are potentially cycle-inducing (or forward-reference-inducing), lazy statics treatment cannot apply to an initializer that has a method call? And, you can have forward references or cycles through field access too (as above)... so, that seems to leave just simple constants, which doesn't seem to offer a lot of bang for the bucks? Am I missing something? > Any Turing-capable programming language is "potentially > cycle-inducing" and finding which programs cycle is undecidable. > We live with it. I don't see how your reasoning applies here in > a special way to lazies. > > This reminds me why the static variables have the restriction > that one can't refer to the other if it's later in the file. It's not > because there is no possible use for this (hence the Foo.J escape) > but because most code benefits from a gentle static error check > that helps the programmer prove that the uninitialized values > of variables are not being used. > > (For local variables, the DU/DA rules perform the same check, > more strictly.) > > Lazy statics will benefit from the same gentle checks. There > will be some legitimate occasions to use the Foo.J escape > to create a lexical loop that you (the human programmer) > know won't turn into a dynamic loop. Better yes, if you > don't use the Foo.J escape, then you know your lazies > won't loop. So I think these features hang together. > >> Sidebar: as a VM machinery, I'd love to see something like DynamicValue - in fact I was talking to Sundar the other day that, if we had it, we could solve some thorny issues we have on Panama/jextract bytecode generation. But as a language construct, this feels shaky, but maybe that's just me? > Yes, I want DynamicValue sooner rather than later. > > The potential looping doesn't bother me. > > The syntax paradigm of stuffing everything into a field initializer > is annoyingly restrictive, but will be good enough to start with. > > We can relax it later, I think, about the same time we do the > non-static version of the feature. What I mean is that some > use cases for lazies has a use-site formulation, where (within > the class at least) each use of a lazy potentially comes with a > proposed value; there need not be a centralized point (the > def-site of the lazy) where the lazy's value is defined. This is > true less for statics and more for non-statics. > > Arguably, a use-site lazy mechanism is a wholly separate > language feature, but I think they should be lumped if possible. > > And I think it's possible; that a use-site lazy generalizes > a def-site lazy in the sense that the central def-site > value (if any) is a default to be overridden in context > by the proposed use-site value (if any). > > ? John > > P.S. I suppose there is rare legitimate code you might write where > the lexical dependency of statics has a loop, which the dynamic logic > of the program breaks. > > (Note that programs have loops of all sorts, and we trust them > to break the loops dynamically even when we can't prove statically > that they terminate.) > > Here's a silly example: > > static final int I = (J_FIRST ? (computeIFromJ(Foo.J = computeJ()) : computeI()) ; > static int J = (J_FIRST ? Foo.J : computeJ()); > > This is what "static { }" blocks and blank finals are for. > > The initial version of the Java language, which specified > the reference checks on statics in static initializers, > also supported the "Foo.J" oddity, and it *didn't* have > blank finals and their associated definite assignment rules. > Those extra rules augment the initialization checks > by allowing a static final to omit its initializer but > requiring an eventual initialization somewhere. > From john.r.rose at oracle.com Sat Mar 2 19:37:35 2019 From: john.r.rose at oracle.com (John Rose) Date: Sat, 2 Mar 2019 13:37:35 -0600 Subject: lazy statics design notes In-Reply-To: <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> Message-ID: On Mar 1, 2019, at 3:47 AM, Maurizio Cimadamore wrote: > > From a technical perspective I don't think there's any issue. You get condy semantics, which, as you say, is well defined w.r.t. cycles and laziness etc. > > From a language perspective I see an issue if we expect, as we said, that most people will just jump in and replace 'static' with 'lazy-static'. That is gonna have all sorts of behavioral incompatibilities. The first time someone gets an error because the value of the lazy-static is null, they will say "those idiots", etc. Whereas we want them to think of adding laziness as a simple, foolproof way to get certain kinds of performance improvements. This is a good point, and may overcome my arguments. It will push the friction forward in time, to the place where we add non-static lazies; at that point we will have a less pleasant choice between transactional memory and reserving the value I am asking we reserve *now*. (BTE, this is a classic example of something that looks great from the VM looking upward doesn't look so great from the user model looking downward.) So, I want to defend my proposal a little more, not only by saying we'll pay a cost later if we don't reserve the value now, but also by noting that your point about "all sorts of behavioral incompatibilities" applies more evenly than you may think, to both proposals (reserve a value, vs. don't reserve). Even if you allow the full range of values, including null, there will be all sorts of more subtle behavioral incompatibilities just from changing the order in which statics are initialized, and (what's more) making the order dynamically data dependent. The bugs that folks will run into from "going lazy" will be bootstrap ordering bugs, causing null pointer exceptions (typically) when one initializer needs access to a non-lazily initialized static in some other class. Bootstrap errors happen all the time when there are delicate inter-class initialization dependencies. Today we see it when static initializers recurse into themselves via another class. Tomorrow we will see it whenever there is a mix of lazy and non-lazy initialization. So, I don't buy that non-reservation of null is going to especially improve the user experience. It will remove one moderately sharp edge, leaving some sharper ones. I'm saying the reserved value is "moderately" sharp, because it is relatively easy to diagnose, compared with bootstrap ordering bugs, which are always non-local in nature. And, just to repeat another bit: We have already bitten the "no nulls" bullet with Optional. We are telling users that, for certain constructs, nulls are out of policy. That too could cause "all sorts of incompatibilities", making it hard to refactor to use the new feature (Optionals). So it feels like a defensible move here also. "Lazies are a language supported form of optionals which automatically change state from empty. You can't put nulls in them." What's hard about that? Especially if we make reflection and/or method handle versions which return an actual Optional if the user wants to do a non-triggering sense of the state. Anyway, on balance, I'd still prefer to make lazies like optionals, and then continue to align them that way when we get to non-static lazies and (if we go there) use-site lazies. I didn't say "if we go there" for non-static lazies because they are so freaking useful *today*, under the disguise of @Stable. We'll want to go there. HTH ? John From forax at univ-mlv.fr Sat Mar 2 21:47:10 2019 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 2 Mar 2019 22:47:10 +0100 (CET) Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> Message-ID: <1670238823.1093986.1551563230882.JavaMail.zimbra@u-pem.fr> Hi John, the semantics of a lazy final instance field and a lazy final static field are different, likewise the semantics of an instance field and a static field are different too, by example the static fields are initialized under a lock but there is no implicit lock around the instance fields initializations in a constructor. The concept of "lazy final" is coarsely the same between static and instance fields, but semantics is slightly different, people will get a NPE if they try to use null with an instance field but not if it's a static field because the constant pool does the bookkeeping. Disallowing null for lazy static field goes a step too far in term of design, because it introduce an arbitrary limitation for the shake of having a common semantics. I think it's an overdesign. And there are two other arguments: - as you said, the static lazy final is very more important than the instance one and we want to help users to over use it so if we can not introduce a discrepancy between a static final and a lazy static final, we should do it. - why null is not a valid value for a lazy final instance field ? We can decide to allow null to signal that the instance field is still not initialized despite that the initialisation sequence has been run. R?mi ----- Mail original ----- > De: "John Rose" > ?: "Maurizio Cimadamore" > Cc: "valhalla-spec-experts" > Envoy?: Samedi 2 Mars 2019 20:37:35 > Objet: Re: lazy statics design notes > On Mar 1, 2019, at 3:47 AM, Maurizio Cimadamore > wrote: >> >> From a technical perspective I don't think there's any issue. You get condy >> semantics, which, as you say, is well defined w.r.t. cycles and laziness etc. >> >> From a language perspective I see an issue if we expect, as we said, that most >> people will just jump in and replace 'static' with 'lazy-static'. That is gonna >> have all sorts of behavioral incompatibilities. > > The first time someone gets an error because the value of the lazy-static is > null, > they will say "those idiots", etc. Whereas we want them to think of adding > laziness > as a simple, foolproof way to get certain kinds of performance improvements. > > This is a good point, and may overcome my arguments. It will push the friction > forward in time, to the place where we add non-static lazies; at that point we > will have a less pleasant choice between transactional memory and reserving > the value I am asking we reserve *now*. > > (BTE, this is a classic example of something that looks great from the VM > looking > upward doesn't look so great from the user model looking downward.) > > So, I want to defend my proposal a little more, not only by saying we'll pay > a cost later if we don't reserve the value now, but also by noting that your > point about "all sorts of behavioral incompatibilities" applies more evenly > than you may think, to both proposals (reserve a value, vs. don't reserve). > > Even if you allow the full range of values, including null, there will be > all sorts of more subtle behavioral incompatibilities just from changing > the order in which statics are initialized, and (what's more) making the > order dynamically data dependent. The bugs that folks will run into > from "going lazy" will be bootstrap ordering bugs, causing null > pointer exceptions (typically) when one initializer needs access to > a non-lazily initialized static in some other class. > > Bootstrap errors happen all the time when there are delicate inter-class > initialization dependencies. Today we see it when static initializers > recurse into themselves via another class. Tomorrow we will see it > whenever there is a mix of lazy and non-lazy initialization. > > So, I don't buy that non-reservation of null is going to especially improve > the user experience. It will remove one moderately sharp edge, leaving > some sharper ones. I'm saying the reserved value is "moderately" sharp, > because it is relatively easy to diagnose, compared with bootstrap ordering > bugs, which are always non-local in nature. > > And, just to repeat another bit: We have already bitten the "no nulls" bullet > with Optional. We are telling users that, for certain constructs, nulls are > out of policy. That too could cause "all sorts of incompatibilities", making > it hard to refactor to use the new feature (Optionals). So it feels like a > defensible move here also. "Lazies are a language supported form of > optionals which automatically change state from empty. You can't put > nulls in them." What's hard about that? Especially if we make reflection > and/or method handle versions which return an actual Optional if the > user wants to do a non-triggering sense of the state. > > Anyway, on balance, I'd still prefer to make lazies like optionals, and > then continue to align them that way when we get to non-static lazies > and (if we go there) use-site lazies. > > I didn't say "if we go there" for non-static lazies because they are so > freaking useful *today*, under the disguise of @Stable. We'll want to > go there. > > HTH > > ? John From brian.goetz at oracle.com Sat Mar 2 21:46:01 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 2 Mar 2019 16:46:01 -0500 Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> Message-ID: <36ADC945-2A73-49D3-BDD6-A20DC8651629@oracle.com> > The first time someone gets an error because the value of the lazy-static is null, > they will say "those idiots", etc. Whereas we want them to think of adding laziness > as a simple, foolproof way to get certain kinds of performance improvements. And, more people are likely to run into this one, and they are likely to encounter this one far earlier, than some of the other sharp edges. > So, I don't buy that non-reservation of null is going to especially improve > the user experience. You keep saying null, when you mean ?default value?. For refs, nulls is often an erroneous value for the sorts of things we are likely to stuff into lazy fields, so it seems not-completely-silly. But for ints, zero is a very sensible value, and for booleans, well, it?s half the range. I don?t really buy the Optional argument either, because the whole reason for having Optional is because nullity is not sufficiently reflected in the type system. (If we had T? types, where blindly dereferencing a T? without a null-safe operation were a compilation error, we wouldn?t need Optional.) Optional allows people to safely express a _restriction type_ on their variables, to get better safety. Ultimately, these arguments are a reflection of implementation tactics in the language semantics. Not only is this likely to leave users scratching their heads, but as our menu of implementation tactics evolves (welcome, condy!), we might not even need the semantic restrictions that previous tactics required. This problem is much the same as the problem of compressing the representation so we can express nullable values without increasing the footprint. Obviously in the general case (int, Point) this is problematic, but for a range of common types (pointers, floats, etc), there are bits that can be transparently repurposed, and for others (shorts, bytes, maybe even ints), required padding often produces extra bits. Let?s leave it to the runtime to find these bits; in the worst case, we box primitives, which gives us full range plus we can use null to mean ?not initialized?. But that?s an implementation tactic, which is invisible to the user. From john.r.rose at oracle.com Sat Mar 2 23:15:16 2019 From: john.r.rose at oracle.com (John Rose) Date: Sat, 2 Mar 2019 17:15:16 -0600 Subject: lazy statics design notes In-Reply-To: <1670238823.1093986.1551563230882.JavaMail.zimbra@u-pem.fr> References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> <1670238823.1093986.1551563230882.JavaMail.zimbra@u-pem.fr> Message-ID: <2AC9E7B5-EE78-4475-BF46-E91ED702040C@oracle.com> Remi, Maurizio, Brian, I shot my last round, and I'm out. I agree we shouldn't tinker with the (value sets of the) types. Instead let's reach for ways to adjoin extra sentinel values in the case of lazies (and optionals, and lazies of optionals), of both null-default and zero-default types. These sentinel values will encode as disjoint from the base value set of the type T (whether T is null-default/ref or zero-default/prim). Sentinels will denote the states outside of the normal "T value is present" state, either: unbound-lazy or empty-optional. A lazy optional needs both sentinels, while a plain lazy or optional needs just one. In the case where T is a reference, the JVM might add in one or two new references (perhaps with tag bits for extra dynamic checking). This can be done outside the safe type system in the case of the JVM, if it puts the right decoding barriers in the right places, to strip the sentinels before using them in a T-safe manner. In the case where T's encoding space is fully tensioned (like int) the sentinel will have to take the form of an extra field of two states. One is "I'm the sentinel" and the other is "there's a T value in my other component". This is just Optional all over again, which uses a sentinel (null!) today. (If two sentinels are required, for a lazy-optional, then the extra field can take three states. Or we append two extra fields.) If we are buffering T on the heap in a stand-alone object, the extra state can (with some ad hoc hacking) be folded into the object header, because it is almost certain that the object header has some slack that's usable for the purpose. Since buffered value object's won't need to store synchronization state (individually, at least), the bits which usually denote synchronization state can be co-opted to store a sentinel state, for a buffered T. This usually won't be necessary, though, since if a T value is buffered, the client that is holding the reference is also capable of holding a real null, which more directly represents an out-of-type value point for T. This is today's situation with Integer, which is null-default, while its payload type int is zeroable but not nullable. If we were to load a value-like Integer onto the stack, the extra sentinel field would have to be manufactured like this: boolean hasPayload = (p == null ? false : true); int payload = (p == null ? int.default : p.value); This pair of values on stack would act like a value type whose default zero bits encode null, while an ordinary int payload value would be accompanied by a 'true' bit that distinguishes it from the null encoding. This value type should, of course, be null-default, even though it carries a zero-default payload. In the case where T's encoding space has some slack (like boolean) a sentinel or two can be created by using unencoded bit patterns. If T is a value type containing a reference or floating point field, then the option exists to "steal" the encoding from inside that field. The all-zero-bits state is favorable in the heap because it is most reliably the first state of the object. In the case of both optional and lazy (lazy-optional is just lazy here), the sentinel encodes the initial state, which encourages us to implement the sentinel with a default value (zero or null) for T. This means that the normal corresponding default (zero or null) should actually be encoded with a special sentinel value. On the stack the all-zero-bits state is less directly useful, but of course it's good if the stack and heap encodings can be as close as possible. The getfield operation which loads a lazy instance field should do two things: 1. check for the encoding of the unbound state (which should be all-zeroes), 2. check for the encoding of the bound-to-default state (which should be a specially crafted sentinel). In case 1, the lazy value binding code must be executed. In case 2, the sentinel must be replaced by a true default value. Something like this probably needs to happen anyway for null-default value types, since the zero-default encoding of a null-default value type needs to be replaced by a null pointer when it is loaded. It looks to me like there are at least three places where a "raw" value is "wrapped" to give it adjusted semantics. First, a null-default value type wraps the underlying zero-default bits by swapping out the zero and swapping in the null. Second, an optional wraps the internal value by adjoining the "empty" value point. Third, a lazy wraps its non-lazy value by adjoining the "unbound" state. Sentinels are just one way to do it; surely there are others. But if you don't use sentinels in some capacity to overlay new values on T's value set, you probably need a side bit to convey the variable's state; as I've said before, managing that correctly seems to require transactional memory. Condy doesn't require a sentinel. But of course HotSpot *internally* uses a sentinel to distinguish a resolved null value from the unresolved state. The unresolved state is a null pointer while a resolved null is a special out-of-type non-null reference (called "the null sentinel") which condy swaps out for a resolved null after it does the null check. That's the same trick as I've described above. Surprise; I wrote it. Great minds may think alike, but mediocre minds think the same thing repeatedly. ? John From forax at univ-mlv.fr Sun Mar 3 13:18:49 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sun, 3 Mar 2019 14:18:49 +0100 (CET) Subject: lazy statics design notes In-Reply-To: <2AC9E7B5-EE78-4475-BF46-E91ED702040C@oracle.com> References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> <1670238823.1093986.1551563230882.JavaMail.zimbra@u-pem.fr> <2AC9E7B5-EE78-4475-BF46-E91ED702040C@oracle.com> Message-ID: <704818761.1133596.1551619129572.JavaMail.zimbra@u-pem.fr> Hi John, for me it's not clear if the sentinel has to be user controlled or not (think vulls by example) for getfield, and given it looks like solving how to do a CAS on a value type (and its interaction with vulls), something we are still working on, i think we should restrain ourselves to try to solve getfield on a lazy final before our work on value type is finished. As you said, for getstatic, we don't have this issue that why i think we should design getstatic of a lazy final without disabling null, getfield will likely have more constraints and that's fine. R?mi ----- Mail original ----- > De: "John Rose" > ?: "Remi Forax" , "Maurizio Cimadamore" , "Brian Goetz" > > Cc: "valhalla-spec-experts" > Envoy?: Dimanche 3 Mars 2019 00:15:16 > Objet: Re: lazy statics design notes > Remi, Maurizio, Brian, I shot my last round, and I'm out. > I agree we shouldn't tinker with the (value sets of the) types. > > Instead let's reach for ways to adjoin extra sentinel values > in the case of lazies (and optionals, and lazies of optionals), > of both null-default and zero-default types. These sentinel > values will encode as disjoint from the base value set of the > type T (whether T is null-default/ref or zero-default/prim). > > Sentinels will denote the states outside of the normal "T value > is present" state, either: unbound-lazy or empty-optional. > A lazy optional needs both sentinels, while a plain lazy or > optional needs just one. > > In the case where T is a reference, the JVM might add in one > or two new references (perhaps with tag bits for extra dynamic > checking). This can be done outside the safe type system in > the case of the JVM, if it puts the right decoding barriers in > the right places, to strip the sentinels before using them in > a T-safe manner. > > In the case where T's encoding space is fully tensioned (like int) > the sentinel will have to take the form of an extra field of > two states. One is "I'm the sentinel" and the other is "there's > a T value in my other component". This is just Optional all > over again, which uses a sentinel (null!) today. > > (If two sentinels are required, for a lazy-optional, then the extra > field can take three states. Or we append two extra fields.) > > If we are buffering T on the heap in a stand-alone object, the > extra state can (with some ad hoc hacking) be folded into the > object header, because it is almost certain that the object header > has some slack that's usable for the purpose. Since buffered value > object's won't need to store synchronization state (individually, > at least), the bits which usually denote synchronization state can > be co-opted to store a sentinel state, for a buffered T. This usually > won't be necessary, though, since if a T value is buffered, the > client that is holding the reference is also capable of holding > a real null, which more directly represents an out-of-type value > point for T. This is today's situation with Integer, which is > null-default, while its payload type int is zeroable but not nullable. > > If we were to load a value-like Integer onto the stack, the extra > sentinel field would have to be manufactured like this: > boolean hasPayload = (p == null ? false : true); > int payload = (p == null ? int.default : p.value); > This pair of values on stack would act like a value type whose > default zero bits encode null, while an ordinary int payload value > would be accompanied by a 'true' bit that distinguishes it from > the null encoding. This value type should, of course, be null-default, > even though it carries a zero-default payload. > > In the case where T's encoding space has some slack (like boolean) > a sentinel or two can be created by using unencoded bit patterns. > If T is a value type containing a reference or floating point field, > then the option exists to "steal" the encoding from inside that field. > > The all-zero-bits state is favorable in the heap because it is most > reliably the first state of the object. In the case of both optional > and lazy (lazy-optional is just lazy here), the sentinel encodes > the initial state, which encourages us to implement the sentinel > with a default value (zero or null) for T. This means that the normal > corresponding default (zero or null) should actually be encoded > with a special sentinel value. > > On the stack the all-zero-bits state is less directly useful, but of > course it's good if the stack and heap encodings can be as close > as possible. > > The getfield operation which loads a lazy instance field should do > two things: 1. check for the encoding of the unbound state (which > should be all-zeroes), 2. check for the encoding of the bound-to-default > state (which should be a specially crafted sentinel). In case 1, the > lazy value binding code must be executed. In case 2, the sentinel > must be replaced by a true default value. Something like this probably > needs to happen anyway for null-default value types, since the > zero-default encoding of a null-default value type needs to be > replaced by a null pointer when it is loaded. > > It looks to me like there are at least three places where a "raw" > value is "wrapped" to give it adjusted semantics. First, a null-default > value type wraps the underlying zero-default bits by swapping > out the zero and swapping in the null. Second, an optional wraps > the internal value by adjoining the "empty" value point. Third, > a lazy wraps its non-lazy value by adjoining the "unbound" state. > > Sentinels are just one way to do it; surely there are others. But if > you don't use sentinels in some capacity to overlay new values on > T's value set, you probably need a side bit to convey the variable's > state; as I've said before, managing that correctly seems to require > transactional memory. > > Condy doesn't require a sentinel. But of course HotSpot *internally* > uses a sentinel to distinguish a resolved null value from the unresolved > state. The unresolved state is a null pointer while a resolved null > is a special out-of-type non-null reference (called "the null sentinel") > which condy swaps out for a resolved null after it does the null check. > That's the same trick as I've described above. Surprise; I wrote it. > Great minds may think alike, but mediocre minds think the same > thing repeatedly. > > ? John From maurizio.cimadamore at oracle.com Sun Mar 3 21:38:48 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Sun, 3 Mar 2019 21:38:48 +0000 Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> Message-ID: On 02/03/2019 19:37, John Rose wrote: > So, I want to defend my proposal a little more, not only by saying we'll pay > a cost later if we don't reserve the value now, but also by noting that your > point about "all sorts of behavioral incompatibilities" applies more evenly > than you may think, to both proposals (reserve a value, vs. don't reserve). Sorry, my point was never specific to your proposal - as it was in general to the idea of exposing lazy-ness as some sort of add-on modifier on existing modifiers (e.g. lazy-static). In most cases it makes things more lazy, and your app will run better (less startup etc.); in some cases there will be deal breaker that will be very hard to diagnose/debug, especially paired with the attitude Brian and Remi referred to in previous emails, where it seemed like we were leaning on people eagerness to get 'performance improvements' and start replacing all statics with lazy ones. I believe that's a dangerous and slippery road to go down to., especially for big and complex apps relying on even bigger frameworks. Maurizio From forax at univ-mlv.fr Sun Mar 3 22:28:57 2019 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 3 Mar 2019 23:28:57 +0100 (CET) Subject: lazy statics design notes In-Reply-To: References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> Message-ID: <692802968.2378.1551652137113.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Maurizio Cimadamore" > ?: "John Rose" > Cc: "valhalla-spec-experts" > Envoy?: Dimanche 3 Mars 2019 22:38:48 > Objet: Re: lazy statics design notes > On 02/03/2019 19:37, John Rose wrote: >> So, I want to defend my proposal a little more, not only by saying we'll pay >> a cost later if we don't reserve the value now, but also by noting that your >> point about "all sorts of behavioral incompatibilities" applies more evenly >> than you may think, to both proposals (reserve a value, vs. don't reserve). > > Sorry, my point was never specific to your proposal - as it was in > general to the idea of exposing lazy-ness as some sort of add-on > modifier on existing modifiers (e.g. lazy-static). In most cases it > makes things more lazy, and your app will run better (less startup > etc.); in some cases there will be deal breaker that will be very hard > to diagnose/debug, especially paired with the attitude Brian and Remi > referred to in previous emails, where it seemed like we were leaning on > people eagerness to get 'performance improvements' and start replacing > all statics with lazy ones. I believe that's a dangerous and slippery > road to go down to., especially for big and complex apps relying on even > bigger frameworks. It's true and not true at the same time, if you compare with a static field been initialized early in the , with , you can have deadlocks, the behaviour of your program depends on the class initialization order (to the point in the JDK we have a way to force a class initialization using Unsafe), without , the behavior of your program may becomes racy, it can be harmless but if the initialization of the field does some side effects it can be messy. So IMO both and lazy final have sharp edges. > > Maurizio R?mi From maurizio.cimadamore at oracle.com Mon Mar 4 10:05:08 2019 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 4 Mar 2019 10:05:08 +0000 Subject: lazy statics design notes In-Reply-To: <692802968.2378.1551652137113.JavaMail.zimbra@u-pem.fr> References: <7CAFC675-248C-477A-9A4E-D9F331559EA6@oracle.com> <0755C5BD-59BE-4B73-9D09-C71421CBE8C5@oracle.com> <74E1B70B-5840-4241-B405-BB4568B2CDE7@oracle.com> <9a786e2f-c03f-6ecd-27df-0aa619827360@oracle.com> <692802968.2378.1551652137113.JavaMail.zimbra@u-pem.fr> Message-ID: <02652c03-f06c-c256-9590-703c70a3131d@oracle.com> On 03/03/2019 22:28, Remi Forax wrote: > So IMO both and lazy final have sharp edges. 100% agree with that; actually, I'd go even further and say that the sharp edges in have a nicer resolution in static lazy approach. But, my point is that they have *different*, incompatible semantics, so, from a migration perspective, adding 'lazy' is problematic. Maurizio From brian.goetz at oracle.com Sat Mar 9 16:56:07 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 9 Mar 2019 16:56:07 +0000 Subject: Towards a plan for L10 and L20 Message-ID: <69B42C74-8CAD-4663-9215-F834E3CE9C50@oracle.com> I?ve been pulling together a rough draft of high-level requirements for the L10 and L20 milestones. These are mostly through the lens of the programming model, but they indirectly affect VM requirements (usually in obvious ways.) (Note to observers: this is mostly capturing the active plan, rather than the rationale and justification for these decisions. These will follow in a separate document.) # Towards requirements for L10 and L20 milestones The last year has been a tremendous one for Project Valhalla; the L-World prototype has proven more successful than we could have hoped, and building on the success of L-World, the end-to-end plan for specialized generics with gradual migration compatibility has started to come into full view. It's time to put a stake in the ground and talk about some deliverables. As L-World started to look like it was going to succeed, we identified three buckets, into which features could be sorted, as a structuring mechanism: - L1 -- First usable prototype of L-World; now delivered - L10 -- First preview-able milestone; this would include the ability to declare value classes which can be flattened into arrays and objects, and instantiate _erased_ generics over values - L100 -- Specializable generics over values, and migrating key generic classes (e.g., Collections and Streams) These constituted not so much a plan, as a recognition that there were several significant phases ahead of us. Let's put some meat on these phases, and carve things a little finer. ## L10 -- First previewable delivery Nontrivial language and JVM features typically go through a round of [_preview_][jep12], in which they are part of the specification and implementation but behind a flag and with the possibility of refinement before they become a permanent part of the platform. For a feature as significant as value types, we will surely want to Preview it before finalization. A preview feature should be complete; in the context of Valhalla, this means that a complete and self-consistent programming model is needed; features should not look like they are "bolted on". The main feature of L10 is the ability to declare and use value classes; we can think of the remaining features as the "closure" of adding this feature to Java, which is to say, all the other things we have to add in to arrive at a sensible, self-consistent, understandable programming model. First and foremost, a value class is a class, and is declared like one: ```{.java} value class Point { int x; int y; Point(int x, int y) { this.x = x; this.y = y; } } ``` (A modifier is not the only option; this could be indicated by a choice of supertype as well; syntax is all TBD.) Value types are non-nullable; each declared value class `V` gives rise to two types: `V`, and its nullable counterpart, `V?`. The value set of `V?` is the union of the value set of `V`, with the singleton set `{ null }`. For a value class `V`, we get the following subtype relationships: V <: V? <: ValObject <: Object In addition, if value class `V` implements interface `I`, then `V <: I`. Value classes have some restrictions, which are enforced by both the compiler and runtime: - They cannot extend any other class - They are implicitly final - Their fields are implicitly final - Their instances are are non-nullable - They cannot be synchronized on (as well as other identity-sensitive operations, such as Object.wait()) The type `V` is translated as `QV;`; the type `V?` is translated as `LV;`. #### Object model We add two new "top" types to the object model; `java.lang.RefObject` and `java.lang.ValObject` (names to be bikeshod.) All "regular" classes ("identity classes") are subtypes of `RefObject`; all value types are subtypes of `ValObject`. (Interfaces can not be subtypes of either `RefObject` or `ValObject`, but interfaces can be implemented by both value classes and identity classes. It is often helpful to think of `Object` as being an "honorary" interface.) Whether `RefObject` is a class or interface is also currently an open issue. While it is disruptive to retrofit new top types into the hierarchy, there are several good reasons for doing so. The first is that the object hierarchy is a powerful pedagogical tool -- not only is "everything an object", but "everything is an `Object`." Users learning the language, upon learning that there are identity objects and value objects, will see this division prominently reflected in the root types of the object hierarchy. Another reason is that there will likely be behavior which is common to all identity objects, or to all value objects, and these types represent a sensible place to declare that behavior, using tools (static methods, final methods, etc) that users already understand. The final (and perhaps most important) reason is that it lets us talk about this important distinction in the language. Having these as types means users can dynamically test whether something is a value object or identity object when they need to: ``` if (x instanceof RefObject) { ... } if (y instanceof ValObject) { ... } ``` Methods that rely on identity can declare this in their signature: ``` void m(RefObject o) { ... } ``` And generic classes that only make sense to be instantiated with reference types can do so in the standard way: ``` class Foo { ... } ``` For all the same reasons, we will want to reflect nullability in the type system (such as with an interface type `Nullable`, which would be implemented by all identity types, plus nullable value types.) Unlike primitives, and unlike earlier Vahalla designs, there _are no box types_. `V?` is not a box for `V`; boxes serve to connect non-Object values to `Object`, but values _already_ are `Object`s. #### Intrinsic operations Being objects, value types inherit all the members of `Object`, and we must provide sensible default behaviors for them. For identity objects, the default behavior for `equals()`, `hashCode()`, and `toString()` are identity-based (identity equality, identity hash code, and the name of the class appended with the identity hash code); for value objects, they should be state-based. We define a relation over all values (identity classes, value classes, and primitives), called _substitutability_, as follows: - Two identity instances are substitutable if they refer to the same object. - Two primitives are substitutable if they are `==` (modulo special pleading for `NaN`, as per`Float::equals` and `Double::equals`). - Two value instances `a` and `b` are substitutable if they are of the same type, and for each of the fields `f` of that type, `a.f` and `b.f` are substitutable. We then say that for any two values, `a == b` iff a and b are substitutable. The default implementation of `Object::equals` for value classes implements `a == b`, as it does for identity classes. Similarly, we define a total _substitutability hash code_ function, as follows: - For an identity instance, it is the value of `System::identityHashCode`; - For a primitive, it is the value of the `hashCode` method of the corresponding wrapper type; - For a value, it is constructed deterministically from the substitutability hash codes of the value's fields. The method `System::identityHashCode` should return the substitutability hash code for value arguments; as for reference classes, the default `Object::hashCode` for value classes also returns the substitutability hash code. (If we were starting clean, we might prefer separate API points for identity and substitutability, and then a merged API point.) Certain operations that are nominally allowable on all objects are forbidden (and result in runtime exceptions): synchronization, and `Object::wait` and friends. It is an open question what we should do for weak references to values that contain references to identity objects; perhaps weak references are restricted to `RefObject`. Values are instantiated with instance creation expressions: `new V(...)` (though such expressions are not necessarily translated in the same way as for identity classes.) Value classes have constructors, and these constructors are written like constructors for reference types. Because all fields are final, they must initialize all the fields of the class. #### Mirrors and reflection For each value class `V`, there are two reflection mirrors: a standard mirror (for `V`), and a nullable mirror (for `V?`). The latter is used for reflection over members who use `V?` in their signature; the method `Object::getClass` returns the standard mirror for all instances of `V`. Similarly, mirrors for `V[].class` and `V?[].class` are needed. A value class name `V` can appear on the RHS of `instanceof`; both `V` and `V?` can be used as cast targets. We will likely want a reflective method `Class::isValueClass`. Fields, methods, and constructors for value classes can be reflected using existing abstractions. #### Arrays Arrays are covariant; if `T <: U`, then `T[] <: U[]`. The subtyping relations above therefore give rise to their array counterparts: V[] <: V?[] <: ValObject[] <: Object[] The array type `V[]` is translated as `[QV;`; the array type `V?[]` is translated as `[LV;`. #### Nullable types Nullable value types (`V?`) are nullable in the same sense that all reference types are -- `null` is a member of their value set, but the dereference operators can throw NPE when applied to a null operand. (We realize there is likely to be a highly-vocal constituency who are really hoping that null-freedom would be enforced by the compiler (and therefore that we'd introduce null-safe operators such as `?.`) Our decision here is not out of ignorance that this is potentially desirable, nor out of ignorance that doing it this way makes it even harder to achieve the null-safe nirvana that such users long for.) #### Serialization Value classes are classes; to not be able to opt into serialization like other classes would be a significant irregularity. However, many of the serialization mechanisms (like `readObject`) depend on mutatation; additional mechanisms for safely serializing value classes may be needed. This is an open issue. #### Values and generics in L10 With respect to generics, there are two undesirable fates that L10 must steer clear of. We know that specialized generics are coming, and L10 embodies a deliberate choice to ship values before we ship specializable generics. One wrong move would be to simply ban the use of generics over values; this would be a huge loss for reuse, as there are so many useful, well-tested, well-understood generic libraries out there. The other wrong move would be to interpret `Foo` as an erased instantiation of `Foo`. For erased type parameters, `null` is always considered to be a member of the value set, which means that we might get unexpected NPEs when generic code puts a `null` where it is within its rights to do so. (In the worst case, methods that use `null` as a sentinel, like `Map::get`, become unusable.) What we will do is allow the instantiation of erased generics with _nullable_ values; we can say `Foo`, but not `Foo` -- just as we can say `Foo` but not `Foo` today. (This leaves us free to assign a meaning to `Foo` later for specializable generics.) So users can declare value classes, and generify over their nullable counterparts, with erasure, and later, they'll be able to generify over the value itself, with specialization. One can think of this as all type variables of erased generic classes (which is all generic classes, today) as having an implicit bound `T extends Nullable`. For any type `T`, the expression `T.default` evaluates to the default value for type `T` -- the value initially held by fields or array elements. For identity classes, this is `null`; for value classes, this is one where all fields have their default (zero) value. The locution `T.default` can be used both for concrete types `T` and for type variables (for type variables in erased generic classes, this is equivalent to `null`.) ## L20 -- Migration support for value-based classes The next sensible milestone after L10 adds one new feature: the ability to migrate existing [value-based classes][vbc] to value types. While theoretically we could merge this into L10, the reason to separate them is that L10 is useful to a variety of use cases (numeric-intensive code, machine learning, optimized data structures) who have no immediate need for migration, and we don't want to delay the delivery of L10 (and the critical feedback that will come with broad distribution) for the sake of optimizing some JDK classes. Value instances, like all other values, are initialized with an all-zero value (null, zero, false, etc.) However, for some value classes, the all-zero value is not a natural member of the domain, and asking class implementations to deal with it is likely to be a sharp edge. For these types -- and also for value types migrated from value-based classes -- we introduce a new mechanism: _null-default value classes_. This is a value class whose default all-zero bit value is interpreted as `null`, rather than one whose fields all hold their default value. (The opposite of null-default is _zero-default_.) A null-default value class is declared with the `null-default` modifier, and is a nullable type (implicitly implements `Nullable`): ```{.java} null-default value class Person { String first; String last; } ``` A `null-default` value class _is implicitly zero-hostile_; if the state on exit from the constructor has zeros for all fields, an exception is thrown. Classes that are intended to be compatibly migrated from value-based classes to value classes must be declared `null-default` (and therefore their implementations must conform to the zero-hostility requirements). Inner value classes are implicitly `null-default`. For a null-default type, `T.default` evaluates to `null`. For a null-default value type `T`, `T?` denotes the same type as `T`. #### Translation To ease migration compatibility, we adopt a hybrid translation strategy for null-default value classes. When a null-default value class appears in a _method descriptor_, we translate it with an `L` descriptor; only when it appears in a _field descriptor_ do we use the more precise `Q` descriptor. This is a trade-off; using slightly looser types in method descriptors may give up some calling-convention optimizations, but allows us to compatibly migrate classes like `LocalDateTime`. (This strategy of "loose types on the stack, sharp types on the heap" will show up again later when we get to migration of erased generics to specialized.) #### Field linkage Because we use the sharper types in field descriptors, it is possible that existing code will have `Constant_FieldRef_info` for a migrated field that refers to the field by `L` descriptor, but the descriptor in the target class has been migrated to `Q`. Link resolution of field bytecodes is adjusted to paper over this potential mismatch. #### Null-default value types and erased generics Null-default value types are nullable, and so can be used to instantiate erased generic classes (unlike zero-default value classes, which require an explicit indication of nullability at the use site). For a migrated type `T`, there will likely be existing code that uses generics such as `List`; when these types are migrated to null-default value classes, these locutions continue to be valid (and continue to mean the same thing -- erased instantiation of `List` with `T`). #### Library support As part of this milestone, we should expect to migrate classes such as `Optional`, `LocalDateTime`, and other suitable value-based classes. From brian.goetz at oracle.com Mon Mar 11 19:30:09 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Mar 2019 15:30:09 -0400 Subject: The gift that keeps on giving Message-ID: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> One thing we need to figure out about value types is ? serialization. (Pause for everyone to wishfully say ?can?t we just disallow it for values??, and three pauses for people to get over this.) The problem is that serialization today proceeds by mutation, which might be something we could deal with, but the mechanisms for ?safer? serialization (readObject, etc) also rely on mutation, and that?s harder. I?m working on a story here, but for now, let?s just put this on the list of legacy pain that we will eventually have to deal with. From forax at univ-mlv.fr Mon Mar 11 21:26:39 2019 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 11 Mar 2019 22:26:39 +0100 (CET) Subject: The gift that keeps on giving In-Reply-To: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> References: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> Message-ID: <1761938921.330317.1552339599498.JavaMail.zimbra@u-pem.fr> Hi Brian, given that a value type is constructed by a factory method (the constructor is desugared to a static method), why not making the serialization aware of that factory method. R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: "valhalla-spec-experts" > Envoy?: Lundi 11 Mars 2019 20:30:09 > Objet: The gift that keeps on giving > One thing we need to figure out about value types is ? serialization. > > (Pause for everyone to wishfully say ?can?t we just disallow it for values??, > and three pauses for people to get over this.) > > The problem is that serialization today proceeds by mutation, which might be > something we could deal with, but the mechanisms for ?safer? serialization > (readObject, etc) also rely on mutation, and that?s harder. > > I?m working on a story here, but for now, let?s just put this on the list of > legacy pain that we will eventually have to deal with. From brian.goetz at oracle.com Mon Mar 11 22:53:14 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Mar 2019 18:53:14 -0400 Subject: The gift that keeps on giving In-Reply-To: <1761938921.330317.1552339599498.JavaMail.zimbra@u-pem.fr> References: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> <1761938921.330317.1552339599498.JavaMail.zimbra@u-pem.fr> Message-ID: <53A62B7C-899E-48F4-BD78-C165FF77DB54@oracle.com> Well, consider this value: value class X { int x; public X() { x = 0; } public X withX(int x) { ALOAD this ILOAD x WITHFIELD ?x? ARETURN } } How do I serialize new X().withX(3) ? How do I deserialize it with the lame ctor that X has? If you pull on that string, what you end up with is a secret constructor / factory that takes one arg per field and initializes all the fields with no invariant checking, and serialization scraping the fields and deserialization calling that constructor. Which is about as awful as existing serialization (with all the security risks it entails). So, let?s call that our last choice, and look for something better :) > On Mar 11, 2019, at 5:26 PM, Remi Forax wrote: > > Hi Brian, > given that a value type is constructed by a factory method (the constructor is desugared to a static method), why not making the serialization aware of that factory method. > > R?mi > > ----- Mail original ----- >> De: "Brian Goetz" >> ?: "valhalla-spec-experts" >> Envoy?: Lundi 11 Mars 2019 20:30:09 >> Objet: The gift that keeps on giving > >> One thing we need to figure out about value types is ? serialization. >> >> (Pause for everyone to wishfully say ?can?t we just disallow it for values??, >> and three pauses for people to get over this.) >> >> The problem is that serialization today proceeds by mutation, which might be >> something we could deal with, but the mechanisms for ?safer? serialization >> (readObject, etc) also rely on mutation, and that?s harder. >> >> I?m working on a story here, but for now, let?s just put this on the list of >> legacy pain that we will eventually have to deal with. From forax at univ-mlv.fr Mon Mar 11 23:21:56 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 12 Mar 2019 00:21:56 +0100 (CET) Subject: The gift that keeps on giving In-Reply-To: <53A62B7C-899E-48F4-BD78-C165FF77DB54@oracle.com> References: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> <1761938921.330317.1552339599498.JavaMail.zimbra@u-pem.fr> <53A62B7C-899E-48F4-BD78-C165FF77DB54@oracle.com> Message-ID: <786893537.341029.1552346516052.JavaMail.zimbra@u-pem.fr> oops, i've forgotten to mention that the constructor / factory method known by the serialization should work like a copy constructor. with your example: value class X implements Serializable { int x; public X() { x = 0; } public X withX(int x) { ALOAD this ILOAD x WITHFIELD ?x? ARETURN } // this constructor is required by the deserialization mechanism otherwise it doesn't compile private X(X unsafeXThatComesFromSerialization) { this.x = unsafeXThatComesFromSerialization.x; // checks the arguments here } } R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoy?: Lundi 11 Mars 2019 23:53:14 > Objet: Re: The gift that keeps on giving > Well, consider this value: > > value class X { > int x; > > public X() { x = 0; } > > public X withX(int x) { > ALOAD this > ILOAD x > WITHFIELD ?x? > ARETURN > } > } > > How do I serialize new X().withX(3) ? How do I deserialize it with the lame > ctor that X has? > > If you pull on that string, what you end up with is a secret constructor / > factory that takes one arg per field and initializes all the fields with no > invariant checking, and serialization scraping the fields and deserialization > calling that constructor. Which is about as awful as existing serialization > (with all the security risks it entails). So, let?s call that our last choice, > and look for something better :) > > > > >> On Mar 11, 2019, at 5:26 PM, Remi Forax wrote: >> >> Hi Brian, >> given that a value type is constructed by a factory method (the constructor is >> desugared to a static method), why not making the serialization aware of that >> factory method. >> >> R?mi >> >> ----- Mail original ----- >>> De: "Brian Goetz" >>> ?: "valhalla-spec-experts" >>> Envoy?: Lundi 11 Mars 2019 20:30:09 >>> Objet: The gift that keeps on giving >> >>> One thing we need to figure out about value types is ? serialization. >>> >>> (Pause for everyone to wishfully say ?can?t we just disallow it for values??, >>> and three pauses for people to get over this.) >>> >>> The problem is that serialization today proceeds by mutation, which might be >>> something we could deal with, but the mechanisms for ?safer? serialization >>> (readObject, etc) also rely on mutation, and that?s harder. >>> >>> I?m working on a story here, but for now, let?s just put this on the list of > >> legacy pain that we will eventually have to deal with. From brian.goetz at oracle.com Mon Mar 11 23:39:46 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Mar 2019 19:39:46 -0400 Subject: The gift that keeps on giving In-Reply-To: <786893537.341029.1552346516052.JavaMail.zimbra@u-pem.fr> References: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> <1761938921.330317.1552339599498.JavaMail.zimbra@u-pem.fr> <53A62B7C-899E-48F4-BD78-C165FF77DB54@oracle.com> <786893537.341029.1552346516052.JavaMail.zimbra@u-pem.fr> Message-ID: <0DA8FB87-9E5D-4937-94FC-1E86714FC185@oracle.com> Heh, because by "I?m working on a story here, but for now, let?s just put this on the list of legacy pain that we will eventually have to deal with? I meant ?let?s all design this off the top of our heads right now? :) Yes, generating an insecure all-fields ctor and pushing the scraped fields to it is one possibility (as is the readResolve/writeReplace protocol.) But I?d like to do something better. Stay tuned. > On Mar 11, 2019, at 7:21 PM, forax at univ-mlv.fr wrote: > > oops, i've forgotten to mention that the constructor / factory method known by the serialization should work like a copy constructor. > > with your example: > value class X implements Serializable { > int x; > > public X() { x = 0; } > > public X withX(int x) { > ALOAD this > ILOAD x > WITHFIELD ?x? > ARETURN > } > > // this constructor is required by the deserialization mechanism otherwise it doesn't compile > private X(X unsafeXThatComesFromSerialization) { > this.x = unsafeXThatComesFromSerialization.x; // checks the arguments here > } > } > > R?mi > > ----- Mail original ----- >> De: "Brian Goetz" >> ?: "Remi Forax" >> Cc: "valhalla-spec-experts" >> Envoy?: Lundi 11 Mars 2019 23:53:14 >> Objet: Re: The gift that keeps on giving > >> Well, consider this value: >> >> value class X { >> int x; >> >> public X() { x = 0; } >> >> public X withX(int x) { >> ALOAD this >> ILOAD x >> WITHFIELD ?x? >> ARETURN >> } >> } >> >> How do I serialize new X().withX(3) ? How do I deserialize it with the lame >> ctor that X has? >> >> If you pull on that string, what you end up with is a secret constructor / >> factory that takes one arg per field and initializes all the fields with no >> invariant checking, and serialization scraping the fields and deserialization >> calling that constructor. Which is about as awful as existing serialization >> (with all the security risks it entails). So, let?s call that our last choice, >> and look for something better :) >> >> >> >> >>> On Mar 11, 2019, at 5:26 PM, Remi Forax wrote: >>> >>> Hi Brian, >>> given that a value type is constructed by a factory method (the constructor is >>> desugared to a static method), why not making the serialization aware of that >>> factory method. >>> >>> R?mi >>> >>> ----- Mail original ----- >>>> De: "Brian Goetz" >>>> ?: "valhalla-spec-experts" >>>> Envoy?: Lundi 11 Mars 2019 20:30:09 >>>> Objet: The gift that keeps on giving >>> >>>> One thing we need to figure out about value types is ? serialization. >>>> >>>> (Pause for everyone to wishfully say ?can?t we just disallow it for values??, >>>> and three pauses for people to get over this.) >>>> >>>> The problem is that serialization today proceeds by mutation, which might be >>>> something we could deal with, but the mechanisms for ?safer? serialization >>>> (readObject, etc) also rely on mutation, and that?s harder. >>>> >>>> I?m working on a story here, but for now, let?s just put this on the list of >>>> legacy pain that we will eventually have to deal with. From forax at univ-mlv.fr Mon Mar 11 23:44:48 2019 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 12 Mar 2019 00:44:48 +0100 (CET) Subject: The gift that keeps on giving In-Reply-To: <0DA8FB87-9E5D-4937-94FC-1E86714FC185@oracle.com> References: <5B2FE4C7-3E78-412E-A2DA-27B152D6BC38@oracle.com> <1761938921.330317.1552339599498.JavaMail.zimbra@u-pem.fr> <53A62B7C-899E-48F4-BD78-C165FF77DB54@oracle.com> <786893537.341029.1552346516052.JavaMail.zimbra@u-pem.fr> <0DA8FB87-9E5D-4937-94FC-1E86714FC185@oracle.com> Message-ID: <1619613139.341609.1552347888304.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoy?: Mardi 12 Mars 2019 00:39:46 > Objet: Re: The gift that keeps on giving > Heh, because by " I?m working on a story here, but for now, let?s just put this > on the list of legacy pain that we will eventually have to deal with? I meant > ?let?s all design this off the top of our heads right now? :) > Yes, generating an insecure all-fields ctor and pushing the scraped fields to it > is one possibility (as is the readResolve/writeReplace protocol.) But I?d like > to do something better. Stay tuned. ok, cool ! R?mi >> On Mar 11, 2019, at 7:21 PM, [ mailto:forax at univ-mlv.fr | forax at univ-mlv.fr ] >> wrote: >> oops, i've forgotten to mention that the constructor / factory method known by >> the serialization should work like a copy constructor. >> with your example: >> value class X implements Serializable { >> int x; >> public X() { x = 0; } >> public X withX(int x) { >> ALOAD this >> ILOAD x >> WITHFIELD ?x? >> ARETURN >> } >> // this constructor is required by the deserialization mechanism otherwise it >> doesn't compile >> private X(X unsafeXThatComesFromSerialization) { >> this.x = unsafeXThatComesFromSerialization.x; // checks the arguments here >> } >> } >> R?mi >> ----- Mail original ----- >>> De: "Brian Goetz" < [ mailto:brian.goetz at oracle.com | brian.goetz at oracle.com ] > >>> ?: "Remi Forax" < [ mailto:forax at univ-mlv.fr | forax at univ-mlv.fr ] > >>> Cc: "valhalla-spec-experts" < [ mailto:valhalla-spec-experts at openjdk.java.net | >>> valhalla-spec-experts at openjdk.java.net ] > >>> Envoy?: Lundi 11 Mars 2019 23:53:14 >>> Objet: Re: The gift that keeps on giving >>> Well, consider this value: >>> value class X { >>> int x; >>> public X() { x = 0; } >>> public X withX(int x) { >>> ALOAD this >>> ILOAD x >>> WITHFIELD ?x? >>> ARETURN >>> } >>> } >>> How do I serialize new X().withX(3) ? How do I deserialize it with the lame >>> ctor that X has? >>> If you pull on that string, what you end up with is a secret constructor / >>> factory that takes one arg per field and initializes all the fields with no >>> invariant checking, and serialization scraping the fields and deserialization >>> calling that constructor. Which is about as awful as existing serialization >>> (with all the security risks it entails). So, let?s call that our last choice, >>> and look for something better :) >>>> On Mar 11, 2019, at 5:26 PM, Remi Forax < [ mailto:forax at univ-mlv.fr | >>>> forax at univ-mlv.fr ] > wrote: >>>> Hi Brian, >>>> given that a value type is constructed by a factory method (the constructor is >>>> desugared to a static method), why not making the serialization aware of that >>>> factory method. >>>> R?mi >>>> ----- Mail original ----- >>>>> De: "Brian Goetz" < [ mailto:brian.goetz at oracle.com | brian.goetz at oracle.com ] > >>>>> ?: "valhalla-spec-experts" < [ mailto:valhalla-spec-experts at openjdk.java.net | >>>>> valhalla-spec-experts at openjdk.java.net ] > >>>>> Envoy?: Lundi 11 Mars 2019 20:30:09 >>>>> Objet: The gift that keeps on giving >>>>> One thing we need to figure out about value types is ? serialization. >>>>> (Pause for everyone to wishfully say ?can?t we just disallow it for values??, >>>>> and three pauses for people to get over this.) >>>>> The problem is that serialization today proceeds by mutation, which might be >>>>> something we could deal with, but the mechanisms for ?safer? serialization >>>>> (readObject, etc) also rely on mutation, and that?s harder. >>>>> I?m working on a story here, but for now, let?s just put this on the list of >>>>> legacy pain that we will eventually have to deal with. From karen.kinnear at oracle.com Wed Mar 13 14:13:15 2019 From: karen.kinnear at oracle.com (Karen Kinnear) Date: Wed, 13 Mar 2019 10:13:15 -0400 Subject: Valhalla EG notes Mar 27 * clock change * Message-ID: Reminder - the US changed clocks last weekend, Europe did not. Also there will be no EG meeting March 27th. Attendees: Brian, John, Remi, Frederic, Tobi, Karen I. BSM CallInfo and symbolic use of constraints - upcoming doc from John 2. Generic Specialization Requirements - upcoming doc from Karen http://cr.openjdk.java.net/~acorn/Generics%20in%20LWorld%20Requirements.pdf 3. Template Proposal expansion - upcoming doc from John Link to Template Classes: http://cr.openjdk.java.net/~jrose/values/template-classes.html Link to Model 3: http://cr.openjdk.java.net/~briangoetz/valhalla/eg-attachments/model3-01.html I. Generic Specialization and class file: Brian: approach from two perspectives - language model -> class file to enable sharing and runtime What is a class, species, mirror? Migration and backward compatibility John: Template proposal allows single source to define both template and the class at the same time Brian: ?puns? with the meaning of class today - java class, class file, live type, ? John: working on JVMS draft for templates e.g. how are names used, what constant pool entries need to change chapter 4 is fairly straightforward, get in trouble in chapter 5 with resolution with mix of class name and template parameters each of which might have a separate defining loader Brian: In Model 3 we just kept strings and gave up the loader issues Remi: can solve this at runtime, like a MethodType Remi: need to ensure look at requirements relative to other languages, not just java John: let us know what degrees of freedom are missing Brian: e.g. languages with covariant generics will be disappointed Note that all languages that want to interoperate with java need to understand java?s generic type system so we don?t force all to use raw types Remi: Need to know up front how holes are filled John: All constant pool references in a class can be templated, including superclass and superinterface - allows other languages to give other supers to each species names in bytecodes are used in two ways: 1. resolved: e.g. constant_class : ?active? , ?live? 2. quoted, not resolved: ?passive? or ?flat? would like: if quote a name, don?t make me put live values in it, want flat UTF8s Karen: client that defines the species resolves the live types for the type parameters, they may not be visible to the template class. So we need a way to store both the UTF8 and the already-resolved live type. John: Might fall back on Object if not denotable by template class Karen: we need to retain loader constraints John: maybe extend loader constraints e.g. List from List Also - redefineclasses needs a way to mark the live types type parameters which were resolved in another context, since we can not re-resolve them John: can re-execute condos and indys with redefineclasses ? (zoom crashed) Brian: Type inference truncates e.g. ListMT extends Comparable today cuts off at 2 levels, to truncate to a finite type John: could use Object here Brian: added link (above) to Model3 for concepts from the language perspective - mostly still holds together Karen: does not include the sharing requirements John: goal for methods: unshare smallest possible piece of CP, if hot, unshare more goal for classes: subset of species metadata Remi: Do we need sharing? Is a prototype possible Brian: Model 1 prototype was a separate class file with on sharing, no wildcards Karen: My interest is the semantic sharing requirements the internal metadata sharing is an implementation choice Brian: Species all share the same class share wildcard at language level share a subset of statics - which needs to be clear in class file [editor?s note - see Generic Specialization Requirements for Karen?s understanding of the sharing requirements] John: goal - share bytecodes generic method: caller may create a generic method and put in his own constant pool, so reusable by him open issue - does it need to be reusable ty other callers Remi: Not want to discuss a class specialization mechanism that does not work for method specialization John: Want MethodHandle as specialization handle in addition to types Brian: Might need a double dispatch for generic methods Challenge: generic methods are generally nested in generic classes Old: treated inherited dynamically, but actually static Johnn: implies - in class file format - perhaps support for multiple classes Brian: today: outer/inner - 2 class files, could benefit if entire nest in one class file with shared type variables challenge: backward compatibility with existing class file names John explore 1 class file with multiple templates Brian: e.g. generic method with a lambda initializer - desugar to a method - which class file? Nice if same segment II. Acmp John: Are we willing to accept substitutability as a not too terrible approach since the alternatives are worse? Brian: yes Remi: No Tobi: Understand where Brian is coming from return false =- is hard for the user biggest concern: performance impact & cost of substitutability John: Do you need to prototype and measure cost on IBM JIT? Or ok with hotspot numbers? Karen: Hotspot prototype has measured performance on existing code, i.e. cost to non-value type users and it was not significant. YMMV, point is that is possible. We have not optimized substitutability, so no numbers on cost for value types Note: code cache size varies by implementation, and will vary for other value type uses, so not clear the meaning of measuring for hotspot. John: could take existing wrappers, e.g. j.l.Integer, call them value types and measure performance Remi: Not like returning false either Understand Brian?s concerns with reflective issues 2 other options: substitutability test vs. call equals challenge: == changes the encapsulation model, equals is provided by the author JohnL how handle backward compatibility? Remi: Call equals only for value types Brian: Kevin B and Remi uncomfortable want: in generic code: == or .equals paradigm -> .equals only in an ideal world all would use .equals Can we give useful semantics and demote == at the same time? javac could encourage .equals == we have a constraint that we don?t want to break existing code Remi: System.fastEquals - only for Object John: could invent new names for semantics or performance Brian: Need to invent anyway, then decide what to make to == Remi: If provide a subsitutitability test mechanism there is no author control, want opt-in Brian: like all-zero value John: Need to think about the balance for the junior java programmer Remi: === on value type should map to ,equals Brian LWorld: Users will not have to decide, just the author Remi: Not compile == on VT, require .equals Karen: Interface/Object - you do not know statically Remi: acmp doesn?t work today for String or j.l.Integer (ed. note - notes might be inaccurate) Brian: yes it does Brian: Goal is no existing code breaks, some performance, could guide away from == Karen: folks opt-in to value type encapsulation Remi: concern substitutability test Brian: each issue will create some legacy constraints locking, array covariance, equality John recommend call .equals Remi implies value type should call .equals if substitutability is not the right answer. Only works inside the class Brian: Most value types will be happy with substitutability, some may want to opt-out Remi want opt-in John: think about int Brian: let?s figure out an alternate mechanism then figure out the default Remi: value record - all fields visible, no encapsulation issue Brian Complex, Point, numerics, tuples, - no encapsulation cursors, native pointers - may want encapsulation AI: let?s figure out mechanisms then map APIs John: need a translation strategy Remi: if both same value class, then check John: one approach is methods: e.g. in chat ValObject::isSame(x){ return System.substEquals(this, x);} Brian: e.g. ?NAV? - not a value, or a bit, perhaps not an upscale to java code to determine corrections welcome, thanks, Karen From karen.kinnear at oracle.com Wed Mar 13 17:41:28 2019 From: karen.kinnear at oracle.com (Karen Kinnear) Date: Wed, 13 Mar 2019 13:41:28 -0400 Subject: Questions on Template Classes proposal Message-ID: John, Lois and I have been studying the Template Classes in the VM proposal: http://cr.openjdk.java.net/~jrose/values/template-classes.html We had some questions we were hoping you could either answer in email or in the next draft. thanks, Karen Where in the template class file are holes allowed? "almost anywhere in the constant pool" Type parameters today are allowed in ClassFile, field_info and method_info for class, interface, constructor, method or field who declarations use type variables or parameterized types (JVMS 4.7.9) Add at use sites for field and method descriptors. Assume this applies both to the reference class and the parameters/return types Add at use sites for CONSTANT_Class_info: e.g. superclass comment about other languages wanting to assign different superclasses for species What about the user mode assumption that: if Foo extends Bar, Foo is a subclass of Bar superinterfaces Add for annotations for field and method descriptors other attributes? Where are holes allowed in specialized class files? "API types, class instances and method invocations never have holes" Assume add "field descriptors" "Because of overloading the type part of a method needs to be spelled with non-class holes intact, since that is how the API element seems to be distinguished from its peers. This seems to be a necessary exception to the rule that constants with holes cannot participate in resolution." bytecodes can not be type-checked until all CP entries are complete potential w/partial verification/2 pass - but that is an implementation optimization, not semantic is the key point here that verifier can not see any holes, but e.g. attributes only viewed by e.g. reflection may see holes which are not caught by verification? clearer if "no holes in specialized class file", verification only extends as far as it does today Is it the case that a specialization could always have two segments There is a requirement that only the root segment can be shared Could we therefore have two segments for a given specialization - a common root segment and a per-species segment and not need multiple segments in the specialized class file and then, from an implementation standpoint, we could have a common root for all the raw types Could we see more complex examples more than one parameter type segments that depends on multiple parameter types template classfile and specialized result "reduction" JVMS level does the specialization including the root segment does verification verify the root segment note: implementation may be able to optimize e.g. implementation may be able to verify the root segment and separately verify specialization relative to the root segment Why extend super-interfaces to Constant_Dynamic? When would the BSM be invoked? Is the intent here to allow superinterfaces with holes - couldn't that be handled via a template type species? Relationship between: raw type, template class, erased species proposal is that the erased species has exact match of members as template class proposal is that the template class is not instantiable challenge is: older class files with instantiated raw type "new Foo" class file: new LFoo; ? requires instantiation one proposal: re-translate the bytecode into new LFoo; and keep a non-instantiable template class, and keep the statics in java.lang.Class as they are today TODO: walk examples of raw type bytecodes and BC Clarifications/explorations on relationship of template class potential for multiple inheritance Concern about the requirement for ordering of holes - must depend on an earlier one, also the requirement to point to the "last" hole in a segment does this cause problems for Lookup.defineClass with re-use of byte[] and constant pool extension mechanisms? does this cause problems for bytecode instrumentors, redefinclasses - what if they want to add an additional hole in a segment - do they need to need to change all references to the "last" hole in a segment? Could we explore having two segments in the template classfile, one root and one specializable, and each entry you could trace dependencies instead of requiring ordered segments? Request from vm runtime in the specialized class file, we need both the name/UTF8 string AND the Constant_Class to have a place to store the passed-in resolved type parameter It would be valuable to be able to share any resolved classes to achieve "same resolution" results going forward Redefineclasses will need to have a way to indentify types resolved externally and passed in as live-types, so they are NOT re-reresolvable Detail: How are Template Attributes used e.g. by Class, method, field? What does it mean to have a template attribute on a static field? Is that a way of clarifying which conditional species it belongs to? Template Attribute and TemplateParameter dependency relationship? Later email: not clear why a BSM-based template expansion logic would not use existing class loaders, system dictionary and loader constraints From brian.goetz at oracle.com Wed Mar 13 18:21:08 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 13 Mar 2019 14:21:08 -0400 Subject: Questions on Template Classes proposal In-Reply-To: References: Message-ID: <4088B3F8-8DBB-4238-8B28-224EBCC0B7BC@oracle.com> Some quick answers? > Where in the template class file are holes allowed? "almost anywhere in the constant pool? The constant pool is divided into ?segments?, and the holes are a property of the segment. In the original doc, the constraint was that the holes were the first N constants in the segment. An alternate way to do this is how M3 did it, where there was a ?table of contents? early in the classfile, which enumerated the segments, and identified the holes that go with each. Whether a hole is defined by a constant pool entry or an entry in a segment table is largely a ?syntax? choice. Since each segment is associated with a number of holes, and classes/members are associated with segments, it stands to reason that we need to type-check the relationship between the number and kinds of holes in a segment, and an attempt to specialize the class or method defined in that segment. If I have a segment with two holes of kind ?TYPE?, its probably an error to instantiate it with three type parameters, or parameters that are not of kind TYPE. (Though there may be some need to support mismatches for reasons of compatible evolution.) Where we would previously reference a class (Constant_Class, or LFoo; descriptor), we instead need a way to reference a species. The M3 document showed one way to do this; a ParamType constant, which captures the template name and the parameters. Then, a species is allowed basically anywhere a class was previously ? in descriptor strings, in Constant_Class, as the operand of a new/checkcast/instanceof, as the owner of a Field or Method ref, etc. > > What about the user mode assumption that: if Foo extends Bar, Foo is a subclass of Bar This is handled by the translation scheme. If Foo extends Bar, then the superclass of Foo will be Bar. In M3, we?d write this as ParamType[Bar, TypeVar[0//Object]], which means that when Foo is specialized with a real type T, the super type is Bar, and when Foo is specialized with erased, the suer type is erased Bar. > Is it the case that a specialization could always have two segments As the proposal is written, a specialized class would have at least two segments ? the ?true constants? segment, and the ?specialized class? segment. The intent, though, is that nested generic members (methods, and maybe even inner classes) would have their own segment too. And segments inherit holes from their parents. Note too that compilers often desugar one member into more than one; if a generic method has a lambda in it, that lambda will get its own method, but it should go into the segment for the generic method, not the class. Then the right things happen automatically. Bringing inner classes into a single template may sound like a radical move, but its a powerful one; inner classes share so much with their enclosing class, that being able to inherit holes and constants from the class would result in lots more sharing. > Why extend super-interfaces to Constant_Dynamic? When would the BSM be invoked? Is the intent here to allow superinterfaces with holes - couldn't that be handled via a template type species? John is looking ahead here to a feature we would like to have. Suppose I have an interface like Comparable. And suppose I want to create a Box type, and I want to lift the comparability of T onto Box. That is, if T is ordered, then I can derive an order on Box from the order on T. And I would like that to be reflected as a conditional super type. I might express that in Java code like: class Box > implements Comparable> { ? } Having a bootstrap that comes up with the list of supertypes as function of the type parameters allows such things to be represented. > Relationship between: raw type, template class, erased species There are two questions embedded here: - are the above all types, and if so what is their subtyping relationships, - what is the relationship between the _members_ of these various types Our working story is that in the VM, there are _class_ and_species_ types. From a template class, we derive a class type and a mechanism for deriving the species types from their type parameters. The compiler translates uses of wildcard and raw types as the class. The Class is a super type of all the species of that template. Separately, the compiler ensures (through the translation model) that the _class_ has all the same members as the erased species. So for any member m in the erased species, call sites / access sites for c.m will link, where c is of the class type. > proposal is that the template class is not instantiable Yes but ? Old code (and maybe new) will have `new Foo` byptecodes in them; we?d like to redirect these to instantiate the erased species. > Request from vm runtime > in the specialized class file, we need both the name/UTF8 string AND the Constant_Class to have a place to store the passed-in resolved type parameter Yes, definitely. M3 sidestepped this as a prototyping move, but we need to meet this head-on, and its a nasty problem. From john.r.rose at oracle.com Thu Mar 14 03:44:36 2019 From: john.r.rose at oracle.com (John Rose) Date: Wed, 13 Mar 2019 20:44:36 -0700 Subject: Questions on Template Classes proposal In-Reply-To: <4088B3F8-8DBB-4238-8B28-224EBCC0B7BC@oracle.com> References: <4088B3F8-8DBB-4238-8B28-224EBCC0B7BC@oracle.com> Message-ID: <83D22A90-078E-4093-89BD-3DC742396FFB@oracle.com> This is going to be a fun conversation. As you know, I've been quietly hammering on a new version of templates for weeks and have (re-)discovered a number of hard problems, plus some solutions. Thanks so much for making a close reading of the previous draft; your questions are greatly improving the new draft. First I'll respond to Brian's comments, then go through Karen's. > On Mar 13, 2019, at 11:21 AM, Brian Goetz wrote: > > > Some quick answers? > > >> ? Where in the template class file are holes allowed? "almost anywhere in the constant pool? Short answer (explanations below): A specialized hole is a loadable constant (used by condy, ldc, etc.). In addition, a specialized hole can filter a couple of new "descriptor patching" operators in the constant pool, assuming the hole contains an appropriate type or type sentinel ("erased"). > The constant pool is divided into ?segments?, and the holes are a property of the segment. In the original doc, the constraint was that the holes were the first N constants in the segment. An alternate way to do this is how M3 did it, where there was a ?table of contents? early in the classfile, which enumerated the segments, and identified the holes that go with each. > > Whether a hole is defined by a constant pool entry or an entry in a segment table is largely a ?syntax? choice. > > Since each segment is associated with a number of holes, and classes/members are associated with segments, it stands to reason that we need to type-check the relationship between the number and kinds of holes in a segment, and an attempt to specialize the class or method defined in that segment. If I have a segment with two holes of kind ?TYPE?, its probably an error to instantiate it with three type parameters, or parameters that are not of kind TYPE. (Though there may be some need to support mismatches for reasons of compatible evolution.) > > > Where we would previously reference a class (Constant_Class, or LFoo; descriptor), we instead need a way to reference a species. The M3 document showed one way to do this; a ParamType constant, which captures the template name and the parameters. Then, a species is allowed basically anywhere a class was previously ? in descriptor strings, in Constant_Class, as the operand of a new/checkcast/instanceof, as the owner of a Field or Method ref, etc. The forthcoming draft of the template class proposal takes this approach, with some superficial changes (versus M3) in the way the CP data are organized. One oddity in my updated template proposal is that it makes descriptors (both field and method descriptors) patchable via a uniform mechanism. Thus, there are more places where the JVMS applies "bimorphically" to both field and method descriptors; the way you patch holes in a field type is the same way you patch holes in a method type. Also, the special role of the "erased" sentinel is moved out of the JVMS, with (instead) a mechanism for replacing a descriptor which "fails" with another descriptor nominated by the compiler. (I hope this has more uses than just patching "erased" sentinels per se. The result should be the same as for M3, with more tricks in the back pocket.) In the following explanations you may with to consult this diagram of constant pool relations, which is derived from my current draft: http://cr.openjdk.java.net/~jrose/values/template-jvms-4.4.pdf In order to feed the enhanced descriptors back into uses of CONSTANT_Class, the latter is allowed to consume a patched field descriptor, if it names a class or interface or array (not a primitive). If the CONSTANT_Class consumes a plain Utf8 string it's not a descriptor, as we have today. The patching operator is called CONSTANT_Descriptor; it takes a template string with marked holes and a variable number of CP references. There is also an alternation operator CONSTANT_DefaultType to handle the choice between non-type sentinels like "erased" (or in general non-denotable types), and their compiler-assigned bounds. I found an optional trick to avoid variadic CP entries: The CONSTANT_Descriptor is followed by enough blank constant pool entries to hold the data, even if CP entries are fixed in size. (There is a tag CONSTANT_Extra to hold the space, which makes class files easier to parse. Such constants are useless. We can also break the glass and make these CP fields variadic, but I remember we chose *not* to do that for indy constants. We could also use an attribute to carry the variadic information, as with BootstrapMethods. I don't really care.) So the bytecodes which refer to CONSTANT_Class still do so, without exception. And CONSTANT_Methodref and its two brothers also refer uniformly to CONSTANT_Class. That's my current take on how to conserve complexity relative to the present JVMS. As in the previous draft of template classes, there is also a CONSTANT_Hole constant. The holes are the first move in the game, and require a patching mechanism, such as the one Brian described or I just described. To approach an answer to Karen's question: A CONSTANT_Hole is tightly associated with a segment. In fact, the current draft requires that each CP segment occupies a *contiguous* series of CP slots, and that the holes must be the first entries in the segment. Actually, the first entry of all is a CONSTANT_Segment structure, followed by its holes. This is similar to the CONSTANT_Descriptor followed by CONSTANT_Extra structures that it requires; I'm using the same tactic for variadic CP entries in both cases. But (unlike CONSTANT_Extra constants) the CONSTANT_Holes are individually addressable. The CONSTANT_Segment is also addressable as an opaque constant; this may be be useful for doing certain complicated inter-segment operations; a condy may take one or more reified segments as inputs, for example. The specific list of uses of a CONSTANT_Hole is an interesting design question, which I'm sure we will discuss. My current take on this is to make it happen in fewer places, rather than more, and mediate holes through CONSTANT_Class constants. This corrals the design complexity into a smaller space, that of descriptor patching. It does create more CP entries, so at some point (much later) we may wish to allow some users of CONSTANT_Class constants to "short circuit" straight through to whatever the CONSTANT_Class points at. I'd rather not do this in the first draft. More details later. Not in the current draft: We could relax the restriction that segments must occupy contiguous spans of slots in the CP, either allowing a single segment to be sprinkled throughout the CP, or else having segments somehow occupy a completely different set of CP indexes, such as downward from 0xFFFF. In my enthusiasm, I wrote up a JVMS section for the first option (sprinkling). The complexity of it horrified both me and Brian, and I deleted it. We might want to add it back later, for the convenience of all 5 ASM implementors, and other parties as yet unknown. But for now, there's plenty of complexity in other places, and I'd rather not defend sparse segment allocations. A hole can also be directly ldc-ed, and that's important when the content of the hole is not a field-type. Here I'm aiming optimistically for something like C++ templates which take non-type arguments: functions, numbers, booleans, other tokens. The set of applications is very rich here, so I'm aiming at it for the JVM too. E.g., a tuple type could be parameterized by a list of types. This makes sense when you realize that a template might be expanded using a library-defined bootstrap method, not just a intrinsic hardwired JVM behavior. The fallback position is to require all non-class arguments to be carried by types. (C++ does this sometimes, but it's cheaper for them than it would be for us.) Clearly non-type template arguments to templates is a debatable topic, and one we'll want to craft a careful answer for. Another reason to define a hole as a "loadable constant" is to allow it to serve as a direct argument to condy/indy. In fact, once you allow condy to accept hole data as inputs, it makes great sense to allow condy to return the favor, and *produce* hole-like data as an output. Thus, wherever a hole constant can be used, a condy constant can *also* be used. This allows a segment to define derived constants (derived from its holes) which serve the rest of the segment as extra holes. >> ? >> ? >> ? >> What about the user mode assumption that: if Foo extends Bar, Foo is a subclass of Bar > > This is handled by the translation scheme. If Foo extends Bar, then the superclass of Foo will be Bar. In M3, we?d write this as ParamType[Bar, TypeVar[0//Object]], which means that when Foo is specialized with a real type T, the super type is Bar, and when Foo is specialized with erased, the suer type is erased Bar. Yes. In the descriptor-centric syntax I'm working with it's isomorphic: Descriptor["LBar[_];", DefaultType[(Hole#42), (Object)]] In addition, the relation between Foo and Bar about *templates* only. Meanwhile Foo, Foo, etc., are species (specialized classes) that relate in the ordinary way to Bar, Bar, etc., simply because that's what the translation strategy asks for, by plugging in "class_info.super_class=Bar" in the specialization Foo. No JVM magic there. There is new JVM magic for templates, though: Foo has a *template super* of Foo, and Bar has a *template super* of Bar. This kind of super relation is new, and distinct from the existing relations, which are now called *class super* and *interface super*. Inheritance works through all of them. By distinguishing *template super* as separate concept, we avoid the fruitless puzzling over whether a template is a super class (but we need two now), or a super interface (but those only carry public members), or a super protocol (a new kind of type altogether). Template supers cause layouts and v-tables to become variable, so that's the place where extra implementation care is required, and existing concepts fail. I don't think it's necessary that Foo (the template) has Bar (the template) as any kind of super, as long as every species Foo is wired up to both Foo (the template) and Bar. On the other hand, when one template depends on another as with method templates inside of class templates, or with nested class templates, then constants from the parent template will appear in the child template. This is a kind of scoping, but not inheritance, so I think the only new inheritance mechanism here is from a species to its class template. > >> ? >> ? Is it the case that a specialization could always have two segments > > As the proposal is written, a specialized class would have at least two segments ? the ?true constants? segment, and the ?specialized class? segment. The intent, though, is that nested generic members (methods, and maybe even inner classes) would have their own segment too. And segments inherit holes from their parents. (Yes. See above.) > Note too that compilers often desugar one member into more than one; if a generic method has a lambda in it, that lambda will get its own method, but it should go into the segment for the generic method, not the class. Then the right things happen automatically. This is a good example of why each class and each method needs more than just an ACC_TEMPLATE bit, but rather a numeric segment designator. The same point probably even applies to fields. For example, if some desugaring mechanism needs a static constant, with a condy that depends on holes, then it *might* want a static field whose segment designator is aligned with whatever thingy is in that segment. Thus, the correct way to view segments is as "locations" for mixtures of classes, fields, and methods, even as those classes, fields and methods inter-relate among themselves, using the relation of "membership". The connection between "membership" and "location" is surprisingly loose. You can't model "location" as a kind of membership, any more than you can model "template super" as an existing type relation. Specifically, suppose C contains M or F or N as a member (C=class, M=method, F=field, N=nested class). Then S(C) can be the same segment as S(M), S(F), or S(N), and often will be. But S(M), S(F), and S(N) can *also* be parent or child segments of S(C). They cannot be "cousins", I think. So if C(M) is the same as S(C), then C and M are specialized together. If S(M) is a parent of S(C), that means that M is invariant in C, and so you can resolve M in the C-template (without specializing C). Because of template-super inheritance, every C-species inherits M (unspecialized) from its template-super. Also, you can implement wildcards by resolving M directly against the C-template. If S(M) is a child of S(C), then M is a generic method in its own right. If S(C) is the global segment, we have C as a normal class (not a template) containing template methods. Think Arrays.sort. The same considerations apply, with small modifications, to S(F) and S(C). The only combination that *doesn't* show up clearly in our requirements, yet, is for S(F) to be a child of S(C), *and* F to be non-static. So there are 17 of 18 combinations: (static/instance) S(M/F/N) is (same as/child of/parent of) S(C). That's a pretty rich model, but I think it's the right one. (Exercises for the reader: Figure out what it could mean for S(C) to be a sibling of S(M), not parent or child. Also, how might a source language ask for S(M) to be a grandparent of S(C)? Might a translation strategy need this? Are only direct children relevant to the above, or can S(M) be a grandchild segment of S(C)?) > Bringing inner classes into a single template may sound like a radical move, but its a powerful one; inner classes share so much with their enclosing class, that being able to inherit holes and constants from the class would result in lots more sharing. > >> ? >> ? Why extend super-interfaces to Constant_Dynamic? When would the BSM be invoked? Is the intent here to allow superinterfaces with holes - couldn't that be handled via a template type species? > > John is looking ahead here to a feature we would like to have. Suppose I have an interface like Comparable. And suppose I want to create a Box type, and I want to lift the comparability of T onto Box. That is, if T is ordered, then I can derive an order on Box from the order on T. And I would like that to be reflected as a conditional super type. I might express that in Java code like: > > class Box > > implements Comparable> { ? } > > Having a bootstrap that comes up with the list of supertypes as function of the type parameters allows such things to be represented. Yep. Part of my thinking is that, if we are going to the trouble of patching Collection into List, let's see if we can supply hooks for ad hoc logic to do slightly more elaborate patching. Some cases: - List extends Collection ? basic interface user model - List extends Comparable ? optional interface (ad hoc BSM logic) - MyRecord extends Serializable ? optional marker (ad hoc BSM logic) - MyHandler extends Foo ? proxy template (ad hoc BSM logic) Some of these are a little mind-blowing from the language design point of view, but as a group they are all routine grist for the JVM's mill. > >> ? Relationship between: raw type, template class, erased species > > There are two questions embedded here: > - are the above all types, and if so what is their subtyping relationships, > - what is the relationship between the _members_ of these various types > > Our working story is that in the VM, there are _class_ and_species_ types. From a template class, we derive a class type and a mechanism for deriving the species types from their type parameters. The compiler translates uses of wildcard and raw types as the class. The Class is a super type of all the species of that template. Yes. I think it's also useful, in the JVM, to distinguish a plain class from a class template. But (as described above), a C-species (a type) inherits every member M of its C-template (a type). The details of the inheritance differ depending on whether M is static or not, and whether S(M) is same/parent/child of S(C). In the case of "same S", then the two are specialized together, and it's not really inheritance at all, since the M-species shows up at the same time as the C-species, while the M-template is not usable in any way. In the case of S(M) being in a child segment, M is a generic, and isn't usable even now, until it is further specialized. In the case of S(M) being a parent of S(C), then M is resolvable in C-template, invariantly, before specialization. *That* is inheritance of the C-species from its "template super", using a new inheritance mechanism. > Separately, the compiler ensures (through the translation model) that the _class_ has all the same members as the erased species. So for any member m in the erased species, call sites / access sites for c.m will link, where c is of the class type. Yep. This is a major use case (but not the only one) for S(M) being a parent of S(C). Since M is located in a parent segment, it is accessible without S(C) being available, which is the requirement for backward compatibility with erased generics. It is also a requirement (maybe the same requirement, or maybe not) for the C-template to export an invariant protocol for dynamically typed access (via wildcards, or reflection, or both). Doing this requires invariant M members. In this vein, a rather difficult requirement is invariant access to instance fields. Old code accesses a public field Box.value of type Object. The new reified version of this field is Box, again of type Object. But now there is also Box.value which has type byte. That's a hot potato; you can ask the translation strategy to build bridges around it, but you still have old code doing "getfield Box.value:Object" when the box might have a byte or any old thing in its field. I think the JVM needs to catch this particular hot potato, briefly, and say that a CONSTANT_Fieldref can resolve covariantly on the field type, not invariantly (as today). Thus, if old code says "getfield Box.value:Object" and the field is really of type "DoubleComplex", then (since Object is a super of DoubleComplex) the getfield should resolve. The interpreter and complier have to assume the burden of checking the type dynamically, which is nasty, but similar to the problem with array loads. Same deal with putfield (like array stores). This *doesn't* cover primitives, so we have to toss the hot potato at that point back to the language designers, and have them do one of two things: 1. Disallow primitive parameters, 2. Wrap primitive fields appropriately in values which take the bound type. This become simpler when in LW1000 when we heal the rift between primitives and values. > >> ? >> ? proposal is that the template class is not instantiable > > Yes but ? > > Old code (and maybe new) will have `new Foo` byptecodes in them; we?d like to redirect these to instantiate the erased species. To me, this is another bridging problem. The move I think is fruitful here is to separate *template resolution* from *class resolution*, and *template references* from *class references*. In bytecode I propose to spell a template with its brackets, always, even if there are only hole symbols inside. Then a class of the same name will fail to load, or (rather) run a fallback path which will derive the requested "vanilla" class from the template. BTW, I am proposing that, contrary to M3, we rely on currently illegal characters only in spelling descriptors, and that means overloading array brackets. So the template for `java.util.List` might be named at the source code level by a wildcard `java.util.List`, but in the JVM it needs a separate name `java/util/List[_]`. The rule is that when you change dots to slashes, you also straighten the pointy brackets. The underscore character means "erased" in M3, but has a more generic meaning in the current templates proposal; it just means "this descriptor hole will need filling", like "#" in M3. I'm gratuitously changing the character at that point, but I think the underscore fits better with traditional notations, and that matters, slightly. > >> ? >> ? Request from vm runtime >> ? >> ? in the specialized class file, we need both the name/UTF8 string AND the Constant_Class to have a place to store the passed-in resolved type parameter > > Yes, definitely. M3 sidestepped this as a prototyping move, but we need to meet this head-on, and its a nasty problem. As Karen says, we need to be very careful about when resolution occurs. Given that names are sometimes used resolved and other times used unresolved (but with CLCs) the landscape is already treacherous, and will surely get more trickier, as we add more complex names and dependencies between names. I started writing a fuller response here and it got long. There are as many design choices here as anywhere else in the templates problem. So let's break that discussion out separately. Now to Karen's other specifics? On Mar 13, 2019, at 10:41 AM, Karen Kinnear wrote: > > John, > > Lois and I have been studying the Template Classes in the VM proposal: > http://cr.openjdk.java.net/~jrose/values/template-classes.html > > We had some questions we were hoping you could either answer in email or in the next draft. > > thanks, > Karen > ? Where in the template class file are holes allowed? "almost anywhere in the constant pool" > ? Type parameters today are allowed in ClassFile, field_info and method_info for class, interface, constructor, method or field who declarations use type variables or parameterized types (JVMS 4.7.9) Yes. BTW, Brian and I are now using the new term "class_info" for the specific parts of the ClassFile which define the class or interface. This is because (a) it's more in line with field_info and method_info, and (b) we want to preserve the option of putting more than one class_info structures into a single ClassFile structure, so they can share constant pool and segments. (Note that submerging class_info into ClassFile only makes sense if there is a strict 1-1 relation between the CP and the class_info. This is not a useful economy when we start to break up the CP, and/or put nested classes under the same CP.) > ? Add at use sites for field and method descriptors. Assume this applies both to the reference class and the parameters/return types Yes. Also, wherever a Hole can go, a Condy can also go. The user of a hole is willing to pick up the resolved value of the hole and decode it appropriately to the use. The same user, when presented with a condy, forces resolution, and *then* decodes the resulting value just as if it were a specialized hole. In this way, holes and condys are almost the same thing; they are defined in different ways but used in the same ways. > ? Add at use sites for CONSTANT_Class_info: > ? e.g. superclass > ? comment about other languages wanting to assign different superclasses for species > ? What about the user mode assumption that: if Foo extends Bar, Foo is a subclass of Bar > ? superinterfaces (Stuff about supers was discussed earlier.) I want to relax this a little. I think it would be helpful to place a Class between a Hole (or Condy) for these uses, so that the "decode" logic I mentioned above can be centralized, on the code that handles CONSTANT_Class. The basic rule is, if you are using a Hole (or Condy) inside the "guts" of a Class, MethodType, or NameAndType constant, use it directly. Otherwise, wrap it first in a Class, and then use it like today. > ? Add for annotations for field and method descriptors > ? other attributes? Wrap in a Class in those cases. Also for instanceof and the other bytecodes that consume Class today, wrap the descriptor in a Class. This requirement, of wrapping in a Class for non-descriptor usage, applies uniformly to every kind of descriptor other than Class itself: Utf8, Condy, Hole, DefaultType, Descriptor (aka. ParamType). You shouldn't wrap a Class in a Class, though, even though you can wrap a different kind of descriptor in a Class, and a Class in other kinds of descriptor. That wrinkle isn't captured by the diagrams I posted. > ? Where are holes allowed in specialized class files? > ? "API types, class instances and method invocations never have holes" > ? Assume add "field descriptors" Yes, thanks. > ? "Because of overloading the type part of a method needs to be spelled with non-class holes intact, since that is how the API element seems to be distinguished from its peers. This seems to be a necessary exception to the rule that constants with holes cannot participate in resolution." This bit interacts with the hard problem we address today with class loader constraints. We need a way to ensure that "passive" (non-resolved) uses of type names during API linkage mean the same thing for both caller and callee. I think it boils down to creating the species (with or without non-class holes) in the caller and also in the callee, and determining that they are identical, before letting the link resolution declare victory. This is different from CLCs. It's a long conversation. But the above statement is hopefully not going to be necessary; hopefully we just won't allow some kinds of species to serve as API types, at least not via flat strings. (BTW, I see it as an *anti-requirement* to be able to assign a stable name to every species of every template. This means that some species just won't make sense as passive type names in APIs. You can start to see this if you realize that a generic method specialization doesn't need a name, as an API type; you just call it via resolution instead of making a passive reference to it. Then there are VMACs, which don't ever have a name. And CLCs are complex and unwieldy, so you probably can't allow Temp to exist for two Foo's in different CLs, at least in some kinds of Temp. Behavioral data like MHs and lambdas makes for great template arguments but cannot be uniquely named, until we solve the halting problem. And so on. If you try to unwind all such objections, back to the goal of giving a unique name to every species, you'll end up with a much, much weaker mechanism, for a dubious gain.) > ? bytecodes can not be type-checked until all CP entries are complete > ? potential w/partial verification/2 pass - but that is an implementation optimization, not semantic > ? is the key point here that verifier can not see any holes, but e.g. attributes only viewed by e.g. reflection may see holes which are not caught by verification? The easiest way to extend the verifier is to run it only after all holes are filled. There are alternative paths we should examine, however. Brian has suggested that a Hole with a non-denotable type could be filled, for verification purposes, by a "nonce", a value known to be different from every other class name. Such a nonce might not be resolvable (so the verifier must be conservative about assignability and subtyping). As long as such a "nonce" type is uniformly compatible with a-class instructions (not iload/iastore/etc., but aload/aastore/etc.) you can load it from arrays and fields, move it through stack and locals, store it out to arrays and fields, and use it in *local* method calls. This can be verified once, before template instantiation. There's more: This "nonce" is really nothing other than a quasi-symbolic ("indexical"!) reference to a local constant pool entry. In fact, we could require that it is a real and true CONSTANT_Class entry, even if it is built on top of descriptors, holes, etc. This puts extra constraints on translation strategies, in that they must be careful to use the same CONSTANT_Class entry for everything the verifier touches, but the benefit is nice: You can verify your template before specialization. > ? clearer if "no holes in specialized class file", verification only extends as far as it does today If we don't adopt indexical types in the verifier, we still have the burden of computing, in the verifier, the relation (equal, assignable, neither) between two verifier types. Those are derived from CONSTANT_Class constants and also from "passive" names that occur in various descriptors. In fact, here's another reason to try to require passive species names to be wrapped in CONSTANT_Class, uniformly. The verifier tries hard not to resolve names, but it must sometimes. In the case of complicated species references, two structurally distinct CP references might resolve to the same metadata pointer. Should the verifier just assume they won't, and require an explicit checkcast in places where you can't tell for sure? Probably. But there's a slippery slope here. How dissimilar should two constants (or names) be before the verifier gives up trying to prove they are identical? This is an extension of the current problem with subtyping, in that distinct names might be related, but the verifier can only extract the information it wants by resolving them and querying the metadata. I don't have a firm design on this point, but I think we should allow the verifier (a) to use indexical types based on CP indexes, and (b) to "punt" if two indexical types refer to different CP indexes, even if a little more effort might discover that they somehow "boil down" to the same name. And the verifier should "punt" for an indexical type vs. a named type, except in the current case, backed up by CLCs, where the name is for a non-species and the indexical points to a CONSTANT_Class whose Utf8 is that same name. (I guess I've just demonstrated that the verifier already uses indexicals. That's really what an ITEM_Object structure is, in a stack map. But the ITEM_Object has a "magic" equivalence with its underlying string, as derived from various nearby descriptors. It's that equivalence that must be broken, in the case of species.) > ? Is it the case that a specialization could always have two segments > ? There is a requirement that only the root segment can be shared > ? Could we therefore have two segments for a given specialization - a common root segment and a per-species segment and not need multiple segments in the specialized class file Not sure what the proposed idea is here. Some constants are invariant, and some are variant. More than that, a variant constant is variant with respect to a *particular* non-root segment. Why do we need more than two segments? In a word, nesting. Method templates might nest inside of class templates, and classes in classes too. Enumerating all the use cases gets up to the 18 or so combinations I mentioned above. And it's not necessarily just depth-2, since the language allows very deep nesting (in principle). When the dust settles, you find that the natural structure here is a single root segment for global (invariant) constants plus a parent-child relation for segments under the root segment. Backing off from that, note that, even if there were just 2 segments, there's a problem with assigning indexes to their constants. We don't want to add a new "segment" field to every bytecode instruction (do we? what about a a segment prefix?) so the bytecode instructions need a way to combine both factors (segment S, constant in S) into one 16-bit index. The same uncomfortable conversation applies to pretty much every 16-bit holder of CP references in today's class file format. So we need something like (a) a combined, flat, compact index space for all constants in all segments and (b) a way to decode indexes in that space into the two factor form, (S, K in S). The simplest way to do that, and what I'm proposing to start with, is having each segment declare exclusive rights to a specific range of CP indexes. The "global segment" is all the bits and pieces left. There is a table, loaded after the constant pool but before every class_info, which declares these segments. Thus, the CP can be loaded "segment agnostic", then the segments are checked, and finally the class is loaded (or classes are loaded). The current published draft has segment info interspersed into the CP; I now think it's a little easier to wedge segment metadata between the CP proper and the class_info(s). Putting segment metadata in an attribute is a lose, because it makes it impossible to check on the fly, while class_info gets loaded. So we have ClassFile = header constant* segment_info* class_info*. And then class_info = header field_info* method_info* class_attr* > ? and then, from an implementation standpoint, we could have a common root for all the raw types Yes. I'd prefer to say "invariant" or "global" constants. The infrastructure for "raw" stuff is the concern of language translators, not the JVM proper. > ? Could we see more complex examples > ? more than one parameter type > ? segments that depends on multiple parameter types > ? template classfile and specialized result "reduction" Map has a two-hole segment Map.Entry is a type nested inside Map. In the case of a "reduction", I think you mean a classfile or class_info which somehow provides an ad hoc, manual specialization. A good example of that might be an EnumMap, if somehow a general TreeMap could be persuaded to specialized to EnumMap if it detected that its K parameter were an enum. Even simpler, we could try to partially specialize TreeMap to a BooleanMap whose state is always just two fields of type V, falseValue and trueValue. I'd like to provide JVM hooks for such things, without knowing the full details of the user model. From a "hooks only" point of view, the TreeMap template should have a specialization BSM which can decide if the K type (and/or V type) meets some special condition, and serve out a specialized version of the template, which has the same API but different fields and methods (maybe supers too). One way to do this would be to allow the template to just return a foreign type for some of its specializations; this is very visible in the user model, but easy to do in the JVM. So TreeMap would return a BooleanMap and we muddle on. I think that's a reasonable first thing to try, although it requires lots of laxity at the user model level. More subtly, the next thing to try will be to keep BSM as above, but have it return an instantiation of a class_info other than the template in question. It might have a similar name, like TreeMap which boils down to TreeMap[Z_], on top of a Hole reference in a relevant segment (not the main segment for TreeMap, but one specially for this ad hoc class_info). If you ask for the "class" of this specialization, you'll be told "TreeMap", even though there are several class_info items named TreeMap[something] in the ClassFile. A separate thing to do, which I want to do anyway, is to allow optional members of templates. In that case, one TreeMap template would be just stuffed to the gills with all the fields and methods that any version of TreeMap might want, but the BSM would take care to enable only the ones necessary for a particular ad hoc version, or the general version. (What's an optional member of a template? Ah, that's a good conversation. I haven't written this one up yet. I think the cleanest way to formulate those is in terms of *optional child segments*. I can think of many different ways to gate their optionality, and BSM-based instantiation is probably the most general and therefore adequate.) > ? JVMS level > ? does the specialization including the root segment Yes. Segments inherit from their parent segments. Everybody inherits from the root. Inheritance in this case means "do I have a right to use this index", not "will somebody find this index if they search me for it". That's somewhat similar to inheritance with classes, but it applies independently to related segments. > ? does verification verify the root segment Verification of bytecodes applies as usual to all methods *located in* the root segment, regardless of whether their containing class is also located in the root segment. BTW this means that the verifier must restrict itself to the template-super type of 'this' for non-static methods. > ? note: implementation may be able to optimize Yes; even if we find a corner case which requires us to re-verify some non-template method for every specialization of an enclosing class, we can still look for such opportunities to secretly verify. Basically, try verifying the invariant method, and if something "goes wrong" mark it for re-verifying in each specialization. Or (the other way around) if we can verify a method (template or not, variant or invariant) when the template is loaded, mark it as "done" and don't repeat the work on every specialization. (Basic Hacking 101.) > ? e.g. implementation may be able to verify the root segment and separately verify specialization relative to the root segment Yes. If the model allows strong guarantees of verification before specialization, we can put that into the JVM spec. Otherwise we say that errors are reported at specialization time, whether or not they were (secretly) detected at template-load time. > ? Why extend super-interfaces to Constant_Dynamic? When would the BSM be invoked? Is the intent here to allow superinterfaces with holes - couldn't that be handled via a template type species? Addressed above. Basic answer: Because we can. Modified answer: We probably want to wrap such variant supers inside CONSTANT_Class, as discussed above. Another point: We might want a template to inject a *whole list of supers* into a specialization, like a multi-proxy. In that case, the definition of the specialization should happen via a reflective "load specialization" VM-call that may refer to a class_info inside of the caller, but may *also* say "please add the following lists of field, methods, and supers". This would be slightly similar to the VMAC stuff we have now, but it would inject the dynamically selected "stuff" at specialization time. It seems to be a natural option in a BSM-centric world. It suggests that there are simple automatic rules that the JVM can execute for template specialization, without calling a BSM, and then various levels of BSM-based customization, starting with "fill in a condy", and then "fill in a super" (via a condy), and finally "fill in a whole class-specialization" and/or "add to the standard class-specialization". > ? Relationship between: raw type, template class, erased species > ? proposal is that the erased species has exact match of members as template class I'd like to support this, but not (in the JVM) mandate it. In other words, the erased species might be the same as the raw type, and also the wild type, but that's a call I wish to leave to the language folks. This informs my treatment of the "erased" sentinel in M3. It's not deeply "baked into" any version of the template class proposals, but rather supported. A CONSTANT_DefaultType descriptor allows the translation strategy to swap out a sentinel for a better type. > ? proposal is that the template class is not instantiable Yes. See above; it's really not a class, but a "class template". This serves as a type (can be resolved against) but it's inherently abstract. *If* we need migration, then the template C[_] can nominate a specialization to use in place of C, since (after an upgrade) the class C is absent, even though old code (compiled before the upgrade) thinks C exists. To me this feels like a decision for a BSM to make. "Do I allow myself to be presented as a plain class, and if so which specialization is it?" > ? challenge is: older class files with instantiated raw type "new Foo" class file: new LFoo; ? requires instantiation > ? one proposal: re-translate the bytecode into new LFoo; If possible, I prefer to stick to keeping the bytecodes the same and, instead, flexing the resolution of the Class of the Methodref. > ? and keep a non-instantiable template class, and keep the statics in java.lang.Class as they are today > ? TODO: walk examples of raw type bytecodes and BC If we separate template names from class names, then resolving a class just routes, as above, to a request to the template to make a species. Then all the bytecodes apply to that resolved species. > ? Clarifications/explorations on relationship of template class > ? potential for multiple inheritance I don't see a general solution for MI, and so I'd prefer to define a very specific new kind of inheritance, from a "template super", as discussed above. I think we've probably already discussed all of the characteristics of such a thing, including elastic layouts and elastic v-tables. (BTW I think the special problems of virtual dispatch to generic overrides are *not* part of the phenomenon of template supers; they are a separate hard problem.) > ? Concern about the requirement for ordering of holes - must depend on an earlier one, also the requirement to point to the "last" hole in a segment Brian and I are both comfortable, at the moment, with having holes be more like arguments (with a position) and less like fields (with a name). The basic structure is that a CP segment is of this form: N: CONSTANT_Segment[J] N+1: CONSTANT_Hole[kind_1] ? more holes ? N+J: CONSTANT_Hole[kind_J] N+J+1: (some other constant) ? more constants ? N+L-1: (the last constant constant) This just floats into the CP, and then much later on there is a metadata block of this form: segment_info { parent_segment: 0 (global) segment_constant_index: N (back link to CONSTANT_Segment) highest_index: N+L-1 (gives span in CP) holes_count: J (validates the sequence of CONSTANT_Hole items) segment_hole_info[J] { ? more stuff for each of the J holes ? } } > ? does this cause problems for Lookup.defineClass with re-use of byte[] and constant pool extension mechanisms? I don't think so. > ? does this cause problems for bytecode instrumentors, redefinclasses - what if they want to add an additional hole in a segment - do they need to need to change all references to the "last" hole in a segment? Yes, this causes a problem for those people, as I suggested above. I don't want to solve this problem yet, because its complexity will obscure the basic problem. If we *do* need to solve it, I think I have a simple-enough, expressive-enough, compressive-enough way for a segment_info to describe which constants are in it; basically you add more "stuff" instead of the "highest_index" field, which gives you a compressed bursty bitmap. Then the classfile rewriter can add stuff to the end of the CP, and edit any relevant segment. No CP indexes need editing in this case. The JVM will have to maintain an extra side table, indexed by CP index, and yielding (1) the segment, and (2) a compact re-indexing of the constant within the segment. This is (drum roll please) what HotSpot is already doing with the CP cache, except in this new design each segment gets its own CP-cache-like array. As with the CP cache, such reindexing *might* show up in the internal form of the bytecodes (rewritten secretly) or it might not; I think my tilt is towards "not". But all that's more than I want to propose for the first version. > ? Could we explore having two segments in the template classfile, one root and one specializable, and each entry you could trace dependencies instead of requiring ordered segments? Two segments is not enough. But you are right that we should require appropriate referential integrity of constants. For example, a CONSTANT_Methodref must refer only to a CONSTANT_Class and CONSTANT_NameAndType that are either (1) in the same segment, (2) inherited from its parent segment or one of its ancestors, or (3) in the global segment. (Actually 3 is a special case of 2 since the global segment is an ancestor of everybody else.) Doing this is just basic sanity. It can and should be checked as soon as the segment_info table is loaded, before any class_info is inspected. As a further constraint on translation strategies, I would like to *require* that constants *not* be placed into segments where they don't need to be placed. That is, if a constant only depends on constants declared in the global segment, that's where it needs to go also. The very rare case of a condy which needs to "trigger" in each specialization, but doesn't "look at" any of its nearby holes, needs to take the CONSTANT_Segment of its desired segment as an argument, in order to be valid in that segment. So I want to constrain constant placement in both directions: If a constant depends on some constant in some segment S, it must be placed in either S or one of its children/descendants. And vice versa, if a constant depends on no constant in some segment S, then it must *not* be placed in S or any of its children/descendants. This implies that the basic segment tree structure can be inferred or recovered from the raw constant pool, kind of like stack maps can be inferred or recovered from the bytecodes. And yet it's worth placing redundantly in the classfile format, because (a) it is expensive to compute, and (b) we need a compact numbering of segments anyway (which the inference doesn't produce), and also (c) segments need extra metadata (which we don't want to stuff into the constant pool). > ? Request from vm runtime > ? in the specialized class file, we need both the name/UTF8 string AND the Constant_Class to have a place to store the passed-in resolved type parameter > ? It would be valuable to be able to share any resolved classes to achieve "same resolution" results going forward Yes, this is super important, as already noted. It has a special importance when checking *across* API points, as we do with CLCs. > ? Redefineclasses will need to have a way to indentify types resolved externally and passed in as live-types, so they are NOT re-reresolvable Yes. For each specialization (class or method, maybe fields too) there must be a live "species" object which gathers together (a) the Hole values being proposed and (b) all of the resolution state of the constants in that segment. (Oddly enough, the layout of this object is really easy to compute, given the current design of contiguous CP segments. It's? a segment? of the constant pool.) If you redefine a template local to this segment, you have to figure out how to keep the resolution state in the segment, and re-associate it with the new version of the template. I don't think there's anything really new here, except that (as noted) the appending technique will force some sort of discontinuity into segments. > ? Detail: How are Template Attributes used e.g. by Class, method, field? What does it mean to have a template attribute on a static field? Is that a way of clarifying which conditional species it belongs to? The new version of the proposal gets rid of these attributes. Instead, the *location* of each piece of metadata is hard-wired into it, as a u16 segment_index item right along side the other items. This goes for class_info, field_info, and method_info. This allows a simple and comprehensive rule for referential integrity: A xxx_info block can refer only to constants in its own (clearly marked) segment, or inherited (perhaps from the root segment) into its own segment. This happens independently of what any related metadata is doing with its own segment. Yes, it's really that pervasive; see the discussion about the 18 or so cases above. Brian and I are comfortable with not prematurely "compressing" this. (Actually a previous version *did* compress, and we decided it was, as with non-contiguous segments, undesirable complexity at least for a first cut. It's not the thing to optimize now.) It's time for some code: ``` ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 segments_count; segment_info segments[segments_count]; u2 classes_count; class_info classes[classes_count]; } segment_info { u2 parent_segment; u2 segment_constant_index; u2 highest_index; u1 holes_count; segment_hole_info holes[holes_count]; } class_info { u2 segment_index; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } field_info { u2 segment_index; u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } method_info { u2 segment_index; u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } ``` (You read that right. I'm proposing 16 bits of new "slack" on every class, field, and method info struct. We'll compress it later if we really must. And, yes, we've considered several obvious alternatives, such as reshuffling the foo_info inside of the segment_info blocks where they live, but then they need a different pointer, to their metadata container. And, it doesn't work, or help, for parts of a class to "inherit" a segment from the class itself, unless there is a way to override that "inheritance". And putting such an "override" into an Attribute would be nice, except it makes it hard to structurally check the part containing the Attribute until after the whole part is read. Having two bytes up front is far cleaner. I have an alternative design that cheaply removes the extra bytes when they are inherited, but it incrementally complicates the whole proposal, and so? not now.) > ? Template Attribute and TemplateParameter dependency relationship? The basic integrity constraint holds: If some part of a class file has a constant reference CP[N], then that part *must* be inside a chunk that is located in the segment where CP[N] is defined. That goes for xxx_info blocks and for CP constants themselves, if they mention other CP constants. > ? Later email: not clear why a BSM-based template expansion logic would not use existing class loaders, system dictionary and loader constraints See discussion above. The fit is imperfect; there are some corners to round here. There are maybe some new uses of these existing mechanisms, but we have to be sure they are appropriate to the new uses. That was a good day's work! Good luck reading it. Oh, wait, you did if you got here. Congratulations! ? John From brian.goetz at oracle.com Thu Mar 14 13:14:59 2019 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Mar 2019 09:14:59 -0400 Subject: Questions on Template Classes proposal In-Reply-To: <83D22A90-078E-4093-89BD-3DC742396FFB@oracle.com> References: <4088B3F8-8DBB-4238-8B28-224EBCC0B7BC@oracle.com> <83D22A90-078E-4093-89BD-3DC742396FFB@oracle.com> Message-ID: > > (What's an optional member of a template? Ah, that's a good > conversation. I haven't written this one up yet. I think the cleanest > way to formulate those is in terms of *optional child segments*. > I can think of many different ways to gate their optionality, > and BSM-based instantiation is probably the most general > and therefore adequate.) I have opinions about this ?. In the language, optional membership will be based on computations deriving from holes. For example, we might have a conditional method > T sum() The `T implements Monoid` is something that can be computed by a condy that evaluates down to a boolean, so I think its fair to say that optionality can be gated by a boolean constant. And given that optional members will likely go together, we can make a child segment containing all the members gated by common predicates. This is a boon for type checking / verification, since a method conditional on a predicate might well invoke another method conditional on that predicate (or a sub-predicate) or access a conditional field. By grouping into segments, then we never have to worry about a member in species Foo accessing another member that is not part of Foo. > I don't see a general solution for MI, and so I'd prefer to define a > very specific new kind of inheritance, from a "template super", > as discussed above. To put a pin in: I think we?re going to want a notion of ?static interface super? as well, which is an interface contract that we expect the class literal to adhere to. (This is how we get to, for example, operator overloading, or the conditional `T extends Monoid` example above.) Basically, using interfaces to describe a set of _class behaviors_ as well as the usual set of _instance behaviors_. From john.r.rose at oracle.com Thu Mar 14 18:21:23 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 14 Mar 2019 11:21:23 -0700 Subject: Questions on Template Classes proposal In-Reply-To: References: <4088B3F8-8DBB-4238-8B28-224EBCC0B7BC@oracle.com> <83D22A90-078E-4093-89BD-3DC742396FFB@oracle.com> Message-ID: Yes from me to all your points here. A conditional child segment doesn?t need any new holes, just there BSM based predicate you mention. This works equally well for conditional fields, methods, and nested types. Conditional supers seem to require some special pleading with DefaultType to carry the conditional type; or else pure reflective class spinning; not sure about the best way. Probably a separately templatable super_info struct would be cleanest. > On Mar 14, 2019, at 6:14 AM, Brian Goetz wrote: > >> >> (What's an optional member of a template? Ah, that's a good >> conversation. I haven't written this one up yet. I think the cleanest >> way to formulate those is in terms of *optional child segments*. >> I can think of many different ways to gate their optionality, >> and BSM-based instantiation is probably the most general >> and therefore adequate.) > > > I have opinions about this ?. > > In the language, optional membership will be based on computations deriving from holes. For example, we might have a conditional method > > > > T sum() > > The `T implements Monoid` is something that can be computed by a condy that evaluates down to a boolean, so I think its fair to say that optionality can be gated by a boolean constant. And given that optional members will likely go together, we can make a child segment containing all the members gated by common predicates. > > This is a boon for type checking / verification, since a method conditional on a predicate might well invoke another method conditional on that predicate (or a sub-predicate) or access a conditional field. By grouping into segments, then we never have to worry about a member in species Foo accessing another member that is not part of Foo. > >> I don't see a general solution for MI, and so I'd prefer to define a >> very specific new kind of inheritance, from a "template super", >> as discussed above. > > To put a pin in: I think we?re going to want a notion of ?static interface super? as well, which is an interface contract that we expect the class literal to adhere to. (This is how we get to, for example, operator overloading, or the conditional `T extends Monoid` example above.) Basically, using interfaces to describe a set of _class behaviors_ as well as the usual set of _instance behaviors_. > From john.r.rose at oracle.com Fri Mar 15 05:06:35 2019 From: john.r.rose at oracle.com (John Rose) Date: Thu, 14 Mar 2019 22:06:35 -0700 Subject: Questions on Template Classes proposal In-Reply-To: <83D22A90-078E-4093-89BD-3DC742396FFB@oracle.com> References: <4088B3F8-8DBB-4238-8B28-224EBCC0B7BC@oracle.com> <83D22A90-078E-4093-89BD-3DC742396FFB@oracle.com> Message-ID: To make the diagram easier to use, I have added a legend that explains its various notations. On Mar 13, 2019, at 8:44 PM, John Rose wrote: > > In the following explanations you may with to consult this diagram of > constant pool relations, which is derived from my current draft: > > http://cr.openjdk.java.net/~jrose/values/template-jvms-4.4.pdf > From john.r.rose at oracle.com Fri Mar 29 16:08:39 2019 From: john.r.rose at oracle.com (John Rose) Date: Fri, 29 Mar 2019 12:08:39 -0400 Subject: generic specialization design discussion Message-ID: <25220157-E4FF-45A7-B8F3-A4A963AB286E@oracle.com> This week I gave some presentations of my current thinking about specializations to people (from Oracle and IBM) gathered in Burlington. Here it is FTR. If you read it you will find lots of questions, as well as requirements and tentative answers. http://cr.openjdk.java.net/~jrose/pres/201903-TemplateDesign.pdf This is a checkpoint. I have more tentative answers on the drawing board that didn't fit into the slide deck. Stay tuned. ? John