with and binary backward compatibility

forax at univ-mlv.fr forax at univ-mlv.fr
Tue Jun 14 14:57:50 UTC 2022


----- Original Message -----
> From: "Dan Heidinga" <heidinga at redhat.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Sent: Tuesday, June 14, 2022 3:10:46 PM
> Subject: Re: with and binary backward compatibility

> Remi, to restate your concern slightly - it sounds like there may be
> binary (and source?) compatibility concerns with the implementation of
> with'ers.  You've pitched a possible implementation using
> invokedynamic (every compiler writer's favourite swiss army knife) but
> prefer something that is more explicit in the bytecode.

Yes, BTW there is a simple way to avoid those binary compatibility issues, restrict "with" to the nest or the package/module (like sealed).

> I'm still
> working my way through the full reconstruction document but I assume
> compatibility and implementation have been given some thought even if
> they don't show up in that document.

The problem is that Brian believes that all constructors and deconstructors are dual, i feel this is the wrong model, it does not work that way, a constructor if not canonical creates values before calling the canonical constructor while all deconstructors all match the same way but some provide fewer bindings than the others. The binary compatibility story of "with" shows that you can not do a circle point -> bindings -> point using any constructors but the canonical constructor.  

> 
> Let's aim for the high order bits first - figuring out if the feature
> is desirable and if the general direction works before deep diving
> into class file representation and binary compatibility.

agree

> 
> So I'd suggest putting a pin in this topic and circling back to it
> after further discussion of the core concept.
> 
> Just my two cents =)
> 
> --Dan

Rémi

> 
> On Tue, Jun 14, 2022 at 8:23 AM Remi Forax <forax at univ-mlv.fr> wrote:
>>
>> Hi all,
>> Let say we have a Point with 2 components
>>   record Point(int x, int y) { }
>>
>> Then we change the record to add a 3rd components in a more or less backward
>> compatible way
>>   record Point(int x, int y, int z) {
>>     Point(int x, int y) {
>>       this(x, y, 0);  // creation of the new value 0
>>     }
>>   }
>>
>> Now, let say there is a 'with' somewhere in another code
>>
>>   var newPoint = point with { x = 3; };
>>
>> If this code is compiled when the record Point had only two components, so this
>> is equivalent to
>>
>>   Point(int x, int y) = point;  // i.e. int x = point.x(); int y = point.y();
>>   x = 3;
>>   var newPoint = new Point(x, y);
>>
>> The problem is that if we run that code with the new version of Point (the one
>> with 3 components),
>> newPoint.z is not equals to point.z but to 0, so once there is a 'with'
>> somewhere, there is no backward compatibility anymore.
>>
>> We can try to restore the backward compatibility by compiling to a slightly
>> different code using invokedynamic and a desugared method corresponding to the
>> body of the 'with'
>>
>>   var newPoint = invokedynamic (point) [desugared$method];  // equivalent to a
>>   call using with
>>
>>   static Point desugared$method(int x, int y, MethodHandle mh) {  // content of
>>   the body
>>     x = 3;
>>     return mh.invokeExact(x, y);
>>   }
>>
>> an at runtime, we generate a tree of method handles that does more or less
>>   stub(Point point) {
>>     return desugared$method(point.x(), point.y(), (a, b) -> new Point(a, b,
>>     point.z())
>>   }
>>
>> because this code is generated at runtime, it will be always compatible with the
>> latest version of Point.
>>
>> If we want to support this encoding, it means that the local variables of the
>> enclosing method need to be effectively final so the body of with can be lifted
>> to a private static method (exactly like a lambda).
>>
>>
>> If we generalize this a bit, we can also use the same trick for the record
>> pattern, in that case the pattern Point(int a, int b) is equivalent at runtime
>> to Point(int a, int b, _) once the runtime found that the canonical
>> deconstructor emits the values of 3 components.
>> I'm not sure it's a path i want to follow because i would prefer the record
>> pattern to match the shape excatly, but i find it more attractive than the idea
>> to have overloaded deconstructors.
>>
>> regards,
>> Rémi


More information about the amber-spec-experts mailing list