summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-03-11 12:09:21 -0700
committerLukas Rytz <lukas.rytz@gmail.com>2015-03-11 13:47:07 -0700
commitf8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc (patch)
treed7745fc7de76b699304e1ddac255e531134400ff /src
parent027e97981d9b6a3783e9ab247cc898017b3de821 (diff)
downloadscala-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')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala6
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala76
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala134
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala8
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala69
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala81
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala2
8 files changed, 232 insertions, 148 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index 32a421c570..e40e928761 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -103,6 +103,8 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
isCZRemote = isRemote(claszSymbol)
thisName = internalName(claszSymbol)
+ val classBType = classBTypeFromSymbol(claszSymbol)
+
cnode = new asm.tree.ClassNode()
initJClass(cnode)
@@ -120,10 +122,12 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
addClassFields()
- innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses
+ innerClassBufferASM ++= classBType.info.nestedClasses
gen(cd.impl)
addInnerClassesASM(cnode, innerClassBufferASM.toList)
+ cnode.visitAttribute(classBType.inlineInfoAttribute)
+
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 81d8adb7de..51a17b7fe4 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -10,8 +10,8 @@ import scala.annotation.switch
import scala.tools.asm
import asm.Opcodes
import scala.tools.asm.tree.{InnerClassNode, ClassNode}
-import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo}
-import scala.tools.nsc.backend.jvm.opt.{CallGraph, ByteCodeRepository, Inliner}
+import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
+import scala.tools.nsc.backend.jvm.opt._
import opt.OptimizerReporting._
import scala.collection.convert.decorateAsScala._
@@ -47,6 +47,9 @@ abstract class BTypes {
// When building the call graph, we need to know if global inlining is allowed (the component doesn't have a global)
def inlineGlobalEnabled: Boolean
+ // When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes
+ def inlinerEnabled: Boolean
+
/**
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
* construction.
@@ -61,23 +64,6 @@ abstract class BTypes {
val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType])
/**
- * Build the [[InlineInfo]] for the methods of a class, given its internal name.
- *
- * The InlineInfo is part of the ClassBType's [[ClassInfo]]. Note that there are two ways to build
- * a ClassBType: from a class symbol (methods in [[BTypesFromSymbols]]) or from a [[ClassNode]].
- * The InlineInfo however contains information that can only be retrieved from the symbol of
- * the class (e.g., is a method annotated @inline).
- *
- * This method (implemented in [[BTypesFromSymbols]]) looks up the class symbol in the symbol
- * table, using the classfile name of the class.
- *
- * The method tries to undo some of the name mangling, but the lookup does not succeed for all
- * classes. In case it fails, the resulting ClassBType will simply not have an InlineInfo, and
- * we won't be able to inline its methods.
- */
- def inlineInfosFromSymbolLookup(internalName: InternalName): Map[String, MethodInlineInfo]
-
- /**
* Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType
* is constructed by parsing the corresponding classfile.
*
@@ -187,11 +173,53 @@ abstract class BTypes {
NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag)
}
- classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfosFromSymbolLookup(classBType.internalName))
+ val inlineInfo = inlineInfoFromClassfile(classNode)
+
+ classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)
classBType
}
/**
+ * Build the InlineInfo for a class. For Scala classes, the information is stored in the
+ * ScalaInlineInfo attribute. If the attribute is missing, the InlineInfo is built using the
+ * metadata available in the classfile (ACC_FINAL flags, etc).
+ */
+ def inlineInfoFromClassfile(classNode: ClassNode): InlineInfo = {
+ def fromClassfileAttribute: Option[InlineInfo] = {
+ // TODO: if this is a scala class and there's no attribute, emit an inliner warning if the InlineInfo is used
+ if (classNode.attrs == null) None
+ else classNode.attrs.asScala.collect({ case a: InlineInfoAttribute => a}).headOption.map(_.inlineInfo)
+ }
+
+ def fromClassfileWithoutAttribute = {
+ // when building MethodInlineInfos for the members of a ClassSymbol, we exclude those methods
+ // in scalaPrimitives. This is necessary because some of them have non-erased types, which would
+ // require special handling. Excluding is OK because they are never inlined.
+ // Here we are parsing from a classfile and we don't need to do anything special. Many of these
+ // primitives don't even exist, for example Any.isInstanceOf.
+ val methodInfos = classNode.methods.asScala.map(methodNode => {
+ val info = MethodInlineInfo(
+ effectivelyFinal = BytecodeUtils.isFinalMethod(methodNode),
+ traitMethodWithStaticImplementation = false,
+ annotatedInline = false,
+ annotatedNoInline = false)
+ (methodNode.name + methodNode.desc, info)
+ }).toMap
+ InlineInfo(
+ traitImplClassSelfType = None,
+ isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode),
+ methodInfos = methodInfos,
+ warning = None)
+ }
+
+ // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT
+ // being compiled. For those classes, the info is only needed if the inliner is enabled, othewise
+ // we can save the memory.
+ if (!inlinerEnabled) BTypes.EmptyInlineInfo
+ else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute
+ }
+
+ /**
* A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType
* referring to BTypes.
*/
@@ -831,6 +859,8 @@ abstract class BTypes {
)
}
+ def inlineInfoAttribute: InlineInfoAttribute = InlineInfoAttribute(info.inlineInfo)
+
def isSubtypeOf(other: ClassBType): Boolean = {
if (this == other) return true
@@ -945,13 +975,11 @@ abstract class BTypes {
* @param nestedClasses Classes nested in this class. Those need to be added to the
* InnerClass table, see the InnerClass spec summary above.
* @param nestedInfo If this describes a nested class, information for the InnerClass table.
- * @param inlineInfos The [[InlineInfo]]s for the methods declared in this class. The map is
- * indexed by the string s"$name$descriptor" (to disambiguate overloads).
- * Entries may be missing, see comment on [[inlineInfosFromSymbolLookup]].
+ * @param inlineInfo Information about this class for the inliner.
*/
final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int,
nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo],
- inlineInfos: Map[String, MethodInlineInfo])
+ inlineInfo: InlineInfo)
/**
* Information required to add a class to an InnerClass table.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 9fdb92b47c..a217e54ed8 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,10 +7,8 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
-import scala.tools.asm.tree.ClassNode
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source
import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository}
-import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName}
+import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -42,48 +40,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val callGraph: CallGraph[this.type] = new CallGraph(this)
- /**
- * See doc in [[BTypes.inlineInfosFromSymbolLookup]].
- * TODO: once the optimzier uses parallelism, lock before symbol table accesses
- */
- def inlineInfosFromSymbolLookup(internalName: InternalName): Map[String, MethodInlineInfo] = {
- val name = internalName.replace('/', '.')
-
- // TODO: de-mangle more class names
-
- def inEmptyPackage = name.indexOf('.') == -1
- def isModule = name.endsWith("$")
- def isTopLevel = {
- // TODO: this is conservative, there's also $'s introduced by name mangling, e.g., $colon$colon
- // for this, use NameTransformer.decode
- if (isModule) name.indexOf('$') == (name.length - 1)
- else name.indexOf('$') == -1
- }
-
- val lookupName = {
- if (isModule) newTermName(name.substring(0, name.length - 1))
- else newTypeName(name)
- }
-
- // for now we only try classes that look like top-level
- val classSym = if (!isTopLevel) NoSymbol else {
- val member = {
- if (inEmptyPackage) {
- // rootMirror.getClassIfDefined fails for classes / modules in the empty package.
- // maybe that should be fixed.
- rootMirror.EmptyPackageClass.info.member(lookupName)
- } else {
- if (isModule) rootMirror.getModuleIfDefined(lookupName)
- else rootMirror.getClassIfDefined(lookupName)
- }
- }
- if (isModule) member.moduleClass else member
- }
-
- if (classSym == NoSymbol) Map.empty
- else buildInlineInfos(classSym)
- }
-
final def initializeCoreBTypes(): Unit = {
coreBTypes.setBTypes(new CoreBTypes[this.type](this))
}
@@ -92,6 +48,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
def inlineGlobalEnabled: Boolean = settings.YoptInlineGlobal
+ def inlinerEnabled: Boolean = settings.YoptInlinerEnabled
+
// helpers that need access to global.
// TODO @lry create a separate component, they don't belong to BTypesFromSymbols
@@ -168,27 +126,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
/**
- * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
- * cannot change the typer context of the completer at this point and make it silent: the context
- * captured when creating the completer in the namer. However, we can temporarily replace
- * global.reporter (it's a var) to store errors.
- */
- def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = {
- if (sym.rawInfo.isComplete) false
- else {
- val originalReporter = global.reporter
- val storeReporter = new reporters.StoreReporter()
- try {
- global.reporter = storeReporter
- sym.info
- } finally {
- global.reporter = originalReporter
- }
- storeReporter.infos.exists(_.severity == storeReporter.ERROR)
- }
- }
-
- /**
* Builds a [[MethodBType]] for a method symbol.
*/
final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = {
@@ -401,9 +338,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val nestedInfo = buildNestedInfo(classSym)
- val inlineInfos = buildInlineInfos(classSym)
+ val inlineInfo = buildInlineInfo(classSym, classBType.internalName)
- classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfos)
+ classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)
classBType
}
@@ -458,30 +395,39 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
}
- private def buildInlineInfos(classSym: Symbol): Map[String, MethodInlineInfo] = {
- if (!settings.YoptInlinerEnabled) Map.empty
+ /**
+ * Build the InlineInfo for a ClassBType from the class symbol.
+ *
+ * Note that the InlineInfo is only built from the symbolic information for classes that are being
+ * compiled. For all other classes we delegate to inlineInfoFromClassfile. The reason is that
+ * mixed-in methods are only added to class symbols being compiled, but not to other classes
+ * extending traits. Creating the InlineInfo from the symbol would prevent these mixins from being
+ * inlined.
+ *
+ * So for classes being compiled, the InlineInfo is created here and stored in the ScalaInlineInfo
+ * classfile attribute.
+ */
+ private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
+ def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
+
+ // phase travel required, see implementation of `compiles`. for nested classes, it checks if the
+ // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level,
+ // so `compiles` would return `false`.
+ if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol
else {
- // Primitve methods cannot be inlined, so there's no point in building an InlineInfo. Also, some
- // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
- classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
- case methodSym =>
- if (completeSilentlyAndCheckErroneous(methodSym)) {
- // Happens due to SI-9111. Just don't provide any InlineInfo for that method, we don't
- // need fail the compiler.
- None
- } else {
- val methodBType = methodBTypeFromSymbol(methodSym)
- val name = methodSym.javaSimpleName.toString // same as in genDefDef
- val signature = name + methodBType.descriptor
- val info = MethodInlineInfo(
- effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden,
- traitMethodWithStaticImplementation = false, // temporary, fixed in future commit
- annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
- annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)
- )
- Some((signature, info))
- }
- }).toMap
+ // For classes not being compiled, the InlineInfo is read from the classfile attribute. This
+ // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class
+ // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos
+ // for those mixin members, which prevents inlining.
+ byteCodeRepository.classNode(internalName) match {
+ case Some(classNode) =>
+ inlineInfoFromClassfile(classNode)
+ case None =>
+ // TODO: inliner warning if the InlineInfo for that class is being used
+ // We can still use the inline information built from the symbol, even though mixin
+ // members will be missing.
+ buildFromSymbol
+ }
}
}
@@ -503,7 +449,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
nestedClasses = nested,
nestedInfo = None,
- Map.empty // no InlineInfo needed, scala never invokes methods on the mirror class
+ InlineInfo(None, true, Map.empty, None) // no InlineInfo needed, scala never invokes methods on the mirror class
)
c
})
@@ -538,8 +484,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
// legacy, to be removed when the @remote annotation gets removed
- final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr)
- final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0)
+ final def isRemote(s: Symbol) = s hasAnnotation definitions.RemoteAttr
+ final def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0
/**
* Return the Java modifiers for the given symbol.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
index ea4dd0c032..fb58f1b189 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -10,6 +10,7 @@ package opt
import scala.tools.asm
import asm.tree._
import scala.collection.convert.decorateAsScala._
+import scala.tools.asm.Attribute
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.ClassFileLookup
import OptimizerReporting._
@@ -64,6 +65,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class
*/
def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Option[(MethodNode, InternalName)] = {
// In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`.
+ // We don't inline array methods (they are native anyway), so just return None.
if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None
else {
classNode(ownerInternalNameOrArrayDescriptor).flatMap(c =>
@@ -80,9 +82,13 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class
classPath.findClassFile(fullName) map { classFile =>
val classNode = new asm.tree.ClassNode()
val classReader = new asm.ClassReader(classFile.toByteArray)
+
+ // Passing the InlineInfoAttributePrototype makes the ClassReader invoke the specific `read`
+ // method of the InlineInfoAttribute class, instead of putting the byte array into a generic
+ // Attribute.
// We don't need frames when inlining, but we want to keep the local variable table, so we
// don't use SKIP_DEBUG.
- classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES)
+ classReader.accept(classNode, Array[Attribute](InlineInfoAttributePrototype), asm.ClassReader.SKIP_FRAMES)
// SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after
// inlining.
// TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining?
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
index 74f46d04f9..e221eef636 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -83,6 +83,10 @@ object BytecodeUtils {
def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0
+ def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0
+
+ def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE)) != 0
+
def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
var result = instruction
do { result = result.getNext }
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
index ac40ab8904..020db738e8 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -9,7 +9,9 @@ package opt
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.BasicAnalyzer
+import ByteCodeRepository.{Source, CompilationUnit}
class CallGraph[BT <: BTypes](val btypes: BT) {
import btypes._
@@ -22,6 +24,43 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
}
def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = {
+
+ /**
+ * Analyze a callsite and gather meta-data that can be used for inlining decisions.
+ *
+ * @return Three booleans indicating whether
+ * 1. the callsite can be safely inlined
+ * 2. the callee is annotated `@inline`
+ * 3. the callee is annotated `@noinline`
+ */
+ def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): (Boolean, Boolean, Boolean) = {
+ val methodSignature = calleeMethodNode.name + calleeMethodNode.desc
+
+ // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared*
+ // within a class (not for inherited methods). Since we already have the classBType of the
+ // callee, we only check there for the methodInlineInfo, we should find it there.
+ calleeDeclarationClassBType.info.inlineInfo.methodInfos.find(_._1 == methodSignature) match {
+ case Some((_, methodInlineInfo)) =>
+ val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit
+ // A non-final method can be inline if the receiver type is a final subclass. Example:
+ // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined
+ def isStaticallyResolved: Boolean = {
+ // TODO: type analysis can render more calls statically resolved
+ // Example: `new A.f` can be inlined, the receiver type is known to be exactly A.
+ methodInlineInfo.effectivelyFinal || {
+ // TODO: inline warning when the receiver class cannot be found on the classpath
+ classBTypeFromParsedClassfile(receiverTypeInternalName).exists(_.info.inlineInfo.isEffectivelyFinal)
+ }
+ }
+
+ (canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline)
+
+ case None =>
+ // TODO: issue inliner warning
+ (false, false, false)
+ }
+ }
+
// TODO: run dataflow analyses to make the call graph more precise
// - producers to get forwarded parameters (ForwardedParam)
// - typeAnalysis for more precise argument types, more precise callee
@@ -40,16 +79,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
byteCodeRepository.classNodeAndSource(declarationClass) map {
case (declarationClassNode, source) =>
val declarationClassBType = classBTypeFromClassNode(declarationClassNode)
- val methodSignature = method.name + method.desc
- val (safeToInline, annotatedInline, annotatedNoInline) = declarationClassBType.info.inlineInfos.get(methodSignature) match {
- case Some(inlineInfo) =>
- val canInlineFromSource = inlineGlobalEnabled || source == ByteCodeRepository.CompilationUnit
- // TODO: for now, we consider a callee safeToInline only if it's final
- // type analysis can render more calls safeToInline (e.g. when the precise receiver type is known)
- (canInlineFromSource && inlineInfo.effectivelyFinal, Some(inlineInfo.annotatedInline), Some(inlineInfo.annotatedNoInline))
- case None =>
- (false, None, None)
- }
+ val (safeToInline, annotatedInline, annotatedNoInline) = analyzeCallsite(method, declarationClassBType, call.owner, source)
Callee(
callee = method,
calleeDeclarationClass = declarationClassBType,
@@ -81,17 +111,21 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
/**
* A callsite in the call graph.
+ *
* @param callsiteInstruction The invocation instruction
* @param callsiteMethod The method containing the callsite
* @param callsiteClass The class containing the callsite
- * @param callee The callee. For virtual calls, an override of the callee might be invoked.
+ * @param callee The callee, as it appears in the invocation instruction. For virtual
+ * calls, an override of the callee might be invoked. Also, the callee
+ * can be abstract. `None` if the callee MethodNode cannot be found in
+ * the bytecode repository.
* @param argInfos Information about the invocation receiver and arguments
* @param callsiteStackHeight The stack height at the callsite, required by the inliner
*/
final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType,
callee: Option[Callee], argInfos: List[ArgInfo],
callsiteStackHeight: Int) {
- override def toString = s"Invocation of ${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteMethod.name}"
+ override def toString = s"Invocation of ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteClass.internalName}.${callsiteMethod.name}"
}
/**
@@ -104,14 +138,17 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
/**
* A callee in the call graph.
- * @param callee The called method. For virtual calls, an override may actually be invoked.
+ *
+ * @param callee The callee, as it appears in the invocation instruction. For
+ * virtual calls, an override of the callee might be invoked. Also,
+ * the callee can be abstract.
* @param calleeDeclarationClass The class in which the callee is declared
* @param safeToInline True if the callee can be safely inlined: it cannot be overridden,
* and the inliner settings (project / global) allow inlining it.
- * @param annotatedInline Defined if it is known whether the callee is annotated @inline
- * @param annotatedNoInline Defined if it is known whether the callee is annotated @noinline
+ * @param annotatedInline True if the callee is annotated @inline
+ * @param annotatedNoInline True if the callee is annotated @noinline
*/
final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType,
safeToInline: Boolean,
- annotatedInline: Option[Boolean], annotatedNoInline: Option[Boolean])
+ annotatedInline: Boolean, annotatedNoInline: Boolean)
}
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 _ =>
}
/**
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 1f832ba81e..94e88589f5 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -255,6 +255,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon"))
val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation
+ // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes)
+ currentRun.symSource(lambdaClass) = funOwner.sourceFile
lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass)
assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name)
assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name)