Skip to content

Commit 7be7786

Browse files
committed
SI-8359 Emit invokedynamic for lambdas
Suitable lambdas are identified in Delambdafy and marked with such with a tree annotation that includes the data needed by the backend to emit an invokedynamic instruction. GenBCode to rewrite instantiation of such anonymous function classes with an invokedynamic instruction. At this stage, I don't plan to merge the support for this into GenASM. Between these points, the lambda capture is represented as an application of a dummy factory symbol: ``` <dummy>(captures...) : FunctionN ``` Demo: ``` % wget http://central.maven.org/maven2/org/scala-lang/modules/scala-java8-compat_2.11/0.3.0/scala-java8-compat_2.11-0.3.0.jar % qscala -classpath scala-java8-compat_2.11-0.3.0.jar -Ydelambdafy:method -target:jvm-1.8 -Ybackend:GenBCode Welcome to Scala version 2.11.6-20150309-144147-c91c978c81 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> (() => "").getClass res0: Class[_ <: () => String] = class $$Lambda$1/871790326 ``` I have also corrected an error in a previous commit. The newly added symbol test, `isDelambdafyTarget`, needs to check for the `ARTIFACT` flag, as that is what is added to the method by `Uncurry`.
1 parent 555f8f0 commit 7be7786

File tree

5 files changed

+101
-12
lines changed

5 files changed

+101
-12
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package backend
1010
package jvm
1111

1212
import scala.annotation.switch
13+
import scala.reflect.internal.Flags
1314

1415
import scala.tools.asm
1516
import GenBCode._
@@ -632,6 +633,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
632633
case _ =>
633634
abort(s"Cannot instantiate $tpt of kind: $generatedType")
634635
}
636+
case Apply(_, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] =>
637+
val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get
638+
genLoadArguments(args, paramTKs(app))
639+
genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface)
635640

636641
case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
637642
val nativeKind = tpeTK(expr)
@@ -1280,6 +1285,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
12801285
def genSynchronized(tree: Apply, expectedType: BType): BType
12811286
def genLoadTry(tree: Try): BType
12821287

1288+
def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) {
1289+
val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC)
1290+
1291+
val targetHandle =
1292+
new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL,
1293+
classBTypeFromSymbol(lambdaTarget.owner).internalName,
1294+
lambdaTarget.name.toString,
1295+
asmMethodType(lambdaTarget).descriptor)
1296+
val receiver = if (isStaticMethod) None else Some(lambdaTarget.owner)
1297+
val (capturedParams, lambdaParams) = lambdaTarget.paramss.head.splitAt(lambdaTarget.paramss.head.length - arity)
1298+
// Requires https://github.com/scala/scala-java8-compat on the runtime classpath
1299+
val returnUnit = lambdaTarget.info.resultType.typeSymbol == UnitClass
1300+
val functionalInterfaceDesc: String = classBTypeFromSymbol(functionalInterface).descriptor
1301+
val desc = (receiver.toList ::: capturedParams).map(sym => toTypeKind(sym.info)).mkString(("("), "", ")") + functionalInterfaceDesc
1302+
1303+
// TODO specialization
1304+
val constrainedType = new MethodBType(lambdaParams.map(p => toTypeKind(p.tpe)), toTypeKind(lambdaTarget.tpe.resultType)).toASMType
1305+
val abstractMethod = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply))
1306+
val methodName = abstractMethod.name.toString
1307+
val applyN = {
1308+
val mt = asmMethodType(abstractMethod)
1309+
mt.toASMType
1310+
}
1311+
1312+
bc.jmethod.visitInvokeDynamicInsn(methodName, desc, lambdaMetaFactoryBootstrapHandle,
1313+
// boostrap args
1314+
applyN, targetHandle, constrainedType
1315+
)
1316+
}
12831317
}
12841318

1319+
val lambdaMetaFactoryBootstrapHandle =
1320+
new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
1321+
"java/lang/invoke/LambdaMetafactory", "metafactory",
1322+
"(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;")
1323+
12851324
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ abstract class BCodeIdiomatic extends SubComponent {
412412
jmethod.instructions.add(node)
413413
if (settings.YoptInlinerEnabled) callsitePositions(node) = pos
414414
}
415+
final def invokedynamic(owner: String, name: String, desc: String) {
416+
jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
417+
}
415418

416419
// can-multi-thread
417420
final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }

src/compiler/scala/tools/nsc/transform/Delambdafy.scala

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
7979
sealed abstract class TransformedFunction
8080
// A class definition for the lambda, an expression insantiating the lambda class
8181
case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction
82+
case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction
8283

8384
// here's the main entry point of the transform
8485
override def transform(tree: Tree): Tree = tree match {
@@ -93,6 +94,9 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
9394
lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg)
9495

9596
super.transform(newExpr)
97+
case InvokeDynamicLambda(apply) =>
98+
// ... or an invokedynamic call
99+
super.transform(apply)
96100
}
97101
case _ => super.transform(tree)
98102
}
@@ -124,6 +128,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
124128
if (!thisReferringMethods.contains(target))
125129
target setFlag STATIC
126130

131+
val isStatic = target.hasFlag(STATIC)
127132

