diff options
Diffstat (limited to 'src/compiler')
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) |