From brian.goetz at oracle.com Fri Oct 5 14:31:52 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Oct 2018 10:31:52 -0400 Subject: Entering the next phase of Project Valhalla Message-ID: <18e17c98-37b4-4587-ad7a-bdb3462a35ab@oracle.com> We had an in-person meeting last week in Burlington MA to thrash out where we are, and where we're going next.? It was a super-productive meeting with broad attendance -- thanks to everyone who made the trip in person, or dialed in.? This was a good time to take stock, since we've been doing Valhalla for a while -- over four years.? And, like all worthwhile projects, we hadn't realized quite how much we had bitten off.? (This is good, because if we had, we'd probably have given up.) I think this marks the beginning of the Phase III of the project. Phase I focused mostly on language and libraries, trying to wrap our heads around exactly what a clean migration to value types and specialized generics would look like -- including how we'd migrate core APIs like Collections and Streams, and understanding the limitations of the VM we had, so we could envision the VM we needed.? This phase produced 3 prototypes (Models 1-3), whose areas of exploration included specialization mechanics (M1), handling of wildcards (M2) and classfile representations for specialization and erasure (M3).? At which point we realized the list of VM requirements was implausibly long and we needed to come at this from the other direction for a while. Phase II attacked the problem from the VM up, with two more rounds of prototypes -- MVT and LW1.? LW1 was a risky experiment; we hoped, but weren't sure we could get away with, sharing the L-carrier and a* bytecodes between references and values, without losing performance.? If we could do so, many of the problems we discovered in Phase I could go away.? And, all evidence seems to suggest that this was successful, and we have a much richer base to build on. So, I think this demarcates the start of Phase III -- where we have a solid enough proof-of-concept that we can largely graduate from the "risky experiments and wild theories" portion of the program (at least for the VM).? Fantastic work from everyone involved to get us here! Looking ahead, our next target is L2 -- which will capture the choices we've made so far, provide a useful testbed for doing library experiments, and set the stage for drilling into the remaining open questions between here and L10.? L10 is our target for a first preview, which should support value types and erased generics over values. Stay tuned for a round of technical writeups capturing decisions and open issues.... From brian.goetz at oracle.com Fri Oct 5 14:48:31 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Oct 2018 10:48:31 -0400 Subject: The return of Q-types (was: Entering the next phase of Project Valhalla) In-Reply-To: <18e17c98-37b4-4587-ad7a-bdb3462a35ab@oracle.com> References: <18e17c98-37b4-4587-ad7a-bdb3462a35ab@oracle.com> Message-ID: <00e41f1a-a0d9-fcb2-e0fb-b48922c987b3@oracle.com> > Stay tuned for a round of technical writeups capturing decisions and > open issues.... John has written a nice summary of the decisions regarding VM types here: ??? http://cr.openjdk.java.net/~jrose/values/q-types.html From brian.goetz at oracle.com Fri Oct 5 15:15:45 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Oct 2018 11:15:45 -0400 Subject: Values and erased generics Message-ID: <8dd31c05-249a-973c-3517-62c20dbf7038@oracle.com> Here?s a summary of the story we came up with for erased generics over values. It builds on the typing story outlined in John?s ?Q-Types in L-World? writeup. Background In MVT, there were separate carrier types (Q and L) for values and references. The Q carriers were not nullable, and an explicit conversion was required between L and Q types. This offered perfect nullity information to the JVM, but little ability to abstract over both values and references. This showed up in lots of places: * Values themselves could not implement interfaces, only their companion box type could. * Could not operate on values with |a*| instructions. * Could not store values in |Object| variables. Each of these conflicted with the desire for genericity (whether specialized or erased). In Q-world, we couldn?t have erased generics over values, because erased code could not operate on values for multiple reasons (wrong carriers, wrong bytecodes). And looking ahead to specialized generics, having separate bytecodes for references and values increased the complexity of the specialization transform. L-World started out as an experiment to validate the following hypothesis: We can, without significant performance compromises, reuse the |L| carrier and |a*| bytecodes for value types, and allow them to be proper subtypes of their interfaces (and |Object|). In LW1, we took a hybrid approach; nullability and flattenability become properties of the variable (field, array element, stack slot, local variable), rather than a property of the type itself. This means we could be tolerant of nulls in local variables and stack slots, only rejecting nulls when they hit the heap, and we then relied on the translation strategy to insert null checks to prevent introduction of nulls. This allowed value-oblivious code to act as a ?conduit? for values between value-aware code and a value-aware heap layout. One of the conclusions of the LW1 experiment was that the JIT very much wants better nullity information than this; without enhancing our ability to prove that a given use of an L-type is null-free, we cannot fully optimize calling conventions. Erased generics in LW1 One of the motivations for L-World was that using L-carriers for values would likely better interoperate with generics by providing a common carrier and by allowing the |a*| bytecodes to operate uniformly on both values and references. However, the assumption that a type variable |T| is nullable interacts poorly here; given that values are proper L-types in LW1, it seems tempting to allow users to parameterize erased generics with values: |List points = new ArrayList(); | with the compiler translating |Point| as |LPoint|. Existing erased generics would ?just work? ? mostly. However, there are several sharp edges: * There are some API points that deliberately use nulls as sentinels, such as returning |null| from |Map::get| to signal that the key is not in the map. This would then NPE in the compiler-inserted null check when we tried to assign the result of |get(k)| to a value-typed |V|. * Some generic classes may accidentally try to convert the default (null) value of an uninitialized |Object| field or |Object[]| array element to a |T|, which would again NPE when it crossed the boundary from erased generic code to value-aware code. * If value arrays are subtypes of |Object[]|, and a |V[]| is passed to code that expects an |Object[]|, attempt to store a null in that array would NPE. The return of the Q MVT had explicit L-types and Q-types; LW1 has only L-types, relying on the |ValueTypes| attribute to determine whether a given L-type describes a value or not. In LW2, we will back off slightly from this unification, so as to provide the VM with end-to-end information about the flow of values and their potential nullity; for a given value class |V|, one can denote both the non-nullable type |QV;| and the nullable type |LV;|, where |QV <: LV|. The value set of |LV| is that of |QV|, plus |null|; both share the |L| carrier. No conversion is needed from |QV| to |LV|; |checkcast| and |instanceof| perform a null check when converting from |LV| to |QV|. |Q*| fields and array elements will be flattenable; |L*| will not. (As a side benefit, the |ValueTypes| attribute is no longer needed, as descriptors fully capture their nullability constraints.) This gives language compilers some options; we can translate uses of a value type to either the |L| or |Q| variants. Of course, we don?t want to blindly translate value types as L-types, as this would sacrifice the main goal (flattenability), but we could use them where values meet erased generics. Meet the new box (not the same as the old box) Essentially, the L-value types can be thought of as the ?new boxes?, serving the interop role that primitive boxes do. (Fortunately, they are cheaper than primitive boxes; the boxing conversion is a no-op, and the unboxing conversion is a null check.) Just as the JVM wants to be able to separately denote ?non-nullable value? and ?nullable value?, so does the language. In general, we want for values to be non-nullable, but there are exceptions: * When dealing with erased generic code, since an erased type variable |T| is nullable; * When dealing with legacy code involving a value-based class that is migrated to a value type, since existing code may treat it as nullable. So, let?s say that a value class |V| gives rise to two types: |V.Val| and |V.Box|. The former translates to |QV|; the latter to |LV|. The former is non-nullable; the latter is nullable. And there exists a boxing/unboxing conversion between them, just like with |int| and |Integer| ? but in this case, the cost of the ?boxing? conversion is much lower. Erased generics over boxes Now, erased generics fall out for free: we just require clients to generify over the box type. This is no different from how we deal with primitives today ? generify over the box. ?Works like an int.? |ArrayList ints = new ArrayList<>(); ArrayList points = new ArrayList<>(); | Since |V.Box| is nullable, we have no problem with returning null from |Map::get|. Migration considerations Nullability also plays into migration concerns. A baseline goal is that migrating a value-based class to a value type, or migrating an erased generic class to specialized, should be source and binary compatible. That means, we don?t want to perturb the meaning of |Foo| in clients or subtypes when either |V| or |Foo| migrates. For existing value-based classes, such as |LocalDate|, there are plenty of existing locutions such as: |LocalDate d = null; if (d == null) { ... } ArrayList dates = ... dates.add(null); | If we want migration of |LocalDate| to a value type to be source compatible, then this constrains us to translate |LocalDate| to |LLocalDate| forever. This suggests that |LocalDate| should be an alias for |LocalDate.Box|; otherwise the meaning of existing code would change. On the other hand, for a newly written value type which was never a VBC, we want the opposite. If |Point| is not an alias for |Point.Val|, users will have to say |Point.Val| everywhere they want flattening, which is cumbersome and easy to forget. And since flattening is the whole point of value types to begin with, this seems like it would be letting the migration tail wag the dog. Taken together, this means we want some sort of contextual decision as to whether to interpret |Foo| as |Foo.Box| or |Foo.Val|. This could be based on the provenance of |Foo| (was it migrated from a VBC or not), or could be some sort of aliased import (|import Foo as Foo.Val|). The declaration-site approach seems preferable to me; it gives the author of the class the choice of which face of their class to present to the world. Classes for which migration compatibility is of primary concern (e.g., |Optional|) get compatibility at the cost of biasing towards boxing; those for which flattening is of primary concern (e.g., |Complex|) get flattening at the cost of compatibility. In this approach, we put the pain on clients of migrated classes ? they have to take an extra step to get flattening. In the long run, there will likely be more born-as-value classes than migrated-to-value classes, so this seems the right place to put the pain. Note that the |Box| syntactic convention scales nicely to type variables; we can write specialized generic code like: | T.Box box(T t) { } | Whatever syntactic convention we use (e.g., |T?|) would want to have similar behavior. (Another consideration in the choice of denotation is the number of potential type operators we may need. Our work in specialized generics suggests there may be at least a few more coming.) Primitives as values ? a sketch We would like a path to treating primitives and values uniformly, especially as we get to specialized generics; we don?t want to have deal with the 1-slot vs 2-slot distinction when we specialize, nor do we want to deal with using |iload| vs |aload|. We can extend our lightweight boxing approach to allow us to heal the primitive/value divide. For each of our primitive types, we hand-code (or generate) a value class: |value class IntWrapper { int x; } | We introduce a bidirectional conversion between |int| and |IntWrapper|; when the user goes to generify over |int|, we instead generify over |IntWrapper|, and add appropriate conversions at the boundary (like the casts we currently insert in erased generics.) We can then translate |int.Box| as |LIntWrapper;|, and we can support erased generics over the lighter value-boxes rather than the heavy legacy boxes. Unfortunately, now we have three types that perform the role of boxes: the new value wrappers like |IntWrapper|, their lightweight box type |IntWrapper.Box|, and the legacy heavy box |java.lang.Integer|. To keep our boxes straight, we could call them: * Box ? the legacy heavy box (|java.lang.Integer| and friends) * Lox ? the new lightweight value boxes (L-types of value classes) * Pox ? the primitive wrapper value classes So |X.Box| denotes the lox for values (and probably X itself for reference classes), and the lox-of-a-pox for primitives (making it total.) When we get to specialized generics, when we instantiate a generic class with a primitive type, we silently wrap them with their pox on the way in, which is a value, and we?ve reduced generics-over-primitives to generics-over-values. This is a huge complexity reducer for the specializer, as it need not deal with the fact that long and double take two slots, or with changing a* bytecodes to the corresponding primitive bytecodes. It is an open question how aggressively we can deprecate or denigrate the legacy boxes (probably not much, but hope springs eternal.) Open issues There were a few issues we left for further study; more on these to follow. *Array covariance*. There was some degree of discomfort in pushing array covariance into |aaload| now. When we have specialized generics, we?ll be able to handle this through interfaces; it seems a shame to permanently weigh down intrinsic array access with potential megamorphism. We?re going to try to avoid plunking for array covariance now, and see how painful that is. *Equality.* There was some discomfort with the user-model consequences of disallowing |==| on values. It is likely that we?d translate |val==| as a substitutibility test; if we?re going to do that, it?s not obvious whether we shouldn?t just lump this on |acmp|. *Locking.* The same generics-reuse arguments as we made for nullability support also could be applied to locking on loxes. No one really likes the idea of supporting locking here, but just as surprise NPEs were sharp edge, surprise IMSEs might be as well. *Construction.* We have not yet outlined either the language-level construction constraints or the translation of constructors to bytecode. ? From forax at univ-mlv.fr Sat Oct 6 09:55:01 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 6 Oct 2018 11:55:01 +0200 (CEST) Subject: var, static factory method and value type constructor Message-ID: <1156361484.584055.1538819701673.JavaMail.zimbra@u-pem.fr> I've observed an interesting side effect of the introduction of var that troubles my students, the introduction of var make the generics method call with type arguments needed to be mentioned more frequent. writing List list = List.of(); foo(list); // foo takes a List of String works out of the box but var list = List.of(); foo(list); will not compile because list is inferred as List Explicitly specifying the type arguments var list = List.of(); foo(list); works but this syntax is far from obvious for my students. This lead me to think again about introducing a syntax for supporting static factory method (yes, i know, it seems remote, but it's connected). My students has no issue with specifying the type argument when calling a constructor var list = new ArrayList(); so why not resurrect the proposal to have a syntax to be able to declare static factory method at language level and being able to call it with new ? I propose to re-use 'new' when defining a static factory method interface Fun { static Fun new() { return ... } } and teach the compiler that new Fun() should be translated to invokestatic Fun.new() in this case. It is obvioulsly related to value types and the way the compiler currently desugars constructor of value type. The desugaring of the constructor call is exactly the same as this proposal, what we are adding here is the fact that a developer can ask for such translation by adding a static factory instead of being only a magic translation from the compiler. R?mi From brian.goetz at oracle.com Mon Oct 8 14:43:58 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 8 Oct 2018 10:43:58 -0400 Subject: var, static factory method and value type constructor In-Reply-To: <1156361484.584055.1538819701673.JavaMail.zimbra@u-pem.fr> References: <1156361484.584055.1538819701673.JavaMail.zimbra@u-pem.fr> Message-ID: <990a700c-8c1a-3b86-630f-1673b22cc57b@oracle.com> The fact that factory methods are pure convention, rather than something understood by the language and tool chain (e.g., should be segregated from other static methods by Javadoc), has caused challenges for a number of features, and so some more formal notion of "factory method" is a reasonable thing to put on the table.? This comes up, for example, in records -- one of the unfortunate compromises (without a linguistic notion of factories) is users get forced to choose between using records and encapsulating the constructor. The syntax you propose has the problem (though some might say it is a strength) that it gives up one of the best things about factory methods: naming.? Being able to have separate names is useful both to class authors (makes it more clear what the member does) and clients (code is more self-descriptive.) All that said, does this help your students in this case beyond having a more obvious and uniform place to put the type witnesses -- new Foo() rather than Foo.of() -- or is that the whole story? On 10/6/2018 5:55 AM, Remi Forax wrote: > I've observed an interesting side effect of the introduction of var that troubles my students, > the introduction of var make the generics method call with type arguments needed to be mentioned more frequent. > > writing > List list = List.of(); > foo(list); // foo takes a List of String > works out of the box but > var list = List.of(); > foo(list); > will not compile because list is inferred as List > > Explicitly specifying the type arguments > var list = List.of(); > foo(list); > works but this syntax is far from obvious for my students. > > This lead me to think again about introducing a syntax for supporting static factory method (yes, i know, it seems remote, but it's connected). > My students has no issue with specifying the type argument when calling a constructor > var list = new ArrayList(); > so > why not resurrect the proposal to have a syntax to be able to declare static factory method at language level and being able to call it with new ? > > I propose to re-use 'new' when defining a static factory method > interface Fun { > static Fun new() { > return ... > } > } > > and teach the compiler that new Fun() should be translated to invokestatic Fun.new() in this case. > > It is obvioulsly related to value types and the way the compiler currently desugars constructor of value type. The desugaring of the constructor call is exactly the same as this proposal, > what we are adding here is the fact that a developer can ask for such translation by adding a static factory instead of being only a magic translation from the compiler. > > R?mi > From forax at univ-mlv.fr Tue Oct 9 14:17:27 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 9 Oct 2018 16:17:27 +0200 (CEST) Subject: var, static factory method and value type constructor In-Reply-To: <990a700c-8c1a-3b86-630f-1673b22cc57b@oracle.com> References: <1156361484.584055.1538819701673.JavaMail.zimbra@u-pem.fr> <990a700c-8c1a-3b86-630f-1673b22cc57b@oracle.com> Message-ID: <1196883846.338516.1539094647495.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "valhalla-spec-experts" > Envoy?: Lundi 8 Octobre 2018 16:43:58 > Objet: Re: var, static factory method and value type constructor > The fact that factory methods are pure convention, rather than something > understood by the language and tool chain (e.g., should be segregated > from other static methods by Javadoc), has caused challenges for a > number of features, and so some more formal notion of "factory method" > is a reasonable thing to put on the table.? This comes up, for example, > in records -- one of the unfortunate compromises (without a linguistic > notion of factories) is users get forced to choose between using records > and encapsulating the constructor. > > The syntax you propose has the problem (though some might say it is a > strength) that it gives up one of the best things about factory methods: > naming.? Being able to have separate names is useful both to class > authors (makes it more clear what the member does) and clients (code is > more self-descriptive.) as usual with names, sometimes you want names, sometimes you do not want them, we have pojo class vs anonymous class, we have method references vs lambdas, we have a plain old type for local variables vs var, we have a plain old static call vs a static import, in all these examples it's about giving a name or not giving a name to something, likewise, for factory method, sometimes you want a name, sometimes you don't want a name. With a static method, there is a name, with a constructor, there is no name. Here, i want to focus on the no name part, we have in the JDK, factory methods that have a not very meaningful name, Path.of/List.of, MethodType.methodType, etc. those "constructors" are static methods for an implementation reason and not because they have a meaningful name (they don't). For those cases, i want to re-use "new" as the factory method name (because the name is not meaningful) and i want users to be able to use 'new' so it makes Java easier to learn by removing things you do not have to learn yet to be able to use JDK classes. > > All that said, does this help your students in this case beyond having a > more obvious and uniform place to put the type witnesses -- new Foo() > rather than Foo.of() -- or is that the whole story ? Having a simple way to use the basic classes of the JDK will help my students when they start to learn the langage. The other side of this discussion is the support of constructors inside a value type, currently javac transforms the constructors as static factories with a weird name. Syntactic sugar like this has a cost for our users because at runtime, you see the compiler scaffolding, when debugging, when using reflection, in the stacktrace, etc. Interpreting the scaffolding is easy if there is a way to express the construction in Java, having a way for user to create a factory method that acts as a constructor will help. R?mi > > On 10/6/2018 5:55 AM, Remi Forax wrote: >> I've observed an interesting side effect of the introduction of var that >> troubles my students, >> the introduction of var make the generics method call with type arguments needed >> to be mentioned more frequent. >> >> writing >> List list = List.of(); >> foo(list); // foo takes a List of String >> works out of the box but >> var list = List.of(); >> foo(list); >> will not compile because list is inferred as List >> >> Explicitly specifying the type arguments >> var list = List.of(); >> foo(list); >> works but this syntax is far from obvious for my students. >> >> This lead me to think again about introducing a syntax for supporting static >> factory method (yes, i know, it seems remote, but it's connected). >> My students has no issue with specifying the type argument when calling a >> constructor >> var list = new ArrayList(); >> so >> why not resurrect the proposal to have a syntax to be able to declare static >> factory method at language level and being able to call it with new ? >> >> I propose to re-use 'new' when defining a static factory method >> interface Fun { >> static Fun new() { >> return ... >> } >> } >> >> and teach the compiler that new Fun() should be translated to invokestatic >> Fun.new() in this case. >> >> It is obvioulsly related to value types and the way the compiler currently >> desugars constructor of value type. The desugaring of the constructor call is >> exactly the same as this proposal, >> what we are adding here is the fact that a developer can ask for such >> translation by adding a static factory instead of being only a magic >> translation from the compiler. >> >> R?mi From karen.kinnear at oracle.com Tue Oct 9 22:41:31 2018 From: karen.kinnear at oracle.com (Karen Kinnear) Date: Tue, 9 Oct 2018 18:41:31 -0400 Subject: Valhalla EG meeting Wed Oct 10, 2018 Message-ID: <22378F85-0A66-4C79-9BED-3717EFC0D7B2@oracle.com> Meeting reminder for October 10th. Background documents for next phase of Value Types: Entering the next phase of Project Valhalla - Brian - http://mail.openjdk.java.net/pipermail/valhalla-spec-experts/2018-October/000760.html Values and erased generics - Brian - http://mail.openjdk.java.net/pipermail/valhalla-spec-experts/2018-October/000762.html Q-Types in L-World 10 - John - http://cr.openjdk.java.net/~jrose/values/q-types.html thanks, Karen From frederic.parain at oracle.com Wed Oct 10 13:51:55 2018 From: frederic.parain at oracle.com (Frederic Parain) Date: Wed, 10 Oct 2018 09:51:55 -0400 Subject: JVMS draft proposal for LW2 Message-ID: Greetings, Here?s a proposal of a JVMS draft to move from the LW1/container model to LW2, based on the discussions we had during the Valhalla offsite in Burlington. http://cr.openjdk.java.net/~fparain/L-world/LW2-JVMS-draft-20181009.pdf This is a very rough draft, trying to introduce the notions of nullable and null-free value types. This change is more difficult to specify than expected. If the notion of reference is already defined in the JVMS, the distinction between reference and type is not always very clean, which makes harder to distinct the type of the reference from the type of the instance pointed by the reference. Several new concepts have been defined in this draft: nullable type, null-free-type, type descriptor, fundamental type. Their application across the JVMS s not consistent yet, but it is likely to be future work to clean up the JVMS once the right set of new concepts have been defined. Some concepts from LW1 have been removed: the ValueTypes attribute and the value types consistency checks. Q-signatures have been added back, but are slightly different in meaning than their first version from vbytecodes/MVT. This is a document aiming at clarifying the path toward LW2, and enable engineering teams to start prototyping. It is still incomplete and many questions are still unanswered. So, it?s more a support for discussion and exploration rather than a proposal for a full LW2 specification. Thank you, Fred From brian.goetz at oracle.com Thu Oct 11 14:14:29 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 11 Oct 2018 10:14:29 -0400 Subject: Value types, encapsulation, and uninitialized values Message-ID: Our story is "Codes like a class, works like an int". A key part of this is that value types support the same lifecycle as objects, and the same ability to hide their internals. Except, the current story falls down here, because authors must content with the special all-zero default value, because, unlike classes, we cannot guarantee that instances are the result of a constructor. For some classes (e.g., Complex, Point, etc), this forced-on-you default is just fine, but for others (e.g., wrappers for native resources), this is not unlike the regrettable situation with serialization, where class implementations may confront instances that could not have resulted from a constructor. Classes guard against this through the magic of null; an instance method will never have to contend with a null receiver, because by the time we transfer control to the method, we'd already have gotten an NPE. Values do not have this protection. While there are many things for which we can say "users will learn", I do not think this is one of them; if a class has a constructor, it will be assumed that the receiver in a method invocation will be on an instance that has resulted from construction. I do not think we can expose the programming model as-is; it claims to be like classes, but in this aspect is more like structs. So, some values (but not all) will want some sort of protection against uninitialized values. One approach here would be to try to emulate null, by, say, injecting checks for the default value prior to dereferences. Another would be to take the route C# did, and allow users to specify a no-arg constructor, which would customize the default value. (Since both are opt-ins, we can educate users about the costs of selecting these tools, and users can get the benefits of flatness and density even if these have additional runtime costs.) The latter route is less rich, but probably workable. Both eliminate the (likely perennial) surprise over uninitialized values for zero-sensitive classes. From forax at univ-mlv.fr Thu Oct 11 18:05:28 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 11 Oct 2018 20:05:28 +0200 (CEST) Subject: Value types, encapsulation, and uninitialized values In-Reply-To: References: Message-ID: <298534247.84574.1539281128722.JavaMail.zimbra@u-pem.fr> Being able use the default constructor to initialize the default value has a circular issue currently because a constructor is actually transformed to a default + with + with etc, so to call the default constructor you need to have acces to the default value. The circle can be broken if the default factory corresponding to the default constructor takes the initial default value as parameter, so the VM can pass a all zeroes value as parameter and record the resulting value returned. I wonder if it's not better at least for the VM to require that the classfile of a value type should always have a default static factory. In the language, we can have a special construct to explain that the all zeroes values should be used. By example class Complex { Complex() default; ... } And there is another question, if we have a user defined default is what if there is an exception when the default static factory is called, does it means that the bytecode default and the creation of an arry should throw an Error (like a linkage error) ? R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: "valhalla-spec-experts" > Envoy?: Jeudi 11 Octobre 2018 16:14:29 > Objet: Value types, encapsulation, and uninitialized values > Our story is "Codes like a class, works like an int". A key part of > this is that value types support the same lifecycle as objects, and the > same ability to hide their internals. > > Except, the current story falls down here, because authors must content > with the special all-zero default value, because, unlike classes, we > cannot guarantee that instances are the result of a constructor. For > some classes (e.g., Complex, Point, etc), this forced-on-you default is > just fine, but for others (e.g., wrappers for native resources), this is > not unlike the regrettable situation with serialization, where class > implementations may confront instances that could not have resulted from > a constructor. > > Classes guard against this through the magic of null; an instance method > will never have to contend with a null receiver, because by the time we > transfer control to the method, we'd already have gotten an NPE. Values > do not have this protection. While there are many things for which we > can say "users will learn", I do not think this is one of them; if a > class has a constructor, it will be assumed that the receiver in a > method invocation will be on an instance that has resulted from > construction. I do not think we can expose the programming model as-is; > it claims to be like classes, but in this aspect is more like structs. > > So, some values (but not all) will want some sort of protection against > uninitialized values. One approach here would be to try to emulate > null, by, say, injecting checks for the default value prior to > dereferences. Another would be to take the route C# did, and allow > users to specify a no-arg constructor, which would customize the default > value. (Since both are opt-ins, we can educate users about the costs of > selecting these tools, and users can get the benefits of flatness and > density even if these have additional runtime costs.) The latter route > is less rich, but probably workable. Both eliminate the (likely > perennial) surprise over uninitialized values for zero-sensitive classes. From brian.goetz at oracle.com Wed Oct 17 19:38:44 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 17 Oct 2018 15:38:44 -0400 Subject: [LW100] Specialized generics -- translation and binary compatibility issues Message-ID: <70e10244-9da2-602e-dd6a-314acc71520f@oracle.com> Number 2 of 100 in a series of ?What we learned in Phase I of Project Valhalla.? This one focuses on the challenges of evolving a class to be any-generic, while interacting with existing erased code. No solutions here, just recaps of problems and challenges. Let?s imagine a class today: |interface Boxy { T get(); void set(T t); } class Foo implements Boxy { public T t; public T[] tArray; public Foo(T t) { set(t); } public static Foo of(T t) { return new Foo(t); } T get() { return t; } void set(T t) { this.t = t; this.tArray = (T[]) new Object[1] { t }; } } | and client code |Foo fs = new Foo<>("boo"); println(fs.t); println(fs.tArray); println(fs.get()); Foo wc = fs; if (wc instanceof Foo) { ... } | When we compile this code, we?ll encounter |LFoo;| or |Constant_class[Foo]| or just plain |Foo| in the following contexts: * Foo extends Bar * instanceof/checkcast Foo * new Foo * anewarray Foo[] * getfield Foo.t:Object * invokevirtual Foo.get():Object * Method descriptors of |Foo::of| We translate raw |Foo|, |Foo|, and |Foo| all the same way today ? |LFoo|. Tentative simplification: reference instantiations are always erased The specialization transform takes a template class and a set of type parameters and produces a specialized class. This can cause member (and supertype) signatures to change; for example, if we have |T get() | which erases to |Object get() | when we specialize with T=int, we?ll have |int get() | In theory, there?s nothing to stop us from specializing Listwith T=String. However, in the earlier exploration, we settled on the tentative simplification of always erasing reference instantiations, and only specializing value instantiations. This is a tradeoff; we?re still throwing away potentially useful type information (erasure haters will be disappointed), in exchange for much greater sharing, and avoiding some compatibility issues (existing generic code is rife with tricks like ?casting through wildcards? to coerce a |Foo| to |Foo|, which only works as long as we erase; dirty tricks like this are often necessary as there are some things that are hard to express in the generic type system, even though the programmer knows them.) Ignoring multiple type parameters for the moment, when |Foo| becomes specializable, our model is that it will have an /erased/ species ? call it |Foo|. (If you ask it what its type parameters are, it will say ?erased?. That is, we reify the fact that it is erased?) While migrating from erased to specialized generics requires source changes and recompilation at the generic class declaration, it should not require any changes or recompilation for clients. That means that legacy client classfiles that talk about |Foo| must be considered to be talking about |Foo|. (Hierarchies can be specialized from the top down, so it is OK to specialize |Bar| before |Foo|, but not the other way around.) While the generic specialization machinery will have no problem with specializing to L-types, I think its a simplification we should hold on to, that we treat all L type parameters as ?erased? for purposes of specialization. Additional simplification: let?s not worry about primitives In Burlington, we concluded that as long as there?s a Pox class for each primitive, we can convert primitives to/from poxes through source compiler transforms, and not worry about specializing over primitives. Instead, when the user wants to specialize List, we instead specialize for int?s pox. Except for those pesky arrays ? more on that later. Assumption: wild means wild On the other hand, one of the non-simplifying assumptions we want to make is that a wildcard type ? |Foo| ? should describe any instantiation of |Foo|, even when the wildcard-using code doesn?t know about specialization. (Same with raw usages of |Foo|.) For example, if the user has written a method: |takeFoo(Foo anyFoo) { anyFoo.m(); } | in legacy (erased) code, we should be able to call |takeFoo()| with both erased and specialized instances of |Foo|. As we?ll see, this complicates member access, and really complicates arrays. We will find utterances like |invokevirtual Foo.get()Object getfield Foo.m:Object | in legacy code; we want these to work against any specialization of |Foo|. In the case where the instance is erased, things obviously have a decent chance of lining up properly, as the erased members will not have been specialized away. If our receiver is a specialized |Foo|, it gets harder, as the member signatures will have changed due to specialization. Starting in Model 2, we handled this with bridge methods; for each specialized method, we also had an erased bridge. This is possible because there?s an easy coercion from |QPoint| to |LObject|. (There are other ways to get there besides bridges.) Where this completely runs out of gas is in field access; there?s no such thing as a ?bridge field?. So legacy code that does |getfield Foo.t:Object| will fall over at link time, since the type of field |t| in a specialized |Foo| might not be |Object|. Another place this falls short is when a signature has |T[]| in it. Even with bridge methods, without either array covariance (this is what I meant when I said it might come back) or a willingness to copy, a legacy client that invokes a method that returns a |T[]| will invoke it expecting an |Object[]|, but without array covariance, a |Point.Val[]| is not an |Object[]|. (Note that relatively few methods actually expose |T[]| parameters, so its possible there are other dodges here.) Wildcards One of the central challenges of pushing specialization into the VM is how we?re going to handle wildcards. Given a generic class |Foo|, the wildcard type |Foo| is a supertype of any instantiation |Foo| of |Foo|. The wildcard type also erases to |LFoo|. In Model 2, we modeled wildcards as interfaces, with lots and lots of bridges, but this still fell short in a number of ways: no support for non-public methods or for fields, and we had to deal with fields by hoisting them into virtual bridges on the interface. Note that the wildcard subtyping also matters to the verifier, in addition to handling bytecodes; the verifier must know that any specialization of |Foo| is a subtype of the wildcard |LFoo|. But what does |LFoo| mean? Careful readers will notice that we?ve been playing fast and loose with the meaning of |Foo|; sometimes it means the class, sometimes the wildcard, and sometimes the erased species. The best intuition we?ve been able to come up with is: * There are /classes/ and /crasses/. * A crass describes a single runtime type; it has a layout, methods, constructors, etc. * A (template) class describes a family of runtime types. * A (template) class is like an abstract type; it has members and subtypes, but can?t be instantiated directly. * All the crasses derived from a class are subtypes of the class. * For purposes of instantiation, we interpret |new Foo| as creating an instance of the erased species, and a similar game with || methods. Model 3 classfile extensions In Model 3, we extended the constant pool with some new entries: *TypeVar[n, erasure].* This is a use of a type variable, identified by its index /n/. (There was a table-of-contents attribute listing all the type variables declared in a generic class or method, including those declared in enclosing generic classes or methods.) Since the erasure of a type variable is not merely a property of the type variable, but in fact a property of how it is used, each use of a type variable carries around its own erasure. For field whose type is |T|, the |NameAndType| points not to |Object|, but to |TypeVar[0, Object]|. When specializing a type variable to |erased|, any uses of that type variable are replaced with the erasure in the |TypeVar| entry. *MethodType[D,T?].* This is largely a syntactic mechanism, allowing us to represent method descriptors with holes (but also had the benefit of compressing the constant pool somewhat.) The parameter |D| was a method type descriptor, except that in addition to the existing types, one could specify |#| to indicate a hole; the |T...| parameters are CP indexes to other types (which could be UTF8 strings, or |TypeVar|, or the other type CP entries listed below.) For example, a method |int size(T t) | would have a signature |#1 = TypeVar[0, Object] #2 = MethodType[(#)I, #1] | When specializing a |MethodType|, its parameters are recursively specialized, and then the resulting strings concatenated. *ParamType[C,T?].* This represents a parameterized type, where |C| is a class name, and |T...| are the type parameters. So |List| would be represented as |ParamType[List,I]|, and |List| would be represented as |ParamType[List,TypeVar[0,e]]|. When specializing a |ParamType|, its parameters are recursively specialized, and then the resulting instantiation is computed. *ArrayType[T,rank].* This represents an array of given rank. The type parameters of a |ParamType|, |ArrayType|, or |MethodType| can themselves be a |TypeVar|, |ParamType|, or |ArrayType|, as well as a UTF8. We found that as a template language, these types allowed exactly the sort of expressiveness needed, and specialized efficiently down to concrete descriptors (though in the M3 prototype, we had concrete descriptors of the form |List$0=I| to describe |List|, obviously we don?t want that here.) But these designs captured all the complexity we needed (especially that of erasure), and allowed a mechanical translation int Java 8 classfiles. ? From brian.goetz at oracle.com Wed Oct 17 20:29:11 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 17 Oct 2018 16:29:11 -0400 Subject: [LW100] Specialized generics -- migration In-Reply-To: <70e10244-9da2-602e-dd6a-314acc71520f@oracle.com> References: <70e10244-9da2-602e-dd6a-314acc71520f@oracle.com> Message-ID: <96e5bca7-1c9d-41a1-2ac5-fa0fd56d085c@oracle.com> And, number 3? some of the stories we cooked up for migration. These are just stories at this point, aimed at mitigating method and field access against specialized instances from legacy (erased) code. Bridge attributes Rather than generating bridge methods as bytecode, which is both brittle and verbose, instead we could express bridges as attributes on the bridgee. For example, using the Model 3 notation, we?d translate: |T identity(T t) { return t; } | as |[Bridge[(LObject)LObject)]] TypeVar[T//LObject] identity(TypeVar[T//LObject]) { aload_1 areturn } | When we specialize for T=QPoint, we?d get: |[Bridge[(LObject)LObject)]] QPoint identity(QPoint) { aload_1 areturn } | This means that our class has a method |identity(QPoint)QPoint|, but it also ?responds to? invocations of |identity(Object)Object|. Linkage sketch: when we go to resolve the erased method (with signature E), we won?t find it, so we?ll search again looking for methods of that name that have a bridge attribute that matches what we?re looking for. If we find it (call it M), what we effectively want to call is |MethodHandle[M].asType(E)|. The adaptations from |QPoint| to/from |LObject| are known to |asType()|, so this is easily computable. We install this as the linkage target. (Yes, I know linkage doesn?t work this way now.) So, what we?ve done is turned eager bridges into lazy ones, and reified the bridge descriptors with the bridgee, and we compute bridges at the invocation site, when they are invoked. This reduces the classfile footprint for the declaration, and puts the linkage cost on legacy invokers. Bonus ? loop-free bridges It also potentially does something else, which we?ve wanted for a long time ? loop-resistent bridges. The brittleness of existing bridges can lead to ?bridge loops? under separate compilation, because we are prematurely selecting an invocation mode and burning it into the bridge. By pushing bridge computation to the invocation site, we can use the invocation mode present at the invocation site ? no loops! This is essentially a multi-way win: * Kill legacy bridges o Smaller class files o Reifies relationship between bridge and bridgee * Loop-free bridges * Support legacy erased invocation of specialized methods Field bridges Just as we have a problem with invoking erased methods on specialized instances from legacy code, we have the same problem with field access. We can run essentially the same play for ?field bridges? as well: |[Bridge[Object]] [TypeVar[T//Object]] f | When we encounter a |getfield Foo.f:Object | after the initial resolution failure, we again look for fields with that name and a bridge attribute of the desired type, and if we find it, we replace the field operation with the equivalent of |M.asType(E) | where M is the field-access method handle, and E is the erased descriptor we want to bridge it to. This is messier as the linkage state for fields is ?thinner? than that for methods, but conceptually the same play. ? From brian.goetz at oracle.com Wed Oct 17 21:03:15 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 17 Oct 2018 17:03:15 -0400 Subject: [LW10] Value equality Message-ID: If we expect values to ?work like an int?, then |==| comparisons on value types should work like they do on primitives: |value class Point { ... } ... client code ... /* mutable */ Point location; void setLocation(Point p) { if (p != location) { fireExpensivePropertyChangeEvent(location, p); this.location = p; } } | Users will reasonably expect this idiom to work, just as they would for |int| properties. There have been at least four suggested treatments of |val==|: * Illegal; compilation error * Always false; user must follow up with |.equals()| test * Substitutibility test ? are these the same value * Delegate to |.equals()| (call this ?deep substitutibility?) I think the first is unreasonable and should be discarded, based on the above illustration of failure to ?work like an int?; I think the second is the same. We expect generic code to use the LIFE idiom: |(x == y) || x.equals(y) | The LIFE idiom arose because |ref==| is fast, and so we can optimize away the potentially expensive |.equals()| call when called with identical objects. But, if the |val==| test becomes more expensive, then what started out as an optimization, might lead to duplication of nontrivial work, because the |.equals()| implementation may well also have an |==| test. But, we don?t want to let the optimization tail wag the dog here. Separately, we have been reluctant to push value substitutibility into |acmp|, for fear of perturbing the performance model there. So, let?s posit a |vcmp|, a sibling to |{ildf}cmp|, and translate |val==| to that, and let |acmp| continue to return false when either operand is a value. Now, for pure value code, like the above, we translate |val==| to |vcmp|, and we work like an int. And for generic code, the LIFE idiom: |x == y || x.equals(y) | (when |x| and |y| are of type |T|) translates to an |acmp| backed up by an |invokevirtual|, and when |T| is specialized to a value, the |acmp| is fast and always false, and when specialized to a reference, is fast and computes |ref==|. I believe this gives everyone what they want: * value equality is intuitive and appropriately fast * Existing generics continue using LIFE, and get fast (enough) translation both for reference and value instantiations * Generic code that forgets |.equals()| is equally wrong for reference and value instantiations * Generic code that goes straight to |.equals()| works correctly for reference and value instantiation ? From karen.kinnear at oracle.com Sat Oct 20 05:15:42 2018 From: karen.kinnear at oracle.com (Karen Kinnear) Date: Fri, 19 Oct 2018 22:15:42 -0700 Subject: No meeting Wednesday October 24 Message-ID: <244F069B-DD52-4043-929F-679B39D1A8C7@oracle.com> Good luck with your Oracle Code presentations! Thanks, Karen From brian.goetz at oracle.com Thu Oct 25 19:04:30 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 25 Oct 2018 15:04:30 -0400 Subject: Array covariance Message-ID: <20d561ae-927f-c37b-71d8-8e12944ee8dd@oracle.com> Since the Burlington meeting, Simms has been prodding me to offer an alternative to supporting array covariance for value arrays. John and I kicked around the following idea yesterday. At this point, I?d call it a half-baked sketch, but so far it seems promising. Why covariance at all? First, let?s talk about why arrays in Java are covariant in the first place. And the reason is, unless you have either generics or covariant arrays, you can?t write methods that operate on all (or almost all) array types, like Arrays.sort(). With covariance, we can write |void sort(Object[], Comparator) { ? } | and we can sort any (reference) array. I wasn?t in the room, but I suspect that array covariance was something that was grudgingly accepted because it was the only way to write code that worked on arbitrary arrays. Now, imagine if we had (invariant) generics in Java 1.0. Then we would have written | void sort(T[], Comparator) { ? } | This makes more sense from a user-model perspective, but what do we erase |T[]| to? Unless we also had covariant arrays, we?d not be able to erase |T[]| to |Object[]|, but there may be a smaller hammer we can use than covariant arrays for this. Covariance isn?t bad in itself, but the limitations of Java 1.0 arrays belie the problem ? layout polymorphism. Covariance among arrays of Object subtypes is fine because they all have the same layout, but primitive arrays were left out from the beginning. We could extend covariance to value/primitive arrays ? we?ve prove its possible ? but there?s a cost, which is largely: we take what is a clean and predictable performance model for array access, and pollute it with the possibility of megamorphic access sites. What else is missing about arrays? Just as primitives are outside of the generic type system (we can?t say |ArrayList|, yet), so are arrays. There?s no way to express ?any array? in the type system; methods like |System.arraycopy| are forced to use |Object| as a proxy, and then dynamically ensure that what?s passed is actually an array: |void arraycopy(Object sourceArray, int sourceStart, Object destArray, int destStart, int length) | It would be much nicer if we could say |AnyArray| instead of |Object| here; it would be saying what we mean, and would allow us to move some type checking from runtime to compilation time. (It also might give us a path to not having to write nine versions of |Arrays.fill()|.) Similarly, there are lots of operations on arrays that are painful, ad-hoc, and require VM intrinsification to perform well, like |Arrays.copyOf()|. (Plus, there?s the usual litany of array problems ? no final elements, no volatile elements, fixed layout, etc.) Arrays are primitive The reason for having arrays in the language and VM is the same reason we have primitives ? they are a compromise of OO principles to gain a practical performance model. There?s few good things about arrays, but one of them is they have simple, transparent cost models in time and space. Polluting |Object[]| with layout-polymorphism compromises the cost model. The analogy we should be thinking of is: array is-to collection as primitive is-to object That is, arrays are the ground types that our higher-typed programs bottom out in (or specialize to), not necessarily the types we want in people?s faces. What would arrays look like in Valhalla? Obviously, we?d like for value arrays to be supported at least as well as other arrays. Right now, without array covariance, we?re back in the same situation the Java 1.0 designers were ? that if you want to write |Arrays.sort()|, you need covariance. Currently we have no way to extend the various nine-way method sets, even if we were willing to write a tenth method. But, in Valhalla, we don?t want 9- or 10-way method sets ? we want a single method. We want for |Arrays.fill()|, for example, to be something like: | void fill(T[] array, T element) | and not have to have eight siblings to clean up the primitive cases. But, even this syntax is paying homage to the irregularity of array-as-primitive. I think what we?d really want is something like |interface NativeArray { int length(); T get(int index); void set(int index, T val); Class componentType(); } | and then we?d retrofit |Foo[]| to implement |NativeArray|. (We?d likely make NA a restricted interface, that is only implemented by native arrays, for now.) Then, our single fill method could be (in LW100): | void fill(NativeArray array, T element) | (Impatient readers will want to point out that we could generify over the index parameter as well. One impossible problem at a time.) And similarly, arraycopy can be: |void arraycopy(NativeArray src, int srcStart, NativeArray dest, int destStart, len) | Bold claim: In such a world, /there is no need for arrays to be covariant/. If you want to operate on more than one kind of array, use generics. If you want covariance, say |NativeArray|. Getting there in steps Suppose we start in steps; let?s start with an erased interface, suitable for later generification: |interface NativeArray { int length(); Object get(int index); void set(int index, Object val); Class componentType(); } | We make NA a restricted interface (reject any classes that explicitly try to implement it at class load time, as if it were sealed), and inject it as a super type into all existing array types (including primitives and values.) The language overloads the |[]| operators to bind to the |get| and |set| methods and continues to simulate the synthetic |length| field. Now, someone who wants to write just one version of |Arrays.fill| can do so: |void fill(NativeArray a, Object element) { for (int i=0; i { int length(); T get(int index); void set(int index, T val); Class componentType(); } | and treated |Foo[]| (for reference Foo) as implementing |NA|, and for values and primitives, treat |V[]| as implementing |NA|. This makes our erased generics story work for: | fill(NativeArray a, T element) | but lays down some potholes for migration to specialized generics, as when we get to Valhalla, we?d like for |V[]| to implement |Array|, not |Array|. So, this is a hole to fill, but it seems a promising direction. The result is that we have monomorphic type profiles at |aaload| sites, and potentially polymorphic profiles at |invokeinterface NativeArray.get| sites ? which is more justifiable than polluting aaload profiles. And when we get to full specialization, many of these polymorphic profiles may flatten out (as we can safely speculate on |T[]| at specialized |NativeArray| sites.) Overloading The |NA| story works nicely with existing overload rules too. If we currently have: |fill(int[], int) fill(Object[], Object) | we can add in an overload | fill(NativeArray, T) | Existing source and binary code that wants one of the original methods will continue to get it, as |Object[]| is more specific than |NativeArray| and so will be selected in preference to the NA version. So the new method would only be selected by value instantiations, since none of the existing versions are applicable. (Over time, we might want to remove some of the hand-specialized versions, and turn them into bridges for the specialized generic version, reducing us from 9 methods to one method with 8 bridges.) Translation The real stumbling block is: what do we do with |T[]| in generic APIs. Currently, we erase these to array-of-erasure-of-T, so usually |Object[]|. This is one of the things pushing us towards covariance. But, can we change this translation? What if we erased |T[]| to NativeArray? Obviously, we?d have a binary compatibility issue, that we?d have to fill in with bridges. And these bridges might conflict with actual overloads that take |NativeArray|: |void m(T[] array) { ? } void m(NativeArray array) { ? } | but, maybe since there?s no existing code that uses NA, that?s OK. This triggers all the issues discussed recently regarding migration ? there?s a 2x2 matrix of { source, binary } x { client, subclass } compatibility. When used in argument position, passing an |Object[]| where a NA is expected is fine, since |Object[]| is a subtype of NA. The problem would come when |T[]| is used in return position (or as a field.) But, these are fairly rare, and implementations (at least in the JDK) are well behaved: |// Stream T[] toArray(IntFunction generator) // Arrays T[] copyOf(T[] array) | So erasing to NA, with bridges, and performing the same generic type checks we do (perhaps with some extra casts) may be a viable path for managing the source compatibility aspects. OK, so if we erase |T[]| to NativeArray, what does that buy us? It /almost/ means we can write our |Arrays.*| methods with an extra overload: | void fill(NativeArray, T) | But, we don?t have generics over values yet, hrm. So this doesn?t quite get us to our Arrays.* methods, dang. OK, let?s put this idea on hold for a bit. Zag before you zig We could write an erased version: |void fill(NativeArray, Object) | which will catch all the flat value arrays, but we lose the compile-time type checking that the element type is correct. But, we may be able to pull a move here. What if we were to allow any-vars (in L10) /as long as the resulting signature was invariant in T/? Then we could say (with erased generics!) | void fill(NativeArray, T.box element) | because this would erase to |(NativeArray, Object)V|.In other words, a small down payment on specialized generics (where it supports compile-time type checking, but wouldn?t actually affect any bytecode generation), might allow us to write the generic-over-arrays code we want. So, the compiler would type-check that the passed value is an instance of (or convertible to) the box type for which the passed array is an array, and then erase the array to NativeArray and erase the element to its box. This would even work for |int[]| arrays. Then, in L100, we migrate NativeArray to be a true specializable interface, and we migrate the |fill| method to | void fill(NativeArray, T element) | Summary There are lots of holes to fill in, but: * We need a suitable translation target for |T[]| in specialized generics regardless. One path is full covariance (including for primitive arrays); another is to migrate the relatively few methods that truck in |T[]| to a different translation. * We are going to need some tools for signature migration no matter what. I?ve outlined two under separate cover; one is minty-bridges (where a method/field says ?I am willing to respond to this alternate descriptor?), and the other is a technique for migrating signatures so that if subclasses override the old signature, we have a way to restore order. We are going to need these to get to L100 regardless. * We can borrow some bits from the future to provide a programming model for value arrays that is not tied to covariance, and that heals the rift between separate array types, which currently are completely unrelated. It also moves megamorphic call sites to where they belong ? |invokeinterface|. ? From forax at univ-mlv.fr Thu Oct 25 19:46:58 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 25 Oct 2018 21:46:58 +0200 (CEST) Subject: Array covariance In-Reply-To: <20d561ae-927f-c37b-71d8-8e12944ee8dd@oracle.com> References: <20d561ae-927f-c37b-71d8-8e12944ee8dd@oracle.com> Message-ID: <275832896.688990.1540496818667.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "valhalla-spec-experts" > Envoy?: Jeudi 25 Octobre 2018 21:04:30 > Objet: Array covariance > Since the Burlington meeting, Simms has been prodding me to offer an alternative > to supporting array covariance for value arrays. John and I kicked around the > following idea yesterday. At this point, I?d call it a half-baked sketch, but > so far it seems promising. Why covariance at all? > First, let?s talk about why arrays in Java are covariant in the first place. And > the reason is, unless you have either generics or covariant arrays, you can?t > write methods that operate on all (or almost all) array types, like > Arrays.sort(). With covariance, we can write > void sort(Object[], Comparator) { ? } > and we can sort any (reference) array. I wasn?t in the room, but I suspect that > array covariance was something that was grudgingly accepted because it was the > only way to write code that worked on arbitrary arrays. > Now, imagine if we had (invariant) generics in Java 1.0. Then we would have > written > void sort(T[], Comparator) { ? } > This makes more sense from a user-model perspective, but what do we erase T[] > to? Unless we also had covariant arrays, we?d not be able to erase T[] to > Object[] , but there may be a smaller hammer we can use than covariant arrays > for this. > Covariance isn?t bad in itself, but the limitations of Java 1.0 arrays belie the > problem ? layout polymorphism. Covariance among arrays of Object subtypes is > fine because they all have the same layout, but primitive arrays were left out > from the beginning. We could extend covariance to value/primitive arrays ? > we?ve prove its possible ? but there?s a cost, which is largely: we take what > is a clean and predictable performance model for array access, and pollute it > with the possibility of megamorphic access sites. It's not megamorphic because you have a common code to get the component size for any kind of arrays, value type arrays or object type arrays, arraycopy already does that. The issue with a megamorphic method call is that you're loosing inlining. Here, the access is slower but you are not going to go dark like with a method call. > What else is missing about arrays? > Just as primitives are outside of the generic type system (we can?t say > ArrayList , yet), so are arrays. There?s no way to express ?any array? in > the type system; methods like System.arraycopy are forced to use Object as a > proxy, and then dynamically ensure that what?s passed is actually an array: > void arraycopy(Object sourceArray, int sourceStart, Object destArray, int > destStart, int length) > It would be much nicer if we could say AnyArray instead of Object here; it would > be saying what we mean, and would allow us to move some type checking from > runtime to compilation time. (It also might give us a path to not having to > write nine versions of Arrays.fill() .) > Similarly, there are lots of operations on arrays that are painful, ad-hoc, and > require VM intrinsification to perform well, like Arrays.copyOf() . > (Plus, there?s the usual litany of array problems ? no final elements, no > volatile elements, fixed layout, etc.) Arrays are primitive > The reason for having arrays in the language and VM is the same reason we have > primitives ? they are a compromise of OO principles to gain a practical > performance model. There?s few good things about arrays, but one of them is > they have simple, transparent cost models in time and space. Polluting Object[] > with layout-polymorphism compromises the cost model. Note that you have exactly the same issue with fields and inheritance, if you have class A { T field; } class B extends A { String s; } B.s and B.s may be not at the same offset in memory, so some getfield opcodes are layout-polymorphic too. > The analogy we should be thinking of is: >> array is-to collection as primitive is-to object > That is, arrays are the ground types that our higher-typed programs bottom out > in (or specialize to), not necessarily the types we want in people?s faces. > What would arrays look like in Valhalla? > Obviously, we?d like for value arrays to be supported at least as well as other > arrays. Right now, without array covariance, we?re back in the same situation > the Java 1.0 designers were ? that if you want to write Arrays.sort() , you > need covariance. Currently we have no way to extend the various nine-way method > sets, even if we were willing to write a tenth method. > But, in Valhalla, we don?t want 9- or 10-way method sets ? we want a single > method. We want for Arrays.fill() , for example, to be something like: > void fill(T[] array, T element) > and not have to have eight siblings to clean up the primitive cases. > But, even this syntax is paying homage to the irregularity of > array-as-primitive. I think what we?d really want is something like > interface NativeArray { > int length(); > T get(int index); > void set(int index, T val); > Class componentType(); > } > and then we?d retrofit Foo[] to implement NativeArray . (We?d likely make > NA a restricted interface, that is only implemented by native arrays, for now.) > Then, our single fill method could be (in LW100): > void fill(NativeArray array, T element) > (Impatient readers will want to point out that we could generify over the index > parameter as well. One impossible problem at a time.) > And similarly, arraycopy can be: > void arraycopy(NativeArray src, int srcStart, NativeArray dest, int destStart, > len) > Bold claim: In such a world, there is no need for arrays to be covariant . If > you want to operate on more than one kind of array, use generics. If you want > covariance, say NativeArray . Getting there in steps > Suppose we start in steps; let?s start with an erased interface, suitable for > later generification: > interface NativeArray { > int length(); > Object get(int index); > void set(int index, Object val); > Class componentType(); > } > We make NA a restricted interface (reject any classes that explicitly try to > implement it at class load time, as if it were sealed), and inject it as a > super type into all existing array types (including primitives and values.) The > language overloads the [] operators to bind to the get and set methods and > continues to simulate the synthetic length field. Now, someone who wants to > write just one version of Arrays.fill can do so: > void fill(NativeArray a, Object element) { > for (int i=0; i a[i] = element; > } > Note that this works for all arrays now ? values, primitives, object, with some > boxing. OK, progress. > One obvious wart in the above is that we?ve taken a step back in terms of type > safety. We?d like for NA to be generic in its element type, but we can?t yet > use values or primitives as type parameters. Suppose we said instead: > interface NativeArray { > int length(); > T get(int index); > void set(int index, T val); > Class componentType(); > } > and treated Foo[] (for reference Foo) as implementing NA , and for values > and primitives, treat V[] as implementing NA . This makes our erased > generics story work for: > fill(NativeArray a, T element) > but lays down some potholes for migration to specialized generics, as when we > get to Valhalla, we?d like for V[] to implement Array , not Array . > So, this is a hole to fill, but it seems a promising direction. > The result is that we have monomorphic type profiles at aaload sites, and > potentially polymorphic profiles at invokeinterface NativeArray.get sites ? > which is more justifiable than polluting aaload profiles. And when we get to > full specialization, many of these polymorphic profiles may flatten out (as we > can safely speculate on T[] at specialized NativeArray sites.) Overloading > The NA story works nicely with existing overload rules too. If we currently > have: > fill(int[], int) > fill(Object[], Object) > we can add in an overload > fill(NativeArray, T) > Existing source and binary code that wants one of the original methods will > continue to get it, as Object[] is more specific than NativeArray Object> and so will be selected in preference to the NA version. So the new > method would only be selected by value instantiations, since none of the > existing versions are applicable. > (Over time, we might want to remove some of the hand-specialized versions, and > turn them into bridges for the specialized generic version, reducing us from 9 > methods to one method with 8 bridges.) or add fill() as an instance method on NativeArray with the right signature and spread the news that people should use the instance methods instead of the static method of java.util.Arrays to get full performance. Only the method toString()/equals() and hashCode() have a compatibility issues on an array. > Translation > The real stumbling block is: what do we do with T[] in generic APIs. Currently, > we erase these to array-of-erasure-of-T, so usually Object[] . This is one of > the things pushing us towards covariance. But, can we change this translation? > What if we erased T[] to NativeArray? > Obviously, we?d have a binary compatibility issue, that we?d have to fill in > with bridges. And these bridges might conflict with actual overloads that take > NativeArray : > void m(T[] array) { ? } > void m(NativeArray array) { ? } > but, maybe since there?s no existing code that uses NA, that?s OK. > This triggers all the issues discussed recently regarding migration ? there?s a > 2x2 matrix of { source, binary } x { client, subclass } compatibility. > When used in argument position, passing an Object[] where a NA is expected is > fine, since Object[] is a subtype of NA. The problem would come when T[] is > used in return position (or as a field.) But, these are fairly rare, and > implementations (at least in the JDK) are well behaved: > // Stream > T[] toArray(IntFunction generator) > // Arrays > T[] copyOf(T[] array) > So erasing to NA, with bridges, and performing the same generic type checks we > do (perhaps with some extra casts) may be a viable path for managing the source > compatibility aspects. Class.getTypeParameters() being the black sheep ! > OK, so if we erase T[] to NativeArray, what does that buy us? It almost means we > can write our Arrays.* methods with an extra overload: > void fill(NativeArray, T) > But, we don?t have generics over values yet, hrm. So this doesn?t quite get us > to our Arrays.* methods, dang. [...] > Summary > There are lots of holes to fill in, but: > * We need a suitable translation target for T[] in specialized generics > regardless. One path is full covariance (including for primitive arrays); > another is to migrate the relatively few methods that truck in T[] to a > different translation. > * We are going to need some tools for signature migration no matter what. I?ve > outlined two under separate cover; one is minty-bridges (where a method/field > says ?I am willing to respond to this alternate descriptor?), and the other is > a technique for migrating signatures so that if subclasses override the old > signature, we have a way to restore order. We are going to need these to get to > L100 regardless. > * We can borrow some bits from the future to provide a programming model for > value arrays that is not tied to covariance, and that heals the rift between > separate array types, which currently are completely unrelated. It also moves > megamorphic call sites to where they belong ? invokeinterface . > ? transforming a polymorphic-layout issue to a megamorphic call issue seems like a regression to me, not a win. R?mi From kevinb at google.com Fri Oct 26 19:03:27 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Fri, 26 Oct 2018 12:03:27 -0700 Subject: Value types, encapsulation, and uninitialized values In-Reply-To: References: Message-ID: Sorry for slow response. It is true that there exist value types which have a "zero", which I would define as some *natural* choice of default value that will be *blindingly obvious* to any consumer. Of course, Point/Complex are the same two examples that had leapt to my mind. These are the best case, the only value types that will *really* (almost) join the same category as `int` and `double`.* Unfortunately I suspect these are very rare in practice. Most code is business logic, and most business aggregate "values" probably don't have safe defaults. Worse, I suspect that what few there are are probably outnumbered by types like Instant that have a *false* zero. If users implement these as value types in a way that elevates treats their false zero like a real zero, I think that would be sad. These are only suspicions, which I could do more work to substantiate based on our codebase. (That work just has to go onto the old to-do list.) Most value types, I think, don't have a zero, and I believe it will be quite damaging to treat them as if they do. If Java doesn't either provide or simulate nullability, then users will be left jumping through a lot of hoops to simulate nullability themselves (`implements IsValid`??). This looks like a very bad outcome to me, and this has been one of my major concerns about the whole feature for the last couple years. (It's a relief for me to see you sharing the same concerns to some degree.) These zero-less types we could divide into two groups: those that at least have some combination of field values that is *invalid*, and those that don't, and are unwilling to add a field now in order to achieve that. The latter have no possible way to be nullable, and the former have a possible way to be nullable that is just plain *weird* (user provides a sample value that is not allowed to actually exist, so that that can be used as the internal representation of what is surfaced in the language as null.). As weird as that is, at least it would provide actual nullability instead of awkward attempts to simulate what nullability already is, so I think it actually does a better job of confining the weirdness? (*I'm glossing over the fact that the existence of these zeroes still isn't always a great thing; the inability to distinguish between intentional and accidental zeroes is already a (very minor) source of bugs today. It's unclear what purpose is really served by an array of Durations being initialized to all zero-length durations.) On Thu, Oct 11, 2018 at 7:14 AM Brian Goetz wrote: So, some values (but not all) will want some sort of protection against > uninitialized values. One approach here would be to try to emulate > null, by, say, injecting checks for the default value prior to > dereferences. In this case, it's more than just a default value, it's that the user provides any example of a *bogus* value, and the best way I can think of for Java to handle this would be to surface it as *null* itself. Otherwise you're inventing a second kind of nullability. Granted though, this is weird. But it has a lot of good properties. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From brian.goetz at oracle.com Sat Oct 27 14:38:14 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 27 Oct 2018 10:38:14 -0400 Subject: Value types, encapsulation, and uninitialized values In-Reply-To: References: Message-ID: <0e62995e-9fe8-a978-f340-59fb1a849b58@oracle.com> > It is true that there exist value types which have a "zero", which I > would define as some *natural* choice of default value that will be > *blindingly obvious* to any consumer. Of course, Point/Complex are the > same two examples that had leapt to my mind. These are the best case, > the only value types that will /really/?(almost) join the same > category as `int` and `double`.* I think the truth is somewhere in the middle.? Consider the common use cases for values.? Most common would be: ?- Numerics ?- Tuples / product types ?- Mediating wrappers (e.g., Optional) ?- Structured domain entities -- timestamps, distances, etc Of these categories, the first two fall into the zeroable bucket; the latter two are in the "usually not" bucket, but even then sometimes the zero may well be a reasonable default (e.g., Optional).? So let's just say that both categories are populated, and neither should be ignored as negligible. To be clear, there _are_ going to be costs for types that declare themselves nullable.? They'll get the benefit of "flat and dense", but will lose out on some other benefits, such as scalarization. Overall I think this is a fair tradeoff; users can opt into this treatment, which signals a willingness to pay the costs.? (One of the reasons we haven't addressed this issue until now, is that we were less confident in our ability to estimate the costs.) > Unfortunately I suspect these are very rare in practice. Most code is > business logic Well, most of your code, anyway.? The scientific programming / ML folks might have a different opinion! > These zero-less types we could divide into two groups: those that at > least have some combination of field values that is /invalid/, and > those that don't, and are unwilling to add a field now in order to > achieve that. The latter have no possible way to be nullable, and the > former have a possible way to be nullable that is just plain > /weird/?(user provides a sample value that is not allowed to actually > exist, so that that can be used as the internal representation of what > is surfaced in the language as null.). As weird as that is, at least > it would provide actual nullability instead of awkward attempts to > simulate what nullability already is, so I think it actually does a > better job of confining the weirdness? Agree that leaning on the existing behavior of nullability is the winning move here, rather than creating a new notion of null-like for values ('vull', NullValueException, etc.) The game here is to give the user enough control to say "this is what a null is", without punishing all other users for that flexibility.? (Its tempting to say "just use the no-arg constructor to dispense an unused bit pattern", as C# does, but this has serious costs throughout the rest of the system, including on value types that don't take advantage of this.)? So we're focusing on how we can optimize _detection_ of uninitialized values.? So, some interesting buckets: ?- Types like `int`, where all bit patterns are valid.? These guys have a stark choice; add an extra boolean (which could come with a severe density price tag), or forego uninitialized-value detection. ?- Types that have naturally unused bits, like LocalDateTime (96 bits) or better, FourteenBitShort.? The normal alignment requirements will hand us these unused bits. ?- Types that have fields that are never zero/null in an properly initialized class.? I suspect this is a common case, and this could be used to considerably reduce the cost of the "are you uninitialized" check. In the first case, even checking all the bits for zero isn't enough; you have to inject a new field.? But then you are reduced to the case that is shared with the other buckets -- that there's always a no-bigger-than-a-word bitfield whose zeroes can be used as a proxy for uninitialized.? This is starting to look attractive, as we can bring the cost of a null-check down to a single-word mask-and-test. There's a certain degree of VM gymnastics we would need to do to move the costs to where they don't punish everyone else.? The functional requirements would be: ?- field access / method invocation when receiver is an uninitialized instance of "nullable" values throws an NPE; ?- "boxing" a "null" Foo.val to Foo.box yields true null; ?- "unboxing" a null Foo.box yields a "null" Foo.val (rather than NPE) I can think of at least three or four ways to get there, with different cost models. From dl at cs.oswego.edu Sun Oct 28 12:11:49 2018 From: dl at cs.oswego.edu (Doug Lea) Date: Sun, 28 Oct 2018 08:11:49 -0400 Subject: Value types, encapsulation, and uninitialized values In-Reply-To: References: Message-ID: <2704787b-68c3-d872-7cf7-22993d079b8d@cs.oswego.edu> Sorry for even slower response... On 10/11/18 10:14 AM, Brian Goetz wrote: > > Except, the current story falls down here, because authors must content > with the special all-zero default value, because, unlike classes, we > cannot guarantee that instances are the result of a constructor. Can we enumerate the cases where this is encoutered? The main one is: If a value class does not have a no-arg constructor, why not disallow array construction (dynamically if necessary), but also supply special methods such as createAndFillArray(int size, T proto) and a few others to still allow safe array construction when it applies. In most cases where not-yet-present is a common case, one would think that people would choose to make T an Object type or use Optional rather than using a pure value type. -Doug From brian.goetz at oracle.com Sun Oct 28 19:53:36 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 28 Oct 2018 15:53:36 -0400 Subject: Value types, encapsulation, and uninitialized values In-Reply-To: <2704787b-68c3-d872-7cf7-22993d079b8d@cs.oswego.edu> References: <2704787b-68c3-d872-7cf7-22993d079b8d@cs.oswego.edu> Message-ID: There?s a matrix of possibilities as to what to do here. On one axis, we have user model issues ? what does the user see when they have an uninitialized value? I can imagine at least four possibilities: - Zeroes. This is simple but sharp-edged; any value could be zeroes, and both clients and value class implementors have to deal with this forced-on-you value, no matter how ill-suited it may be to the domain. - User-provided. Here, the no-arg constructor is consulted (if present, wave hands about what happens when it is not a pure function). Values are not nullable, but every value is the result of running a constructor, so users are not surprised by seeing a value that was invisibly manufactured. But, every value class that cares has to still implement some sort of ?null object pattern? for itself. Which may or may not be a burden (C#?s experience suggests it?s not too bad.) - Nullable. Here, after suitable opt-in, null is made to appear to be a member of the value set for the class, and this is the default value. Dereferencing a null results in a NPE. - Vullable. This is like nullable, but somehow specific to values. I think everyone agrees that ?zeroes? is too sharp-edged to inflict on the user base. Vullable asks the user to learn a new like-but-not-quite-the-same-as null concept. So this is probably less desirable than nullable. Any of them have to be opt-ins, because they all have costs we don?t want to inflict on all values. So the two reasonable rows are ?user-provided? and ?nullable?. On the other axis, we have implementation strategies, which we?ve not discussed yet in detail. Suffice it to say there are quite a few ways we could implement either, varying in their performance cost, safety guarantees, and intrusion. At one end of this spectrum is to say that the JVM doesn?t know anything about this at all, and just let the static compiler try and arrange the illusion. (For example, if a class has fields of this kind of value type, and that field isn?t DA at exit from all constructors, the compiler inserts extra initialization code, etc etc.) At the other end of the spectrum, we can push awareness of nullable value types into getfield, invoke*, etc. And in between, there are at least three or four ways to get to each of the credible user models. I think what Doug is saying is: ( user-provided , language-managed ) is the sweet spot here; it keeps the VM model simple, and all the cost of opting into special treatment is borne by classes that want the special treatment ? and other languages get to make their own choices about how to use value types. It provides no real safety guarantees ? races may still allow values to be observed in their zero state, and other languages could manufacture values that bypass the checks ? but it provides enough relief from the sharp edges. > On Oct 28, 2018, at 8:11 AM, Doug Lea
wrote: > > > Sorry for even slower response... > > On 10/11/18 10:14 AM, Brian Goetz wrote: >> >> Except, the current story falls down here, because authors must content >> with the special all-zero default value, because, unlike classes, we >> cannot guarantee that instances are the result of a constructor. > > Can we enumerate the cases where this is encoutered? The main one is: > > If a value class does not have a no-arg constructor, why not disallow > array construction (dynamically if necessary), but also supply special > methods such as createAndFillArray(int size, T proto) and a few others > to still allow safe array construction when it applies. > > In most cases where not-yet-present is a common case, one would think > that people would choose to make T an Object type or use Optional > rather than using a pure value type. > > -Doug > From dl at cs.oswego.edu Sun Oct 28 21:08:24 2018 From: dl at cs.oswego.edu (Doug Lea) Date: Sun, 28 Oct 2018 17:08:24 -0400 Subject: Value types, encapsulation, and uninitialized values In-Reply-To: References: <2704787b-68c3-d872-7cf7-22993d079b8d@cs.oswego.edu> Message-ID: <7d88e8c8-4d1a-d723-9831-b408decdecd3@cs.oswego.edu> On 10/28/18 3:53 PM, Brian Goetz wrote: > There?s a matrix of possibilities as to what to do here. > > On one axis, we have user model issues ? what does the user see when > they have an uninitialized value? I can imagine at least four > possibilities: > > - Zeroes. This is simple but sharp-edged; any value could be zeroes, > and both clients and value class implementors have to deal with this > forced-on-you value, no matter how ill-suited it may be to the > domain. Aside: Even if it not the only mode supported, the all-zeroes case should be simple and fast --numerics etc usages tend to create large numbers of them, especially in arrays, that should be able to use big blocks of zero-filled memory. As always, such concerns shouldn't overwhelm considerations, but we still do not want to accidentally outlaw high-performance implementations. > - User-provided. Here, the no-arg constructor is consulted (if > present, wave hands about what happens when it is not a pure > function). Values are not nullable, but every value is the result of > running a constructor, so users are not surprised by seeing a value > that was invisibly manufactured. But, every value class that cares > has to still implement some sort of ?null object pattern? for itself. > Which may or may not be a burden (C#?s experience suggests it?s not > too bad.) As I was surmising in last post, I think the main case of "cares" is arrays, for which there are OK solutions. I don't think it is strictly necessary, but adopting another C++ism, copy-constructors (with special rules) might address any remaining cases? > > - Nullable. Here, after suitable opt-in, null is made to appear to > be a member of the value set for the class, and this is the default > value. Dereferencing a null results in a NPE. I'm still having trouble seeing anything wrong with telling people to continue to use Objects and/or Optionals when not-(yet)-present is a common usage context. Also note that VMs will continue to occasionally be able to flatten such instances as if values, and few new opportunities for doing so will be possible if Values were nullable. > - Vullable. This is like nullable, but somehow specific to values. Another aside: for a concurrency-friendly variant, see papers about "LVars", for example: https://dl.acm.org/citation.cfm?doid=2535838.2535842 > On the other axis, we have implementation strategies, which we?ve not > discussed yet in detail. Suffice it to say there are quite a few > ways we could implement either, varying in their performance cost, > safety guarantees, and intrusion. At one end of this spectrum is to > say that the JVM doesn?t know anything about this at all, and just > let the static compiler try and arrange the illusion. (For example, > if a class has fields of this kind of value type, and that field > isn?t DA at exit from all constructors, the compiler inserts extra > initialization code, etc etc.) At the other end of the spectrum, we > can push awareness of nullable value types into getfield, invoke*, > etc. And in between, there are at least three or four ways to get to > each of the credible user models. > > I think what Doug is saying is: ( user-provided , language-managed ) > is the sweet spot here; it keeps the VM model simple, and all the > cost of opting into special treatment is borne by classes that want > the special treatment ? and other languages get to make their own > choices about how to use value types. It provides no real safety > guarantees ? races may still allow values to be observed in their > zero state, and other languages could manufacture values that bypass > the checks ? but it provides enough relief from the sharp edges. Yes. Thanks for helping to clarify! -Doug > > > >> On Oct 28, 2018, at 8:11 AM, Doug Lea
wrote: >> >> >> Sorry for even slower response... >> >> On 10/11/18 10:14 AM, Brian Goetz wrote: >>> >>> Except, the current story falls down here, because authors must >>> content with the special all-zero default value, because, unlike >>> classes, we cannot guarantee that instances are the result of a >>> constructor. >> >> Can we enumerate the cases where this is encoutered? The main one >> is: >> >> If a value class does not have a no-arg constructor, why not >> disallow array construction (dynamically if necessary), but also >> supply special methods such as createAndFillArray(int size, T >> proto) and a few others to still allow safe array construction when >> it applies. >> >> In most cases where not-yet-present is a common case, one would >> think that people would choose to make T an Object type or use >> Optional rather than using a pure value type. >> >> -Doug >> > >