diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-03-11 11:38:17 -0700 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-03-11 15:18:22 -0700 |
commit | f8731c5b17274d68de3469e34727e24a937ffc84 (patch) | |
tree | ee0659cee396cc334a3015c21c9c46cdbc83e847 /src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala | |
parent | 57c07204ca452564b930085cfa9e8b099e45b2a9 (diff) | |
download | scala-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.scala | 83 |
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))) } } } |