Fix lambda deserialization in classes with 252+ lambdas#5857
Fix lambda deserialization in classes with 252+ lambdas#5857retronym merged 2 commits intoscala:2.12.xfrom
Conversation
|
Unfortunately, this change uses exceptions as control flow for lambda#252 and above, but that seems to be the best way around the compatibility constraint that the newly generated code for I've prototyped a change to avoid the overhead of filling in the stack trace of the |
| mv.visitVarInsn(ASTORE, 1) | ||
| mv.visitVarInsn(ALOAD, 1) | ||
| mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/IllegalArgumentException", "getCause", "()Ljava/lang/Throwable;", false) | ||
| mv.visitJumpInsn(IFNULL, nextLabel(i)) |
There was a problem hiding this comment.
should there be an ALOAD 1; ATHROW here?
There was a problem hiding this comment.
Actually, I've just noticed that the second throw IllegalArgumentException in LambdaDeserializer is detritus that should have been removed in 131402f.
I'll remove that throw and then simplify this PR (no need to check getCause == null here, just swallow any IAE unconditionally in all but the terminal indy call.
|
Looks good otherwise. I guess for 2.13 we can have a solution that doesn't use exceptional control flow? You could mark the commit |
I should have removed the try/catch when I removed the code path that could throw that exception in 131402f.
|
Pushed the discussed simplification.
I'd rather merge this into 2.13.x and then adapt the result, I've opened a ticket to track that: https://github.com/scala/scala-dev/issues/373 |
|
Sure, that's fine too. |
| def nextLabel(i: Int) = if (i == numGroups - 2) terminalLabel else initialLabels(i + 1) | ||
|
|
||
| for ((label, i) <- initialLabels.iterator.zipWithIndex) { | ||
| mv.visitTryCatchBlock(label, nextLabel(i), nextLabel(i), "java/lang/IllegalArgumentException") |
There was a problem hiding this comment.
Can you add jlIllegalArgumentExceptionRef to CoreBTypes, like jlClassCastExceptionRef, then use .internalName? This ensures that the ClassBType is part of the classBTypeFromInternalName map, which is excpected for computing stack map frames during class writing.
| * case i: IllegalArgumentException => // swallow | ||
| * } | ||
| * </FOR> | ||
| * return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup${NUM_GROUPS-1}](l) |
There was a problem hiding this comment.
the generated structure is more like
try return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup$0](l)
catch {
case i: IllegalArgumentException =>
try return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup$1](l)
catch {
case i: IllegalArgumentException =>
...
return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup${NUM_GROUPS-1}](l)
}
}
There was a problem hiding this comment.
Is there a difference? In any case, your version is clearer, so I'll use that.
There was a problem hiding this comment.
Just a detail to make the comment align better with the implementation.
| lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable | ||
| lazy val jiSerializableRef : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable | ||
| lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException | ||
| lazy val jlIllegalArgExceptionRef : ClassBType = classBTypeFromSymbol(IllegalArgExceptionClass) // java/lang/ClassCastException |
| def nextLabel(i: Int) = if (i == numGroups - 2) terminalLabel else initialLabels(i + 1) | ||
|
|
||
| for ((label, i) <- initialLabels.iterator.zipWithIndex) { | ||
| mv.visitTryCatchBlock(label, nextLabel(i), nextLabel(i), coreBTypes.jlIllegalArgExceptionRef.internalName) |
Create a lambda deserializer per group of target methods, and call these sequentially trapping the particular pattern of exception that is thrown when a target method is absent from the map. Fixes scala/bug#10232 ``` // access flags 0x100A private static synthetic $deserializeLambda$(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; TRYCATCHBLOCK L0 L1 L1 java/lang/IllegalArgumentException L0 ALOAD 0 INVOKEDYNAMIC lambdaDeserialize(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; [ // handle kind 0x6 : INVOKESTATIC scala/runtime/LambdaDeserialize.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; // arguments: // handle kind 0x6 : INVOKESTATIC Test$.$anonfun$main$1$adapted(Lscala/Function1;)Ljava/lang/Object;, // handle kind 0x6 : INVOKESTATIC Test$.$anonfun$lambdas$1(Ljava/lang/Object;)Ljava/lang/String;, ... Test$.$anonfun$lambdas$249(Ljava/lang/Object;)Ljava/lang/String;, // handle kind 0x6 : INVOKESTATIC Test$.$anonfun$lambdas$250(Ljava/lang/Object;)Ljava/lang/String; ] ARETURN L1 ALOAD 0 INVOKEDYNAMIC lambdaDeserialize(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; [ // handle kind 0x6 : INVOKESTATIC scala/runtime/LambdaDeserialize.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; // arguments: // handle kind 0x6 : INVOKESTATIC Test$.$anonfun$lambdas$251(Ljava/lang/Object;)Ljava/lang/String;, // handle kind 0x6 : INVOKESTATIC Test$.$anonfun$lambdas$252(Ljava/lang/Object;)Ljava/lang/String;, ... // handle kind 0x6 : INVOKESTATIC Test$.$anonfun$lambdas$256(Ljava/lang/Object;)Ljava/lang/String; ] ARETURN MAXSTACK = 2 MAXLOCALS = 1 ```
Create a lambda deserializer per group of target methods,
and call these sequentially trapping the particular pattern
of exception that is thrown when a target method is absent
from the map.
Fixes scala/bug#10232