Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix lambda deserialization in classes with 252+ lambdas #5857

Merged
merged 2 commits into from
Apr 24, 2017

Conversation

retronym
Copy link
Member

@retronym retronym commented Apr 19, 2017

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

@scala-jenkins scala-jenkins added this to the 2.12.3 milestone Apr 19, 2017
@retronym
Copy link
Member Author

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 $lambdaDeserialize$ should work when run with older standard libraries.

I've prototyped a change to avoid the overhead of filling in the stack trace of the IllegalArgumentException used for control flow (2.12.x...retronym:ticket/10232-stackless), but I'm reluctant to merge that it also relies on hacks to avoid requiring API changes to the runtime support.

@retronym retronym requested a review from lrytz April 19, 2017 01:45
@retronym retronym changed the title Fix lambda serialization in classes with 252+ lambdas Fix lambda deserialization in classes with 252+ lambdas Apr 19, 2017
mv.visitVarInsn(ASTORE, 1)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/IllegalArgumentException", "getCause", "()Ljava/lang/Throwable;", false)
mv.visitJumpInsn(IFNULL, nextLabel(i))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should there be an ALOAD 1; ATHROW here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@lrytz
Copy link
Member

lrytz commented Apr 19, 2017

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 [nomerge].

I should have removed the try/catch when I removed the code path that
could throw that exception in 131402f.
@retronym
Copy link
Member Author

retronym commented Apr 20, 2017

Pushed the discussed simplification.

I guess for 2.13 we can have a solution that doesn't use exceptional control flow? You could mark the commit [nomerge].

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

@lrytz
Copy link
Member

lrytz commented Apr 20, 2017

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")
Copy link
Member

@lrytz lrytz Apr 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* case i: IllegalArgumentException => // swallow
* }
* </FOR>
* return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup${NUM_GROUPS-1}](l)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference? In any case, your version is clearer, so I'll use that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a detail to make the comment align better with the implementation.

@@ -103,6 +103,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is out of synch..

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coreBTypes._ is already imported

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

```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants