summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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)