From cay.horstmann at gmail.com Mon Mar 4 15:35:17 2024 From: cay.horstmann at gmail.com (Cay Horstmann) Date: Mon, 4 Mar 2024 16:35:17 +0100 Subject: Compiler bugs Message-ID: Hi, I built the Valhalla image from https://github.com/openjdk/valhalla and get errors with very basic programs: import java.lang.management.*; value record Point(int x, int y) {} public class Problem1 { public static void main(String[] args) { int NPOINTS = 100_000; System.out.println(new Point(3, 4)); } } $ javac Problem1.java $ java Problem1 Exception in thread "main" java.lang.IncompatibleClassChangeError: Value type Point has an identity type as supertype value class Point { int x; int y; Point(int x, int y) { this.x = x; this.y = y; } } public class Problem2 { public static void main(String[] args) { int NPOINTS = 100_000; Point[] ps = new Point[NPOINTS]; for (int i = 0; i < ps.length; i++) { ps[i] = new Point(i, i); } } } # SIGSEGV (0xb) at pc=0x00007f27b82c9570, pid=111935, tid=111936 # # JRE version: OpenJDK Runtime Environment (22.0) (build 22-internal-adhoc.cay.valhalla) # Java VM: OpenJDK 64-Bit Server VM (22-internal-adhoc.cay.valhalla, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64) # Problematic frame: # J 6 c2 Point.(II)V (15 bytes) @ 0x00007f27b82c9570 [0x00007f27b82c94a0+0x00000000000000d0] # Are you interested in getting such reports, or is the implementation known to be unstable right now? Cheers, Cay -- Cay S. Horstmann | http://horstmann.com | mailto:cay at horstmann.com From forax at univ-mlv.fr Mon Mar 4 15:45:19 2024 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 4 Mar 2024 16:45:19 +0100 (CET) Subject: Compiler bugs In-Reply-To: References: Message-ID: <527481977.20764208.1709567119441.JavaMail.zimbra@univ-eiffel.fr> ----- Original Message ----- > From: "cay horstmann" > To: "Valhalla Expert Group Observers" > Sent: Monday, March 4, 2024 4:35:17 PM > Subject: Compiler bugs > Hi, Hello, > I built the Valhalla image from https://github.com/openjdk/valhalla and get > errors with very basic programs: > > import java.lang.management.*; > > value record Point(int x, int y) {} > > public class Problem1 { > public static void main(String[] args) { > int NPOINTS = 100_000; > System.out.println(new Point(3, 4)); > } > } > > $ javac Problem1.java > $ java Problem1 > Exception in thread "main" java.lang.IncompatibleClassChangeError: Value type > Point has an identity type as supertype > > value class Point { > int x; > int y; > Point(int x, int y) { this.x = x; this.y = y; } > } > > public class Problem2 { > public static void main(String[] args) { > int NPOINTS = 100_000; > Point[] ps = new Point[NPOINTS]; > for (int i = 0; i < ps.length; i++) { > ps[i] = new Point(i, i); > } > } > } > > # SIGSEGV (0xb) at pc=0x00007f27b82c9570, pid=111935, tid=111936 > # > # JRE version: OpenJDK Runtime Environment (22.0) (build > 22-internal-adhoc.cay.valhalla) > # Java VM: OpenJDK 64-Bit Server VM (22-internal-adhoc.cay.valhalla, mixed mode, > sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64) > # Problematic frame: > # J 6 c2 Point.(II)V (15 bytes) @ 0x00007f27b82c9570 > [0x00007f27b82c94a0+0x00000000000000d0] > # > > Are you interested in getting such reports, or is the implementation known to be > unstable right now? The implementation is unstable right now https://mail.openjdk.org/pipermail/valhalla-dev/2024-February/012042.html Also, wrong list, it should be valhalla-dev given that it's an implementation issue. > > Cheers, > > Cay > regards, R?mi > > -- > > Cay S. Horstmann | http://horstmann.com | mailto:cay at horstmann.com From kevinb9n at gmail.com Mon Mar 4 18:56:36 2024 From: kevinb9n at gmail.com (Kevin Bourrillion) Date: Mon, 4 Mar 2024 10:56:36 -0800 Subject: Migrating library classes to value classes Message-ID: Hey everyone, Most language changes affect only implementation code, but a few like record classes and now value classes are API-affecting: they permit new API shapes to be expressed, and thereby new ways for a client class to depend on a library class. These features are very carefully made backward-compatible, but there's a sort of second-level backward compatibility as well: can a library owner fully exploit the feature in a way that is *itself* backward-compatible for its clients? Looking at things this way brings in a whole host of new issues (since libraries often get stuck on old versions and often get run on a wide range of Java versions newer than the one they built and tested on, among other reasons). Record classes seem to come out well in this regard. From what I can tell, there should be many "record-like" classes today that can smoothly become records that all clients will still compile against and work with. Some `getFoo()` methods might have to be preserved as pass-throughs to `foo()` and maybe some details like that, but it looks pretty clean to me (?). But migrating to a value class is rescinding functionality and so inherently *not* backward-compatible. So, libraries can only do it via a multi-step process, over time. This thread is happening because I think this project probably wants to support that process, but I'm not clear yet on how it would do so. If we did nothing else, we'd be counting on something like this JSpecify feature request to happen (please skim that, it helps explain what I'm even talking about in this thread). If we feel fine about hoping for the best on that front, then you can skip the rest of this message (I'd shift my argument to petitioning Oracle to support that effort!). That is, the remainder is motivated by the assumption that Java itself wants to actively support these migrations. Anyway: it looks very compelling to me to put that `@ValueClass` annotation, such as described, into Java itself, and have javac support it in the very same way it's going to support value class validation. (Even, of course, applying it to Integer and friends.) I'd even ask whether we could take it a step further. Once Java introduces the `value` keyword, there is *still* a lot of value (ugh) in having the annotation, and having it mean *exactly* the same thing, with all the same effects. This has a feeling of heresy to it, but it does something pretty powerful I think: it allows library owners to write *forward*-compatible code, that does the right thing whether its clients are using Java N-1 or Java N. Which makes the event of its users upgrading to Java N ... a little closer to a *non-*event. I suspect this of being reasonable. Since that Java N-1 client won't be able to use `non-atomic` or `!` or `?` or whatever we're calling the kind of constructor that makes the class a "never-null" type, I think the Java N-1 VM is within its rights to do almost nothing about the `value` keyword. (Perhaps prevent synchronization, and let's stick a pin in a discussion about equality semantics for the moment.) Which is good because it wouldn't know *how* to do those things. In fact, the earlier a version of Java we could backport this annotation and its javac support to, the better, AFAICT. With each version earlier we could push it, that much more code gets to become safely forward-compatible today. This is a much longer discussion but let me cut this message off here as a conversation-starter. Please start poking holes! -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Mon Mar 4 22:26:08 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Mon, 4 Mar 2024 22:26:08 +0000 Subject: Migrating library classes to value classes In-Reply-To: References: Message-ID: <280583A5-2442-49EE-80F1-04080978D02D@oracle.com> > On Mar 4, 2024, at 10:56?AM, Kevin Bourrillion wrote: > > But migrating to a value class is rescinding functionality and so inherently *not* backward-compatible. So, libraries can only do it via a multi-step process, over time. This thread is happening because I think this project probably wants to support that process, but I'm not clear yet on how it would do so. To help define the problem: assuming you already have a class that has the shape of a value class (final fields, no internal identity assumptions, final or abstract class, compatible superclass), adding 'value' is a binary- and (nearly*) source-compatible change. Then there's behavioral compatibility; the JEP helpfully enumerates the potential issues: >> ? The == operator may treat two instances as the same, where previously they were considered different >> ? Attempts to synchronize on an instance will fail, either at compile time or run time >> ? The results of toString, equals, and hashCode, if they haven't been overridden, may be different (preferably, the existing class already overrides equals and hashCode in a way that eliminates the dependency on identity) >> ? Assumptions about unique ownership of an instance may be violated (for example, an identical instance may be created at two different program points) >> ? Performance will generally improve, but may have different characteristics that are surprising (*The compile-time error regarding synchronization is, in fact, a source incompatibility. We think it's helpful to catch the error earlier, but it will make some programs fail to compile.) In the JDK, we are very concerned about compatibility, so we defined "value-based class" to require private (or non-private deprecated) constructors and construction via identity-ambiguous factories. This provides a specified basis for most of the behavioral changes that come with 'value', except for the fail-fast behavior of synchronization. So I guess I'd divide the issues into two categories: 1) Things that can be controlled by the class declaration There are the basic class structure things (fields, sub- and superclasses). These are pretty self-evident?if you've got a candidate class, you probably already meet these requirements. In anticipation of 'value' coming, maybe you'd like to add some 'final' modifiers in the right place for things that were not explicitly final before. Surely if you're prepared to abandon identity, you've already overridden equals/hashCode to support a notion of equality that isn't identity-based. 'toString' probably, too, although I think you can assume your users won't/shouldn't depend on the contents of the random number appearing in the default 'toString' output. If you're also worried about how people will use '==', you can match the JDK and provide factories that don't commit to identity details. You can even link to the "value based class" definition in the Java SE docs, and specify that your class follows these rules. Would it be useful to have some tooling that checks all of these things for you? Eh, maybe. I'm not sure there's that much to check?most of it pretty naturally flows from the fact that you're declaring a class that fits in the value-candidate bucket; and making a deliberate choice to avoid identity guarantees via factory-based construction and some extra javadoc is no harder than making a deliberate choice to add a '@ValueBased' annotation. 2) Corner-case use site failures The main purpose of JEP 390 was to more strongly discourage synchronization on types like Integer, because that code will break when Integer becomes a value class. I think that's already a corner case?most people do not write code like this. But java.lang.Integer gets used by everyone, and so an occasionally usage is bound to show up. s/Integer/SomeLibraryClass/ and I think we're talking about orders of magnitude fewer use cases. Still possible, of course, but I'm pretty skeptical in the likelihood of all of the stars aligning with any frequency: - User code that is targeted to an older VM right now, but will want to target a newer VM later - A user synchronizing on SomeLibraryClass - A usage that can be detected with static tooling - A user willing to use the tooling - A version of the tooling new enough to implement this check You can do the same exercise with WeakReference. Maybe when we're done there will be one or two other identity-related things like this that simply stop working. But they're just so rare, it's hard to imagine tooling dedicated to catching them (and static tools won't catch everything anyway). --- When we were working out the details of JEP 390, we considered making the @ValueBased annotation public/standard, and decided against it, because we wanted to avoid introducing layers of new concepts for programmers. "How is a value-based class different from a value class?" is not something we want showing up in Java tutorials. Instead?and I realize this is totally theoretical for me and very practical for you, so I appreciate any hard-earned wisdom you can provide?I'm not that concerned about saying version N of a library migrates some classes to be true value classes, and that should be just fine for most of their users. If somebody has a special issue with synchronization or ==, they should stick with library version N-1 until they can fix it. > In fact, the earlier a version of Java we could backport this annotation and its javac support to, the better, AFAICT. With each version earlier we could push it, that much more code gets to become safely forward-compatible today. Our normal approach to javac would be to put new warnings in the latest version only. Best we could do is, say, javac 23. And since we expect support for value classes to follow soon after, most of the benefit would be to people who write, e.g., 17- or 21-targeted code, but compile it using the latest JDK. From forax at univ-mlv.fr Mon Mar 4 22:44:22 2024 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 4 Mar 2024 23:44:22 +0100 (CET) Subject: Migrating library classes to value classes In-Reply-To: References: Message-ID: <118608900.21236271.1709592262602.JavaMail.zimbra@univ-eiffel.fr> > From: "Kevin Bourrillion" > To: valhalla-spec-experts at openjdk.org > Sent: Monday, March 4, 2024 7:56:36 PM > Subject: Migrating library classes to value classes > Hey everyone, > Most language changes affect only implementation code, but a few like record > classes and now value classes are API-affecting: they permit new API shapes to > be expressed, and thereby new ways for a client class to depend on a library > class. > These features are very carefully made backward-compatible, but there's a sort > of second-level backward compatibility as well: can a library owner fully > exploit the feature in a way that is *itself* backward-compatible for its > clients? Looking at things this way brings in a whole host of new issues (since > libraries often get stuck on old versions and often get run on a wide range of > Java versions newer than the one they built and tested on, among other > reasons). > Record classes seem to come out well in this regard. From what I can tell, there > should be many "record-like" classes today that can smoothly become records > that all clients will still compile against and work with. Some `getFoo()` > methods might have to be preserved as pass-throughs to `foo()` and maybe some > details like that, but it looks pretty clean to me (?). > But migrating to a value class is rescinding functionality and so inherently > *not* backward-compatible. So, libraries can only do it via a multi-step > process, over time. This thread is happening because I think this project > probably wants to support that process, but I'm not clear yet on how it would > do so. > If we did nothing else, we'd be counting on something like [ > https://github.com/jspecify/jspecify/issues/488 | this JSpecify feature request > ] to happen (please skim that, it helps explain what I'm even talking about in > this thread). If we feel fine about hoping for the best on that front, then you > can skip the rest of this message (I'd shift my argument to petitioning Oracle > to support that effort!). That is, the remainder is motivated by the assumption > that Java itself wants to actively support these migrations. > Anyway: it looks very compelling to me to put that `@ValueClass` annotation, > such as described, into Java itself, and have javac support it in the very same > way it's going to support value class validation. (Even, of course, applying it > to Integer and friends.) > I'd even ask whether we could take it a step further. Once Java introduces the > `value` keyword, there is still a lot of value (ugh) in having the annotation, > and having it mean *exactly* the same thing, with all the same effects. This > has a feeling of heresy to it, but it does something pretty powerful I think: > it allows library owners to write forward -compatible code, that does the right > thing whether its clients are using Java N-1 or Java N. Which makes the event > of its users upgrading to Java N ... a little closer to a non- event. > I suspect this of being reasonable. Since that Java N-1 client won't be able to > use `non-atomic` or `!` or `?` or whatever we're calling the kind of > constructor that makes the class a "never-null" type, I think the Java N-1 VM > is within its rights to do almost nothing about the `value` keyword. (Perhaps > prevent synchronization, and let's stick a pin in a discussion about equality > semantics for the moment.) Which is good because it wouldn't know how to do > those things. > In fact, the earlier a version of Java we could backport this annotation and its > javac support to, the better, AFAICT. With each version earlier we could push > it, that much more code gets to become safely forward-compatible today. > This is a much longer discussion but let me cut this message off here as a > conversation-starter. Please start poking holes! I do not think you need support of javac (i.e. change the spec) for that because we already have the multi-release jar, and a multi-release jar is basically a forward-compatible vehicle for libraries. Using a multi-release jar that uses preview features has been used in the past by several opensource projects, the most popular is, i believe, Lucene that uses the preview versions of Panama to manage (unmap) mmap files. For a class that you want to see both as an identity class and a value class, either you use Maven or Gradle (see [1]) but you have to hand manage the different versions or you use a special bytecode rewriter that look for annotations internal to your project and duplicate the classes (it seems a good use case for the classfile API) with one version being an identity class and one version being a value classes. And you can generalize that approach using JSpecify annotations for tagging fields/parameters that uses value classes with an implicit constructor. I've used the bytecode rewriter approach in my project civilizer [2], which is based on a previous version of Valhalla (the last binary preview) and it works well. R?mi [1] [ https://maven.apache.org/plugins/maven-compiler-plugin/multirelease.html | https://maven.apache.org/plugins/maven-compiler-plugin/multirelease.html ] [2] [ https://github.com/forax/civilizer | https://github.com/forax/civilizer ] -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb9n at gmail.com Mon Mar 4 22:57:20 2024 From: kevinb9n at gmail.com (Kevin Bourrillion) Date: Mon, 4 Mar 2024 14:57:20 -0800 Subject: Migrating library classes to value classes In-Reply-To: <118608900.21236271.1709592262602.JavaMail.zimbra@univ-eiffel.fr> References: <118608900.21236271.1709592262602.JavaMail.zimbra@univ-eiffel.fr> Message-ID: On Mon, Mar 4, 2024 at 2:44?PM Remi Forax wrote: For a class that you want to see both as an identity class and a value > class, either you use Maven or Gradle (see [1]) but you have to hand manage > the different versions or you use a special bytecode rewriter that look for > annotations internal to your project and duplicate the classes (it seems a > good use case for the classfile API) with one version being an identity > class and one version being a value classes. > And don't forget the testing impact. I view this as not a good solution, and think that approaches that don't require users to rig up such machinery on their side are worth finding when possible. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Mon Mar 4 23:22:05 2024 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 5 Mar 2024 00:22:05 +0100 (CET) Subject: Migrating library classes to value classes In-Reply-To: References: <118608900.21236271.1709592262602.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <103314958.21241989.1709594525465.JavaMail.zimbra@univ-eiffel.fr> > From: "Kevin Bourrillion" > To: "Remi Forax" > Cc: "valhalla-spec-experts" > Sent: Monday, March 4, 2024 11:57:20 PM > Subject: Re: Migrating library classes to value classes > On Mon, Mar 4, 2024 at 2:44 PM Remi Forax < [ mailto:forax at univ-mlv.fr | > forax at univ-mlv.fr ] > wrote: >> For a class that you want to see both as an identity class and a value class, >> either you use Maven or Gradle (see [1]) but you have to hand manage the >> different versions or you use a special bytecode rewriter that look for >> annotations internal to your project and duplicate the classes (it seems a good >> use case for the classfile API) with one version being an identity class and >> one version being a value classes. > And don't forget the testing impact. Yes, you need to rewite *before* testing :) [ https://github.com/forax/civilizer/blob/master/pom.xml#L69 | https://github.com/forax/civilizer/blob/master/pom.xml#L69 ] > I view this as not a good solution, and think that approaches that don't require > users to rig up such machinery on their side are worth finding when possible. Having two classes for any value classes is also what the JDK does for all @ValueBased classes. The other solution is to backport behavior of the new VMs to the old ones but this is similar to Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From frederic.parain at oracle.com Tue Mar 5 15:57:02 2024 From: frederic.parain at oracle.com (Frederic Parain) Date: Tue, 5 Mar 2024 10:57:02 -0500 Subject: Migrating library classes to value classes In-Reply-To: <280583A5-2442-49EE-80F1-04080978D02D@oracle.com> References: <280583A5-2442-49EE-80F1-04080978D02D@oracle.com> Message-ID: <540c1199-f464-42cf-9b93-36b045ceeac2@oracle.com> On 3/4/24 5:26 PM, Dan Smith wrote: >> On Mar 4, 2024, at 10:56?AM, Kevin Bourrillion wrote: >> >> But migrating to a value class is rescinding functionality and so inherently *not* backward-compatible. So, libraries can only do it via a multi-step process, over time. This thread is happening because I think this project probably wants to support that process, but I'm not clear yet on how it would do so. > To help define the problem: assuming you already have a class that has the shape of a value class (final fields, no internal identity assumptions, final or abstract class, compatible superclass), adding 'value' is a binary- and (nearly*) source-compatible change. > > Then there's behavioral compatibility; the JEP helpfully enumerates the potential issues: > >>> ? The == operator may treat two instances as the same, where previously they were considered different >>> ? Attempts to synchronize on an instance will fail, either at compile time or run time >>> ? The results of toString, equals, and hashCode, if they haven't been overridden, may be different (preferably, the existing class already overrides equals and hashCode in a way that eliminates the dependency on identity) >>> ? Assumptions about unique ownership of an instance may be violated (for example, an identical instance may be created at two different program points) >>> ? Performance will generally improve, but may have different characteristics that are surprising > (*The compile-time error regarding synchronization is, in fact, a source incompatibility. We think it's helpful to catch the error earlier, but it will make some programs fail to compile.) > > In the JDK, we are very concerned about compatibility, so we defined "value-based class" to require private (or non-private deprecated) constructors and construction via identity-ambiguous factories. This provides a specified basis for most of the behavioral changes that come with 'value', except for the fail-fast behavior of synchronization. > > So I guess I'd divide the issues into two categories: > > 1) Things that can be controlled by the class declaration > > There are the basic class structure things (fields, sub- and superclasses). These are pretty self-evident?if you've got a candidate class, you probably already meet these requirements. In anticipation of 'value' coming, maybe you'd like to add some 'final' modifiers in the right place for things that were not explicitly final before. > > Surely if you're prepared to abandon identity, you've already overridden equals/hashCode to support a notion of equality that isn't identity-based. 'toString' probably, too, although I think you can assume your users won't/shouldn't depend on the contents of the random number appearing in the default 'toString' output. > > If you're also worried about how people will use '==', you can match the JDK and provide factories that don't commit to identity details. You can even link to the "value based class" definition in the Java SE docs, and specify that your class follows these rules. > > Would it be useful to have some tooling that checks all of these things for you? Eh, maybe. I'm not sure there's that much to check?most of it pretty naturally flows from the fact that you're declaring a class that fits in the value-candidate bucket; and making a deliberate choice to avoid identity guarantees via factory-based construction and some extra javadoc is no harder than making a deliberate choice to add a '@ValueBased' annotation. > > 2) Corner-case use site failures > > The main purpose of JEP 390 was to more strongly discourage synchronization on types like Integer, because that code will break when Integer becomes a value class. I think that's already a corner case?most people do not write code like this. https://bugs.openjdk.org/browse/JDK-8326603 https://bugs.openjdk.org/browse/JDK-8326604 https://bugs.openjdk.org/browse/JDK-8326605 https://bugs.openjdk.org/browse/JDK-8326606 > But java.lang.Integer gets used by everyone, and so an occasionally usage is bound to show up. > > s/Integer/SomeLibraryClass/ and I think we're talking about orders of magnitude fewer use cases. Still possible, of course, but I'm pretty skeptical in the likelihood of all of the stars aligning with any frequency: > > - User code that is targeted to an older VM right now, but will want to target a newer VM later > - A user synchronizing on SomeLibraryClass > - A usage that can be detected with static tooling > - A user willing to use the tooling > - A version of the tooling new enough to implement this check > > You can do the same exercise with WeakReference. Maybe when we're done there will be one or two other identity-related things like this that simply stop working. But they're just so rare, it's hard to imagine tooling dedicated to catching them (and static tools won't catch everything anyway). > > --- > > When we were working out the details of JEP 390, we considered making the @ValueBased annotation public/standard, and decided against it, because we wanted to avoid introducing layers of new concepts for programmers. "How is a value-based class different from a value class?" is not something we want showing up in Java tutorials. > > Instead?and I realize this is totally theoretical for me and very practical for you, so I appreciate any hard-earned wisdom you can provide?I'm not that concerned about saying version N of a library migrates some classes to be true value classes, and that should be just fine for most of their users. If somebody has a special issue with synchronization or ==, they should stick with library version N-1 until they can fix it. > >> In fact, the earlier a version of Java we could backport this annotation and its javac support to, the better, AFAICT. With each version earlier we could push it, that much more code gets to become safely forward-compatible today. > Our normal approach to javac would be to put new warnings in the latest version only. Best we could do is, say, javac 23. And since we expect support for value classes to follow soon after, most of the benefit would be to people who write, e.g., 17- or 21-targeted code, but compile it using the latest JDK. > From daniel.smith at oracle.com Wed Mar 6 16:05:16 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Wed, 6 Mar 2024 16:05:16 +0000 Subject: EG meeting *canceled*, 2024-03-06 Message-ID: <2C049125-5778-4FD1-8A07-E8E0E63009E3@oracle.com> No meeting today. I'd like to talk more about migration sometime, but let's plan to cover that next time. From forax at univ-mlv.fr Mon Mar 11 13:33:57 2024 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 11 Mar 2024 14:33:57 +0100 (CET) Subject: raw floating-point bits in '==' value object comparisons (again/still) Message-ID: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> Last week, I explain at JChateau (think JCrete in France, less sun, more chateaux) how value types work from the user POV, among other subject describing the semantics of ==. First, most of the attendee knew the semantics difference between == on double and Double.equals(). I suppose it's because people that attend to such (un-)conference have a more intimate knowledge of Java than an average developer. Second, no attendee knew that NaN was a prefix. So it let me think again on that subject. 1) The argument that of Dan that we want to be able to create a class with two different NaN, does not hold because instead of storing the values as double, the values can be stored as long. value class C { private double d; C(double d) { this.d = d; } long bits() { return Double.doubleToRawLongBits(d); } } C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); assert c1.bits() != c2.bits(); can be rewritten as value class C { private long l; C(double d) { this.l = Double.doubleToRawLongBits(d); } long bits() { return l; } } 2) The de-duplication of value instances by the GC works with both the bitwise equivalence and the representational equivalence. If the GC only de-duplicate the value instance based only on the bitwise equivalence, it is a valid algorithm under the representational equivalence. So I not convinced that the bitwise equivalence should be choosen instead of the representational equivalence, for me two semantics instead of three is a win. R?mi From ccherlin at gmail.com Mon Mar 11 15:27:30 2024 From: ccherlin at gmail.com (Clement Cherlin) Date: Mon, 11 Mar 2024 10:27:30 -0500 Subject: raw floating-point bits in '==' value object comparisons (again/still) In-Reply-To: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> References: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> Message-ID: On Mon, Mar 11, 2024 at 8:34?AM Remi Forax wrote: > > Last week, I explain at JChateau (think JCrete in France, less sun, more chateaux) how value types work from the user POV, among other subject describing the semantics of ==. > > First, most of the attendee knew the semantics difference between == on double and Double.equals(). I suppose it's because people that attend to such (un-)conference have a more intimate knowledge of Java than an average developer. Second, no attendee knew that NaN was a prefix. > > So it let me think again on that subject. > > 1) The argument that of Dan that we want to be able to create a class with two different NaN, does not hold because instead of storing the values as double, the values can be stored as long. > > value class C { > private double d; > C(double d) { this.d = d; } > long bits() { return Double.doubleToRawLongBits(d); } > } > > C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); > C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); > assert c1.bits() != c2.bits(); > > can be rewritten as > > value class C { > private long l; > C(double d) { this.l = Double.doubleToRawLongBits(d); } > long bits() { return l; } > } > > > 2) The de-duplication of value instances by the GC works with both the bitwise equivalence and the representational equivalence. > > If the GC only de-duplicate the value instance based only on the bitwise equivalence, it is a valid algorithm under the representational equivalence. > > > So I not convinced that the bitwise equivalence should be choosen instead of the representational equivalence, for me two semantics instead of three is a win. > > R?mi Let's make nobody happy and define new operators for new semantics, ? la JavaScript: Given double d1, d2; d1 === d2 uses the algorithm implemented in Double.equals() d1 ==== d2 uses the algorithm implemented in Double.equals() but with doubleToRawLongBits instead of doubleToLongBits. Same goes for floats. For float ===/==== double, the float is coerced to double first, just as with == today. These operators behave exactly the same as == for reference types (except primitive wrappers, which they unbox first if both are non-null) and non-floating-point primitive types, and apply their respective definitions of equality recursively to the fields of value types. Alternatively, === is shorthand for Objects.equals() and we can all stop using == for 99% of purposes (again, like JavaScript). Cheers, Clement Cherlin From forax at univ-mlv.fr Mon Mar 11 15:42:08 2024 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 11 Mar 2024 16:42:08 +0100 (CET) Subject: raw floating-point bits in '==' value object comparisons (again/still) In-Reply-To: References: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <333902886.25889879.1710171728110.JavaMail.zimbra@univ-eiffel.fr> ----- Original Message ----- > From: "Clement Cherlin" > To: "Valhalla Expert Group Observers" > Sent: Monday, March 11, 2024 4:27:30 PM > Subject: Re: raw floating-point bits in '==' value object comparisons (again/still) > On Mon, Mar 11, 2024 at 8:34?AM Remi Forax wrote: >> >> Last week, I explain at JChateau (think JCrete in France, less sun, more >> chateaux) how value types work from the user POV, among other subject >> describing the semantics of ==. >> >> First, most of the attendee knew the semantics difference between == on double >> and Double.equals(). I suppose it's because people that attend to such >> (un-)conference have a more intimate knowledge of Java than an average >> developer. Second, no attendee knew that NaN was a prefix. >> >> So it let me think again on that subject. >> >> 1) The argument that of Dan that we want to be able to create a class with two >> different NaN, does not hold because instead of storing the values as double, >> the values can be stored as long. >> >> value class C { >> private double d; >> C(double d) { this.d = d; } >> long bits() { return Double.doubleToRawLongBits(d); } >> } >> >> C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); >> C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); >> assert c1.bits() != c2.bits(); >> >> can be rewritten as >> >> value class C { >> private long l; >> C(double d) { this.l = Double.doubleToRawLongBits(d); } >> long bits() { return l; } >> } >> >> >> 2) The de-duplication of value instances by the GC works with both the bitwise >> equivalence and the representational equivalence. >> >> If the GC only de-duplicate the value instance based only on the bitwise >> equivalence, it is a valid algorithm under the representational equivalence. >> >> >> So I not convinced that the bitwise equivalence should be choosen instead of the >> representational equivalence, for me two semantics instead of three is a win. >> >> R?mi > > Let's make nobody happy and define new operators for new semantics, ? > la JavaScript: > > Given double d1, d2; > d1 === d2 uses the algorithm implemented in Double.equals() > d1 ==== d2 uses the algorithm implemented in Double.equals() but with > doubleToRawLongBits instead of doubleToLongBits. > > Same goes for floats. For float ===/==== double, the float is coerced > to double first, just as with == today. > > These operators behave exactly the same as == for reference types > (except primitive wrappers, which they unbox first if both are > non-null) and non-floating-point primitive types, and apply their > respective definitions of equality recursively to the fields of value > types. > > Alternatively, === is shorthand for Objects.equals() and we can all > stop using == for 99% of purposes (again, like JavaScript). The current plan to makes float/double and j.l.Float/j.l.Double more similar by allowing .equals() on primitive types. so double d1 = ... double d1 = ... d1.equals(d2) is equivalent to new Double(d1).equals(new Double(d2)) which reduces the need to use ==. The open question is allowing .equals() on literals, like 2.equals(a) because currenty the lexer will consumer '2.' as a double instead as '2' '.' > > Cheers, > Clement Cherlin regards, R?mi From liangchenblue at gmail.com Mon Mar 11 16:19:15 2024 From: liangchenblue at gmail.com (-) Date: Mon, 11 Mar 2024 11:19:15 -0500 Subject: raw floating-point bits in '==' value object comparisons (again/still) In-Reply-To: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> References: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> Message-ID: Hi Remi, I believe we can stick with bitwise equivalence, at least for now. The bitwise equivalence, in essence, is a remedy for the compatibility issues around == with the removal of identity. Thus, it should not replace and is still not preferable to equals(), even though the migration happens to make == a good implementation in many more cases. For a Double object d, there are sets S0 that are all objects with the same identity as d, S1 that are all objects with the same bitwise representation (headers ignored of course) as d, S2 that are all objects with the same representational equivalence as d, and Sequals that are all objects that equals(d). We can see (<= for "is subset of") {d} == S0 <= S1 <= S2 <= (actually ==) Sequals, and whatever the set d == holds true against (call it S==) has S== <= Sequals. With the removal of identity, S0 is gone, so S1 becomes S==. What you call for is to use S2 for S==. I believe that the move from S1 to S2 will be a backward-compatible change in the future, but not from S2 to S1. Given the significant performance benefits of using S1 for S== instead of S2, I believe we can stay with the bitwise equivalence and investigate using S2 for S== if the future hardware improvements make it feasible. Regards, Chen Liang On Mon, Mar 11, 2024 at 8:34?AM Remi Forax wrote: > Last week, I explain at JChateau (think JCrete in France, less sun, more > chateaux) how value types work from the user POV, among other subject > describing the semantics of ==. > > First, most of the attendee knew the semantics difference between == on > double and Double.equals(). I suppose it's because people that attend to > such (un-)conference have a more intimate knowledge of Java than an average > developer. Second, no attendee knew that NaN was a prefix. > > So it let me think again on that subject. > > 1) The argument that of Dan that we want to be able to create a class with > two different NaN, does not hold because instead of storing the values as > double, the values can be stored as long. > > value class C { > private double d; > C(double d) { this.d = d; } > long bits() { return Double.doubleToRawLongBits(d); } > } > > C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); > C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); > assert c1.bits() != c2.bits(); > > can be rewritten as > > value class C { > private long l; > C(double d) { this.l = Double.doubleToRawLongBits(d); } > long bits() { return l; } > } > > > 2) The de-duplication of value instances by the GC works with both the > bitwise equivalence and the representational equivalence. > > If the GC only de-duplicate the value instance based only on the bitwise > equivalence, it is a valid algorithm under the representational equivalence. > > > So I not convinced that the bitwise equivalence should be choosen instead > of the representational equivalence, for me two semantics instead of three > is a win. > > R?mi > -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Mon Mar 11 17:43:36 2024 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 11 Mar 2024 18:43:36 +0100 (CET) Subject: raw floating-point bits in '==' value object comparisons (again/still) In-Reply-To: References: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <1756886169.25984842.1710179016764.JavaMail.zimbra@univ-eiffel.fr> > From: "-" > To: "Remi Forax" , "valhalla-spec-experts" > > Sent: Monday, March 11, 2024 5:19:15 PM > Subject: Re: raw floating-point bits in '==' value object comparisons > (again/still) > Hi Remi, > I believe we can stick with bitwise equivalence, at least for now. The bitwise > equivalence, in essence, is a remedy for the compatibility issues around == > with the removal of identity. Thus, it should not replace and is still not > preferable to equals(), even though the migration happens to make == a good > implementation in many more cases. This is true for both the bitwise equivalence and the representational equivalent. The idea is not to replace equals(), but to provide an implementation of == for value types. > For a Double object d, there are sets S0 that are all objects with the same > identity as d, S1 that are all objects with the same bitwise representation > (headers ignored of course) as d, S2 that are all objects with the same > representational equivalence as d, and Sequals that are all objects that > equals(d). We can see (<= for "is subset of") {d} == S0 <= S1 <= S2 <= > (actually ==) Sequals, and whatever the set d == holds true against (call it > S==) has S== <= Sequals. > With the removal of identity, S0 is gone, so S1 becomes S==. What you call for > is to use S2 for S==. I believe that the move from S1 to S2 will be a > backward-compatible change in the future, but not from S2 to S1. Given the > significant performance benefits of using S1 for S== instead of S2, I believe > we can stay with the bitwise equivalence and investigate using S2 for S== if > the future hardware improvements make it feasible. I think you are too optimistic about the performance benefits of the bitwise equivalence compared to the representational equivalence. And having the right semantics is more important. The representational equivalence semantics is the same as the bitwise equivalence semantics for all types but floating points. So for a lot of value classes, there is no difference. You may think that having the bitwise equivalence semantics allows to compare the content the fields using wider registers but this is not true if you are using a concurrent GC (references may need patching), this is not true if a field is a non-null value type represented as a pointer (the default value may be encoded as null). So it's far from clear to me that there is a "significant" performance benefit. Yes, checking if a field is not the nominal NaN as a cost, but given that in most case, the branch will be never taken, we may have hard time to see the difference. And again, I prefer a clear and simple semantics compared to mostly the same semantics with a foot gun attached that fires if a NaN is encoded in a funny way. > Regards, > Chen Liang R?mi > On Mon, Mar 11, 2024 at 8:34 AM Remi Forax < [ mailto:forax at univ-mlv.fr | > forax at univ-mlv.fr ] > wrote: >> Last week, I explain at JChateau (think JCrete in France, less sun, more >> chateaux) how value types work from the user POV, among other subject >> describing the semantics of ==. >> First, most of the attendee knew the semantics difference between == on double >> and Double.equals(). I suppose it's because people that attend to such >> (un-)conference have a more intimate knowledge of Java than an average >> developer. Second, no attendee knew that NaN was a prefix. >> So it let me think again on that subject. >> 1) The argument that of Dan that we want to be able to create a class with two >> different NaN, does not hold because instead of storing the values as double, >> the values can be stored as long. >> value class C { >> private double d; >> C(double d) { this.d = d; } >> long bits() { return Double.doubleToRawLongBits(d); } >> } >> C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); >> C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); >> assert c1.bits() != c2.bits(); >> can be rewritten as >> value class C { >> private long l; >> C(double d) { this.l = Double.doubleToRawLongBits(d); } >> long bits() { return l; } >> } >> 2) The de-duplication of value instances by the GC works with both the bitwise >> equivalence and the representational equivalence. >> If the GC only de-duplicate the value instance based only on the bitwise >> equivalence, it is a valid algorithm under the representational equivalence. >> So I not convinced that the bitwise equivalence should be choosen instead of the >> representational equivalence, for me two semantics instead of three is a win. >> R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Mon Mar 11 18:58:24 2024 From: john.r.rose at oracle.com (John Rose) Date: Mon, 11 Mar 2024 11:58:24 -0700 Subject: raw floating-point bits in '==' value object comparisons (again/still) In-Reply-To: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> References: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <5663AB92-753B-4E42-B6F5-919086C1B96C@oracle.com> On 11 Mar 2024, at 6:33, Remi Forax wrote: > Last week, I explain at JChateau (think JCrete in France, less sun, more chateaux) how value types work from the user POV, among other subject describing the semantics of ==. > > First, most of the attendee knew the semantics difference between == on double and Double.equals(). I suppose it's because people that attend to such (un-)conference have a more intimate knowledge of Java than an average developer. Second, no attendee knew that NaN was a prefix. So you are using conversations with people who admittedly did not understand the problem, until you explained it to them in the moment, to motivate a design change? That does not seem promising as a way to predict the user experience after years of learned familiarity. In short: Sorry, I don?t buy this argument. > > So it let me think again on that subject. > > 1) The argument that of Dan that we want to be able to create a class with two different NaN, does not hold because instead of storing the values as double, the values can be stored as long. > > value class C { > private double d; > C(double d) { this.d = d; } > long bits() { return Double.doubleToRawLongBits(d); } > } > > C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); > C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); > assert c1.bits() != c2.bits(); > > can be rewritten as > > value class C { > private long l; > C(double d) { this.l = Double.doubleToRawLongBits(d); } > long bits() { return l; } > } (Agreed there would be a workaround. But that is a tiny corner of the issue.) > 2) The de-duplication of value instances by the GC works with both the bitwise equivalence and the representational equivalence. > > If the GC only de-duplicate the value instance based only on the bitwise equivalence, it is a valid algorithm under the representational equivalence. And where in the JVMS or JLS would the GC get permission to make such decisions? We would be smuggling a second same-ness condition back into the JLS and JVMS. That?s what?s required to specify JVM and language behaviors like this. We?d have an ?is same? and an ?is really really and truly the same? condition for value objects containing floats. That strikes me as bad VM physics, regardless of the motivation to align with some pre-existing library API. Please, no. There would be real performance costs of such ?bad physics?. The JVM would have to keep a ?second set of books? about whether two value objects were really (and really truly) the same, separate from the effects of bytecode-issued acmp instructions. There would still be an open question of whether to normalize NaN bits (those silly NaN sub-flavors are a root of the problem here). If we chose to normalize NaN bits in the heap, then we would incur a performance cost for every single value object construction (where a float or double is in the picture). If we chose to not normalize such heap bits, then we would incur a performance cost on every single comparison of such value objects. And all to gain some supposed teachability benefit, which mostly disappears a year or two after the curriculum changes. Sorry, no. I?m kind of sorry about the burden for those o(1000) expert users who know about the internals of Double::equals in Java but don?t know about multiple NaNs, as of this moment. But I?m not sorry enough to consent to harming JVM performance to cater to their particular expectations in 2024. > So I not convinced that the bitwise equivalence should be choosen instead of the representational equivalence, for me two semantics instead of three is a win. > > R?mi From forax at univ-mlv.fr Mon Mar 11 23:03:45 2024 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 12 Mar 2024 00:03:45 +0100 (CET) Subject: raw floating-point bits in '==' value object comparisons (again/still) In-Reply-To: <5663AB92-753B-4E42-B6F5-919086C1B96C@oracle.com> References: <487419019.25754010.1710164037039.JavaMail.zimbra@univ-eiffel.fr> <5663AB92-753B-4E42-B6F5-919086C1B96C@oracle.com> Message-ID: <1873388809.26069442.1710198225628.JavaMail.zimbra@univ-eiffel.fr> ----- Original Message ----- > From: "John Rose" > To: "Remi Forax" > Cc: "valhalla-spec-experts" > Sent: Monday, March 11, 2024 7:58:24 PM > Subject: Re: raw floating-point bits in '==' value object comparisons (again/still) > On 11 Mar 2024, at 6:33, Remi Forax wrote: > >> Last week, I explain at JChateau (think JCrete in France, less sun, more >> chateaux) how value types work from the user POV, among other subject >> describing the semantics of ==. >> >> First, most of the attendee knew the semantics difference between == on double >> and Double.equals(). I suppose it's because people that attend to such >> (un-)conference have a more intimate knowledge of Java than an average >> developer. Second, no attendee knew that NaN was a prefix. > > So you are using conversations with people who admittedly did not understand the > problem, until you explained it to them in the moment, to motivate a design > change? That does not seem promising as a way to predict the user experience > after years of learned familiarity. I think you misunderstood me, it was surprising to me that the semantics of floating points in Java is well know while at the same time the encoding of floating points is not. This section is not about changing the design. > > In short: Sorry, I don?t buy this argument. > >> >> So it let me think again on that subject. Now, i'm talking about design. >> >> 1) The argument that of Dan that we want to be able to create a class with two >> different NaN, does not hold because instead of storing the values as double, >> the values can be stored as long. >> >> value class C { >> private double d; >> C(double d) { this.d = d; } >> long bits() { return Double.doubleToRawLongBits(d); } >> } >> >> C c1 = new C(Double.longBitsToDouble(0x7ff0000000000001L)); >> C c2 = new C(Double.longBitsToDouble(0x7ff0000000000002L)); >> assert c1.bits() != c2.bits(); >> >> can be rewritten as >> >> value class C { >> private long l; >> C(double d) { this.l = Double.doubleToRawLongBits(d); } >> long bits() { return l; } >> } > > (Agreed there would be a workaround. But that is a tiny corner of the issue.) > >> 2) The de-duplication of value instances by the GC works with both the bitwise >> equivalence and the representational equivalence. >> >> If the GC only de-duplicate the value instance based only on the bitwise >> equivalence, it is a valid algorithm under the representational equivalence. > > And where in the JVMS or JLS would the GC get permission to make such decisions ? The de-duplication is an optimization, it does not have to deduplicate all instances that are equivalent using the representational equivalence, it can only de-duplicate the ones that are bitwise equivalent. > We would be smuggling a second same-ness condition back into the JLS and JVMS. > That?s what?s required to specify JVM and language behaviors like this. We?d > have an ?is same? and an ?is really really and truly the same? condition for > value objects containing floats. No, the deduplication is an optimization. > That strikes me as bad VM physics, regardless > of the motivation to align with some pre-existing library API. Please, no. The main motivation is not having 3 semantics where we can only have 2 and having the wrapper types == and .equals() to be equivalent (reduce the differences between a primitive and its wrapper type). > > There would be real performance costs of such ?bad physics?. The JVM would have > to keep a ?second set of books? about whether two value objects were really > (and really truly) the same, separate from the effects of bytecode-issued acmp > instructions. There would still be an open question of whether to normalize > NaN bits (those silly NaN sub-flavors are a root of the problem here). Yes, that's the root of the problem. > > If we chose to normalize NaN bits in the heap, then we would incur a performance > cost for every single value object construction (where a float or double is in > the picture). This is not what i'm proposing. > If we chose to not normalize such heap bits, then we would incur > a performance cost on every single comparison of such value objects. yes, but this is exactly how j.l.Float.equals() and j.l.Double.equals() actually works, this is how a record with float or double component actually works, and you explain in another mail about liliput that "The key principle here is probably that there should be a slow path which is rare and easy to test.". A NaN sub-flavor (i.e. not the canonical NaN) is rare and easy to test. R?mi From joe.darcy at oracle.com Wed Mar 13 05:16:23 2024 From: joe.darcy at oracle.com (Joseph D. Darcy) Date: Tue, 12 Mar 2024 22:16:23 -0700 Subject: Value object equality & floating-point values In-Reply-To: <378C86C4-134C-48A3-B6C3-7282E83305ED@oracle.com> References: <1908581584.3891098.1707598626337.JavaMail.zimbra@univ-eiffel.fr> <378C86C4-134C-48A3-B6C3-7282E83305ED@oracle.com> Message-ID: Catching up on email... On 2/13/2024 1:09 PM, Dan Smith wrote: >> On Feb 12, 2024, at 6:54?PM, Joe Darcy wrote: >>> >>> To put it simply, the spec *already allows* normalization (for the >>> very good reason that no Java developer should ever be exposed to >>> different NaNs). >> The origin of that paragraph stems from idiosyncrasies of 64-bit double support on the x87 FPU -- tl;dr a format changing load/store instruction rather than a move instruction had to be used to get values on and off the floating-point stack. Fortunately, JEP 306: "Restore Always-Strict Floating-Point Semantics" removed most such issues from a spec perspective, which had not been a serious concern from an implementation perspective for some time. > Joe, does this imply that primitive floating-point data movement *in general* may not preserve NaN bits? E.g., the following assert may fail? > > value class C { > double d; > C(double d) { this.d = d; } > > static double TMP; > } > > double d1 = ...; > C.TMP = d1; > double d2 = C.TMP; > C c1 = new C(d1); > C c2 = new C(d2); > assert c1 == c2; // bitwise equivalence test On current architectures, that should work, but the test matrix should include a variety of platform-specific signaling NaN values. (Signaling NaNs are a feature of IEEE 754 outside of the computation model of the Java language and JVM.) HTH, -Joe From daniel.smith at oracle.com Wed Mar 20 15:25:39 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Wed, 20 Mar 2024 15:25:39 +0000 Subject: EG meeting, 2024-03-20 Message-ID: Late notice, but confirming that today's meeting is on, we'll see who drops in. Wednesday, 4pm UTC (9am PDT, 12pm EDT) Note the time shift for daylight savings in the US. Tentatively, we can discuss value class migration and the "Migrating library classes to value classes" thread. From kevinb9n at gmail.com Fri Mar 15 00:13:47 2024 From: kevinb9n at gmail.com (Kevin Bourrillion) Date: Thu, 14 Mar 2024 17:13:47 -0700 Subject: Migrating library classes to value classes In-Reply-To: <280583A5-2442-49EE-80F1-04080978D02D@oracle.com> References: <280583A5-2442-49EE-80F1-04080978D02D@oracle.com> Message-ID: Sorry for delay. I've raised a complex topic. Most of what I say in here will need more deep thought and discussion. I don't even have the vocabulary for half of it. As a general point: whenever we say "the user can wait for Java N", sometimes that really means "the user can wait until they're ready to de-support Java N-1" which is a lot longer time. And, it's not about "I don't want to wait", but more about "I want to be able to serve the Java N+ users I have right now today and I can't yet". On Mon, Mar 4, 2024 at 2:26?PM Dan Smith wrote: In the JDK, we are very concerned about compatibility, so we defined > "value-based class" to require private (or non-private deprecated) > constructors and construction via identity-ambiguous factories. This > provides a specified basis for most of the behavioral changes that come > with 'value', except for the fail-fast behavior of synchronization. > Ah I forgot to reference that. Yes, I'm talking about making that same concept more official/enforceable. (aside: we can backpedal from the constructor part of it now, right?) 1) Things that can be controlled by the class declaration > > There are the basic class structure things (fields, sub- and > superclasses). These are pretty self-evident?if you've got a candidate > class, you probably already meet these requirements. In anticipation of > 'value' coming, maybe you'd like to add some 'final' modifiers in the right > place for things that were not explicitly final before. > > Surely if you're prepared to abandon identity, you've already overridden > equals/hashCode to support a notion of equality that isn't identity-based. > 'toString' probably, too, although I think you can assume your users > won't/shouldn't depend on the contents of the random number appearing in > the default 'toString' output. > When a user first goes through some classes to decide which to make value classes (whether by keyword or by annotation), I think they're likely enough to uncover various problems they have to resolve. This is who I'm thinking about. I think the sooner they can disavow identity and be prompted to fix up those problems, the better. Partly because the enforcement they'd get is healthy for their codebase *anyway* even if they never upgrade to value classes. And partly it just defrays effort they'd have to do later, and makes the upgrade itself go smoothly. (But these aren't the whole point.) Back to the record classes example: if someone can't get on Java 16 yet, I try to nudge them to use AutoValue, which even has specific advice to keep your classes forward-compatible with their future recordy selves. These users would basically be "pre-adopting" records, which I think is cool (including in ways I haven't 100% articulated yet). > Would it be useful to have some tooling that checks all of these things > for you? Eh, maybe. I'm not sure there's that much to check?most of it > pretty naturally flows from the fact that you're declaring a class that > fits in the value-candidate bucket; and making a deliberate choice to avoid > identity guarantees via factory-based construction and some extra javadoc > is no harder than making a deliberate choice to add a '@ValueBased' > annotation. > Perfect, so this is the specific part I'm calling into question. Most code in the world isn't super-well-thought out (and just wait until AI is writing it all haha). The code just happens to seem to work. If a project wants to do better, the only practical ways is with the help of one tool or another. In plenty of cases it's fine to leave that to third-party tools; I'm wondering whether this is one of them or not. 2) Corner-case use site failures > > The main purpose of JEP 390 was to more strongly discourage > synchronization on types like Integer, because that code will break when > Integer becomes a value class. I think that's already a corner case?most > people do not write code like this. But java.lang.Integer gets used by > everyone, and so an occasionally usage is bound to show up. > > s/Integer/SomeLibraryClass/ and I think we're talking about orders of > magnitude fewer use cases. I'm more interested in whether it should warn on `==`! Yes, I do think (with a very appropriate retcon) that Valhalla's change to it is as compatible as can be (uh ignore float). But, I'm still as concerned about new-`==` as I was a couple years ago. On most should-be-value classes today, `==` is flat-out wrong. Tomorrow, it's just as incorrect -- except it will *happen to work* more often, maybe much more often. I think it will become an "attractive nuisance", and that the sooner we can spur people to stop using `==` on classes that are or intend to become value classes, the better. When we were working out the details of JEP 390, we considered making the > @ValueBased annotation public/standard, and decided against it, because we > wanted to avoid introducing layers of new concepts for programmers. "How is > a value-based class different from a value class?" is not something we want > showing up in Java tutorials. > Hmm. I would hope this could be simple. A developer makes the same decision either way, and they just use whichever of `value` or `@ValueBased` is available to them. Instead?and I realize this is totally theoretical for me and very practical > for you, so I appreciate any hard-earned wisdom you can provide?I'm not > that concerned about saying version N of a library migrates some classes to > be true value classes, and that should be just fine for most of their > users. If somebody has a special issue with synchronization or ==, they > should stick with library version N-1 until they can fix it. > I have work to do in explaining what hurts me about this. > In fact, the earlier a version of Java we could backport this annotation > and its javac support to, the better, AFAICT. With each version earlier we > could push it, that much more code gets to become safely forward-compatible > today. > > Our normal approach to javac would be to put new warnings in the latest > version only. Best we could do is, say, javac 23. And since we expect > support for value classes to follow soon after, most of the benefit would > be to people who write, e.g., 17- or 21-targeted code, but compile it using > the latest JDK. > (Why *not* do it sooner, particularly if it's a helpful thing anyway?) And whenever we talk about something following "soon after", I just think about the fact that the version in between is out there in the world being used *forever*... -------------- next part -------------- An HTML attachment was scrubbed... URL: