Skip to content

Commit 3365a36

Browse files
committed
Add support for serializable lambdas
See scala/scala3#5836 for more information.
1 parent 9ef70dd commit 3365a36

File tree

3 files changed

+55
-11
lines changed

3 files changed

+55
-11
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package jvm
1212
import scala.annotation.switch
1313

1414
import scala.tools.asm
15-
import scala.tools.asm.Label
15+
import scala.tools.asm.{Handle, Label, Opcodes}
1616
import scala.tools.nsc.backend.jvm.BCodeHelpers.InvokeStyle
1717

1818
/*
@@ -27,6 +27,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
2727
import int._
2828
import bTypes._
2929
import coreBTypes._
30+
import BCodeBodyBuilder._
3031

3132
/*
3233
* Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions.
@@ -1448,8 +1449,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
14481449
def genLoadTry(tree: Try): BType
14491450

14501451
def genInvokeDynamicLambda(ctor: Symbol, lambdaTarget: Symbol, environmentSize: Int, functionalInterface: Symbol): BType = {
1452+
import java.lang.invoke.LambdaMetafactory.FLAG_SERIALIZABLE
1453+
14511454
debuglog(s"Using invokedynamic rather than `new ${ctor.owner}`")
14521455
val generatedType = classBTypeFromSymbol(functionalInterface)
1456+
// Lambdas should be serializable if they implement a SAM that extends Serializable or if they
1457+
// implement a scala.Function* class.
1458+
val isSerializable = functionalInterface.isSerializable || functionalInterface.isFunctionClass
14531459
val isInterface = lambdaTarget.owner.isEmittedInterface
14541460
val invokeStyle =
14551461
if (lambdaTarget.isStaticMember) asm.Opcodes.H_INVOKESTATIC
@@ -1481,18 +1487,45 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
14811487
val mt = asmMethodType(abstractMethod)
14821488
mt.toASMType
14831489
}
1484-
bc.jmethod.visitInvokeDynamicInsn(methodName, desc, lambdaMetaFactoryBootstrapHandle,
1485-
// boostrap args
1486-
applyN, targetHandle, constrainedType
1487-
)
1490+
val bsmArgs0 = Seq(applyN, targetHandle, constrainedType)
1491+
val bsmArgs =
1492+
if (isSerializable)
1493+
bsmArgs0 +: Int.box(FLAG_SERIALIZABLE)
1494+
else
1495+
bsmArgs0
1496+
1497+
val metafactory =
1498+
if (isSerializable)
1499+
lambdaMetaFactoryAltMetafactoryHandle // altMetafactory needed to be able to pass the SERIALIZABLE flag
1500+
else
1501+
lambdaMetaFactoryMetafactoryHandle
1502+
1503+
bc.jmethod.visitInvokeDynamicInsn(methodName, desc, metafactory, bootstrapArgs: _*)
14881504

14891505
generatedType
14901506
}
14911507
}
1492-
val lambdaMetaFactoryBootstrapHandle =
1493-
new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
1494-
int.LambdaMetaFactory.javaBinaryName, int.MetafactoryName,
1495-
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
1496-
/* itf = */ int.LambdaMetaFactory.isEmittedInterface)
1508+
}
14971509

1510+
object BCodeBodyBuilder {
1511+
val lambdaMetaFactoryMetafactoryHandle = new Handle(
1512+
Opcodes.H_INVOKESTATIC,
1513+
"java/lang/invoke/LambdaMetafactory",
1514+
"metafactory",
1515+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
1516+
/* itf = */ false)
1517+
1518+
val lambdaMetaFactoryAltMetafactoryHandle = new Handle(
1519+
Opcodes.H_INVOKESTATIC,
1520+
"java/lang/invoke/LambdaMetafactory",
1521+
"altMetafactory",
1522+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
1523+
/* itf = */ false)
1524+
1525+
val lambdaDeserializeBootstrapHandle = new Handle(
1526+
Opcodes.H_INVOKESTATIC,
1527+
"scala/runtime/LambdaDeserialize",
1528+
"bootstrap",
1529+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;",
1530+
/* itf = */ false)
14981531
}

src/compiler/scala/tools/nsc/backend/jvm/BackendInterface.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
511511
def isJavaEntryPoint: Boolean
512512
def isJavaDefaultMethod: Boolean
513513
def isClassConstructor: Boolean
514+
def isSerializable: Boolean
514515

515516
/**
516517
* True for module classes of modules that are top-level or owned only by objects. Module classes
@@ -585,6 +586,11 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
585586

586587

587588
def samMethod(): Symbol
589+
590+
/** Is this the symbol of one of the `scala.Function*` class ?
591+
* Note that this will return false for subclasses of these classes.
592+
*/
593+
def isFunctionClass: Boolean
588594
}
589595

590596
abstract class TypeHelper {
@@ -723,7 +729,6 @@ abstract class BackendInterfaceDefinitions { self: BackendInterface =>
723729

724730
val ScalaATTRName: String = "Scala"
725731
val ScalaSignatureATTRName: String = "ScalaSig"
726-
val MetafactoryName: String = "metafactory"
727732

728733
def doLabmdasFollowJVMMetafactoryOrder: Boolean = true
729734

@@ -745,6 +750,8 @@ abstract class BackendInterfaceDefinitions { self: BackendInterface =>
745750
val JavaSerializableClass: Symbol = requiredClass[java.io.Serializable]
746751
val SerializableClass: Symbol = requiredClass[scala.Serializable]
747752
val ClassCastExceptionClass: Symbol = requiredClass[java.lang.ClassCastException]
753+
val IllegalArgExceptionClass: Symbol = requiredClass[java.lang.IllegalArgumentException]
754+
val SerializedLambdaClass: Symbol = requiredClass[java.lang.invoke.SerializedLambda]
748755

749756
val ClassfileAnnotationClass: Symbol = requiredClass[scala.annotation.ClassfileAnnotation]
750757
val BoxedNumberClass: Symbol = requiredClass[java.lang.Number]

src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: BackendInterface]](val bTypes: B
119119
lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable
120120
lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable
121121
lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException
122+
lazy val jlIllegalArgExceptionRef: ClassBType = classBTypeFromSymbol(IllegalArgExceptionClass)
123+
lazy val jliSerializedLambdaRef: ClassBType = classBTypeFromSymbol(SerializedLambdaClass)
122124

123125
lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef])
124126
lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef])
@@ -249,6 +251,8 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: BackendInterface]](va
249251
def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference
250252
def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference
251253
def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference
254+
def jlIllegalArgExceptionRef : ClassBType = _coreBTypes.jlIllegalArgExceptionRef
255+
def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef
252256

253257
def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef
254258
def srByteRef : ClassBType = _coreBTypes.srByteRef

0 commit comments

Comments
 (0)