Skip to content

Commit 6c75bc4

Browse files
committed
Merge pull request scala#4463 from retronym/topic/indylambda-emit-indy
Use LambdaMetafactory where possible for lambda creation.
2 parents 9e29061 + 3bf208f commit 6c75bc4

File tree

9 files changed

+186
-23
lines changed

9 files changed

+186
-23
lines changed

build.xml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ TODO:
266266
-->
267267
<if><not><isset property="maven-deps-done"></isset></not><then>
268268
<mkdir dir="${user.home}/.m2/repository"/>
269+
270+
<artifact:remoteRepository id="sonatype-release" url="https://oss.sonatype.org/content/repositories/releases"/>
271+
<artifact:remoteRepository id="sonatype-snapshots" url="https://oss.sonatype.org/content/repositories/snapshots"/>
272+
<artifact:remoteRepository id="extra-repo" url="${extra.repo.url}"/>
273+
269274
<!-- This task has an issue where if the user directory does not exist, so we create it above. UGH. -->
270275
<artifact:dependencies pathId="extra.tasks.classpath" filesetId="extra.tasks.fileset">
271276
<dependency groupId="biz.aQute" artifactId="bnd" version="1.50.0"/>
@@ -307,6 +312,36 @@ TODO:
307312
<artifact:remoteRepository id="sonatype-release" url="https://oss.sonatype.org/content/repositories/releases"/>
308313
<artifact:remoteRepository id="extra-repo" url="${extra.repo.url}"/>
309314

315+
<!-- scala-java8-compat, used by the experimental -target jvm-1.8 support. -->
316+
<if><isset property="scala-java8-compat.package"/><then>
317+
<property name="scala-java8-compat.version" value="0.2.0"/>
318+
<property name="scala-java8-compat.binary.version" value="2.11"/>
319+
<artifact:dependencies pathId="scala-java8-compat.classpath" filesetId="scala-java8-compat.fileset">
320+
<dependency groupId="org.scala-lang.modules" artifactId="scala-java8-compat_${scala-java8-compat.binary.version}" version="${scala-java8-compat.version}">
321+
<exclusion groupId="org.scala-lang" artifactId="scala-library"/>
322+
</dependency>
323+
</artifact:dependencies>
324+
<property name="scala-java8-compat-classes" value="${build-quick.dir}/scala-java8-compat"/>
325+
<delete dir="${scala-java8-compat-classes}"/>
326+
<unzip dest="${scala-java8-compat-classes}">
327+
<fileset refid="scala-java8-compat.fileset"/>
328+
<patternset>
329+
<include name="**/*.class"/>
330+
</patternset>
331+
</unzip>
332+
<path id="scala-java8-compat.libs">
333+
<pathelement location="${scala-java8-compat-classes}"/>
334+
</path>
335+
<fileset id="scala-java8-compat.fileset" dir="${scala-java8-compat-classes}">
336+
<include name="**/*"/>
337+
</fileset>
338+
</then>
339+
<else>
340+
<path id="scala-java8-compat.libs"/>
341+
<fileset id="scala-java8-compat.fileset" dir="." excludes="**"/>
342+
</else>
343+
</if>
344+
310345
<!-- prepare, for each of the names below, the property "@{name}.cross", set to the
311346
necessary cross suffix (usually something like "_2.11.0-M6". -->
312347
<prepareCross name="scala-xml" />
@@ -718,6 +753,7 @@ TODO:
718753
<pathelement location="${build-locker.dir}/classes/library"/>
719754
<path refid="forkjoin.classpath"/>
720755
<path refid="aux.libs"/>
756+
<path refid="scala-java8-compat.libs"/>
721757
</path>
722758

723759
<path id="locker.reflect.build.path">
@@ -739,6 +775,7 @@ TODO:
739775
<pathelement location="${build-quick.dir}/classes/library"/>
740776
<path refid="forkjoin.classpath"/>
741777
<path refid="aux.libs"/>
778+
<path refid="scala-java8-compat.libs"/>
742779
</path>
743780

744781
<path id="quick.actors.build.path">
@@ -827,6 +864,7 @@ TODO:
827864
<path id="pack.library.files">
828865
<fileset dir="${build-quick.dir}/classes/library"/>
829866
<fileset dir="${forkjoin-classes}"/>
867+
<fileset refid="scala-java8-compat.fileset"/>
830868
</path>
831869

832870
<path id="pack.actors.files">

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: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,29 @@ import scala.reflect.internal.Symbols
99
import scala.collection.mutable.LinkedHashMap
1010

1111
/**
12-
* This transformer is responsible for turning lambdas into anonymous classes.
12+
* This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes
13+
* or to a tree that will be convereted to invokedynamic by the JVM 1.8+ backend.
14+
*
1315
* The main assumption it makes is that a lambda {args => body} has been turned into
1416
* {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda.
1517
* Currently Uncurry is responsible for that transformation.
1618
*
17-
* From a lambda, Delambdafy will create
19+
* From a lambda, Delambdafy will create:
20+
*
21+
* Under -target:jvm-1.7 and below:
22+
*
1823
* 1) a new top level class that
1924
a) has fields and a constructor taking the captured environment (including possibly the "this"
2025
* reference)
2126
* b) an apply method that calls the target method
2227
* c) if needed a bridge method for the apply method
2328
* 2) an instantiation of the newly created class which replaces the lambda
2429
*
25-
* TODO the main work left to be done is to plug into specialization. Primarily that means choosing a
26-
* specialized FunctionN trait instead of the generic FunctionN trait as a parent and creating the
27-
* appropriately named applysp method
30+
* Under -target:jvm-1.8 with GenBCode:
31+
*
32+
* 1) An application of the captured arguments to a fictional symbol representing the lambda factory.
33+
* This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
34+
* The captured arguments include `this` if `liftedBody` is unable to be made STATIC.
2835
*/
2936
abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer {
3037
import global._
@@ -79,6 +86,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
7986
sealed abstract class TransformedFunction
8087
// A class definition for the lambda, an expression instantiating the lambda class
8188
case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction
89+
case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction
8290

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

95103
super.transform(newExpr)
104+
case InvokeDynamicLambda(apply) =>
105+
// ... or an invokedynamic call
106+
super.transform(apply)
96107
}
97108
case _ => super.transform(tree)
98109
}
@@ -124,6 +135,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
124135
if (!thisReferringMethods.contains(target))
125136
target setFlag STATIC
126137

