JDK-8308023 - Exponential classfile blowup with nested try/finally

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Jul 31 09:44:03 UTC 2023


On 30/07/2023 22:24, Archie Cobbs wrote:
> In summary, JSR/RET is gone now, and the verifier also makes it 
> difficult to synthesize "subroutines". This makes the compiler's job a 
> lot harder wrt. finally blocks.

I tend to agree with this analysis. I think there are some tricks that 
could be done to reduce the overall size, or avoid combinatorial 
explosion, but, as observed in this thread, the caveat is that such 
tricks would only work with certain code shapes. There are two things 
that typically require duplication:

* the stack types might be different in the two exit points (because of 
the extra exception)
* the target instruction we should "jump to" immediately after executing 
the finalizer might vary depending on the exit point

This means that not all duplicated finalizers we see in the generated 
bytecode are really 100% duplicates. The case Jan brings up is a little 
different, as there's nested try/finally there (so the innermost 
exceptional exit point is repeated twice). That said, all really bad 
combinatorial cases will end up with nested try/finally in some shape 
and form, so perhaps targeting this specific idiom is a pragmatic 
compromise.

I just wanted to add a concrete note to the problem of performance 
w.r.t. shape of generated try/finally blocks. Look at this bug:

https://bugs.openjdk.org/browse/JDK-8267532

The problem here is that using a TWR is slower than not using it. The 
issue has to do with the fact that the compiler emits to calls to 
Resource::close, one in the "hot path" and one in the exceptional path. 
Unfortunately, the call to "close" in the exceptional path is not 
inlined (because it's never taken), so the compiler sees a way for the 
Resource to escape outside the method, meaning that now the resource 
object can no longer be scalarized (thus resulting in increase in GC 
activity).

So, my general sense is that having a combinatorial number of exit 
points (which will only rarely be taken) might be detrimental to 
performance anyway - as C2 will see a lot of "cold" code paths by which 
objects can escape. That said, even if javac deduplicates the exit 
points, I'm not sure that C2 might be able to keep the code in the same 
shape as generated by javac, as C2 likes to emit one version of the code 
for the "common, hot path", and one version of the code for the 
"uncommon, cold path" (which is typically executed when some invariants 
are violated). So, reducing bytecode footprint in javac might well 
result in more work for the C2 compiler which would have to disentangle 
the nest of try/finally blocks.

Maurizio



More information about the compiler-dev mailing list