with and binary backward compatibility

Dan Heidinga heidinga at redhat.com
Tue Jun 14 13:10:46 UTC 2022


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.  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.

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.

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

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