summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools')
-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 }