summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@typesafe.com>2015-05-04 11:17:59 +0200
committerLukas Rytz <lukas.rytz@typesafe.com>2015-05-04 11:17:59 +0200
commit6c75bc40c71d8688fb62759b6e1a23e5e0ba5e93 (patch)
tree2368ed2115504180cea8dd112bd73ec96e62d8af /src
parent9e29061fb75a71c271c5e0a9824aabe93bc269fe (diff)
parent3bf208fd26e0ff272e9aaf9e35446daac4a99901 (diff)
downloadscala-6c75bc40c71d8688fb62759b6e1a23e5e0ba5e93.tar.gz
scala-6c75bc40c71d8688fb62759b6e1a23e5e0ba5e93.tar.bz2
scala-6c75bc40c71d8688fb62759b6e1a23e5e0ba5e93.zip
Merge pull request #4463 from retronym/topic/indylambda-emit-indy
Use LambdaMetafactory where possible for lambda creation.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala39
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala3
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala103
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala17
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala2
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala4
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala2
7 files changed, 147 insertions, 23 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 45a89ac594..92db57c533 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -9,12 +9,17 @@ import scala.reflect.internal.Symbols
import scala.collection.mutable.LinkedHashMap
/**
- * This transformer is responsible for turning lambdas into anonymous classes.
+ * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes
+ * or to a tree that will be convereted to invokedynamic by the JVM 1.8+ backend.
+ *
* The main assumption it makes is that a lambda {args => body} has been turned into
* {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda.
* Currently Uncurry is responsible for that transformation.
*
- * From a lambda, Delambdafy will create
+ * From a lambda, Delambdafy will create:
+ *
+ * Under -target:jvm-1.7 and below:
+ *
* 1) a new top level class that
a) has fields and a constructor taking the captured environment (including possibly the "this"
* reference)
@@ -22,9 +27,11 @@ import scala.collection.mutable.LinkedHashMap
* c) if needed a bridge method for the apply method
* 2) an instantiation of the newly created class which replaces the lambda
*
- * TODO the main work left to be done is to plug into specialization. Primarily that means choosing a
- * specialized FunctionN trait instead of the generic FunctionN trait as a parent and creating the
- * appropriately named applysp method
+ * Under -target:jvm-1.8 with GenBCode:
+ *
+ * 1) An application of the captured arguments to a fictional symbol representing the lambda factory.
+ * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
+ * The captured arguments include `this` if `liftedBody` is unable to be made STATIC.
*/
abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer {
import global._
@@ -79,6 +86,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
sealed abstract class TransformedFunction
// A class definition for the lambda, an expression instantiating 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 +101,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 +135,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 +211,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 +244,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 +283,39 @@ 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
-
- pkg.info.decls enter anonymousClassDef.symbol
-
- 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.toList ++ captureArgs): _*), TypeTree(abstractFunctionErasedType))
-
- val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
+ val allCaptureArgs: List[Tree] = {
+ val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil
+ val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList
+ thisArg ::: captureArgs
+ }
- DelambdafyAnonClass(anonymousClassDef, typedNewStat)
+ val functionalInterface = java8CompatFunctionalInterface(target, originalFunction.tpe)
+ if (functionalInterface.exists) {
+ // Create a symbol representing a fictional lambda factory method that accepts the captured
+ // arguments and returns a Function.
+ 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 arity = originalFunction.vparams.length
+
+ // We then apply this symbol to the captures.
+ val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply]
+
+ // The backend needs to know the target of the lambda and the functional interface in order
+ // to emit the invokedynamic instruction. We pass this information as tree attachment.
+ apply.updateAttachment(LambdaMetaFactoryCapable(target, arity, functionalInterface))
+ InvokeDynamicLambda(apply)
+ } else {
+ val anonymousClassDef = makeAnonymousClass
+ pkg.info.decls enter anonymousClassDef.symbol
+ val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType))
+ val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
+ DelambdafyAnonClass(anonymousClassDef, typedNewStat)
+ }
}
/**
@@ -436,4 +465,38 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
super.traverse(tree)
}
}
+
+ final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol)
+
+ // The functional interface that can be used to adapt the lambda target method `target` to the
+ // given function type. Returns `NoSymbol` if the compiler settings are unsuitable, or `LambdaMetaFactory`
+ // would be unable to generate the correct implementation (e.g. functions referring to derived value classes)
+ private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): Symbol = {
+ val canUseLambdaMetafactory: Boolean = {
+ val hasValueClass = exitingErasure {
+ val methodType: Type = target.info
+ methodType.exists(_.isInstanceOf[ErasedValueType])
+ }
+ val isTarget18 = settings.target.value.contains("jvm-1.8")
+ settings.isBCodeActive && isTarget18 && !hasValueClass
+ }
+
+ def functionalInterface: Symbol = {
+ val sym = functionType.typeSymbol
+ val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage
+ val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs)
+ val paramTps :+ restpe = functionType.typeArgs
+ val arity = paramTps.length
+ if (name1.toTypeName == sym.name) {
+ 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)
+ } else {
+ pack.info.decl(name1.toTypeName.prepend("J"))
+ }
+ }
+ if (canUseLambdaMetafactory) functionalInterface else NoSymbol
+ }
}
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 1a24c668ba..53a1347a48 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -303,6 +303,17 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
}
+ def specializedFunctionName(sym: Symbol, args: List[Type]) = exitingSpecialize {
+ require(isFunctionSymbol(sym), sym)
+ val env: TypeEnv = TypeEnv.fromSpecialization(sym, args)
+ specializedClass.get((sym, env)) match {
+ case Some(x) =>
+ x.name
+ case None =>
+ sym.name
+ }
+ }
+
/** Return the specialized name of 'sym' in the given environment. It
* guarantees the same result regardless of the map order by sorting
* type variables alphabetically.
@@ -315,10 +326,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
if (sym.isClass) env.keySet
else specializedTypeVars(sym).intersect(env.keySet)
)
+ specializedName(sym.name, tvars, env)
+ }
+
+ private def specializedName(name: Name, tvars: immutable.Set[Symbol], env: TypeEnv): TermName = {
val (methparams, others) = tvars.toList sortBy ("" + _.name) partition (_.owner.isMethod)
// debuglog("specName(" + sym + ") env: " + env + " tvars: " + tvars)
- specializedName(sym.name, methparams map env, others map env)
+ specializedName(name, methparams map env, others map env)
}
/** Specialize name for the two list of types. The first one denotes
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 6484d96a52..836ea808ac 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -237,7 +237,7 @@ abstract class UnCurry extends InfoTransform
def canUseDelamdafyMethod = (
(inConstructorFlag == 0) // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation
- && !isSpecialized // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
+ && (!isSpecialized || (settings.target.value == "jvm-1.8")) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
)
if (inlineFunctionExpansion || !canUseDelamdafyMethod) {
val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe))
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala
index c86d08e925..5b20d9db8e 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 e9cbfd54eb..abe966920b 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