128133
/**
129134
* Creates the apply method for the anonymous subclass of FunctionN
@@ -199,7 +204,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
199204
val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe
200205

201206
// anonymous subclass of FunctionN with an apply method
202-
def makeAnonymousClass = {
207+
def makeAnonymousClass: ClassDef = {
203208
val parents = addSerializable(abstractFunctionErasedType)
204209
val funOwner = originalFunction.symbol.owner
205210

@@ -232,7 +237,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
232237
// the Optional proxy that will hold a reference to the 'this'
233238
// object used by the lambda, if any. NoSymbol if there is no this proxy
234239
val thisProxy = {
235-
if (target.hasFlag(STATIC))
240+
if (isStatic)
236241
NoSymbol
237242
else {
238243
val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC)
@@ -271,22 +276,58 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
271276
val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod
272277

273278
// TODO if member fields are private this complains that they're not accessible
274-
(localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef], thisProxy)
279+
localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef]
275280
}
276281

277-
val (anonymousClassDef, thisProxy) = makeAnonymousClass
282+
val useLambdaMetafactory = {
283+
val hasValueClass = exitingErasure {
284+
val methodType: Type = targetMethod(originalFunction).info
285+
methodType.exists(_.isInstanceOf[ErasedValueType])
286+
}
287+
val isTarget18 = settings.target.value.contains("jvm-1.8")
288+
settings.isBCodeActive && isTarget18 && !hasValueClass
289+
}
290+
291+
val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil
278292

279-
pkg.info.decls enter anonymousClassDef.symbol
293+
def anonClass: TransformedFunction = {
294+
val anonymousClassDef = makeAnonymousClass
295+
pkg.info.decls enter anonymousClassDef.symbol
296+
val captureArgs = captures map (capture => Ident(capture) setPos originalFunction.pos)
280297

281-
val thisArg = optionSymbol(thisProxy) map (_ => gen.mkAttributedThis(oldClass) setPos originalFunction.pos)
282-
val captureArgs = captures map (capture => Ident(capture) setPos originalFunction.pos)
298+
val newStat =
299+
Typed(New(anonymousClassDef.symbol, thisArg ++ captureArgs: _*), TypeTree(abstractFunctionErasedType))
283300

284-
val newStat =
285-
Typed(New(anonymousClassDef.symbol, (thisArg.toList ++ captureArgs): _*), TypeTree(abstractFunctionErasedType))
301+
val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
286302

287-
val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
303+
DelambdafyAnonClass(anonymousClassDef, typedNewStat)
304+
}
288305

289-
DelambdafyAnonClass(anonymousClassDef, typedNewStat)
306+
if (useLambdaMetafactory) {
307+
val arity = originalFunction.vparams.length
308+
val functionalInterface: Symbol = {
309+
val sym = originalFunction.tpe.typeSymbol
310+
val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage
311+
val returnUnit = restpe.typeSymbol == UnitClass
312+
val functionInterfaceArray =
313+
if (returnUnit) currentRun.runDefinitions.Scala_Java8_CompatPackage_JProcedure
314+
else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction
315+
functionInterfaceArray.apply(arity)
316+
}
317+
if (functionalInterface.exists) {
318+
val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList
319+
val allCaptureArgs = thisArg ++: captureArgs
320+
321+
val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT)
322+
val argTypes: List[Type] = allCaptureArgs.map(_.tpe)
323+
val params = msym.newSyntheticValueParams(argTypes)
324+
msym.setInfo(MethodType(params, originalFunction.tpe))
325+
326+
val tree = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply]
327+
tree.updateAttachment(LambdaMetaFactoryCapable(targetMethod(originalFunction), arity, functionalInterface))
328+
InvokeDynamicLambda(tree)
329+
} else anonClass
330+
} else anonClass
290331
}
291332

292333
/**
@@ -436,4 +477,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
436477
super.traverse(tree)
437478
}
438479
}
480+
481+
final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol)
439482
}

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,10 @@ trait Definitions extends api.StandardDefinitions {
15131513

15141514
def isPolymorphicSignature(sym: Symbol) = PolySigMethods(sym)
15151515
private lazy val PolySigMethods: Set[Symbol] = Set[Symbol](MethodHandle.info.decl(sn.Invoke), MethodHandle.info.decl(sn.InvokeExact)).filter(_.exists)
1516+
1517+
lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.compat.java8")
1518+
lazy val Scala_Java8_CompatPackage_JFunction = (0 to MaxTupleArity).toArray map (i => getMemberIfDefined(Scala_Java8_CompatPackage.moduleClass, TypeName("JFunction" + i)))
1519+
lazy val Scala_Java8_CompatPackage_JProcedure = (0 to MaxTupleArity).toArray map (i => getMemberIfDefined(Scala_Java8_CompatPackage.moduleClass, TypeName("JProcedure" + i)))
15161520
}
15171521
}
15181522
}

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
794794

795795
final def isAnonymousFunction = isSynthetic && (name containsName tpnme.ANON_FUN_NAME)
796796
final def isDelambdafyFunction = isSynthetic && (name containsName tpnme.DELAMBDAFY_LAMBDA_CLASS_NAME)
797-
final def isDelambdafyTarget = isSynthetic && isMethod && (name containsName tpnme.ANON_FUN_NAME)
797+
final def isDelambdafyTarget = isArtifact && isMethod && (name containsName tpnme.ANON_FUN_NAME)
798798
final def isDefinedInPackage = effectiveOwner.isPackageClass
799799
final def needsFlatClasses = phase.flatClasses && rawowner != NoSymbol && !rawowner.isPackageClass
800800

0 commit comments

Comments
 (0)