138+
val isStatic = target.hasFlag(STATIC)
127139

128140
/**
129141
* Creates the apply method for the anonymous subclass of FunctionN
@@ -199,7 +211,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
199211
val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe
200212

201213
// anonymous subclass of FunctionN with an apply method
202-
def makeAnonymousClass = {
214+
def makeAnonymousClass: ClassDef = {
203215
val parents = addSerializable(abstractFunctionErasedType)
204216
val funOwner = originalFunction.symbol.owner
205217

@@ -232,7 +244,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
232244
// the Optional proxy that will hold a reference to the 'this'
233245
// object used by the lambda, if any. NoSymbol if there is no this proxy
234246
val thisProxy = {
235-
if (target.hasFlag(STATIC))
247+
if (isStatic)
236248
NoSymbol
237249
else {
238250
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
271283
val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod
272284

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

277-
val (anonymousClassDef, thisProxy) = makeAnonymousClass
278-
279-
pkg.info.decls enter anonymousClassDef.symbol
280-
281-
val thisArg = optionSymbol(thisProxy) map (_ => gen.mkAttributedThis(oldClass) setPos originalFunction.pos)
282-
val captureArgs = captures map (capture => Ident(capture) setPos originalFunction.pos)
283-
284-
val newStat =
285-
Typed(New(anonymousClassDef.symbol, (thisArg.toList ++ captureArgs): _*), TypeTree(abstractFunctionErasedType))
286-
287-
val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
289+
val allCaptureArgs: List[Tree] = {
290+
val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil
291+
val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList
292+
thisArg ::: captureArgs
293+
}
288294

289-
DelambdafyAnonClass(anonymousClassDef, typedNewStat)
295+
val functionalInterface = java8CompatFunctionalInterface(target, originalFunction.tpe)
296+
if (functionalInterface.exists) {
297+
// Create a symbol representing a fictional lambda factory method that accepts the captured
298+
// arguments and returns a Function.
299+
val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT)
300+
val argTypes: List[Type] = allCaptureArgs.map(_.tpe)
301+
val params = msym.newSyntheticValueParams(argTypes)
302+
msym.setInfo(MethodType(params, originalFunction.tpe))
303+
val arity = originalFunction.vparams.length
304+
305+
// We then apply this symbol to the captures.
306+
val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply]
307+
308+
// The backend needs to know the target of the lambda and the functional interface in order
309+
// to emit the invokedynamic instruction. We pass this information as tree attachment.
310+
apply.updateAttachment(LambdaMetaFactoryCapable(target, arity, functionalInterface))
311+
InvokeDynamicLambda(apply)
312+
} else {
313+
val anonymousClassDef = makeAnonymousClass
314+
pkg.info.decls enter anonymousClassDef.symbol
315+
val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType))
316+
val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
317+
DelambdafyAnonClass(anonymousClassDef, typedNewStat)
318+
}
290319
}
291320

292321
/**
@@ -436,4 +465,38 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
436465
super.traverse(tree)
437466
}
438467
}
468+
469+
final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol)
470+
471+
// The functional interface that can be used to adapt the lambda target method `target` to the
472+
// given function type. Returns `NoSymbol` if the compiler settings are unsuitable, or `LambdaMetaFactory`
473+
// would be unable to generate the correct implementation (e.g. functions referring to derived value classes)
474+
private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): Symbol = {
475+
val canUseLambdaMetafactory: Boolean = {
476+
val hasValueClass = exitingErasure {
477+
val methodType: Type = target.info
478+
methodType.exists(_.isInstanceOf[ErasedValueType])
479+
}
480+
val isTarget18 = settings.target.value.contains("jvm-1.8")
481+
settings.isBCodeActive && isTarget18 && !hasValueClass
482+
}
483+
484+
def functionalInterface: Symbol = {
485+
val sym = functionType.typeSymbol
486+
val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage
487+
val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs)
488+
val paramTps :+ restpe = functionType.typeArgs
489+
val arity = paramTps.length
490+
if (name1.toTypeName == sym.name) {
491+
val returnUnit = restpe.typeSymbol == UnitClass
492+
val functionInterfaceArray =
493+
if (returnUnit) currentRun.runDefinitions.Scala_Java8_CompatPackage_JProcedure
494+
else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction
495+
functionInterfaceArray.apply(arity)
496+
} else {
497+
pack.info.decl(name1.toTypeName.prepend("J"))
498+
}
499+
}
500+
if (canUseLambdaMetafactory) functionalInterface else NoSymbol
501+
}
439502
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,17 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
303303
}
304304
}
305305

306+
def specializedFunctionName(sym: Symbol, args: List[Type]) = exitingSpecialize {
307+
require(isFunctionSymbol(sym), sym)
308+
val env: TypeEnv = TypeEnv.fromSpecialization(sym, args)
309+
specializedClass.get((sym, env)) match {
310+
case Some(x) =>
311+
x.name
312+
case None =>
313+
sym.name
314+
}
315+
}
316+
306317
/** Return the specialized name of 'sym' in the given environment. It
307318
* guarantees the same result regardless of the map order by sorting
308319
* type variables alphabetically.
@@ -315,10 +326,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
315326
if (sym.isClass) env.keySet
316327
else specializedTypeVars(sym).intersect(env.keySet)
317328
)
329+
specializedName(sym.name, tvars, env)
330+
}
331+
332+
private def specializedName(name: Name, tvars: immutable.Set[Symbol], env: TypeEnv): TermName = {
318333
val (methparams, others) = tvars.toList sortBy ("" + _.name) partition (_.owner.isMethod)
319334
// debuglog("specName(" + sym + ") env: " + env + " tvars: " + tvars)
320335

321-
specializedName(sym.name, methparams map env, others map env)
336+
specializedName(name, methparams map env, others map env)
322337
}
323338

324339
/** Specialize name for the two list of types. The first one denotes

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ abstract class UnCurry extends InfoTransform
237237

238238
def canUseDelamdafyMethod = (
239239
(inConstructorFlag == 0) // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation
240-
&& !isSpecialized // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
240+
&& (!isSpecialized || (settings.target.value == "jvm-1.8")) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
241241
)
242242
if (inlineFunctionExpansion || !canUseDelamdafyMethod) {
243243
val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe))

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

test/files/run/t2318.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ object Test {
1111
case _: java.io.FilePermission => ()
1212
case x: java.security.SecurityPermission if x.getName contains ".networkaddress." => () // generality ftw
1313
case x: java.util.PropertyPermission if x.getName == "sun.net.inetaddr.ttl" => ()
14+
case _: java.lang.reflect.ReflectPermission => () // needed for LambdaMetaFactory
1415
case _ => super.checkPermission(perm)
1516
}
1617
}

0 commit comments

Comments
 (0)