summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/compiler/scala/tools/nsc/Reporting.scala12
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala55
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala19
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala38
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala15
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala271
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala53
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala265
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala5
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala83
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala111
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala262
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala28
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala19
18 files changed, 853 insertions, 399 deletions
diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala
index 4d7e9e753f..72a4b69536 100644
--- a/src/compiler/scala/tools/nsc/Reporting.scala
+++ b/src/compiler/scala/tools/nsc/Reporting.scala
@@ -26,7 +26,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w
protected def PerRunReporting = new PerRunReporting
class PerRunReporting extends PerRunReportingBase {
/** Collects for certain classes of warnings during this run. */
- private class ConditionalWarning(what: String, option: Settings#BooleanSetting) {
+ private class ConditionalWarning(what: String, option: Settings#BooleanSetting)(reRunFlag: String = option.name) {
val warnings = mutable.LinkedHashMap[Position, String]()
def warn(pos: Position, msg: String) =
if (option) reporter.warning(pos, msg)
@@ -37,16 +37,16 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w
val warningVerb = if (numWarnings == 1) "was" else "were"
val warningCount = countElementsAsString(numWarnings, s"$what warning")
- reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with ${option.name} for details")
+ reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with $reRunFlag for details")
}
}
// This change broke sbt; I gave it the thrilling name of uncheckedWarnings0 so
// as to recover uncheckedWarnings for its ever-fragile compiler interface.
- private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)
- private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)
- private val _featureWarnings = new ConditionalWarning("feature", settings.feature)
- private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)
+ private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)()
+ private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)()
+ private val _featureWarnings = new ConditionalWarning("feature", settings.feature)()
+ private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeAskedFor) settings.YoptWarnings.name else settings.YinlinerWarnings.name)
private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings)
// TODO: remove in favor of the overload that takes a Symbol, give that argument a default (NoSymbol)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
index 2ebf338f5e..162da4236a 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
@@ -7,7 +7,8 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.nsc.Global
-import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName}
+import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo}
+import BackendReporting.ClassSymbolInfoFailureSI9111
/**
* This trait contains code shared between GenBCode and GenASM that depends on types defined in
@@ -336,7 +337,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
val isEffectivelyFinal = classSym.isEffectivelyFinal
- var warning = Option.empty[String]
+ var warning = Option.empty[ClassSymbolInfoFailureSI9111]
// Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
// primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
@@ -345,7 +346,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
if (completeSilentlyAndCheckErroneous(methodSym)) {
// Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
- warning = Some(s"Failed to get the type of a method of class symbol ${classSym.fullName} due to SI-9111")
+ warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
None
} else {
val name = methodSym.javaSimpleName.toString // same as in genDefDef
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 1b3f124dd8..15b014bdd3 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -13,6 +13,7 @@ import scala.annotation.switch
import scala.tools.asm
import GenBCode._
+import BackendReporting._
/*
*
@@ -93,7 +94,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val thrownKind = tpeTK(expr)
// `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable.
// Similarly for scala.Nothing (again, as defined in src/libray-aux).
- assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference))
+ assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get)
genLoad(expr, thrownKind)
lineNumber(expr)
emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level.
@@ -230,7 +231,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (isArithmeticOp(code)) genArithmeticOp(tree, code)
else if (code == scalaPrimitives.CONCAT) genStringConcat(tree)
- else if (code == scalaPrimitives.HASH) genScalaHash(receiver)
+ else if (code == scalaPrimitives.HASH) genScalaHash(receiver, tree.pos)
else if (isArrayOp(code)) genArrayOp(tree, code, expectedType)
else if (isLogicalOp(code) || isComparisonOp(code)) {
val success, failure, after = new asm.Label
@@ -584,7 +585,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// if (fun.symbol.isConstructor) Static(true) else SuperCall(mix);
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
genLoadArguments(args, paramTKs(app))
- genCallMethod(fun.symbol, invokeStyle, pos = app.pos)
+ genCallMethod(fun.symbol, invokeStyle, app.pos)
generatedType = asmMethodType(fun.symbol).returnType
// 'new' constructor call: Note: since constructors are
@@ -626,7 +627,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName)
bc dup generatedType
genLoadArguments(args, paramTKs(app))
- genCallMethod(ctor, icodes.opcodes.Static(onInstance = true))
+ genCallMethod(ctor, icodes.opcodes.Static(onInstance = true), app.pos)
case _ =>
abort(s"Cannot instantiate $tpt of kind: $generatedType")
@@ -636,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val nativeKind = tpeTK(expr)
genLoad(expr, nativeKind)
val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor)
+ bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType)
case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
@@ -644,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
generatedType = boxType
val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor)
+ bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
case app @ Apply(fun, args) =>
val sym = fun.symbol
@@ -695,10 +696,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// descriptor (instead of a class internal name):
// invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object
val target: String = targetTypeKind.asRefBType.classOrArrayType
- bc.invokevirtual(target, "clone", "()Ljava/lang/Object;")
+ bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos)
}
else {
- genCallMethod(sym, invokeStyle, hostClass, app.pos)
+ genCallMethod(sym, invokeStyle, app.pos, hostClass)
}
} // end of genNormalMethodCall()
@@ -810,7 +811,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
def adapt(from: BType, to: BType) {
- if (!from.conformsTo(to)) {
+ if (!from.conformsTo(to).get) {
to match {
case UNIT => bc drop from
case _ => bc.emitT2T(from, to)
@@ -976,23 +977,23 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
case List(Literal(Constant("")), arg) =>
genLoad(arg, ObjectReference)
- genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false))
+ genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos)
case concatenations =>
- bc.genStartConcat
+ bc.genStartConcat(tree.pos)
for (elem <- concatenations) {
val kind = tpeTK(elem)
genLoad(elem, kind)
- bc.genStringConcat(kind)
+ bc.genStringConcat(kind, elem.pos)
}
- bc.genEndConcat
+ bc.genEndConcat(tree.pos)
}
StringReference
}
- def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) {
+ def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) {
val siteSymbol = claszSymbol
val hostSymbol = if (hostClass0 == null) method.owner else hostClass0
@@ -1036,26 +1037,26 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
if (style.isStatic) {
- if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr) }
- else { bc.invokestatic (jowner, jname, mdescr) }
+ if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) }
+ else { bc.invokestatic (jowner, jname, mdescr, pos) }
}
else if (style.isDynamic) {
- if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) }
- else { bc.invokevirtual (jowner, jname, mdescr) }
+ if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) }
+ else { bc.invokevirtual (jowner, jname, mdescr, pos) }
}
else {
assert(style.isSuper, s"An unknown InvokeStyle: $style")
- bc.invokespecial(jowner, jname, mdescr)
+ bc.invokespecial(jowner, jname, mdescr, pos)
initModule()
}
} // end of genCallMethod()
/* Generate the scala ## method. */
- def genScalaHash(tree: Tree): BType = {
+ def genScalaHash(tree: Tree, applyPos: Position): BType = {
genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ?
genLoad(tree, ObjectReference)
- genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false))
+ genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos)
INT
}
@@ -1187,8 +1188,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null)
if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) {
// `lhs` has reference type
- if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure)
- else genEqEqPrimitive(lhs, rhs, failure, success)
+ if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos)
+ else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos)
}
else if (scalaPrimitives.isComparisonOp(code))
genComparisonOp(lhs, rhs, code)
@@ -1208,7 +1209,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* @param l left-hand-side of the '=='
* @param r right-hand-side of the '=='
*/
- def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label) {
+ def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) {
/* True if the equality comparison is between values that require the use of the rich equality
* comparator (scala.runtime.Comparator.equals). This is the case when either side of the
@@ -1232,7 +1233,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
genLoad(l, ObjectReference)
genLoad(r, ObjectReference)
- genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false))
+ genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos)
genCZJUMP(success, failure, icodes.NE, BOOL)
}
else {
@@ -1248,7 +1249,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// SI-7852 Avoid null check if L is statically non-null.
genLoad(l, ObjectReference)
genLoad(r, ObjectReference)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic)
+ genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
genCZJUMP(success, failure, icodes.NE, BOOL)
} else {
// l == r -> if (l eq null) r eq null else l.equals(r)
@@ -1269,7 +1270,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
markProgramPoint(lNonNull)
locals.load(eqEqTempLocal)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic)
+ genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
genCZJUMP(success, failure, icodes.NE, BOOL)
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 246d565987..3b7dbc18da 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -11,6 +11,7 @@ import scala.tools.asm
import scala.collection.mutable
import scala.tools.nsc.io.AbstractFile
import GenBCode._
+import BackendReporting._
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -67,7 +68,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
override def getCommonSuperClass(inameA: String, inameB: String): String = {
val a = classBTypeFromInternalName(inameA)
val b = classBTypeFromInternalName(inameB)
- val lub = a.jvmWiseLUB(b)
+ val lub = a.jvmWiseLUB(b).get
val lubName = lub.internalName
assert(lubName != "scala/Any")
lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things.
@@ -205,12 +206,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* can-multi-thread
*/
final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
- val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct
+ val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) {
// Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes.
- val Some(e) = nestedClass.innerClassAttributeEntry
+ val Some(e) = nestedClass.innerClassAttributeEntry.get
jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags)
}
}
@@ -341,7 +342,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
*/
final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = {
val r = classBTypeFromSymbol(sym)
- if (r.isNestedClass) innerClassBufferASM += r
+ if (r.isNestedClass.get) innerClassBufferASM += r
r
}
@@ -351,7 +352,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* TODO: clean up the way we track referenced inner classes.
*/
final def toTypeKind(t: Type): BType = typeToBType(t) match {
- case c: ClassBType if c.isNestedClass =>
+ case c: ClassBType if c.isNestedClass.get =>
innerClassBufferASM += c
c
case r => r
@@ -364,7 +365,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
final def asmMethodType(msym: Symbol): MethodBType = {
val r = methodBTypeFromSymbol(msym)
(r.returnType :: r.argumentTypes) foreach {
- case c: ClassBType if c.isNestedClass => innerClassBufferASM += c
+ case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c
case _ =>
}
r
@@ -714,7 +715,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
val mirrorClass = new asm.tree.ClassNode
mirrorClass.visit(
classfileVersion,
- bType.info.flags,
+ bType.info.get.flags,
bType.internalName,
null /* no java-generic-signature */,
ObjectReference.internalName,
@@ -730,7 +731,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)
- innerClassBufferASM ++= bType.info.nestedClasses
+ innerClassBufferASM ++= bType.info.get.nestedClasses
addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)
mirrorClass.visitEnd()
@@ -846,7 +847,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
constructor.visitEnd()
- innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses
+ innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses
addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList)
beanInfoClass.visitEnd()
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index c743ebd16f..9993357eee 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -11,6 +11,7 @@ import scala.tools.asm
import scala.annotation.switch
import scala.collection.mutable
import GenBCode._
+import scala.tools.asm.tree.MethodInsnNode
/*
* A high-level facade to the ASM API for bytecode generation.
@@ -105,7 +106,7 @@ abstract class BCodeIdiomatic extends SubComponent {
*/
abstract class JCodeMethodN {
- def jmethod: asm.MethodVisitor
+ def jmethod: asm.tree.MethodNode
import asm.Opcodes;
import icodes.opcodes.{ Static, Dynamic, SuperCall }
@@ -205,20 +206,21 @@ abstract class BCodeIdiomatic extends SubComponent {
/*
* can-multi-thread
*/
- final def genStartConcat {
+ final def genStartConcat(pos: Position): Unit = {
jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
jmethod.visitInsn(Opcodes.DUP)
invokespecial(
StringBuilderClassName,
INSTANCE_CONSTRUCTOR_NAME,
- "()V"
+ "()V",
+ pos
)
}
/*
* can-multi-thread
*/
- final def genStringConcat(el: BType) {
+ final def genStringConcat(el: BType, pos: Position): Unit = {
val jtype =
if (el.isArray || el.isClass) ObjectReference
@@ -226,14 +228,14 @@ abstract class BCodeIdiomatic extends SubComponent {
val bt = MethodBType(List(jtype), StringBuilderReference)
- invokevirtual(StringBuilderClassName, "append", bt.descriptor)
+ invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos)
}
/*
* can-multi-thread
*/
- final def genEndConcat {
- invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;")
+ final def genEndConcat(pos: Position): Unit = {
+ invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
}
/*
@@ -389,20 +391,26 @@ abstract class BCodeIdiomatic extends SubComponent {
final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
// can-multi-thread
- final def invokespecial(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false)
+ final def invokespecial(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
}
// can-multi-thread
- final def invokestatic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false)
+ final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
}
// can-multi-thread
- final def invokeinterface(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true)
+ final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
}
// can-multi-thread
- final def invokevirtual(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
+ final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
+ addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
+ }
+
+ private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
+ val node = new MethodInsnNode(opcode, owner, name, desc, itf)
+ jmethod.instructions.add(node)
+ if (settings.YoptInlinerEnabled) callsitePositions(node) = pos
}
// can-multi-thread
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index 61606419bd..2a06c62e37 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -4,20 +4,17 @@
*/
-package scala
-package tools.nsc
+package scala.tools.nsc
package backend
package jvm
import scala.collection.{ mutable, immutable }
import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
import scala.tools.nsc.symtab._
-import scala.annotation.switch
import scala.tools.asm
-import scala.tools.asm.util.{TraceMethodVisitor, ASMifier}
-import java.io.PrintWriter
import GenBCode._
+import BackendReporting._
/*
*
@@ -122,11 +119,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
addClassFields()
- innerClassBufferASM ++= classBType.info.nestedClasses
+ innerClassBufferASM ++= classBType.info.get.nestedClasses
gen(cd.impl)
addInnerClassesASM(cnode, innerClassBufferASM.toList)
- cnode.visitAttribute(classBType.inlineInfoAttribute)
+ cnode.visitAttribute(classBType.inlineInfoAttribute.get)
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
@@ -146,9 +143,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val ps = claszSymbol.info.parents
val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol)
- val interfaceNames = classBTypeFromSymbol(claszSymbol).info.interfaces map {
+ val interfaceNames = classBTypeFromSymbol(claszSymbol).info.get.interfaces map {
case classBType =>
- if (classBType.isNestedClass) { innerClassBufferASM += classBType }
+ if (classBType.isNestedClass.get) { innerClassBufferASM += classBType }
classBType.internalName
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 872d1cc522..d2ee944916 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -7,12 +7,15 @@ package scala.tools.nsc
package backend.jvm
import scala.annotation.switch
+import scala.collection.concurrent.TrieMap
+import scala.reflect.internal.util.Position
import scala.tools.asm
import asm.Opcodes
-import scala.tools.asm.tree.{InnerClassNode, ClassNode}
+import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode}
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
+import scala.tools.nsc.backend.jvm.BackendReporting._
+import BackendReporting.RightBiasedEither
import scala.tools.nsc.backend.jvm.opt._
-import opt.OptimizerReporting._
import scala.collection.convert.decorateAsScala._
/**
@@ -41,6 +44,8 @@ abstract class BTypes {
val callGraph: CallGraph[this.type]
+ val backendReporting: BackendReporting
+
// Allows to define per-run caches here and in the CallGraph component, which don't have a global
def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T
@@ -50,6 +55,9 @@ abstract class BTypes {
// When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes
def inlinerEnabled: Boolean
+ // Settings that define what kind of optimizer warnings are emitted.
+ def warnSettings: WarnSettings
+
/**
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
* construction.
@@ -61,7 +69,19 @@ abstract class BTypes {
* Concurrent because stack map frames are computed when in the class writer, which might run
* on multiple classes concurrently.
*/
- val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType])
+ val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
+
+ /**
+ * Store the position of every MethodInsnNode during code generation. This allows each callsite
+ * in the call graph to remember its source position, which is required for inliner warnings.
+ */
+ val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
+
+ /**
+ * Contains the internal names of all classes that are defined in Java source files of the current
+ * compilation run (mixed compilation). Used for more detailed error reporting.
+ */
+ val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty)
/**
* Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType
@@ -73,29 +93,33 @@ abstract class BTypes {
*
* This method supports both descriptors and internal names.
*/
- def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): Option[BType] = (desc(0): @switch) match {
- case 'V' => Some(UNIT)
- case 'Z' => Some(BOOL)
- case 'C' => Some(CHAR)
- case 'B' => Some(BYTE)
- case 'S' => Some(SHORT)
- case 'I' => Some(INT)
- case 'F' => Some(FLOAT)
- case 'J' => Some(LONG)
- case 'D' => Some(DOUBLE)
- case '[' => bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)) map ArrayBType
+ def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match {
+ case 'V' => UNIT
+ case 'Z' => BOOL
+ case 'C' => CHAR
+ case 'B' => BYTE
+ case 'S' => SHORT
+ case 'I' => INT
+ case 'F' => FLOAT
+ case 'J' => LONG
+ case 'D' => DOUBLE
+ case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)))
case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1))
case _ => classBTypeFromParsedClassfile(desc)
}
/**
- * Parse the classfile for `internalName` and construct the [[ClassBType]]. Returns `None` if the
- * classfile cannot be found in the `byteCodeRepository`.
+ * Parse the classfile for `internalName` and construct the [[ClassBType]]. If the classfile cannot
+ * be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined.
*/
- def classBTypeFromParsedClassfile(internalName: InternalName): Option[ClassBType] = {
- classBTypeFromInternalName.get(internalName) orElse {
- byteCodeRepository.classNode(internalName) map classBTypeFromClassNode
- }
+ def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = {
+ classBTypeFromInternalName.getOrElse(internalName, {
+ val res = ClassBType(internalName)
+ byteCodeRepository.classNode(internalName) match {
+ case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res
+ case Right(c) => setClassInfoFromParsedClassfile(c, res)
+ }
+ })
}
/**
@@ -108,26 +132,15 @@ abstract class BTypes {
}
private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = {
- def ensureClassBTypeFromParsedClassfile(internalName: InternalName): ClassBType = {
- classBTypeFromParsedClassfile(internalName) getOrElse {
- // When building a ClassBType from a parsed classfile, we need the ClassBTypes for all
- // referenced types.
- // TODO: make this more robust with respect to incomplete classpaths.
- // Maybe not those parts of the ClassBType that require the missing class are not actually
- // queried during the backend, so every part of a ClassBType that requires parsing a
- // (potentially missing) classfile should be computed lazily.
- assertionError(s"Could not find bytecode for class $internalName")
- }
- }
val superClass = classNode.superName match {
case null =>
assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}")
None
case superName =>
- Some(ensureClassBTypeFromParsedClassfile(superName))
+ Some(classBTypeFromParsedClassfile(superName))
}
- val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(ensureClassBTypeFromParsedClassfile)(collection.breakOut)
+ val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
val flags = classNode.access
@@ -145,15 +158,13 @@ abstract class BTypes {
def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = {
(innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) ||
(innerClassNode.outerName == null && {
- val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name) getOrElse {
- assertionError(s"Could not find bytecode for class ${innerClassNode.name}")
- }
+ val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name).get // TODO: don't get here, but set the info to Left at the end
classNodeForInnerClass.outerClass == classNode.name
})
}
val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({
- case i if nestedInCurrentClass(i) => ensureClassBTypeFromParsedClassfile(i.name)
+ case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name)
})(collection.breakOut)
// if classNode is a nested class, it has an innerClass attribute for itself. in this
@@ -163,11 +174,11 @@ abstract class BTypes {
val enclosingClass =
if (innerEntry.outerName != null) {
// if classNode is a member class, the outerName is non-null
- ensureClassBTypeFromParsedClassfile(innerEntry.outerName)
+ classBTypeFromParsedClassfile(innerEntry.outerName)
} else {
// for anonymous or local classes, the outerName is null, but the enclosing class is
// stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass).
- ensureClassBTypeFromParsedClassfile(classNode.outerClass)
+ classBTypeFromParsedClassfile(classNode.outerClass)
}
val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0
NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag)
@@ -175,7 +186,7 @@ abstract class BTypes {
val inlineInfo = inlineInfoFromClassfile(classNode)
- classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)
+ classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
}
@@ -186,12 +197,16 @@ abstract class BTypes {
*/
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 = {
+ val warning = {
+ val isScala = classNode.attrs != null && classNode.attrs.asScala.exists(a => a.`type` == BTypes.ScalaAttributeName || a.`type` == BTypes.ScalaSigAttributeName)
+ if (isScala) Some(NoInlineInfoAttribute(classNode.name))
+ else None
+ }
// 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.
@@ -209,7 +224,7 @@ abstract class BTypes {
traitImplClassSelfType = None,
isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode),
methodInfos = methodInfos,
- warning = None)
+ warning)
}
// The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT
@@ -283,7 +298,7 @@ abstract class BTypes {
* promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the
* Java bytecode type hierarchy.
*/
- final def conformsTo(other: BType): Boolean = {
+ final def conformsTo(other: BType): Either[NoClassBTypeInfo, Boolean] = tryEither(Right({
assert(isRef || isPrimitive, s"conformsTo cannot handle $this")
assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other")
@@ -291,7 +306,7 @@ abstract class BTypes {
case ArrayBType(component) =>
if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true
else other match {
- case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent)
+ case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent).orThrow
case _ => false
}
@@ -300,7 +315,7 @@ abstract class BTypes {
if (other.isBoxed) this == other
else if (other == ObjectReference) true
else other match {
- case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number
+ case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number
case _ => false
}
} else if (isNullType) {
@@ -310,7 +325,7 @@ abstract class BTypes {
} else if (isNothingType) {
true
} else other match {
- case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType)
+ case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow
// case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case
case _ =>
// isNothingType || // documentation only, because `if (isNothingType)` above covers this case
@@ -325,7 +340,7 @@ abstract class BTypes {
assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other")
this == other
}
- }
+ }))
/**
* Compute the upper bound of two types.
@@ -765,9 +780,22 @@ abstract class BTypes {
* A ClassBType represents a class or interface type. The necessary information to build a
* ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols.
*
- * Currently non-final due to SI-9111
+ * The `info` field contains either the class information on an error message why the info could
+ * not be computed. There are two reasons for an erroneous info:
+ * 1. The ClassBType was built from a class symbol that stems from a java source file, and the
+ * symbol's type could not be completed successfully (SI-9111)
+ * 2. The ClassBType should be built from a classfile, but the class could not be found on the
+ * compilation classpath.
+ *
+ * Note that all ClassBTypes required in a non-optimzied run are built during code generation from
+ * the class symbols referenced by the ASTs, so they have a valid info. Therefore the backend
+ * often invokes `info.get` (which asserts the info to exist) when reading data from the ClassBType.
+ *
+ * The inliner on the other hand uses ClassBTypes that are built from classfiles, which may have
+ * a missing info. In order not to crash the compiler unnecessarily, the inliner does not force
+ * infos using `get`, but it reports inliner warnings for missing infos that prevent inlining.
*/
- /*final*/ case class ClassBType(internalName: InternalName) extends RefBType {
+ final case class ClassBType(internalName: InternalName) extends RefBType {
/**
* Write-once variable allows initializing a cyclic graph of infos. This is required for
* nested classes. Example: for the definition `class A { class B }` we have
@@ -775,14 +803,14 @@ abstract class BTypes {
* B.info.nestedInfo.outerClass == A
* A.info.nestedClasses contains B
*/
- private var _info: ClassInfo = null
+ private var _info: Either[NoClassBTypeInfo, ClassInfo] = null
- def info: ClassInfo = {
+ def info: Either[NoClassBTypeInfo, ClassInfo] = {
assert(_info != null, s"ClassBType.info not yet assigned: $this")
_info
}
- def info_=(i: ClassInfo): Unit = {
+ def info_=(i: Either[NoClassBTypeInfo, ClassInfo]): Unit = {
assert(_info == null, s"Cannot set ClassBType.info multiple times: $this")
_info = i
checkInfoConsistency()
@@ -791,27 +819,29 @@ abstract class BTypes {
classBTypeFromInternalName(internalName) = this
private def checkInfoConsistency(): Unit = {
+ if (info.isLeft) return
+
// we assert some properties. however, some of the linked ClassBType (members, superClass,
// interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a
- // best-effort verification.
- def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c)
+ // best-effort verification. also we don't report an error if the info is a Left.
+ def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c)
def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName
assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this")
assert(
- if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) }
- else if (isInterface) isJLO(info.superClass.get)
- else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface),
- s"Invalid superClass in $this: ${info.superClass}"
+ if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) }
+ else if (isInterface.get) isJLO(info.get.superClass.get)
+ else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get),
+ s"Invalid superClass in $this: ${info.get.superClass}"
)
assert(
- info.interfaces.forall(c => ifInit(c)(_.isInterface)),
- s"Invalid interfaces in $this: ${info.interfaces}"
+ info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)),
+ s"Invalid interfaces in $this: ${info.get.interfaces}"
)
- assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses)
+ assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses)
}
/**
@@ -819,12 +849,12 @@ abstract class BTypes {
*/
def simpleName: String = internalName.split("/").last
- def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0
+ def isInterface: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_INTERFACE) != 0)
- def superClassesTransitive: List[ClassBType] = info.superClass match {
- case None => Nil
- case Some(sc) => sc :: sc.superClassesTransitive
- }
+ def superClassesTransitive: Either[NoClassBTypeInfo, List[ClassBType]] = info.flatMap(i => i.superClass match {
+ case None => Right(Nil)
+ case Some(sc) => sc.superClassesTransitive.map(sc :: _)
+ })
/**
* The prefix of the internal name until the last '/', or the empty string.
@@ -837,15 +867,19 @@ abstract class BTypes {
}
}
- def isPublic = (info.flags & asm.Opcodes.ACC_PUBLIC) != 0
+ def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0)
- def isNestedClass = info.nestedInfo.isDefined
+ def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined)
- def enclosingNestedClassesChain: List[ClassBType] =
- if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain
- else Nil
+ def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = {
+ isNestedClass.flatMap(isNested => {
+ // if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined.
+ if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _)
+ else Right(Nil)
+ })
+ }
- def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map {
+ def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map {
case NestedInfo(_, outerName, innerName, isStaticNestedClass) =>
InnerClassEntry(
internalName,
@@ -853,30 +887,39 @@ abstract class BTypes {
innerName.orNull,
GenBCode.mkFlags(
// the static flag in the InnerClass table has a special meaning, see InnerClass comment
- info.flags & ~Opcodes.ACC_STATIC,
+ i.flags & ~Opcodes.ACC_STATIC,
if (isStaticNestedClass) Opcodes.ACC_STATIC else 0
) & ClassBType.INNER_CLASSES_FLAGS
)
- }
-
- def inlineInfoAttribute: InlineInfoAttribute = InlineInfoAttribute(info.inlineInfo)
+ })
- def isSubtypeOf(other: ClassBType): Boolean = {
- if (this == other) return true
+ def inlineInfoAttribute: Either[NoClassBTypeInfo, InlineInfoAttribute] = info.map(i => {
+ // InlineInfos are serialized for classes being compiled. For those the info was built by
+ // buildInlineInfoFromClassSymbol, which only adds a warning under SI-9111, which in turn
+ // only happens for class symbols of java source files.
+ // we could put this assertion into InlineInfoAttribute, but it is more safe to put it here
+ // where it affect only GenBCode, and not add any assertion to GenASM in 2.11.6.
+ assert(i.inlineInfo.warning.isEmpty, i.inlineInfo.warning)
+ InlineInfoAttribute(i.inlineInfo)
+ })
- if (isInterface) {
- if (other == ObjectReference) return true // interfaces conform to Object
- if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false.
+ def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try {
+ if (this == other) return Right(true)
+ if (isInterface.orThrow) {
+ if (other == ObjectReference) return Right(true) // interfaces conform to Object
+ if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false.
// else: this and other are both interfaces. continue to (*)
} else {
- val sc = info.superClass
- if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other
- if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform
+ val sc = info.orThrow.superClass
+ if (sc.isDefined && sc.get.isSubtypeOf(other).orThrow) return Right(true) // the superclass of this class conforms to other
+ if (!other.isInterface.orThrow) return Right(false) // this and other are both classes, and the superclass of this does not conform
// else: this is a class, the other is an interface. continue to (*)
}
// (*) check if some interface of this class conforms to other.
- info.interfaces.exists(_.isSubtypeOf(other))
+ Right(info.orThrow.interfaces.exists(_.isSubtypeOf(other).orThrow))
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo)
}
/**
@@ -886,34 +929,36 @@ abstract class BTypes {
* http://comments.gmane.org/gmane.comp.java.vm.languages/2293
* https://issues.scala-lang.org/browse/SI-3872
*/
- def jvmWiseLUB(other: ClassBType): ClassBType = {
+ def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = {
def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType
assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other")
- val res: ClassBType = (this.isInterface, other.isInterface) match {
- case (true, true) =>
- // exercised by test/files/run/t4761.scala
- if (other.isSubtypeOf(this)) this
- else if (this.isSubtypeOf(other)) other
- else ObjectReference
-
- case (true, false) =>
- if (other.isSubtypeOf(this)) this else ObjectReference
-
- case (false, true) =>
- if (this.isSubtypeOf(other)) other else ObjectReference
+ tryEither {
+ val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match {
+ case (true, true) =>
+ // exercised by test/files/run/t4761.scala
+ if (other.isSubtypeOf(this).orThrow) this
+ else if (this.isSubtypeOf(other).orThrow) other
+ else ObjectReference
+
+ case (true, false) =>
+ if (other.isSubtypeOf(this).orThrow) this else ObjectReference
+
+ case (false, true) =>
+ if (this.isSubtypeOf(other).orThrow) other else ObjectReference
+
+ case _ =>
+ // TODO @lry I don't really understand the reasoning here.
+ // Both this and other are classes. The code takes (transitively) all superclasses and
+ // finds the first common one.
+ // MOST LIKELY the answer can be found here, see the comments and links by Miguel:
+ // - https://issues.scala-lang.org/browse/SI-3872
+ firstCommonSuffix(this :: this.superClassesTransitive.orThrow, other :: other.superClassesTransitive.orThrow)
+ }
- case _ =>
- // TODO @lry I don't really understand the reasoning here.
- // Both this and other are classes. The code takes (transitively) all superclasses and
- // finds the first common one.
- // MOST LIKELY the answer can be found here, see the comments and links by Miguel:
- // - https://issues.scala-lang.org/browse/SI-3872
- firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive)
+ assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res")
+ Right(res)
}
-
- assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res")
- res
}
private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = {
@@ -1080,11 +1125,15 @@ object BTypes {
* @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class.
* The map is indexed by the string s"$name$descriptor" (to
* disambiguate overloads).
+ *
+ * @param warning Contains an warning message if an error occured when building this
+ * InlineInfo, for example if some classfile could not be found on
+ * the classpath. This warning can be reported later by the inliner.
*/
final case class InlineInfo(traitImplClassSelfType: Option[InternalName],
isEffectivelyFinal: Boolean,
methodInfos: Map[String, MethodInlineInfo],
- warning: Option[String])
+ warning: Option[ClassInlineInfoWarning])
val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None)
@@ -1102,4 +1151,8 @@ object BTypes {
traitMethodWithStaticImplementation: Boolean,
annotatedInline: Boolean,
annotatedNoInline: Boolean)
+
+ // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR
+ val ScalaAttributeName = "Scala"
+ val ScalaSigAttributeName = "ScalaSig"
} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index b90030dd8c..eeb6ed24a2 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -9,6 +9,7 @@ package backend.jvm
import scala.tools.asm
import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository}
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
+import BackendReporting._
/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -34,12 +35,14 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val coreBTypes = new CoreBTypesProxy[this.type](this)
import coreBTypes._
- val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty))
+ val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty))
val inliner: Inliner[this.type] = new Inliner(this)
val callGraph: CallGraph[this.type] = new CallGraph(this)
+ val backendReporting: BackendReporting = new BackendReportingImpl(global)
+
final def initializeCoreBTypes(): Unit = {
coreBTypes.setBTypes(new CoreBTypes[this.type](this))
}
@@ -50,6 +53,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
def inlinerEnabled: Boolean = settings.YoptInlinerEnabled
+ def warnSettings: WarnSettings = {
+ val c = settings.YoptWarningsChoices
+ // cannot extract settings.YoptWarnings into a local val due to some dependent typing issue.
+ WarnSettings(
+ !settings.YoptWarnings.isSetByUser || settings.YoptWarnings.contains(c.atInlineFailedSummary.name) || settings.YoptWarnings.contains(c.atInlineFailed.name),
+ settings.YoptWarnings.contains(c.noInlineMixed.name),
+ settings.YoptWarnings.contains(c.noInlineMissingBytecode.name),
+ settings.YoptWarnings.contains(c.noInlineMissingScalaInlineInfoAttr.name))
+ }
+
// helpers that need access to global.
// TODO @lry create a separate component, they don't belong to BTypesFromSymbols
@@ -104,28 +117,20 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
else {
val internalName = classSym.javaBinaryName.toString
classBTypeFromInternalName.getOrElse(internalName, {
+ // The new ClassBType is added to the map in its constructor, before we set its info. This
+ // allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
+ val res = ClassBType(internalName)
if (completeSilentlyAndCheckErroneous(classSym)) {
- new ErroneousClassBType(internalName)
+ res.info = Left(NoClassBTypeInfoClassSymbolInfoFailedSI9111(classSym.fullName))
+ res
} else {
- // The new ClassBType is added to the map in its constructor, before we set its info. This
- // allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
- setClassInfo(classSym, ClassBType(internalName))
+ setClassInfo(classSym, res)
}
})
}
}
/**
- * Part of the workaround for SI-9111. Makes sure that the compiler only fails if the ClassInfo
- * of the symbol that could not be completed is actually required.
- */
- private class ErroneousClassBType(internalName: InternalName) extends ClassBType(internalName) {
- def msg = s"The class info for $internalName could not be completed due to SI-9111."
- override def info: ClassInfo = opt.OptimizerReporting.assertionError(msg)
- override def info_=(i: ClassInfo): Unit = opt.OptimizerReporting.assertionError(msg)
- }
-
- /**
* Builds a [[MethodBType]] for a method symbol.
*/
final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = {
@@ -202,7 +207,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
case ThisType(sym) => classBTypeFromSymbol(sym)
case SingleType(_, sym) => primitiveOrClassToBType(sym)
case ConstantType(_) => typeToBType(t.underlying)
- case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))
+ case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get)
}
}
}
@@ -340,7 +345,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val inlineInfo = buildInlineInfo(classSym, classBType.internalName)
- classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)
+ classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
}
@@ -421,13 +426,10 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// 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) =>
+ case Right(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
+ case Left(missingClass) =>
+ InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass)))
}
}
}
@@ -444,14 +446,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val c = ClassBType(internalName)
// class info consistent with BCodeHelpers.genMirrorClass
val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
- c.info = ClassInfo(
+ c.info = Right(ClassInfo(
superClass = Some(ObjectReference),
interfaces = Nil,
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
nestedClasses = nested,
nestedInfo = None,
- InlineInfo(None, true, Map.empty, None) // 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
})
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
new file mode 100644
index 0000000000..a06fb4bab8
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
@@ -0,0 +1,265 @@
+package scala.tools.nsc
+package backend.jvm
+
+import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.reflect.internal.util.Position
+
+/**
+ * Interface for emitting inline warnings. The interface is required because the implementation
+ * depends on Global, which is not available in BTypes (only in BTypesFromSymbols).
+ */
+sealed abstract class BackendReporting {
+ def inlinerWarning(pos: Position, message: String): Unit
+}
+
+final class BackendReportingImpl(val global: Global) extends BackendReporting {
+ import global._
+
+ def inlinerWarning(pos: Position, message: String): Unit = {
+ currentRun.reporting.inlinerWarning(pos, message)
+ }
+}
+
+/**
+ * Utilities for error reporting.
+ *
+ * Defines some tools to make error reporting with Either easier. Would be subsumed by a right-biased
+ * Either in the standard library (or scalaz \/) (Validation is different, it accumulates multiple
+ * errors).
+ */
+object BackendReporting {
+ def methodSignature(classInternalName: InternalName, name: String, desc: String) = {
+ classInternalName + "::" + name + desc
+ }
+
+ def methodSignature(classInternalName: InternalName, method: MethodNode): String = {
+ methodSignature(classInternalName, method.name, method.desc)
+ }
+
+ def assertionError(message: String): Nothing = throw new AssertionError(message)
+
+ implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal {
+ def map[U](f: B => U) = v.right.map(f)
+ def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f)
+ def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match {
+ case Left(_) => v
+ case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty
+ }
+ def foreach[U](f: B => U) = v.right.foreach(f)
+
+ def getOrElse[BB >: B](alt: => BB): BB = v.right.getOrElse(alt)
+
+ /**
+ * Get the value, fail with an assertion if this is an error.
+ */
+ def get: B = {
+ assert(v.isRight, v.left.get)
+ v.right.get
+ }
+
+ /**
+ * Get the right value of an `Either` by throwing a potential error message. Can simplify the
+ * implementation of methods that act on multiple `Either` instances. Instead of flat-mapping,
+ * the first error can be collected as
+ *
+ * tryEither {
+ * eitherOne.orThrow .... eitherTwo.orThrow ... eitherThree.orThrow
+ * }
+ */
+ def orThrow: B = v match {
+ case Left(m) => throw Invalid(m)
+ case Right(t) => t
+ }
+ }
+
+ case class Invalid[A](e: A) extends Exception
+
+ /**
+ * See documentation of orThrow above.
+ */
+ def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) }
+
+ final case class WarnSettings(atInlineFailed: Boolean, noInlineMixed: Boolean, noInlineMissingBytecode: Boolean, noInlineMissingScalaInlineInfoAttr: Boolean)
+
+ sealed trait OptimizerWarning {
+ def emitWarning(settings: WarnSettings): Boolean
+ }
+
+ // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here
+ // in scope allows for-comprehensions that desugar into filter calls (for example when using a
+ // tuple de-constructor).
+ implicit object emptyOptimizerWarning extends OptimizerWarning {
+ def emitWarning(settings: WarnSettings): Boolean = false
+ }
+
+ sealed trait MissingBytecodeWarning extends OptimizerWarning {
+ override def toString = this match {
+ case ClassNotFound(internalName, definedInJavaSource) =>
+ s"The classfile for $internalName could not be found on the compilation classpath." + {
+ if (definedInJavaSource) "\nThe class is defined in a Java source file that is being compiled (mixed compilation), therefore no bytecode is available."
+ else ""
+ }
+
+ case MethodNotFound(name, descriptor, ownerInternalName, missingClasses) =>
+ val (javaDef, others) = missingClasses.partition(_.definedInJavaSource)
+ s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." +
+ (if (others.isEmpty) "" else others.map(_.internalName).mkString("\nNote that the following parent classes could not be found on the classpath: ", ", ", "")) +
+ (if (javaDef.isEmpty) "" else javaDef.map(_.internalName).mkString("\nNote that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: ", ",", ""))
+
+ case FieldNotFound(name, descriptor, ownerInternalName, missingClass) =>
+ s"The field node $name$descriptor could not be found because the classfile $ownerInternalName cannot be found on the classpath." +
+ missingClass.map(c => s" Reason:\n$c").getOrElse("")
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case ClassNotFound(_, javaDefined) =>
+ if (javaDefined) settings.noInlineMixed
+ else settings.noInlineMissingBytecode
+
+ case m @ MethodNotFound(_, _, _, missing) =>
+ if (m.isArrayMethod) false
+ else settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+
+ case FieldNotFound(_, _, _, missing) =>
+ settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+ }
+ }
+
+ case class ClassNotFound(internalName: InternalName, definedInJavaSource: Boolean) extends MissingBytecodeWarning
+ case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClasses: List[ClassNotFound]) extends MissingBytecodeWarning {
+ def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '['
+ }
+ case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning
+
+ sealed trait NoClassBTypeInfo extends OptimizerWarning {
+ override def toString = this match {
+ case NoClassBTypeInfoMissingBytecode(cause) =>
+ cause.toString
+
+ case NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName) =>
+ s"Failed to get the type of class symbol $classFullName due to SI-9111."
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings)
+ case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.noInlineMissingBytecode
+ }
+ }
+
+ case class NoClassBTypeInfoMissingBytecode(cause: MissingBytecodeWarning) extends NoClassBTypeInfo
+ case class NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName: String) extends NoClassBTypeInfo
+
+ /**
+ * Used in the CallGraph for nodes where an issue occurred determining the callee information.
+ */
+ sealed trait CalleeInfoWarning extends OptimizerWarning {
+ def declarationClass: InternalName
+ def name: String
+ def descriptor: String
+
+ def warningMessageSignature = BackendReporting.methodSignature(declarationClass, name, descriptor)
+
+ override def toString = this match {
+ case MethodInlineInfoIncomplete(_, _, _, cause) =>
+ s"The inline information for $warningMessageSignature may be incomplete:\n" + cause
+
+ case MethodInlineInfoMissing(_, _, _, cause) =>
+ s"No inline information for method $warningMessageSignature could be found." +
+ cause.map(" Possible reason:\n" + _).getOrElse("")
+
+ case MethodInlineInfoError(_, _, _, cause) =>
+ s"Error while computing the inline information for method $warningMessageSignature:\n" + cause
+
+ case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) =>
+ cause.toString
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings)
+
+ case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings)
+ case MethodInlineInfoMissing(_, _, _, None) => settings.noInlineMissingBytecode
+
+ case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings)
+
+ case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings)
+ }
+ }
+
+ case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning
+ case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning
+ case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning
+ case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning
+
+ sealed trait CannotInlineWarning extends OptimizerWarning {
+ def calleeDeclarationClass: InternalName
+ def name: String
+ def descriptor: String
+
+ def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor)
+
+ override def toString = this match {
+ case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) =>
+ s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" +
+ s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass."
+
+ case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) =>
+ s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause
+
+ case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the
+ |arguments expected by the callee $calleeMethodSig. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ case SynchronizedMethod(_, _, _) =>
+ s"Method $calleeMethodSig cannot be inlined because it is synchronized."
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod =>
+ settings.atInlineFailed
+
+ case IllegalAccessCheckFailed(_, _, _, _, _, cause) =>
+ cause.emitWarning(settings)
+ }
+ }
+ case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning
+ case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning
+ case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
+ case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning
+
+ /**
+ * Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information.
+ */
+ sealed trait ClassInlineInfoWarning extends OptimizerWarning {
+ override def toString = this match {
+ case NoInlineInfoAttribute(internalName) =>
+ s"The Scala classfile $internalName does not have a ScalaInlineInfo attribute."
+
+ case ClassSymbolInfoFailureSI9111(classFullName) =>
+ s"Failed to get the type of a method of class symbol $classFullName due to SI-9111."
+
+ case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) =>
+ s"Failed to build the inline information: $missingClass."
+
+ case UnknownScalaInlineInfoVersion(internalName, version) =>
+ s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler."
+ }
+
+ def emitWarning(settings: WarnSettings): Boolean = this match {
+ case NoInlineInfoAttribute(_) => settings.noInlineMissingScalaInlineInfoAttr
+ case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings)
+ case ClassSymbolInfoFailureSI9111(_) => settings.noInlineMissingBytecode
+ case UnknownScalaInlineInfoVersion(_, _) => settings.noInlineMissingScalaInlineInfoAttr
+ }
+ }
+
+ case class NoInlineInfoAttribute(internalName: InternalName) extends ClassInlineInfoWarning
+ case class ClassSymbolInfoFailureSI9111(classFullName: String) extends ClassInlineInfoWarning
+ case class ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass: ClassNotFound) extends ClassInlineInfoWarning
+ case class UnknownScalaInlineInfoVersion(internalName: InternalName, version: Int) extends ClassInlineInfoWarning
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
index 246235f395..492fe3ae79 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
@@ -99,10 +99,9 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
*
* Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal
* names of NothingClass and NullClass can't be emitted as-is.
- * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$`
*/
- lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$])
- lazy val RT_NULL : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$])
+ lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$])
+ lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$])
lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass)
lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index 173aa0ca30..be1595dc29 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -307,6 +307,10 @@ abstract class GenBCode extends BCodeSyncAndTry {
arrivalPos = 0 // just in case
scalaPrimitives.init()
bTypes.initializeCoreBTypes()
+ bTypes.javaDefinedClasses.clear()
+ bTypes.javaDefinedClasses ++= currentRun.symSource collect {
+ case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString
+ }
Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart)
// initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated.
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)))
}
}
}
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 d2658bcd2a..14e8cccc60 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -85,7 +85,7 @@ object BytecodeUtils {
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 isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0
def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
var result = instruction
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 18b95184e5..cd204e8043 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -7,9 +7,11 @@ package scala.tools.nsc
package backend.jvm
package opt
+import scala.reflect.internal.util.{NoPosition, Position}
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InternalName}
+import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer
import ByteCodeRepository.{Source, CompilationUnit}
@@ -25,39 +27,40 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = {
+ case class CallsiteInfo(safeToInline: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, warning: Option[CalleeInfoWarning])
+
/**
* 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) = {
+ def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = {
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)
+ try {
+ // 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.orThrow.inlineInfo.methodInfos.get(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 || classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal
}
- }
-
- (canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline)
+ val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map(
+ MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _))
+ CallsiteInfo(canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline, warning)
- case None =>
- // TODO: issue inliner warning
- (false, false, false)
+ case None =>
+ val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning)
+ CallsiteInfo(false, false, false, Some(warning))
+ }
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) =>
+ val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
+ CallsiteInfo(false, false, false, Some(warning))
}
}
@@ -72,25 +75,22 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
methodNode.instructions.iterator.asScala.collect({
case call: MethodInsnNode =>
- // TODO: log an inliner warning if the callee method cannot be found in the code repo? eg it's not on the classpath.
- val callee = byteCodeRepository.methodNode(call.owner, call.name, call.desc) flatMap {
- case (method, declarationClass) =>
- // TODO: log inliner warning if callee decl class cannot be found?
- byteCodeRepository.classNodeAndSource(declarationClass) map {
- case (declarationClassNode, source) =>
- val declarationClassBType = classBTypeFromClassNode(declarationClassNode)
- val (safeToInline, annotatedInline, annotatedNoInline) = analyzeCallsite(method, declarationClassBType, call.owner, source)
- Callee(
- callee = method,
- calleeDeclarationClass = declarationClassBType,
- safeToInline = safeToInline,
- annotatedInline = annotatedInline,
- annotatedNoInline = annotatedNoInline
- )
- }
+ val callee: Either[OptimizerWarning, Callee] = for {
+ (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)]
+ (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)]
+ declarationClassBType = classBTypeFromClassNode(declarationClassNode)
+ } yield {
+ val CallsiteInfo(safeToInline, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source)
+ Callee(
+ callee = method,
+ calleeDeclarationClass = declarationClassBType,
+ safeToInline = safeToInline,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ calleeInfoWarning = warning)
}
- val argInfos = if (callee.isEmpty) Nil else {
+ val argInfos = if (callee.isLeft) Nil else {
// TODO: for now it's Nil, because we don't run any data flow analysis
// there's no point in using the parameter types, that doesn't add any information.
// NOTE: need to run the same analyses after inlining, to re-compute the argInfos for the
@@ -104,7 +104,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
callsiteClass = definingClass,
callee = callee,
argInfos = argInfos,
- callsiteStackHeight = analyzer.frameAt(call).getStackSize
+ callsiteStackHeight = analyzer.frameAt(call).getStackSize,
+ callsitePosition = callsitePositions.getOrElse(call, NoPosition)
)
}).toList
}
@@ -117,15 +118,20 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* @param callsiteClass The class containing the callsite
* @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.
+ * can be abstract. Contains a warning message 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
+ * @param callsitePosition The source position of the callsite, used for inliner warnings.
*/
final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType,
- callee: Option[Callee], argInfos: List[ArgInfo],
- callsiteStackHeight: Int) {
- override def toString = s"Invocation of ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteClass.internalName}.${callsiteMethod.name}"
+ callee: Either[OptimizerWarning, Callee], argInfos: List[ArgInfo],
+ callsiteStackHeight: Int, callsitePosition: Position) {
+ override def toString =
+ "Invocation of" +
+ s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" +
+ s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" +
+ s" in ${callsiteClass.internalName}.${callsiteMethod.name}"
}
/**
@@ -147,8 +153,11 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* and the inliner settings (project / global) allow inlining it.
* @param annotatedInline True if the callee is annotated @inline
* @param annotatedNoInline True if the callee is annotated @noinline
+ * @param calleeInfoWarning An inliner warning if some information was not available while
+ * gathering the information about this callee.
*/
final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType,
safeToInline: Boolean,
- annotatedInline: Boolean, annotatedNoInline: Boolean)
+ annotatedInline: Boolean, annotatedNoInline: Boolean,
+ calleeInfoWarning: Option[CalleeInfoWarning])
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
index 4812f2290f..e7dd5abc57 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -9,6 +9,7 @@ package opt
import scala.tools.asm._
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
+import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersion
/**
* This attribute stores the InlineInfo for a ClassBType as an independent classfile attribute.
@@ -119,7 +120,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
InlineInfoAttribute(InlineInfo(self, isFinal, infos, None))
} else {
- val msg = s"Cannot read ScalaInlineInfo version $version in classfile ${cr.getClassName}. Use a more recent compiler."
+ val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg)))
}
}
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 b2459862ea..7ce98ecff1 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -15,9 +15,10 @@ import scala.collection.convert.decorateAsScala._
import scala.collection.convert.decorateAsJava._
import AsmUtils._
import BytecodeUtils._
-import OptimizerReporting._
import collection.mutable
import scala.tools.asm.tree.analysis.{SourceInterpreter, Analyzer}
+import BackendReporting._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
class Inliner[BT <: BTypes](val btypes: BT) {
import btypes._
@@ -27,10 +28,19 @@ class Inliner[BT <: BTypes](val btypes: BT) {
rewriteFinalTraitMethodInvocations()
for (request <- collectAndOrderInlineRequests) {
- val Some(callee) = request.callee
- inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass,
+ val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee
+
+ val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass,
callee.callee, callee.calleeDeclarationClass,
receiverKnownNotNull = false, keepLineNumbers = false)
+
+ for (warning <- r) {
+ if ((callee.annotatedInline && btypes.warnSettings.atInlineFailed) || warning.emitWarning(warnSettings)) {
+ val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else ""
+ val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning"
+ backendReporting.inlinerWarning(request.callsitePosition, msg)
+ }
+ }
}
}
@@ -61,18 +71,51 @@ class Inliner[BT <: BTypes](val btypes: BT) {
*/
def selectCallsitesForInlining: List[Callsite] = {
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
+ case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) =>
+ val res = doInlineCallsite(callsite)
+
+ if (!res) {
+ if (annotatedInline && btypes.warnSettings.atInlineFailed) {
+ // if the callsite is annotated @inline, we report an inline warning even if the underlying
+ // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag).
+ def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined"
+ def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("")
+ if (!safeToInline)
+ backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg)
+ else if (doRewriteTraitCallsite(callsite) && isAbstractMethod(callee))
+ backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg)
+ else
+ backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg)
+ } else if (warning.isDefined && warning.get.emitWarning(warnSettings)) {
+ // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete.
+ backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get)
+ }
+ }
+
+ res
+
+ case Callsite(ins, _, _, Left(warning), _, _, pos) =>
+ if (warning.emitWarning(warnSettings))
+ backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning")
+ false
}).toList
}
+ /**
+ * The current inlining heuristics are simple: inline calls to methods annotated @inline.
+ */
+ def doInlineCallsite(callsite: Callsite): Boolean = callsite match {
+ case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) =>
+ // Usually, safeToInline implies that the callee is not abstract.
+ // But for final trait methods, the callee is abstract: "trait T { @inline final def f = 1}".
+ // A callsite (t: T).f is `safeToInline`, but the callee is the abstract method in the interface.
+ // We try to rewrite these calls to the static impl method, but that may not always succeed,
+ // in which case we cannot inline the call.
+ annotatedInline && safeToInline && !isAbstractMethod(callee)
+
+ case _ => false
+ }
+
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
@@ -81,33 +124,51 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
/**
+ * True for statically resolved trait callsites that should be rewritten to the static implementation method.
+ */
+ def doRewriteTraitCallsite(callsite: Callsite) = callsite.callee match {
+ case Right(Callee(callee, calleeDeclarationClass, true, annotatedInline, annotatedNoInline, infoWarning)) if isAbstractMethod(callee) =>
+ // The pattern matches abstract methods that are `safeToInline`. This can only match the interface method of a final, concrete
+ // trait method. An abstract method (in a trait or abstract class) is never `safeToInline` (abstract methods cannot be final).
+ // See also comment in `doInlineCallsite`
+ for (i <- calleeDeclarationClass.isInterface) assert(i, s"expected interface call (final trait method) when inlining abstract method: $callsite")
+ true
+
+ case _ => false
+ }
+
+ /**
* 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")
+ def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = {
+ if (doRewriteTraitCallsite(callsite)) {
+ val Right(Callee(callee, calleeDeclarationClass, safeToInline, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee
val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc)
- val selfParamType = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType match {
+ val implClassInternalName = calleeDeclarationClass.internalName + "$class"
+
+ val selfParamTypeV: Either[OptimizerWarning, ClassBType] = calleeDeclarationClass.info.map(_.inlineInfo.traitImplClassSelfType match {
case Some(internalName) => classBTypeFromParsedClassfile(internalName)
- case None => Some(calleeDeclarationClass)
- }
+ case None => calleeDeclarationClass
+ })
- val implClassInternalName = calleeDeclarationClass.internalName + "$class"
+ def implClassMethodV(implMethodDescriptor: String): Either[OptimizerWarning, MethodNode] = {
+ byteCodeRepository.methodNode(implClassInternalName, callee.name, implMethodDescriptor).map(_._1)
+ }
// 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 selfClassType, impl class or impl method cannot be found
- selfType <- selfParamType
- implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfType.toASMType +: traitMethodArgumentTypes: _*)
- (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor)
- implClassBType <- classBTypeFromParsedClassfile(implClassInternalName)
+ val res = for {
+ selfParamType <- selfParamTypeV
+ implMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType.toASMType +: traitMethodArgumentTypes: _*)
+ implClassMethod <- implClassMethodV(implMethodDescriptor)
+ implClassBType = classBTypeFromParsedClassfile(implClassInternalName)
+ selfTypeOk <- calleeDeclarationClass.isSubtypeOf(selfParamType)
} yield {
// The self parameter type may be incompatible with the trait type.
@@ -116,16 +177,16 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a
// VerifyError. We run a `SourceInterpreter` to find all producer instructions of the
// receiver value and add a cast to the self type after each.
- if (!calleeDeclarationClass.isSubtypeOf(selfType)) {
+ if (!selfTypeOk) {
val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter)
val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekDown(traitMethodArgumentTypes.length)
for (i <- receiverValue.insns.asScala) {
- val cast = new TypeInsnNode(CHECKCAST, selfType.internalName)
+ val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName)
callsite.callsiteMethod.instructions.insert(i, cast)
}
}
- val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implClassMethodDescriptor, false)
+ val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implMethodDescriptor, false)
callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction)
callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction)
@@ -134,19 +195,26 @@ class Inliner[BT <: BTypes](val btypes: BT) {
callsiteInstruction = newCallsiteInstruction,
callsiteMethod = callsite.callsiteMethod,
callsiteClass = callsite.callsiteClass,
- callee = Some(Callee(
+ callee = Right(Callee(
callee = implClassMethod,
calleeDeclarationClass = implClassBType,
- safeToInline = true,
- annotatedInline = true,
- annotatedNoInline = annotatedNoInline)),
+ safeToInline = safeToInline,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ calleeInfoWarning = infoWarning)),
argInfos = Nil,
- callsiteStackHeight = callsite.callsiteStackHeight
+ callsiteStackHeight = callsite.callsiteStackHeight,
+ callsitePosition = callsite.callsitePosition
)
callGraph.callsites(newCallsiteInstruction) = staticCallsite
}
- case _ =>
+ for (warning <- res.left) {
+ val Right(callee) = callsite.callee
+ val newCallee = callee.copy(calleeInfoWarning = Some(RewriteTraitCallToStaticImplMethodFailed(calleeDeclarationClass.internalName, callee.callee.name, callee.callee.desc, warning)))
+ callGraph.callsites(callsite.callsiteInstruction) = callsite.copy(callee = Right(newCallee))
+ }
+ }
}
/**
@@ -240,7 +308,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
*/
def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType,
callee: MethodNode, calleeDeclarationClass: ClassBType,
- receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[String] = {
+ receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[CannotInlineWarning] = {
canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse {
// New labels for the cloned instructions
val labelsMap = cloneLabels(callee)
@@ -363,7 +431,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
callsiteClass = callsiteClass,
callee = originalCallsite.callee,
argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them)
- callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight
+ callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight,
+ callsitePosition = originalCallsite.callsitePosition
)
case None =>
@@ -386,7 +455,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* @return `Some(message)` if inlining cannot be performed, `None` otherwise
*/
def canInline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType,
- callee: MethodNode, calleeDeclarationClass: ClassBType): Option[String] = {
+ callee: MethodNode, calleeDeclarationClass: ClassBType): Option[CannotInlineWarning] = {
def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}"
def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc"
@@ -416,37 +485,43 @@ class Inliner[BT <: BTypes](val btypes: BT) {
if (isSynchronizedMethod(callee)) {
// Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking
// in finally. But it's probably not worth the effort, scala never emits synchronized methods.
- Some(s"Method ${methodSignature(calleeDeclarationClass.internalName, callee)} is not inlined because it is synchronized")
+ Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc))
} else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) {
- Some(
- s"""The operand stack at the callsite in ${methodSignature(callsiteClass.internalName, callsiteMethod)} contains more values than the
- |arguments expected by the callee ${methodSignature(calleeDeclarationClass.internalName, callee)}. These values would be discarded
- |when entering an exception handler declared in the inlined method.""".stripMargin
- )
+ Some(MethodWithHandlerCalledOnNonEmptyStack(
+ calleeDeclarationClass.internalName, callee.name, callee.desc,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
} else findIllegalAccess(callee.instructions, callsiteClass) map {
- case illegalAccessIns =>
- s"""The callee ${methodSignature(calleeDeclarationClass.internalName, callee)} contains the instruction ${AsmUtils.textify(illegalAccessIns)}
- |that would cause an IllegalAccessError when inlined into class ${callsiteClass.internalName}""".stripMargin
+ case (illegalAccessIns, None) =>
+ IllegalAccessInstruction(
+ calleeDeclarationClass.internalName, callee.name, callee.desc,
+ callsiteClass.internalName, illegalAccessIns)
+
+ case (illegalAccessIns, Some(warning)) =>
+ IllegalAccessCheckFailed(
+ calleeDeclarationClass.internalName, callee.name, callee.desc,
+ callsiteClass.internalName, illegalAccessIns, warning)
}
}
/**
* Returns the first instruction in the `instructions` list that would cause a
- * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`. Returns `None` if
- * all instructions can be legally transplanted.
+ * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`.
+ *
+ * If validity of some instruction could not be checked because an error occurred, the instruction
+ * is returned together with a warning message that describes the problem.
*/
- def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[AbstractInsnNode] = {
+ def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
/**
* Check if a type is accessible to some class, as defined in JVMS 5.4.4.
* (A1) C is public
* (A2) C and D are members of the same run-time package
*/
- def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Boolean = (accessed: @unchecked) match {
+ def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match {
// TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
- case c: ClassBType => c.isPublic || c.packageInternalName == from.packageInternalName
+ case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName)
case a: ArrayBType => classIsAccessible(a.elementType, from)
- case _: PrimitiveBType => true
+ case _: PrimitiveBType => Right(true)
}
/**
@@ -471,27 +546,29 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* run-time package as D.
* (B4) R is private and is declared in D.
*/
- def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Boolean = {
+ def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Either[OptimizerWarning, Boolean] = {
// TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName
val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags
key match {
case ACC_PUBLIC => // B1
- true
+ Right(true)
case ACC_PROTECTED => // B2
- val condB2 = destinationClass.isSubtypeOf(memberDeclClass) && {
- val isStatic = (ACC_STATIC & memberFlags) != 0
- isStatic || memberRefClass.isSubtypeOf(destinationClass) || destinationClass.isSubtypeOf(memberRefClass)
+ tryEither {
+ val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && {
+ val isStatic = (ACC_STATIC & memberFlags) != 0
+ isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow
+ }
+ Right(condB2 || samePackageAsDestination) // B3 (protected)
}
- condB2 || samePackageAsDestination // B3 (protected)
- case 0 => // B3 (default access)
- samePackageAsDestination
+ case 0 => // B3 (default access)
+ Right(samePackageAsDestination)
case ACC_PRIVATE => // B4
- memberDeclClass == destinationClass
+ Right(memberDeclClass == destinationClass)
}
}
@@ -502,49 +579,68 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* byteCodeRepository, it is considered as not legal. This is known to happen in mixed
* compilation: for Java classes there is no classfile that could be parsed, nor does the
* compiler generate any bytecode.
+ *
+ * Returns a warning message describing the problem if checking the legality for the instruction
+ * failed.
*/
- def isLegal(instruction: AbstractInsnNode): Boolean = instruction match {
+ def isLegal(instruction: AbstractInsnNode): Either[OptimizerWarning, Boolean] = instruction match {
case ti: TypeInsnNode =>
// NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference
// "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so
// it can be an internal name, or a full array descriptor.
- bTypeForDescriptorOrInternalNameFromClassfile(ti.desc).exists(classIsAccessible(_))
+ classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc))
case ma: MultiANewArrayInsnNode =>
// "a symbolic reference to a class, array, or interface type"
- bTypeForDescriptorOrInternalNameFromClassfile(ma.desc).exists(classIsAccessible(_))
+ classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc))
case fi: FieldInsnNode =>
- (for {
- fieldRefClass <- classBTypeFromParsedClassfile(fi.owner)
- (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc)
- fieldDeclClass <- classBTypeFromParsedClassfile(fieldDeclClassNode)
+ val fieldRefClass = classBTypeFromParsedClassfile(fi.owner)
+ for {
+ (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc): Either[OptimizerWarning, (FieldNode, InternalName)]
+ fieldDeclClass = classBTypeFromParsedClassfile(fieldDeclClassNode)
+ res <- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass)
} yield {
- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass)
- }) getOrElse false
+ res
+ }
case mi: MethodInsnNode =>
- if (mi.owner.charAt(0) == '[') true // array methods are accessible
- else (for {
- methodRefClass <- classBTypeFromParsedClassfile(mi.owner)
- (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc)
- methodDeclClass <- classBTypeFromParsedClassfile(methodDeclClassNode)
- } yield {
- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass)
- }) getOrElse false
+ if (mi.owner.charAt(0) == '[') Right(true) // array methods are accessible
+ else {
+ val methodRefClass = classBTypeFromParsedClassfile(mi.owner)
+ for {
+ (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc): Either[OptimizerWarning, (MethodNode, InternalName)]
+ methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode)
+ res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass)
+ } yield {
+ res
+ }
+ }
case ivd: InvokeDynamicInsnNode =>
// TODO @lry check necessary conditions to inline an indy, instead of giving up
- false
+ Right(false)
case ci: LdcInsnNode => ci.cst match {
- case t: asm.Type => bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName).exists(classIsAccessible(_))
- case _ => true
+ case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName))
+ case _ => Right(true)
}
- case _ => true
+ case _ => Right(true)
}
- instructions.iterator.asScala.find(!isLegal(_))
+ val it = instructions.iterator.asScala
+ @tailrec def find: Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
+ if (!it.hasNext) None // all instructions are legal
+ else {
+ val i = it.next()
+ isLegal(i) match {
+ case Left(warning) => Some((i, Some(warning))) // checking isLegal for i failed
+ case Right(false) => Some((i, None)) // an illegal instruction was found
+ case _ => find
+ }
+ }
+ }
+ find
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala
deleted file mode 100644
index 5b47bc88c2..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2014 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend.jvm
-package opt
-
-import scala.tools.asm
-import asm.tree._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
-
-/**
- * Reporting utilities used in the optimizer.
- *
- * TODO: move out of opt package, rename: it's already used outside the optimizer.
- * Centralize backend reporting here.
- */
-object OptimizerReporting {
- def methodSignature(classInternalName: InternalName, method: MethodNode): String = {
- classInternalName + "::" + method.name + method.desc
- }
-
- // TODO: clean up reporting of the inliner, test inline failure warnings, etc
- def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason)
- def assertionError(message: String): Nothing = throw new AssertionError(message)
-}
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 43b634eee1..d273995e6e 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -256,6 +256,25 @@ trait ScalaSettings extends AbsScalaSettings
def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal)
def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal
+ object YoptWarningsChoices extends MultiChoiceEnumeration {
+ val none = Choice("none" , "No optimizer warnings.")
+ val atInlineFailedSummary = Choice("at-inline-failed-summary" , "One-line summary if there were @inline method calls that could not be inlined.")
+ val atInlineFailed = Choice("at-inline-failed" , "A detailed warning for each @inline method call that could not be inlined.")
+ val noInlineMixed = Choice("no-inline-mixed" , "In mixed compilation, warn at callsites methods defined in java sources (the inlining decision cannot be made without bytecode).")
+ val noInlineMissingBytecode = Choice("no-inline-missing-bytecode" , "Warn if an inlining decision cannot be made because a the bytecode of a class or member cannot be found on the compilation classpath.")
+ val noInlineMissingScalaInlineInfoAttr = Choice("no-inline-missing-attribute", "Warn if an inlining decision cannot be made because a Scala classfile does not have a ScalaInlineInfo attribute.")
+ }
+
+ val YoptWarnings = MultiChoiceSetting(
+ name = "-Yopt-warnings",
+ helpArg = "warnings",
+ descr = "Enable optimizer warnings",
+ domain = YoptWarningsChoices,
+ default = Some(List(YoptWarningsChoices.atInlineFailed.name))) withPostSetHook (self => {
+ if (self.value subsetOf Set(YoptWarningsChoices.none, YoptWarningsChoices.atInlineFailedSummary)) YinlinerWarnings.value = false
+ else YinlinerWarnings.value = true
+ })
+
private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug."
object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value }