diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2015-03-09 14:20:17 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2015-04-21 16:51:00 +1000 |
commit | 7be7786a67323b5c138eab650c9bf0d23924a688 (patch) | |
tree | 73452d2cd5ef1dd75b6535f038bcbcae40483098 | |
parent | 555f8f09c90b7014f153488e442f2d107b18b6e5 (diff) | |
download | scala-7be7786a67323b5c138eab650c9bf0d23924a688.tar.gz scala-7be7786a67323b5c138eab650c9bf0d23924a688.tar.bz2 scala-7be7786a67323b5c138eab650c9bf0d23924a688.zip |
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`.
5 files changed, 101 insertions, 12 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 15b014bdd3..8ebe27e61b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -10,6 +10,7 @@ package backend package jvm import scala.annotation.switch +import scala.reflect.internal.Flags import scala.tools.asm import GenBCode._ @@ -632,6 +633,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case _ => abort(s"Cannot instantiate $tpt of kind: $generatedType") } + case Apply(_, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => + val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get + genLoadArguments(args, paramTKs(app)) + genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) @@ -1280,6 +1285,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genSynchronized(tree: Apply, expectedType: BType): BType def genLoadTry(tree: Try): BType + def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) { + val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC) + + val targetHandle = + new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL, + classBTypeFromSymbol(lambdaTarget.owner).internalName, + lambdaTarget.name.toString, + asmMethodType(lambdaTarget).descriptor) + val receiver = if (isStaticMethod) None else Some(lambdaTarget.owner) + val (capturedParams, lambdaParams) = lambdaTarget.paramss.head.splitAt(lambdaTarget.paramss.head.length - arity) + // Requires https://github.com/scala/scala-java8-compat on the runtime classpath + val returnUnit = lambdaTarget.info.resultType.typeSymbol == UnitClass + val functionalInterfaceDesc: String = classBTypeFromSymbol(functionalInterface).descriptor + val desc = (receiver.toList ::: capturedParams).map(sym => toTypeKind(sym.info)).mkString(("("), "", ")") + functionalInterfaceDesc + + // TODO specialization + val constrainedType = new MethodBType(lambdaParams.map(p => toTypeKind(p.tpe)), toTypeKind(lambdaTarget.tpe.resultType)).toASMType + val abstractMethod = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply)) + val methodName = abstractMethod.name.toString + val applyN = { + val mt = asmMethodType(abstractMethod) + mt.toASMType + } + + bc.jmethod.visitInvokeDynamicInsn(methodName, desc, lambdaMetaFactoryBootstrapHandle, + // boostrap args + applyN, targetHandle, constrainedType + ) + } } + val lambdaMetaFactoryBootstrapHandle = + new asm.Handle(asm.Opcodes.H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", "metafactory", + "(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;") + } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index 9993357eee..8f2a17a2bf 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -412,6 +412,9 @@ abstract class BCodeIdiomatic extends SubComponent { jmethod.instructions.add(node) if (settings.YoptInlinerEnabled) callsitePositions(node) = pos } + final def invokedynamic(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc) + } // can-multi-thread final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 2d33b35241..315d96d2e9 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -79,6 +79,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre sealed abstract class TransformedFunction // A class definition for the lambda, an expression insantiating the lambda class case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction + case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction // here's the main entry point of the transform override def transform(tree: Tree): Tree = tree match { @@ -93,6 +94,9 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg) super.transform(newExpr) + case InvokeDynamicLambda(apply) => + // ... or an invokedynamic call + super.transform(apply) } case _ => super.transform(tree) } @@ -124,6 +128,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre if (!thisReferringMethods.contains(target)) target setFlag STATIC + val isStatic = target.hasFlag(STATIC) /** * Creates the apply method for the anonymous subclass of FunctionN @@ -199,7 +204,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe // anonymous subclass of FunctionN with an apply method - def makeAnonymousClass = { + def makeAnonymousClass: ClassDef = { val parents = addSerializable(abstractFunctionErasedType) val funOwner = originalFunction.symbol.owner @@ -232,7 +237,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre // the Optional proxy that will hold a reference to the 'this' // object used by the lambda, if any. NoSymbol if there is no this proxy val thisProxy = { - if (target.hasFlag(STATIC)) + if (isStatic) NoSymbol else { 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 val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod // TODO if member fields are private this complains that they're not accessible - (localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef], thisProxy) + localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef] } - val (anonymousClassDef, thisProxy) = makeAnonymousClass + val useLambdaMetafactory = { + val hasValueClass = exitingErasure { + val methodType: Type = targetMethod(originalFunction).info + methodType.exists(_.isInstanceOf[ErasedValueType]) + } + val isTarget18 = settings.target.value.contains("jvm-1.8") + settings.isBCodeActive && isTarget18 && !hasValueClass + } + + val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil - pkg.info.decls enter anonymousClassDef.symbol + def anonClass: TransformedFunction = { + val anonymousClassDef = makeAnonymousClass + pkg.info.decls enter anonymousClassDef.symbol + val captureArgs = captures map (capture => Ident(capture) setPos originalFunction.pos) - val thisArg = optionSymbol(thisProxy) map (_ => gen.mkAttributedThis(oldClass) setPos originalFunction.pos) - val captureArgs = captures map (capture => Ident(capture) setPos originalFunction.pos) + val newStat = + Typed(New(anonymousClassDef.symbol, thisArg ++ captureArgs: _*), TypeTree(abstractFunctionErasedType)) - val newStat = - Typed(New(anonymousClassDef.symbol, (thisArg.toList ++ captureArgs): _*), TypeTree(abstractFunctionErasedType)) + val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) - val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) + DelambdafyAnonClass(anonymousClassDef, typedNewStat) + } - DelambdafyAnonClass(anonymousClassDef, typedNewStat) + if (useLambdaMetafactory) { + val arity = originalFunction.vparams.length + val functionalInterface: Symbol = { + val sym = originalFunction.tpe.typeSymbol + val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage + val returnUnit = restpe.typeSymbol == UnitClass + val functionInterfaceArray = + if (returnUnit) currentRun.runDefinitions.Scala_Java8_CompatPackage_JProcedure + else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction + functionInterfaceArray.apply(arity) + } + if (functionalInterface.exists) { + val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList + val allCaptureArgs = thisArg ++: captureArgs + + val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) + val argTypes: List[Type] = allCaptureArgs.map(_.tpe) + val params = msym.newSyntheticValueParams(argTypes) + msym.setInfo(MethodType(params, originalFunction.tpe)) + + val tree = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] + tree.updateAttachment(LambdaMetaFactoryCapable(targetMethod(originalFunction), arity, functionalInterface)) + InvokeDynamicLambda(tree) + } else anonClass + } else anonClass } /** @@ -436,4 +477,6 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre super.traverse(tree) } } + + final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) } diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 756ed870ca..98fd8e2361 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1513,6 +1513,10 @@ trait Definitions extends api.StandardDefinitions { def isPolymorphicSignature(sym: Symbol) = PolySigMethods(sym) private lazy val PolySigMethods: Set[Symbol] = Set[Symbol](MethodHandle.info.decl(sn.Invoke), MethodHandle.info.decl(sn.InvokeExact)).filter(_.exists) + + lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.compat.java8") + lazy val Scala_Java8_CompatPackage_JFunction = (0 to MaxTupleArity).toArray map (i => getMemberIfDefined(Scala_Java8_CompatPackage.moduleClass, TypeName("JFunction" + i))) + lazy val Scala_Java8_CompatPackage_JProcedure = (0 to MaxTupleArity).toArray map (i => getMemberIfDefined(Scala_Java8_CompatPackage.moduleClass, TypeName("JProcedure" + i))) } } } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 4a39712ad7..a96948cb69 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -794,7 +794,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def isAnonymousFunction = isSynthetic && (name containsName tpnme.ANON_FUN_NAME) final def isDelambdafyFunction = isSynthetic && (name containsName tpnme.DELAMBDAFY_LAMBDA_CLASS_NAME) - final def isDelambdafyTarget = isSynthetic && isMethod && (name containsName tpnme.ANON_FUN_NAME) + final def isDelambdafyTarget = isArtifact && isMethod && (name containsName tpnme.ANON_FUN_NAME) final def isDefinedInPackage = effectiveOwner.isPackageClass final def needsFlatClasses = phase.flatClasses && rawowner != NoSymbol && !rawowner.isPackageClass |