summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-03-11 11:38:17 -0700
committerLukas Rytz <lukas.rytz@gmail.com>2015-03-11 15:18:22 -0700
commitf8731c5b17274d68de3469e34727e24a937ffc84 (patch)
treeee0659cee396cc334a3015c21c9c46cdbc83e847 /src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
parent57c07204ca452564b930085cfa9e8b099e45b2a9 (diff)
downloadscala-f8731c5b17274d68de3469e34727e24a937ffc84.tar.gz
scala-f8731c5b17274d68de3469e34727e24a937ffc84.tar.bz2
scala-f8731c5b17274d68de3469e34727e24a937ffc84.zip
Issue inliner warnings for callsites that cannot be inlined
Issue precise warnings when the inliner fails to inline or analyze a callsite. Inline failures may have various causes, for example because some class cannot be found on the classpath when building the call graph. So we need to store problems that happen early in the optimizer (when building the necessary data structures, call graph, ClassBTypes) to be able to report them later in case the inliner accesses the related data. We use Either to store these warning messages. The commit introduces an implicit class `RightBiasedEither` to make Either easier to use for error propagation. This would be subsumed by a biased either in the standard library (or could use a Validation). The `info` of each ClassBType is now an Either. There are two cases where the info is not available: - The type info should be parsed from a classfile, but the class cannot be found on the classpath - SI-9111, the type of a Java source originating class symbol cannot be completed This means that the operations on ClassBType that query the info now return an Either, too. Each Callsite in the call graph now stores the source position of the call instruction. Since the call graph is built after code generation, we build a map from invocation nodes to positions during code gen and query it when building the call graph. The new inliner can report a large number of precise warnings when a callsite cannot be inlined, or if the inlining metadata cannot be computed precisely, for example due to a missing classfile. The new -Yopt-warnings multi-choice option allows configuring inliner warnings. By default (no option provided), a one-line summary is issued in case there were callsites annotated @inline that could not be inlined.
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala83
1 files changed, 55 insertions, 28 deletions
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 0958601d73..607b7145d6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -11,9 +11,9 @@ import scala.tools.asm
import asm.tree._
import scala.collection.convert.decorateAsScala._
import scala.tools.asm.Attribute
+import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.ClassFileLookup
-import OptimizerReporting._
import BytecodeUtils._
import ByteCodeRepository._
import BTypes.InternalName
@@ -29,10 +29,10 @@ import java.util.concurrent.atomic.AtomicLong
* corresponds to a class being compiled.
* The `Long` field encodes the age of the node in the map, which allows removing
* old entries when the map grows too large.
- * For Java classes in mixed compilation, the map contains `None`: there is no
- * ClassNode generated by the backend and also no classfile that could be parsed.
+ * For Java classes in mixed compilation, the map contains an error message: no
+ * ClassNode is generated by the backend and also no classfile that could be parsed.
*/
-class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source, Long)]]) {
+class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) {
private val maxCacheSize = 1500
private val targetSize = 500
@@ -46,24 +46,24 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class
* We can only remove classes with `Source == Classfile`, those can be parsed again if requested.
*/
private def limitCacheSize(): Unit = {
- if (classes.count(c => c._2.isDefined && c._2.get._2 == Classfile) > maxCacheSize) {
+ if (classes.count(c => c._2.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) {
val removeId = idCounter.get - targetSize
val toRemove = classes.iterator.collect({
- case (name, Some((_, Classfile, id))) if id < removeId => name
+ case (name, Right((_, Classfile, id))) if id < removeId => name
}).toList
toRemove foreach classes.remove
}
}
def add(classNode: ClassNode, source: Source) = {
- classes(classNode.name) = Some((classNode, source, idCounter.incrementAndGet()))
+ classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet()))
}
/**
* The class node and source for an internal name. If the class node is not yet available, it is
* parsed from the classfile on the compile classpath.
*/
- def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = {
+ def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = {
val r = classes.getOrElseUpdate(internalName, {
limitCacheSize()
parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
@@ -75,42 +75,66 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class
* The class node for an internal name. If the class node is not yet available, it is parsed from
* the classfile on the compile classpath.
*/
- def classNode(internalName: InternalName): Option[ClassNode] = classNodeAndSource(internalName).map(_._1)
+ def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1)
/**
* The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
* The declaration of the field may be in one of the superclasses.
*
- * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class.
+ * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring
+ * class, or an error message if the field could not be found
*/
- def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = {
- classNode(classInternalName).flatMap(c =>
- c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse {
- Option(c.superName).flatMap(n => fieldNode(n, name, descriptor))
- })
+ def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Either[FieldNotFound, (FieldNode, InternalName)] = {
+ def fieldNodeImpl(parent: InternalName): Either[FieldNotFound, (FieldNode, InternalName)] = {
+ def msg = s"The field node $name$descriptor could not be found in class $classInternalName or any of its superclasses."
+ classNode(parent) match {
+ case Left(e) => Left(FieldNotFound(name, descriptor, classInternalName, Some(e)))
+ case Right(c) =>
+ c.fields.asScala.find(f => f.name == name && f.desc == descriptor) match {
+ case Some(f) => Right((f, parent))
+ case None =>
+ if (c.superName == null) Left(FieldNotFound(name, descriptor, classInternalName, None))
+ else fieldNode(c.superName, name, descriptor)
+ }
+ }
+ }
+ fieldNodeImpl(classInternalName)
}
/**
* The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`.
* The declaration of the method may be in one of the parents.
*
- * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class.
+ * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring
+ * class, or an error message if the method could not be found.
*/
- 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 =>
- c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) orElse {
- val parents = Option(c.superName) ++ c.interfaces.asScala
- // `view` to stop at the first result
- parents.view.flatMap(methodNode(_, name, descriptor)).headOption
- })
+ def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Either[MethodNotFound, (MethodNode, InternalName)] = {
+ // on failure, returns a list of class names that could not be found on the classpath
+ def methodNodeImpl(ownerInternalName: InternalName): Either[List[ClassNotFound], (MethodNode, InternalName)] = {
+ classNode(ownerInternalName) match {
+ case Left(e) => Left(List(e))
+ case Right(c) =>
+ c.methods.asScala.find(m => m.name == name && m.desc == descriptor) match {
+ case Some(m) => Right((m, ownerInternalName))
+ case None => findInParents(Option(c.superName) ++: c.interfaces.asScala.toList, Nil)
+ }
+ }
+ }
+
+ // find the MethodNode in one of the parent classes
+ def findInParents(parents: List[InternalName], failedClasses: List[ClassNotFound]): Either[List[ClassNotFound], (MethodNode, InternalName)] = parents match {
+ case x :: xs => methodNodeImpl(x).left.flatMap(failed => findInParents(xs, failed ::: failedClasses))
+ case Nil => Left(failedClasses)
}
+
+ // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. We don't have a method node to return in this case.
+ if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[')
+ Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, Nil))
+ else
+ methodNodeImpl(ownerInternalNameOrArrayDescriptor).left.map(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, _))
}
- private def parseClass(internalName: InternalName): Option[ClassNode] = {
+ private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
val fullName = internalName.replace('/', '.')
classPath.findClassFile(fullName) map { classFile =>
val classNode = new asm.tree.ClassNode()
@@ -131,6 +155,9 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class
// https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html
removeLineNumberNodes(classNode)
classNode
+ } match {
+ case Some(node) => Right(node)
+ case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName)))
}
}
}