<div dir="ltr">In my case this happens to not be a concern for structural reasons... The lambdas in question are used to satisfy one of the 3 available calling conventions for functions in my languages (we can compile calls to 'static positional calls', aka, invokestatic on a function like R foo(V v1,V v2,V v3,...),  'static map calls' aka invokestatic on R foo(Map<String, V> params),  or we can capture functions as values and pass them around, in which case we capture the second signature in a lambda.  <div><br></div><div>For each function we just compile 3 static methods with the 3 signatures</div><div>public static CompiledTemplate foo() {  return ThisClass::foo; } <-- this is the lambda capture I am considering replacing with condy</div><div>public static Result foo(Map<String, V> m,...) {  return foo(m.get("v1"), m.get("v2"),...); }</div><div>public static Result foo(V v1, V v2,...) {...} </div><div><br></div><div>So all callers calling the lambda method share an instance if it changes to use condy or doesn't. The method dispatching within the lambda is also itself an `invokestatic` style call, so I don't think there even is a penalty?</div><div><br></div><div>Since the type profiling opportunities are statically analyzable (is the capture a virtual method? is there a signature mismatch between the interface and the delegate?), does this imply that there are some simple rules that the compiler could use to split the difference?  or perhaps LMF itself could opportunistically cache and reuse lambda instances when we expect that the jit has no particular opportunities?</div><div><br></div><div>Or maybe this is all fraught since putting logic in the compiler or runtime that assumes particular behavior of the JIT is just a risky proposition.</div><div><br></div><div>This does remind me of an odd issue I ran into with condy where I actually wanted to split two condys that were identical to be executed twice to preserve identity semantics.  I ended up solving this by just adding <a href="https://github.com/google/closure-templates/blob/18782276599c09ea565cb08dcc1832227797ff8f/java/src/com/google/template/soy/jbcsrc/shared/ExtraConstantBootstraps.java#L54">dummy parameters </a>to my bootstrap methods.  A similar solution could be used for LMF+condy where we preserve 'distinctly' spun classes but gain 'faster linkage'</div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, Oct 21, 2023 at 11:59 AM John Rose <<a href="mailto:john.r.rose@oracle.com">john.r.rose@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">On 20 Oct 2023, at 13:59, Brian Goetz wrote:<br>
<br>
> The optimization is appealing because condy linkage is cheaper than indy linkage, and because it would allow more sharing between identical lambdas in the same file (they'd map to the same condy, which would only have to be resolved once.)<br>
<br>
Using unshared lambdas has a small advantage which is sometimes significant.<br>
Lambdas accumulate type profiles, and those type profiles add an extra<br>
hidden layer of customization to the lambdas, exploited by the JIT sometimes.<br>
<br>
So you might have two identical lambdas, for which the JIT produces completely<br>
different code, because it devirtualizes something in the lambda (after<br>
inlining the desugared body), and devirtualizes differently for the two<br>
different lambdas.<br>
<br>
All I’m saying here is that sharing is not always a win.  Sometimes the<br>
opposite move, splitting and customization, is the performance win,<br>
even though you have two copies of the code in the end.<br>
<br>
So, Luke is proposing an interesting experiment, one which is likely to<br>
improve startup and footprint, but could also leave some technical debt<br>
to the profiler and JIT.  Note also that JIT code quality impacts<br>
startup and warmup; you can see startup itself get worse if you do<br>
something the JIT doesn’t like.<br>
<br>
The fun thing about invokedynamic (unlike any other instruction) is that<br>
it relinks itself for every distinct occurrence of the instruction.<br>
(Other instructions become fully linked as soon as their CP entries<br>
link, so sharing or splitting the instructions does not affect the<br>
resolution of CP entries.)  If you have two identical invokedynamic<br>
instructions, they incur two resolutions.  This means the LMF might<br>
possibly spin two identical but distinct lambda classes.  That might<br>
seem to be a waste, but there are hidden benefits.  The invokedynamic<br>
instruction turns the default towards “split them all” while the<br>
condy constant turns it towards “share them all”.<br>
<br>
</blockquote></div>