diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-03-11 12:09:21 -0700 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-03-11 13:47:07 -0700 |
commit | f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc (patch) | |
tree | d7745fc7de76b699304e1ddac255e531134400ff /src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala | |
parent | 027e97981d9b6a3783e9ab247cc898017b3de821 (diff) | |
download | scala-f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc.tar.gz scala-f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc.tar.bz2 scala-f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc.zip |
Inline final methods defined in traits
In order to inline a final trait method, callsites of such methods are
first re-written from interface calls to static calls of the trait's
implementation class. Then inlining proceeds as ususal.
One problem that came up during development was that mixin methods are
added to class symbols only for classes being compiled, but not for
others. In order to inline a mixin method, we need the InlineInfo,
which so far was built using the class (and method) symbols. So we had
a problem with separate compilation.
Looking up the symbol from a given classfile name was already known to
be brittle (it's also one of the weak points of the current inliner),
so we changed the strategy. Now the InlineInfo for every class is
encoded in a new classfile attribute.
This classfile attribute is relatively small, because all strings it
references (class internal names, method names, method descriptors)
would exist anyway in the constant pool, so it just adds a few
references.
When building the InlineInfo for a class symbol, we only look at the
symbol properties for symbols being compiled in the current run. For
unpickled symbols, we build the InlineInfo by reading the classfile
attribute.
This change also adds delambdafy:method classes to currentRun.symSource.
Otherwise, currentRun.compiles(lambdaClass) is false.
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala | 81 |
1 files changed, 69 insertions, 12 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 2ca8e8b8c4..970cc6803a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -16,7 +16,6 @@ import scala.collection.convert.decorateAsJava._ import AsmUtils._ import BytecodeUtils._ import OptimizerReporting._ -import scala.tools.asm.tree.analysis._ import collection.mutable class Inliner[BT <: BTypes](val btypes: BT) { @@ -24,6 +23,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { import callGraph._ def runInliner(): Unit = { + rewriteFinalTraitMethodInvocations() + for (request <- collectAndOrderInlineRequests) { val Some(callee) = request.callee inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, @@ -58,18 +59,74 @@ class Inliner[BT <: BTypes](val btypes: BT) { * requests is allowed to have cycles, and the callsites can appear in any order. */ def selectCallsitesForInlining: List[Callsite] = { - callsites.iterator.filter({ - case (_, callsite) => callsite.callee match { - case Some(Callee(callee, _, safeToInline, Some(annotatedInline), _)) => - // TODO: fix inlining from traits. - // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". - // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the - // abstract method in the interface. - !isAbstractMethod(callee) && safeToInline && annotatedInline - case _ => false - } + callsites.valuesIterator.filter({ + case Callsite(_, _, _, Some(Callee(callee, _, safeToInline, annotatedInline, _)), _, _) => + // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". + // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the + // abstract method in the interface. + // Even though we such invocations are re-written using `rewriteFinalTraitMethodInvocation`, + // the guard is kept here for the cases where the rewrite fails. + !isAbstractMethod(callee) && safeToInline && annotatedInline + case _ => false - }).map(_._2).toList + }).toList + } + + def rewriteFinalTraitMethodInvocations(): Unit = { + // Rewriting final trait method callsites to the implementation class enables inlining. + // We cannot just iterate over the values of the `callsites` map because the rewrite changes the + // map. Therefore we first copy the values to a list. + callsites.values.toList.foreach(rewriteFinalTraitMethodInvocation) + } + + /** + * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the + * corresponding method in the implementation class. This enables inlining final trait methods. + * + * In a final trait method callsite, the callee is safeToInline and the callee method is abstract + * (the receiver type is the interface, so the method is abstract). + */ + def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = callsite.callee match { + case Some(Callee(callee, calleeDeclarationClass, true, true, annotatedNoInline)) if isAbstractMethod(callee) => + assert(calleeDeclarationClass.isInterface, s"expected interface call (final trait method) when inlining abstract method: $callsite") + + val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) + + val selfParamTypeName = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType.getOrElse(calleeDeclarationClass.internalName) + val selfParamType = asm.Type.getObjectType(selfParamTypeName) + + val implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType +: traitMethodArgumentTypes: _*) + val implClassInternalName = calleeDeclarationClass.internalName + "$class" + + // The rewrite reading the implementation class and the implementation method from the bytecode + // repository. If either of the two fails, the rewrite is not performed. + for { + // TODO: inline warnings if impl class or method cannot be found + (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor) + implClassBType <- classBTypeFromParsedClassfile(implClassInternalName) + } yield { + val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implClassMethodDescriptor, false) + callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction) + callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction) + + callGraph.callsites.remove(callsite.callsiteInstruction) + val staticCallsite = Callsite( + callsiteInstruction = newCallsiteInstruction, + callsiteMethod = callsite.callsiteMethod, + callsiteClass = callsite.callsiteClass, + callee = Some(Callee( + callee = implClassMethod, + calleeDeclarationClass = implClassBType, + safeToInline = true, + annotatedInline = true, + annotatedNoInline = annotatedNoInline)), + argInfos = Nil, + callsiteStackHeight = callsite.callsiteStackHeight + ) + callGraph.callsites(newCallsiteInstruction) = staticCallsite + } + + case _ => } /** |