<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Aug 22, 2023 at 2:03 PM Dan Smith <<a href="mailto:daniel.smith@oracle.com">daniel.smith@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><br>
<br>
> On Aug 22, 2023, at 8:31 AM, John Rose <<a href="mailto:john.r.rose@oracle.com" target="_blank">john.r.rose@oracle.com</a>> wrote:<br>
> <br>
> On 21 Aug 2023, at 10:39, Dan Smith wrote:<br>
> <br>
>>> On Aug 18, 2023, at 9:15 PM, John Rose <<a href="mailto:john.r.rose@oracle.com" target="_blank">john.r.rose@oracle.com</a>> wrote:<br>
>>> <br>
>>> I’ve written up in detail how I think Remi’s suggestion can work.<br>
>>> <br>
>>> <a href="https://cr.openjdk.org/~jrose/values/larval-values.html" rel="noreferrer" target="_blank">https://cr.openjdk.org/~jrose/values/larval-values.html</a><br>
>>> <br>
>>> While this is a rough note, I think all the details are present.<br>
>> <br>
>> The compatibility wins of this strategy do seem nice. But let me scrutinize a few details, because I think there are some trade-offs:<br>
>> <br>
>> 1) A larval value object is an identity object. This means, in the hand-off between the <init> method and the caller, the object must be heap allocated: the caller and the <init> method need an agreed-upon memory location where state will be set up.<br>
>> <br>
>> I can see this being optimized away if the <init> method can be inlined. But if not (e.g., the constructor logic is sufficiently complex/large), that's a new cost for value object creation: every 'new' needs a heap allocation. (For <vnew>, we are able to optimize the return value calling convention without needing inlining.)<br>
>> <br>
>> Am I understanding this correctly? How concerned should we be about the extra allocation cost? (Our working principle to this point has been that optimal compiled code should have zero heap allocations.)<br>
> <br>
> Optimal compiled code can still have this feature, if we choose.<br>
> <br>
> We can direct the compiled version of <init> (for a value class only)<br>
> to alter its calling sequence, dropping the input and returning the<br>
> value. Compiled calls to this guy would omit the input. The<br>
> interpreter adapter for it would adjust the discrepancy. There are<br>
> a number of ways to do this, in detail.<br>
<br>
I would worry about the complexity of such an optimization (the optimized calling convention bears little resemblance to the original, and there needs to be some novel encoding of 'uninitialized' at the call site to express the promise of a value object to be computed later and stored in n different locals/stack positions).<br>
<br>
Another thing that could be done is to have a lightweight on-stack encoding of "larval value object" that could be passed by reference and mutated by an <init> method, but without the overhead of a full heap object. New encodings mean new complexity, but maybe this one would be worth it.<br>
<br>
Or maybe you're right, no need to worry about this corner case, inlining will be fine...<br>
<br>
> (But, also, if the <init> method is complex enough to fail to inline,<br>
> we probably won’t notice the extra cost of a buffered input.<br>
<br>
Yeah, that's fair. I guess the worrying case would be where the existing <vnew> has a high computation time cost but zero memory impact; the <init> strategy would be bad on both dimensions.<br>
<br>
> Note that all of these worries only apply to value classes<br>
> with non-deprecated constructors. New code will use factory methods,<br>
> which doesn’t need to suffer from failed inlines, again because of<br>
> an adjusted heuristic, if we need it.<br>
<br>
There's no particular reason that new code would favor factories instead. (At least, there doesn't need to be. This compilation strategy makes it even easier for us to say in the language "almost nothing about constructors has changed, carry on as you have before.")<br>
<br>
But it's true that, in a performance-sensitive application, an expensive constructor could be rewritten as a static factory with a private constructor that just sets the fields. And the calling convention for that factory will support scalarization. Such a refactoring shrinks the lifespan of the problematic larval object to the point that inlining & eliminating it should be trivial.<br>
<br>
My takeaway is just that we should be cautious here: where before we had a guarantee of no new allocations from value class constructors (modulo some size threshold), now we're in the fuzzy territory of "if everything shakes out okay, you shouldn't notice any impact". This may be fine, but we'll want to keep an eye on it.<br>
<br>
>> 3) The <vnew> approach doesn't have any constraints about leaking 'this', and in particular the javac rule we were envisioning is that the constructor can't leak 'this' until all fields are provably set, but aftewards it's fair game. This <init> strategy is stricter: the verifier disallows leaking 'this' at all from any point in the constructor.<br>
> <br>
> Yes, easy leaking is a feature of <vnew>; the value is always ready.<br>
> (This also means the interpreter has to create a new buffer on every<br>
> state change. I don’t care much about interpreter performance, but<br>
> I think the <init> version of things performs fewer allocations.)<br>
> <br>
>> Are we okay with these restrictions? In practice, this is most likely to trip up people trying to do instance method calls, plus those who are doing things like keeping track of constructed objects. (Even printf logging seems tricky, since 'toString' is off limits.)<br>
> <br>
> If we wish to allow the super call after all, it can serve as the freeze<br>
> point within the constructor. It is still the case that the freeze must<br>
> be performed before the value is usable as an adult, and there is no way<br>
> to perform “late” putfields after the freeze.<br>
<br>
Yeah, you've got me thinking that maybe a rule that says you can set fields before 'super()' but not after would be good enough. (With a language change that says in a value class, the implicit 'super()' call happens at the end rather than the start. If you want to write any post-super() code, you'll need an explicit super call.)<br>
<br>
That sort of bottom-to-top initialization strategy is a change from tradition, but maybe we're mostly equipped to handle it already? (Thanks, JEP 447!)<br>
<br>
> If the language wishes to fully implement “late” putfields<br>
<br>
No. I don't think publishing 'this' before all value object fields are set is on the table.<br>
<br>
>> 4) I'm not sure the prohibition on 'super' calls is actually necessary.<br>
> <br>
> No, but it’s a move of economy. Defining the meaning of super for<br>
> values would be extra work. We could do that; I’d prefer not to.<br>
> Remember that super-constructors for values are already very special<br>
> animals: They must be empty in a special sense. Forbidding calls to<br>
> them seems like the clean move.<br>
<br>
The rule is that super constructors must be empty because we had no concept of mutable state to communicate changes from parent to child. But now that we have larval objects...<br>
<br>
Concretely, what if:<br>
<br>
- putfield is a verifier error on non-identity class types, it only works on uninitializedThis<br>
- as usual, every <init> method (for all kinds of classes) must do a super-<init> invokespecial (or this-<init>? still thinking about that)<br>
<br>
Then:<br>
<br>
- value objects get built bottom-to-top, with fields set before a super() call, and freedom to use 'this' afterwards<br>
- abstract classes can participate too, following the same code shape<br>
- identity classes (abstract and concrete) have a little more freedom, because they can follow the same pattern *or* set their fields after the super() call<br></blockquote><div><br></div><div>Flattened values can't support layout polymorphism which means all uses of a common abstract class will need to rely on pointer polymorphism (this was already true before this change). If abstract classes which are super classes of value classes can now have fields, does that encourage developers to adopt patterns which rely on pointer polymorphism and forfeit flattening? The concern here is less about can we do this but more about should we do it?</div><div><br></div><div>Does this hold together with our story on implicit constructors (generate both an implicit_creation attribute and a method in the class)?</div><div><br></div><div>--Dan</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
I need to think more about this, but it seems to me at the moment that everything falls out cleanly...<br>
<br>
>> (If it works, does this mean we get support for super fields "for free"?)<br>
> <br>
> That is probably true. Do we care?<br>
<br>
I'd be happy to get rid of special rules that have to do with super fields. (Replacing it with a rule that says certain shapes of abstract class constructors imply identity.) Not so much because of particular use cases, but because it makes the language more regular.<br>
<br>
</blockquote></div></div>