summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/reflect/reify/phases/Reshape.scala3
-rw-r--r--src/compiler/scala/tools/cmd/Property.scala3
-rw-r--r--src/compiler/scala/tools/nsc/Driver.scala26
-rw-r--r--src/compiler/scala/tools/nsc/GenericRunnerSettings.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala26
-rw-r--r--src/compiler/scala/tools/nsc/Main.scala3
-rw-r--r--src/compiler/scala/tools/nsc/ast/DocComments.scala19
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala22
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Scanners.scala8
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Members.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala13
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala6
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala27
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala15
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala6
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala24
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala505
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala48
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala171
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala (renamed from src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala)38
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala374
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala82
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala41
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala432
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala62
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala40
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala475
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala230
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala89
-rw-r--r--src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala7
-rw-r--r--src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala1
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala7
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala49
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaVersion.scala48
-rw-r--r--src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala192
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala9
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala20
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Checkable.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala20
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala21
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala125
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Implicits.scala91
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala137
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala35
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala104
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala26
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala42
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala31
-rw-r--r--src/compiler/scala/tools/reflect/WrappedProperties.scala5
-rw-r--r--src/compiler/scala/tools/util/PathResolver.scala14
65 files changed, 2547 insertions, 1304 deletions
diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala
index 6c073c0b4c..091d42bb6d 100644
--- a/src/compiler/scala/reflect/reify/phases/Reshape.scala
+++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala
@@ -325,8 +325,7 @@ trait Reshape {
if (reifyDebug) println(s"reconstructed lazy val is $vdef1")
vdef1::Nil
case ddef: DefDef if ddef.symbol.isLazy =>
- def hasUnitType(sym: Symbol) = (sym.tpe.typeSymbol == UnitClass) && sym.tpe.annotations.isEmpty
- if (hasUnitType(ddef.symbol)) {
+ if (isUnitType(ddef.symbol.info)) {
// since lazy values of type Unit don't have val's
// we need to create them from scratch
toPreTyperLazyVal(ddef) :: Nil
diff --git a/src/compiler/scala/tools/cmd/Property.scala b/src/compiler/scala/tools/cmd/Property.scala
index b1d951a5c4..e6262a7e40 100644
--- a/src/compiler/scala/tools/cmd/Property.scala
+++ b/src/compiler/scala/tools/cmd/Property.scala
@@ -9,6 +9,7 @@ package cmd
import nsc.io._
import java.util.Properties
import java.io.FileInputStream
+import scala.sys.SystemProperties
/** Contains logic for translating a property key/value pair into
* equivalent command line arguments. The default settings will
@@ -58,7 +59,7 @@ trait Property extends Reference {
returning(new Properties)(_ load new FileInputStream(file.path))
def systemPropertiesToOptions: List[String] =
- propertiesToOptions(System.getProperties)
+ propertiesToOptions(new SystemProperties().toList)
def propertiesToOptions(file: File): List[String] =
propertiesToOptions(loadProperties(file))
diff --git a/src/compiler/scala/tools/nsc/Driver.scala b/src/compiler/scala/tools/nsc/Driver.scala
index 6befa76b3f..b30744c4df 100644
--- a/src/compiler/scala/tools/nsc/Driver.scala
+++ b/src/compiler/scala/tools/nsc/Driver.scala
@@ -1,7 +1,7 @@
package scala
package tools.nsc
-import scala.tools.nsc.reporters.ConsoleReporter
+import scala.tools.nsc.reporters.{ ConsoleReporter, Reporter }
import Properties.{ versionMsg, residentPromptString }
import scala.reflect.internal.util.FakePos
@@ -9,39 +9,43 @@ abstract class Driver {
val prompt = residentPromptString
- var reporter: ConsoleReporter = _
+ var reporter: Reporter = _
protected var command: CompilerCommand = _
protected var settings: Settings = _
+ /** Forward errors to the (current) reporter. */
protected def scalacError(msg: String): Unit = {
reporter.error(FakePos("scalac"), msg + "\n scalac -help gives more information")
}
+ /** True to continue compilation. */
protected def processSettingsHook(): Boolean = {
- if (settings.version) { reporter echo versionMsg ; false } else true
+ if (settings.version) { reporter echo versionMsg ; false }
+ else !reporter.hasErrors
}
protected def newCompiler(): Global
- protected def doCompile(compiler: Global) {
+ protected def doCompile(compiler: Global): Unit = {
if (command.files.isEmpty) {
reporter.echo(command.usageMsg)
reporter.echo(compiler.pluginOptionsHelp)
} else {
val run = new compiler.Run()
run compile command.files
- reporter.printSummary()
+ reporter.finish()
}
}
- def process(args: Array[String]) {
+ def process(args: Array[String]): Boolean = {
val ss = new Settings(scalacError)
- reporter = new ConsoleReporter(ss)
+ reporter = new ConsoleReporter(ss) // for reporting early config errors, before compiler is constructed
command = new CompilerCommand(args.toList, ss)
settings = command.settings
if (processSettingsHook()) {
val compiler = newCompiler()
+ reporter = compiler.reporter // adopt the configured reporter
try {
if (reporter.hasErrors)
reporter.flush()
@@ -57,11 +61,9 @@ abstract class Driver {
case _ => throw ex // unexpected error, tell the outside world.
}
}
- }
+ } else if (reporter.hasErrors) reporter.flush()
+ !reporter.hasErrors
}
- def main(args: Array[String]) {
- process(args)
- sys.exit(if (reporter.hasErrors) 1 else 0)
- }
+ def main(args: Array[String]): Unit = sys.exit(if (process(args)) 0 else 1)
}
diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala
index 1289d55c37..e99cce9186 100644
--- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala
+++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala
@@ -9,7 +9,7 @@ import java.net.URL
import scala.tools.util.PathResolverFactory
class GenericRunnerSettings(error: String => Unit) extends Settings(error) {
- def classpathURLs: Seq[URL] = PathResolverFactory.create(this).resultAsURLs
+ lazy val classpathURLs: Seq[URL] = PathResolverFactory.create(this).resultAsURLs
val howtorun =
ChoiceSetting(
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 3469726455..5cb31c1b64 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -15,7 +15,7 @@ import io.{ SourceReader, AbstractFile, Path }
import reporters.{ Reporter, ConsoleReporter }
import util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning }
import scala.reflect.ClassTag
-import scala.reflect.internal.util.{ SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile }
+import scala.reflect.internal.util.{ ScalaClassLoader, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile }
import scala.reflect.internal.pickling.PickleBuffer
import symtab.{ Flags, SymbolTable, SymbolTrackers }
import symtab.classfile.Pickler
@@ -90,7 +90,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
this(new Settings(err => reporter.error(null, err)), reporter)
def this(settings: Settings) =
- this(settings, new ConsoleReporter(settings))
+ this(settings, Global.reporter(settings))
def picklerPhase: Phase = if (currentRun.isDefined) currentRun.picklerPhase else NoPhase
@@ -1373,13 +1373,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
unitbuf += unit
compiledFiles += unit.source.file.path
}
- private def checkDeprecatedSettings(unit: CompilationUnit) {
+ private def warnDeprecatedAndConflictingSettings(unit: CompilationUnit) {
// issue warnings for any usage of deprecated settings
settings.userSetSettings filter (_.isDeprecated) foreach { s =>
currentRun.reporting.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get)
}
- if (settings.target.value.contains("jvm-1.5"))
- currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated: use target for Java 1.6 or above.")
+ val supportedTarget = "jvm-1.8"
+ if (settings.target.value != supportedTarget) {
+ currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated and has no effect, setting to " + supportedTarget)
+ settings.target.value = supportedTarget
+ }
+ settings.conflictWarning.foreach(reporter.warning(NoPosition, _))
}
/* An iterator returning all the units being compiled in this run */
@@ -1470,7 +1474,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
def compileSources(sources: List[SourceFile]) = if (!reporter.hasErrors) {
def checkDeprecations() = {
- checkDeprecatedSettings(newCompilationUnit(""))
+ warnDeprecatedAndConflictingSettings(newCompilationUnit(""))
reporting.summarizeErrors()
}
@@ -1492,7 +1496,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val startTime = currentTime
reporter.reset()
- checkDeprecatedSettings(unitbuf.head)
+ warnDeprecatedAndConflictingSettings(unitbuf.head)
globalPhase = fromPhase
while (globalPhase.hasNext && !reporter.hasErrors) {
@@ -1700,4 +1704,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
object Global {
def apply(settings: Settings, reporter: Reporter): Global = new Global(settings, reporter)
+
+ def apply(settings: Settings): Global = new Global(settings, reporter(settings))
+
+ private def reporter(settings: Settings): Reporter = {
+ //val loader = ScalaClassLoader(getClass.getClassLoader) // apply does not make delegate
+ val loader = new ClassLoader(getClass.getClassLoader) with ScalaClassLoader
+ loader.create[Reporter](settings.reporter.value, settings.errorFn)(settings)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala
index a66ee572a9..e2cf49907b 100644
--- a/src/compiler/scala/tools/nsc/Main.scala
+++ b/src/compiler/scala/tools/nsc/Main.scala
@@ -17,7 +17,8 @@ class MainClass extends Driver with EvalLoop {
new compiler.Run() compile command.files
}
- override def newCompiler(): Global = Global(settings, reporter)
+ override def newCompiler(): Global = Global(settings)
+
override def doCompile(compiler: Global) {
if (settings.resident) resident(compiler)
else super.doCompile(compiler)
diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala
index 6442ef2d54..c70690e697 100644
--- a/src/compiler/scala/tools/nsc/ast/DocComments.scala
+++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala
@@ -129,25 +129,6 @@ trait DocComments { self: Global =>
getDocComment(sym) map getUseCases getOrElse List()
}
- private val wikiReplacements = List(
- ("""(\n\s*\*?)(\s*\n)""" .r, """$1 <p>$2"""),
- ("""<([^\w/])""" .r, """&lt;$1"""),
- ("""([^\w/])>""" .r, """$1&gt;"""),
- ("""\{\{\{(.*(?:\n.*)*)\}\}\}""".r, """<pre>$1</pre>"""),
- ("""`([^`]*)`""" .r, """<code>$1</code>"""),
- ("""__([^_]*)__""" .r, """<u>$1</u>"""),
- ("""''([^']*)''""" .r, """<i>$1</i>"""),
- ("""'''([^']*)'''""" .r, """<b>$1</b>"""),
- ("""\^([^^]*)\^""" .r, """<sup>$1</sup>"""),
- (""",,([^,]*),,""" .r, """<sub>$1</sub>"""))
-
- /** Returns just the wiki expansion (this would correspond to
- * a comment in the input format of the JavaDoc tool, modulo differences
- * in tags.)
- */
- def expandWiki(str: String): String =
- (str /: wikiReplacements) { (str1, regexRepl) => regexRepl._1 replaceAllIn(str1, regexRepl._2) }
-
private def getDocComment(sym: Symbol): Option[DocComment] =
mapFind(sym :: allInheritedOverriddenSymbols(sym))(docComments get _)
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index 70abdf6bc0..f9e6a12241 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -665,6 +665,15 @@ self =>
}
def isLiteral = isLiteralToken(in.token)
+ def isSimpleExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
+ case IDENTIFIER | BACKQUOTED_IDENT |
+ THIS | SUPER | NEW | USCORE |
+ LPAREN | LBRACE | XMLSTART => true
+ case _ => false
+ })
+
+ def isSimpleExprIntro: Boolean = isExprIntroToken(in.token)
+
def isExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
case IDENTIFIER | BACKQUOTED_IDENT |
THIS | SUPER | IF | FOR | NEW | USCORE | TRY | WHILE |
@@ -1565,11 +1574,14 @@ self =>
def prefixExpr(): Tree = {
if (isUnaryOp) {
atPos(in.offset) {
- val name = nme.toUnaryName(rawIdent().toTermName)
- if (name == nme.UNARY_- && isNumericLit)
- simpleExprRest(literal(isNegated = true), canApply = true)
- else
- Select(stripParens(simpleExpr()), name)
+ if (lookingAhead(isSimpleExprIntro)) {
+ val uname = nme.toUnaryName(rawIdent().toTermName)
+ if (uname == nme.UNARY_- && isNumericLit)
+ simpleExprRest(literal(isNegated = true), canApply = true)
+ else
+ Select(stripParens(simpleExpr()), uname)
+ }
+ else simpleExpr()
}
}
else simpleExpr()
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
index cd41c75298..51bb0d3c5b 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
@@ -515,7 +515,7 @@ trait Scanners extends ScannersCommon {
charLitOr(getIdentRest)
else if (isOperatorPart(ch) && (ch != '\\'))
charLitOr(getOperatorRest)
- else {
+ else if (!isAtEnd && (ch != SU && ch != CR && ch != LF || isUnicodeEscape)) {
getLitChar()
if (ch == '\'') {
nextChar()
@@ -525,6 +525,8 @@ trait Scanners extends ScannersCommon {
syntaxError("unclosed character literal")
}
}
+ else
+ syntaxError("unclosed character literal")
}
fetchSingleQuote()
case '.' =>
@@ -690,7 +692,7 @@ trait Scanners extends ScannersCommon {
private def unclosedStringLit(): Unit = syntaxError("unclosed string literal")
- private def getRawStringLit(): Unit = {
+ @tailrec private def getRawStringLit(): Unit = {
if (ch == '\"') {
nextRawChar()
if (isTripleQuote()) {
@@ -707,7 +709,7 @@ trait Scanners extends ScannersCommon {
}
}
- @scala.annotation.tailrec private def getStringPart(multiLine: Boolean): Unit = {
+ @tailrec private def getStringPart(multiLine: Boolean): Unit = {
def finishStringPart() = {
setStrVal()
token = STRINGPART
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index b6f9bcc9ab..a927097b62 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -1145,7 +1145,7 @@ abstract class GenICode extends SubComponent {
// a package here, check if there's a package object.
val sym = (
if (!tree.symbol.isPackageClass) tree.symbol
- else tree.symbol.info.member(nme.PACKAGE) match {
+ else tree.symbol.info.packageObject match {
case NoSymbol => abort("Cannot use package as value: " + tree)
case s =>
devWarning(s"Found ${tree.symbol} where a package object is required. Converting to ${s.moduleClass}")
@@ -1502,7 +1502,7 @@ abstract class GenICode extends SubComponent {
if (!settings.optimise) {
if (l.tpe <:< BoxedNumberClass.tpe) {
if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum
- else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
+ else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumChar
else platform.externalEqualsNumObject
} else platform.externalEquals
} else {
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala
index 64146585e5..c0e0240210 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/Members.scala
@@ -9,6 +9,7 @@ package backend
package icode
import scala.collection.{ mutable, immutable }
+import scala.reflect.internal.Flags
import scala.reflect.internal.util.{ SourceFile, NoSourceFile }
trait ReferenceEquality {
@@ -217,7 +218,7 @@ trait Members {
/** Is this method deferred ('abstract' in Java sense)?
*/
- def isAbstractMethod = symbol.isDeferred || symbol.owner.isInterface || native
+ def isAbstractMethod = symbol.isDeferred || (symbol.owner.isInterface && !symbol.hasFlag(Flags.JAVA_DEFAULTMETHOD)) || native
def isStatic: Boolean = symbol.isStaticMember
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
index cd7e0b83e8..18a495e5fd 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
@@ -55,6 +55,13 @@ object AsmUtils {
node
}
+ def readClass(filename: String): ClassNode = {
+ val f = new java.io.RandomAccessFile(filename, "r")
+ val b = new Array[Byte](f.length.toInt)
+ f.read(b)
+ readClass(b)
+ }
+
/**
* Returns a human-readable representation of the cnode ClassNode.
*/
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
index 93f5159f89..42738d3e1c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
@@ -390,6 +390,17 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
val isEffectivelyFinal = classSym.isEffectivelyFinal
+ val sam = {
+ if (classSym.isImplClass || classSym.isEffectivelyFinal) None
+ else {
+ // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
+ // empty parameter list in later phases and would therefore be picked as SAM.
+ val samSym = exitingPickler(definitions.findSam(classSym.tpe))
+ if (samSym == NoSymbol) None
+ else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym))
+ }
+ }
+
var warning = Option.empty[ClassSymbolInfoFailureSI9111]
// Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
@@ -447,7 +458,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
}
}).toMap
- InlineInfo(traitSelfType, isEffectivelyFinal, methodInlineInfos, warning)
+ InlineInfo(traitSelfType, isEffectivelyFinal, sam, methodInlineInfos, warning)
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 416628d5ba..e5eb0b79d5 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -843,8 +843,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* loading another throwable first).
*
* New (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1)
- * - Requires consistent stack map frames. GenBCode generates stack frames if -target:jvm-1.6
- * or higher.
+ * - Requires consistent stack map frames. GenBCode always generates stack frames.
* - In practice: the ASM library computes stack map frames for us (ClassWriter). Emitting
* correct frames after an ATHROW is probably complex, so ASM uses the following strategy:
* - Every time when generating an ATHROW, a new basic block is started.
@@ -922,7 +921,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genLoadModule(tree: Tree): BType = {
val module = (
if (!tree.symbol.isPackageClass) tree.symbol
- else tree.symbol.info.member(nme.PACKAGE) match {
+ else tree.symbol.info.packageObject match {
case NoSymbol => abort(s"SI-5604: Cannot use package as value: $tree")
case s => abort(s"SI-5604: found package class where package object expected: $tree")
}
@@ -1232,7 +1231,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val equalsMethod: Symbol = {
if (l.tpe <:< BoxedNumberClass.tpe) {
if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum
- else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
+ else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumChar
else platform.externalEqualsNumObject
} else platform.externalEquals
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 65a6b82570..0f381a4325 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -693,7 +693,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* cache = new java.util.HashMap()
* $deserializeLambdaCache$ = cache
* }
- * return scala.compat.java8.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l);
+ * return scala.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l);
* }
*/
def addLambdaDeserialize(clazz: Symbol, jclass: asm.ClassVisitor): Unit = {
@@ -728,11 +728,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
mv.visitVarInsn(ALOAD, 1)
mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;")
mv.visitLabel(l0)
- mv.visitFieldInsn(GETSTATIC, "scala/compat/java8/runtime/LambdaDeserializer$", "MODULE$", "Lscala/compat/java8/runtime/LambdaDeserializer$;")
+ mv.visitFieldInsn(GETSTATIC, "scala/runtime/LambdaDeserializer$", "MODULE$", "Lscala/runtime/LambdaDeserializer$;")
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false)
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ALOAD, 0)
- mv.visitMethodInsn(INVOKEVIRTUAL, "scala/compat/java8/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false)
+ mv.visitMethodInsn(INVOKEVIRTUAL, "scala/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false)
mv.visitInsn(ARETURN)
mv.visitEnd()
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index eb0da7caef..535e1a8620 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -28,9 +28,6 @@ abstract class BCodeIdiomatic extends SubComponent {
import coreBTypes._
val classfileVersion: Int = settings.target.value match {
- case "jvm-1.5" => asm.Opcodes.V1_5
- case "jvm-1.6" => asm.Opcodes.V1_6
- case "jvm-1.7" => asm.Opcodes.V1_7
case "jvm-1.8" => asm.Opcodes.V1_8
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index a9b6a312e9..9875ade113 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -9,7 +9,6 @@ package backend
package jvm
import scala.collection.{ mutable, immutable }
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
import scala.tools.nsc.symtab._
import scala.tools.asm
@@ -140,11 +139,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
- if (settings.YoptAddToBytecodeRepository) {
- // The inliner needs to find all classes in the code repo, also those being compiled
- byteCodeRepository.add(cnode, ByteCodeRepository.CompilationUnit)
- }
-
assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().")
} // end of method genPlainClass()
@@ -587,10 +581,10 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
}
val isNative = methSymbol.hasAnnotation(definitions.NativeAttr)
- val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface)
+ val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) && !methSymbol.hasFlag(Flags.JAVA_DEFAULTMETHOD)
val flags = GenBCode.mkFlags(
javaFlags(methSymbol),
- if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0,
+ if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0,
if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes
)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 0c26e01322..aff2d2d8c9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -11,9 +11,10 @@ import scala.collection.concurrent.TrieMap
import scala.reflect.internal.util.Position
import scala.tools.asm
import asm.Opcodes
-import scala.tools.asm.tree.{MethodNode, MethodInsnNode, InnerClassNode, ClassNode}
+import scala.tools.asm.tree._
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
import scala.tools.nsc.backend.jvm.BackendReporting._
+import scala.tools.nsc.backend.jvm.analysis.Analyzers
import scala.tools.nsc.backend.jvm.opt._
import scala.collection.convert.decorateAsScala._
import scala.tools.nsc.settings.ScalaSettings
@@ -38,16 +39,20 @@ abstract class BTypes {
/**
* Tools for parsing classfiles, used by the inliner.
*/
- val byteCodeRepository: ByteCodeRepository
+ val byteCodeRepository: ByteCodeRepository[this.type]
val localOpt: LocalOpt[this.type]
val inliner: Inliner[this.type]
+ val inlinerHeuristics: InlinerHeuristics[this.type]
+
val closureOptimizer: ClosureOptimizer[this.type]
val callGraph: CallGraph[this.type]
+ val analyzers: Analyzers[this.type]
+
val backendReporting: BackendReporting
// Allows to define per-run caches here and in the CallGraph component, which don't have a global
@@ -56,7 +61,6 @@ abstract class BTypes {
// Allows access to the compiler settings for backend components that don't have a global in scope
def compilerSettings: ScalaSettings
-
/**
* A map from internal names to ClassBTypes. Every ClassBType is added to this map on its
* construction.
@@ -95,6 +99,13 @@ abstract class BTypes {
val unreachableCodeEliminated: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty)
/**
+ * Cache of methods which have correct `maxLocals` / `maxStack` values assigned. This allows
+ * invoking `computeMaxLocalsMaxStack` whenever running an analyzer but performing the actual
+ * computation only when necessary.
+ */
+ val maxLocalsMaxStackComputed: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty)
+
+ /**
* Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType
* is constructed by parsing the corresponding classfile.
*
@@ -234,6 +245,7 @@ abstract class BTypes {
InlineInfo(
traitImplClassSelfType = None,
isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode),
+ sam = inlinerHeuristics.javaSam(classNode.name),
methodInfos = methodInfos,
warning)
}
@@ -554,6 +566,8 @@ abstract class BTypes {
* Terminology
* -----------
*
+ * Diagram here: https://blogs.oracle.com/darcy/entry/nested_inner_member_and_top
+ *
* - Nested class (JLS 8): class whose declaration occurs within the body of another class
*
* - Top-level class (JLS 8): non-nested class
@@ -1104,6 +1118,8 @@ object BTypes {
* Metadata about a ClassBType, used by the inliner.
*
* More information may be added in the future to enable more elaborate inlinine heuristics.
+ * Note that this class should contain information that can only be obtained from the ClassSymbol.
+ * Information that can be computed from the ClassNode should be added to the call graph instead.
*
* @param traitImplClassSelfType `Some(tp)` if this InlineInfo describes a trait, and the `self`
* parameter type of the methods in the implementation class is not
@@ -1122,6 +1138,8 @@ object BTypes {
* @param isEffectivelyFinal True if the class cannot have subclasses: final classes, module
* classes, trait impl classes.
*
+ * @param sam If this class is a SAM type, the SAM's "$name$descriptor".
+ *
* @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).
@@ -1132,10 +1150,11 @@ object BTypes {
*/
final case class InlineInfo(traitImplClassSelfType: Option[InternalName],
isEffectivelyFinal: Boolean,
+ sam: Option[String],
methodInfos: Map[String, MethodInlineInfo],
warning: Option[ClassInlineInfoWarning])
- val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None)
+ val EmptyInlineInfo = InlineInfo(None, false, None, Map.empty, None)
/**
* Metadata about a method, used by the inliner.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 45d9cc3ff3..df34326bbf 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,8 +7,9 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
+import scala.tools.nsc.backend.jvm.analysis.Analyzers
import scala.tools.nsc.backend.jvm.opt._
-import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
+import scala.tools.nsc.backend.jvm.BTypes._
import BackendReporting._
import scala.tools.nsc.settings.ScalaSettings
@@ -36,16 +37,20 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val coreBTypes = new CoreBTypesProxy[this.type](this)
import coreBTypes._
- val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty))
+ val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.classPath, this)
val localOpt: LocalOpt[this.type] = new LocalOpt(this)
val inliner: Inliner[this.type] = new Inliner(this)
+ val inlinerHeuristics: InlinerHeuristics[this.type] = new InlinerHeuristics(this)
+
val closureOptimizer: ClosureOptimizer[this.type] = new ClosureOptimizer(this)
val callGraph: CallGraph[this.type] = new CallGraph(this)
+ val analyzers: Analyzers[this.type] = new Analyzers(this)
+
val backendReporting: BackendReporting = new BackendReportingImpl(global)
final def initializeCoreBTypes(): Unit = {
@@ -444,7 +449,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
case Right(classNode) =>
inlineInfoFromClassfile(classNode)
case Left(missingClass) =>
- InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass)))
+ EmptyInlineInfo.copy(warning = Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass)))
}
}
}
@@ -467,7 +472,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
nestedClasses = nested,
nestedInfo = None,
- InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class
+ inlineInfo = EmptyInlineInfo.copy(isEffectivelyFinal = true))) // no method inline infos needed, scala never invokes methods on the mirror class
c
})
}
@@ -571,7 +576,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
import asm.Opcodes._
GenBCode.mkFlags(
if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
- if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
+ if ((sym.isDeferred && !sym.hasFlag(symtab.Flags.JAVA_DEFAULTMETHOD))|| sym.hasAbstractFlag) ACC_ABSTRACT else 0,
if (sym.isInterface) ACC_INTERFACE else 0,
if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
if (sym.isStaticMember) ACC_STATIC else 0,
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
index b41d0de92f..005d01f187 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
@@ -1,7 +1,7 @@
package scala.tools.nsc
package backend.jvm
-import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode}
+import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.reflect.internal.util.Position
import scala.tools.nsc.settings.ScalaSettings
@@ -228,7 +228,7 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): Boolean = this match {
case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge =>
- settings.YoptWarningEmitAtInlineFailed
+ settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed)
case IllegalAccessCheckFailed(_, _, _, _, _, cause) =>
cause.emitWarning(settings)
@@ -246,9 +246,11 @@ object BackendReporting {
case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
+ // TODO: this should be a subtype of CannotInlineWarning
+ // but at the place where it's created (in findIllegalAccess) we don't have the necessary data (calleeName, calleeDescriptor).
case object UnknownInvokeDynamicInstruction extends OptimizerWarning {
override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)."
- def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed
+ def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed)
}
/**
@@ -260,7 +262,7 @@ object BackendReporting {
override def emitWarning(settings: ScalaSettings): Boolean = this match {
case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings)
- case RewriteClosureIllegalAccess(_, _) => settings.YoptWarningEmitAtInlineFailed
+ case RewriteClosureIllegalAccess(_, _) => settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed)
}
override def toString: String = this match {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
index 03306f30aa..8d0547b607 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
@@ -8,6 +8,7 @@ package backend.jvm
import scala.reflect.internal.util.Statistics
+// Enable with `-Ystatistics:jvm`
object BackendStats {
import Statistics.{newTimer, newSubTimer}
val bcodeTimer = newTimer("time in backend", "jvm")
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
index 4768417c67..61abc55501 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -441,9 +441,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
// -----------------------------------------------------------------------------------------
private val classfileVersion: Int = settings.target.value match {
- case "jvm-1.5" => asm.Opcodes.V1_5
- case "jvm-1.6" => asm.Opcodes.V1_6
- case "jvm-1.7" => asm.Opcodes.V1_7
case "jvm-1.8" => asm.Opcodes.V1_8
}
@@ -1398,10 +1395,11 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
var resTpe: asm.Type = javaType(m.symbol.tpe.resultType)
if (m.symbol.isClassConstructor)
resTpe = asm.Type.VOID_TYPE
+ val isAbstractTraitMeth = isJInterface && !m.symbol.hasFlag(Flags.JAVA_DEFAULTMETHOD)
val flags = mkFlags(
javaFlags(m.symbol),
- if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0,
+ if (isAbstractTraitMeth) asm.Opcodes.ACC_ABSTRACT else 0,
if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes
if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index af962c4ce0..e1a724f1cb 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -14,6 +14,7 @@ import scala.reflect.internal.util.Statistics
import scala.tools.asm
import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
/*
* Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk.
@@ -186,7 +187,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
// -------------- "plain" class --------------
val pcb = new PlainClassBuilder(cunit)
pcb.genPlainClass(cd)
- val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null;
+ val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null
val plainC = pcb.cnode
// -------------- bean info class, if needed --------------
@@ -221,12 +222,18 @@ abstract class GenBCode extends BCodeSyncAndTry {
class Worker2 {
def runGlobalOptimizations(): Unit = {
import scala.collection.convert.decorateAsScala._
- if (settings.YoptBuildCallGraph) {
- q2.asScala foreach {
- case Item2(_, _, plain, _, _) =>
- // skip mirror / bean: wd don't inline into tem, and they are not used in the plain class
- if (plain != null) callGraph.addClass(plain)
- }
+
+ // add classes to the bytecode repo before building the call graph: the latter needs to
+ // look up classes and methods in the code repo.
+ if (settings.YoptAddToBytecodeRepository) q2.asScala foreach {
+ case Item2(_, mirror, plain, bean, _) =>
+ if (mirror != null) byteCodeRepository.add(mirror, ByteCodeRepository.CompilationUnit)
+ if (plain != null) byteCodeRepository.add(plain, ByteCodeRepository.CompilationUnit)
+ if (bean != null) byteCodeRepository.add(bean, ByteCodeRepository.CompilationUnit)
+ }
+ if (settings.YoptBuildCallGraph) q2.asScala foreach { item =>
+ // skip call graph for mirror / bean: wd don't inline into tem, and they are not used in the plain class
+ if (item.plain != null) callGraph.addClass(item.plain)
}
if (settings.YoptInlinerEnabled)
bTypes.inliner.runInliner()
@@ -252,6 +259,9 @@ abstract class GenBCode extends BCodeSyncAndTry {
localOptimizations(item.plain)
addToQ3(item)
} catch {
+ case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") =>
+ reporter.error(NoPosition,
+ s"Could not write class ${item.plain.name} because it exceeds JVM code size limits. ${e.getMessage}")
case ex: Throwable =>
ex.printStackTrace()
error(s"Error while emitting ${item.plain.name}\n${ex.getMessage}")
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala
index 7bbe1e2a49..9e5fbfcc0e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala
@@ -3,17 +3,22 @@ package backend.jvm
package analysis
import scala.annotation.switch
-import scala.collection.{mutable, immutable}
+import scala.collection.mutable
import scala.tools.asm.Opcodes
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis.{Analyzer, Value, Frame, Interpreter}
import opt.BytecodeUtils._
+import AliasSet.SmallBitSet
-object AliasingFrame {
- private var _idCounter: Long = 0l
- private def nextId = { _idCounter += 1; _idCounter }
-}
-
+/**
+ * A subclass of Frame that tracks aliasing of values stored in local variables and on the stack.
+ *
+ * Note: an analysis tracking aliases is roughly 5x slower than a usual analysis (assuming a simple
+ * value domain with a fast merge function). For example, nullness analysis is roughly 5x slower
+ * than a BasicValue analysis.
+ *
+ * See the doc of package object `analysis` for some notes on the performance of alias analysis.
+ */
class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLocals, nStack) {
import Opcodes._
@@ -24,50 +29,66 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
}
/**
- * For each slot (entry in the `values` array of the frame), an id that uniquely represents
- * the object stored in it. If two values have the same id, they are aliases of the same
- * object.
- */
- private val aliasIds: Array[Long] = Array.fill(nLocals + nStack)(AliasingFrame.nextId)
-
- /**
- * The object alias id of for a value index.
- */
- def aliasId(entry: Int) = aliasIds(entry)
-
- /**
- * Returns the indices of the values array which are aliases of the object `id`.
+ * For every value the set of values that are aliases of it.
+ *
+ * Invariants:
+ * - If `aliases(i) == null` then i has no aliases. This is equivalent to having
+ * `aliases(i) == SingletonSet(i)`.
+ * - If `aliases(i) != null` then `aliases(i) contains i`.
+ * - If `aliases(i) contains j` then `aliases(i) eq aliases(j)`, i.e., they are references to the
+ * same (mutable) AliasSet.
*/
- def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.iterator.filter(i => aliasId(i) == id)
+ val aliases: Array[AliasSet] = new Array[AliasSet](getLocals + getMaxStackSize)
/**
* The set of aliased values for a given entry in the `values` array.
*/
- def aliasesOf(entry: Int): Set[Int] = valuesWithAliasId(aliasIds(entry))
+ def aliasesOf(entry: Int): AliasSet = {
+ if (aliases(entry) != null) aliases(entry)
+ else {
+ val init = new AliasSet(new AliasSet.SmallBitSet(entry, -1, -1, -1), 1)
+ aliases(entry) = init
+ init
+ }
+ }
/**
- * Define a new alias. For example, given
- * var a = this // this, a have the same aliasId
- * then an assignment
+ * Define a new alias. For example, an assignment
* b = a
- * will set the same the aliasId for `b`.
+ * adds b to the set of aliases of a.
*/
private def newAlias(assignee: Int, source: Int): Unit = {
- aliasIds(assignee) = aliasIds(source)
+ removeAlias(assignee)
+ val sourceAliases = aliasesOf(source)
+ sourceAliases += assignee
+ aliases(assignee) = sourceAliases
}
/**
- * An assignment
+ * Remove an alias. For example, an assignment
* a = someUnknownValue()
- * sets a fresh alias id for `a`.
- * A stack value is also removed from its alias set when being consumed.
+ * removes a from its former alias set.
+ * As another example, stack values are removed from their alias sets when being consumed.
*/
private def removeAlias(assignee: Int): Unit = {
- aliasIds(assignee) = AliasingFrame.nextId
+ if (aliases(assignee) != null) {
+ aliases(assignee) -= assignee
+ aliases(assignee) = null
+ }
+ }
+
+ /**
+ * Define the alias set for a given value.
+ */
+ private def setAliasSet(assignee: Int, set: AliasSet): Unit = {
+ if (aliases(assignee) != null) {
+ aliases(assignee) -= assignee
+ }
+ aliases(assignee) = set
}
override def execute(insn: AbstractInsnNode, interpreter: Interpreter[V]): Unit = {
- // Make the extendsion methods easier to use (otherwise we have to repeat `this`.stackTop)
+ // Make the extension methods easier to use (otherwise we have to repeat `this`.stackTop)
def stackTop: Int = this.stackTop
def peekStack(n: Int): V = this.peekStack(n)
@@ -166,14 +187,34 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
}
case SWAP =>
+ // could be written more elegantly with higher-order combinators, but thinking of performance
val top = stackTop
- val idTop = aliasIds(top)
- aliasIds(top) = aliasIds(top - 1)
- aliasIds(top - 1) = idTop
+
+ def moveNextToTop(): Unit = {
+ val nextAliases = aliases(top - 1)
+ aliases(top) = nextAliases
+ nextAliases -= (top - 1)
+ nextAliases += top
+ }
+
+ if (aliases(top) != null) {
+ val topAliases = aliases(top)
+ if (aliases(top - 1) != null) moveNextToTop()
+ else aliases(top) = null
+ // move top to next
+ aliases(top - 1) = topAliases
+ topAliases -= top
+ topAliases += (top - 1)
+ } else {
+ if (aliases(top - 1) != null) {
+ moveNextToTop()
+ aliases(top - 1) = null
+ }
+ }
case opcode =>
if (opcode == ASTORE) {
- // Not a separate case because we need to remove the consumed stack value from alias sets after.
+ // not a separate case: we re-use the code below that removes the consumed stack value from alias sets
val stackTopBefore = stackTop - produced + consumed
val local = insn.asInstanceOf[VarInsnNode].`var`
newAlias(assignee = local, source = stackTopBefore)
@@ -198,10 +239,22 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
val firstConsumed = stackTop - produced + 1 // firstConsumed = 3
for (i <- 0 until consumed)
removeAlias(firstConsumed + i) // remove aliases for 3 and 4
+ }
+ }
- // We don't need to set the aliases ids for the produced values: the aliasIds array already
- // contains fresh ids for non-used stack values (ensured by removeAlias).
+ /**
+ * When entering an exception handler, all values are dropped from the stack (and the exception
+ * value is pushed). The ASM analyzer invokes `firstHandlerInstructionFrame.clearStack()`. To
+ * ensure consistent aliasing sets, we need to remove the dropped values from aliasing sets.
+ */
+ override def clearStack(): Unit = {
+ var i = getLocals
+ val end = i + getStackSize
+ while (i < end) {
+ removeAlias(i)
+ i += 1
}
+ super.clearStack()
}
/**
@@ -217,30 +270,124 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
* x = a
* y = b // (x, a) and (y, b)
* }
- * [...] // (x, a)
+ * [...] // (x, a) -- merge of ((x, y, a)) and ((x, a), (y, b))
*/
override def merge(other: Frame[_ <: V], interpreter: Interpreter[V]): Boolean = {
+ // merge is the main performance hot spot of a data flow analysis.
+
+ // in nullness analysis, super.merge (which actually merges the nullness values) takes 20% of
+ // the overall analysis time.
val valuesChanged = super.merge(other, interpreter)
+
+ // in nullness analysis, merging the alias sets takes ~55% of the analysis time. therefore, this
+ // code has been heavily optimized. most of the time is spent in the `hasNext` method of the
+ // andNotIterator, see its comment.
+
var aliasesChanged = false
val aliasingOther = other.asInstanceOf[AliasingFrame[_]]
- for (i <- aliasIds.indices) {
- val thisAliases = aliasesOf(i)
- val thisNotOther = thisAliases diff (thisAliases intersect aliasingOther.aliasesOf(i))
- if (thisNotOther.nonEmpty) {
- aliasesChanged = true
- thisNotOther foreach removeAlias
+
+ val numValues = getLocals + getStackSize
+ // assume (a, b) are aliases both in this frame, and the other frame. when merging the alias set
+ // for a, we already see that a and b will be aliases in the final result. so we can skip over
+ // merging the alias set for b. in this case, while merging the sets for a, knownOk(b) will be
+ // set to `true`.
+ val knownOk = new Array[Boolean](numValues)
+ var i = 0
+ while (i < numValues) {
+ if (!knownOk(i)) {
+ val thisAliases = this.aliases(i)
+ val otherAliases = aliasingOther.aliases(i)
+ if (thisAliases != null && otherAliases != null) {
+ // The iterator yields elements that are in `thisAliases` but not in `otherAliases`.
+ // As a side-effect, for every index `i` that is in both alias sets, the iterator sets
+ // `knownOk(i) = true`: the alias sets for these values don't need to be merged again.
+ val thisNotOtherIt = AliasSet.andNotIterator(thisAliases, otherAliases, knownOk)
+ if (thisNotOtherIt.hasNext) {
+ aliasesChanged = true
+ val newSet = AliasSet.empty
+ while (thisNotOtherIt.hasNext) {
+ val next = thisNotOtherIt.next()
+ newSet += next
+ setAliasSet(next, newSet)
+ }
+ }
+ }
}
+ i += 1
}
+
valuesChanged || aliasesChanged
}
+ private def min(s: SmallBitSet) = {
+ var r = s.a
+ if ( s.b < r) r = s.b
+ if (s.c != -1 && s.c < r) r = s.c
+ if (s.d != -1 && s.d < r) r = s.d
+ r
+ }
+
override def init(src: Frame[_ <: V]): Frame[V] = {
- super.init(src)
- compat.Platform.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliasIds, 0, aliasIds, 0, aliasIds.length)
+ super.init(src) // very quick (just an arraycopy)
+ System.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliases, 0, aliases, 0, aliases.length) // also quick
+
+ val newSets = mutable.HashMap.empty[AliasSet, AliasSet]
+
+ // the rest of this method (cloning alias sets) is the second performance˙hotspot (next to
+ // AliasingFrame.merge). for nullness, it takes ~20% of the analysis time.
+ // the difficulty here is that we have to clone the alias sets correctly. if two values a, b are
+ // aliases, then aliases(a) eq aliases(b). we need to make sure to use the same clone for the
+ // two values.
+
+ var i = 0
+ while (i < aliases.length) {
+ val set = aliases(i)
+ if (set != null) {
+ // size cannot be 0 - alias sets are always at least singletons.
+ // for sets of size 1-4, don't use the `newSets` map - lookup / update is slow
+ if (set.size == 1) {
+ aliases(i) = null
+ } else if (set.size <= 4) {
+ val small = set.set.asInstanceOf[AliasSet.SmallBitSet]
+ val firstOfSet = i == min(small)
+ if (firstOfSet) {
+ val newSet = set.clone()
+ aliases(small.a) = newSet
+ aliases(small.b) = newSet
+ if (small.c != -1) aliases(small.c) = newSet
+ if (small.d != -1) aliases(small.d) = newSet
+ }
+ } else {
+ // the actual hot spot is the hash map operations here: this is where almost all of the 20%
+ // mentioned above is spent.
+ // i also benchmarked an alternative implementation: keep an array of booleans for indexes
+ // that already contain the cloned set. iterate through all elements of the cloned set and
+ // assign the cloned set. this approach is 50% slower than using a hash map.
+ if (newSets contains set) aliases(i) = newSets(set)
+ else {
+ val newSet = set.clone()
+ newSets(set) = newSet
+ aliases(i) = newSet
+ }
+ }
+ }
+ i += 1
+ }
this
}
}
+object AliasingFrame {
+// val start1 = AliasingFrame.timer1.start()
+// AliasingFrame.timer1.stop(start1)
+ import scala.reflect.internal.util.Statistics._
+ val timer1 = newTimer("t1", "jvm")
+ val timer2 = newTimer("t2", "jvm")
+ val timer3 = newTimer("t3", "jvm")
+ val timers = List(timer1, timer2, timer3)
+ def reset(): Unit = for (t <- timers) { t.nanos = 0; t.timings = 0 }
+}
+
/**
* An analyzer that uses AliasingFrames instead of bare Frames. This can be used when an analysis
* needs to track aliases, but doesn't require a more specific Frame subclass.
@@ -249,3 +396,269 @@ class AliasingAnalyzer[V <: Value](interpreter: Interpreter[V]) extends Analyzer
override def newFrame(nLocals: Int, nStack: Int): AliasingFrame[V] = new AliasingFrame(nLocals, nStack)
override def newFrame(src: Frame[_ <: V]): AliasingFrame[V] = new AliasingFrame(src)
}
+
+/**
+ * An iterator over Int (required to prevent boxing the result of next).
+ */
+abstract class IntIterator extends Iterator[Int] {
+ def hasNext: Boolean
+ def next(): Int
+}
+
+/**
+ * An efficient mutable bit set.
+ *
+ * @param set Either a SmallBitSet or an Array[Long]
+ * @param size The size of the set, useful for performance of certain operations
+ */
+class AliasSet(var set: Object /*SmallBitSet | Array[Long]*/, var size: Int) {
+ import AliasSet._
+
+ override def toString: String = set.toString
+
+ /**
+ * An iterator for the elements of this bit set. Note that only one iterator can be used at a
+ * time. Also make sure not to change the underlying AliasSet during iteration.
+ */
+ def iterator: IntIterator = andNotIterator(this, empty, null)
+
+ def +=(value: Int): Unit = this.set match {
+ case s: SmallBitSet => (size: @switch) match {
+ case 0 => s.a = value; size = 1
+ case 1 => if (value != s.a) { s.b = value; size = 2 }
+ case 2 => if (value != s.a && value != s.b) { s.c = value; size = 3 }
+ case 3 => if (value != s.a && value != s.b && value != s.c) { s.d = value; size = 4 }
+ case 4 =>
+ if (value != s.a && value != s.b && value != s.c && value != s.d) {
+ this.set = bsEmpty
+ this.size = 0
+ bsAdd(this, s.a)
+ bsAdd(this, s.b)
+ bsAdd(this, s.c)
+ bsAdd(this, s.d)
+ bsAdd(this, value)
+ }
+ }
+ case bits: Array[Long] =>
+ bsAdd(this, value)
+ }
+
+ def -=(value: Int): Unit = this.set match {
+ case s: SmallBitSet => (size: @switch) match {
+ case 0 =>
+ case 1 =>
+ if (value == s.a) { s.a = -1; size = 0 }
+ case 2 =>
+ if (value == s.a) { s.a = s.b; s.b = -1; size = 1 }
+ else if (value == s.b) { s.b = -1; size = 1 }
+ case 3 =>
+ if (value == s.a) { s.a = s.b; s.b = s.c; s.c = -1; size = 2 }
+ else if (value == s.b) { s.b = s.c; s.c = -1; size = 2 }
+ else if (value == s.c) { s.c = -1; size = 2 }
+ case 4 =>
+ if (value == s.a) { s.a = s.b; s.b = s.c; s.c = s.d; s.d = -1; size = 3 }
+ else if (value == s.b) { s.b = s.c; s.c = s.d; s.d = -1; size = 3 }
+ else if (value == s.c) { s.c = s.d; s.d = -1; size = 3 }
+ else if (value == s.d) { s.d = -1; size = 3 }
+ }
+ case bits: Array[Long] =>
+ bsRemove(this, value)
+ if (this.size == 4)
+ this.set = bsToSmall(this.set.asInstanceOf[Array[Long]])
+ }
+
+ override def clone(): AliasSet = {
+ val resSet = this.set match {
+ case s: SmallBitSet => new SmallBitSet(s.a, s.b, s.c, s.d)
+ case bits: Array[Long] => bits.clone()
+ }
+ new AliasSet(resSet, this.size)
+ }
+}
+
+object AliasSet {
+ def empty = new AliasSet(new SmallBitSet(-1, -1, -1, -1), 0)
+
+ final class SmallBitSet(var a: Int, var b: Int, var c: Int, var d: Int) {
+ override def toString = s"($a, $b, $c, $d)"
+ }
+
+ def bsEmpty: Array[Long] = new Array[Long](1)
+
+ private def bsEnsureCapacity(set: Array[Long], index: Int): Array[Long] = {
+ if (index < set.length) set
+ else {
+ var newLength = set.length
+ while (index >= newLength) newLength *= 2
+ val newSet = new Array[Long](newLength)
+ Array.copy(set, 0, newSet, 0, set.length)
+ newSet
+ }
+ }
+
+ def bsAdd(set: AliasSet, bit: Int): Unit = {
+ val bits = set.set.asInstanceOf[Array[Long]]
+ val index = bit >> 6
+ val resSet = bsEnsureCapacity(bits, index)
+ val before = resSet(index)
+ val result = before | (1l << bit)
+ if (result != before) {
+ resSet(index) = result
+ set.set = resSet
+ set.size += 1
+ }
+ }
+
+ def bsRemove(set: AliasSet, bit: Int): Unit = {
+ val bits = set.set.asInstanceOf[Array[Long]]
+ val index = bit >> 6
+ if (index < bits.length) {
+ val before = bits(index)
+ val result = before & ~(1l << bit)
+ if (result != before) {
+ bits(index) = result
+ set.size -= 1
+ }
+ }
+ }
+
+ def bsContains(set: Array[Long], bit: Int): Boolean = {
+ val index = bit >> 6
+ bit >= 0 && index < set.length && (set(index) & (1L << bit)) != 0L
+ }
+
+// var sizesHist: Array[Int] = new Array[Int](1000)
+
+ /**
+ * Convert a bit array to a SmallBitSet. Requires the bit array to contain exactly four bits.
+ */
+ def bsToSmall(bits: Array[Long]): SmallBitSet = {
+ var a = -1
+ var b = -1
+ var c = -1
+ var i = 0
+ val end = bits.length * 64
+ while (i < end) {
+ if (bsContains(bits, i)) {
+ if (a == -1) a = i
+ else if (b == -1) b = i
+ else if (c == -1) c = i
+ else return new SmallBitSet(a, b, c, i)
+ }
+ i += 1
+ }
+ null
+ }
+
+ /**
+ * An iterator that yields the elements that are in one bit set and not in another (&~).
+ */
+ private class AndNotIt(setA: AliasSet, setB: AliasSet, thisAndOther: Array[Boolean]) extends IntIterator {
+ // values in the first bit set
+ private var a, b, c, d = -1
+ private var xs: Array[Long] = null
+
+ // values in the second bit set
+ private var notA, notB, notC, notD = -1
+ private var notXs: Array[Long] = null
+
+ // holds the next value of `x`, `y` or `z` that should be returned. assigned in hasNext
+ private var abcdNext = -1
+
+ // counts through elements in the `xs` bit set
+ private var i = 0
+ // true if the current value of `i` should be returned by this iterator
+ private var iValid = false
+
+ setA.set match {
+ case s: SmallBitSet => a = s.a; b = s.b; c = s.c; d = s.d
+ case bits: Array[Long] => xs = bits
+ }
+
+ setB.set match {
+ case s: SmallBitSet => notA = s.a; notB = s.b; notC = s.c; notD = s.d
+ case bits: Array[Long] => notXs = bits
+ }
+
+ // for each value that exists both in this AND (&) the other bit, `thisAndOther` is set to true.
+ // hacky side-effect, used for performance of AliasingFrame.merge.
+ private def setThisAndOther(x: Int) = if (thisAndOther != null) thisAndOther(x) = true
+
+ private def checkABCD(x: Int, num: Int): Boolean = {
+ // assert(x == a && num == 1 || x == b && num == 2 || ...)
+ x != -1 && {
+ val otherHasA = x == notA || x == notB || x == notC || x == notD || (notXs != null && bsContains(notXs, x))
+ if (otherHasA) setThisAndOther(x)
+ else abcdNext = x
+ (num: @switch) match {
+ case 1 => a = -1
+ case 2 => b = -1
+ case 3 => c = -1
+ case 4 => d = -1
+ }
+ !otherHasA
+ }
+ }
+
+ // main performance hot spot
+ private def checkXs = {
+ (xs != null) && {
+ val end = xs.length * 64
+
+ while (i < end && {
+ val index = i >> 6
+ if (xs(index) == 0l) { // boom. for nullness, this saves 35% of the overall analysis time.
+ i = ((index + 1) << 6) - 1 // -1 required because i is incremented in the loop body
+ true
+ } else {
+ val mask = 1l << i
+ // if (mask > xs(index)) we could also advance i to the next value, but that didn't pay off in benchmarks
+ val thisHasI = (xs(index) & mask) != 0l
+ !thisHasI || {
+ val otherHasI = i == notA || i == notB || i == notC || i == notD || (notXs != null && index < notXs.length && (notXs(index) & mask) != 0l)
+ if (otherHasI) setThisAndOther(i)
+ otherHasI
+ }
+ }
+ }) i += 1
+
+ iValid = i < end
+ iValid
+ }
+ }
+
+ // this is the main hot spot of alias analysis. for nullness, 38% of the overall analysis time
+ // is spent here. within hasNext, almost the entire time is spent in `checkXs`.
+ //
+ def hasNext: Boolean = iValid || abcdNext != -1 || checkABCD(a, 1) || checkABCD(b, 2) || checkABCD(c, 3) || checkABCD(d, 4) || checkXs
+
+ def next(): Int = {
+ if (hasNext) {
+ if (abcdNext != -1) {
+ val r = abcdNext; abcdNext = -1; r
+ } else {
+ val r = i; i += 1; iValid = false; r
+ }
+ } else Iterator.empty.next()
+ }
+ }
+
+// The number of bits in a bit array. Useful for debugging.
+// def bsSize(bits: Array[Long]) = {
+// var r = 0
+// var i = 0
+// while (i < bits.length) {
+// r += java.lang.Long.bitCount(bits(i))
+// i += 1
+// }
+// r
+// }
+
+ /**
+ * An iterator returning the elements in a that are not also in b (a &~ b).
+ *
+ * If `thisAndOther` is non-null, the iterator sets thisAndOther(i) to true for every value that
+ * is both in a and b (&).
+ */
+ def andNotIterator(a: AliasSet, b: AliasSet, thisAndOther: Array[Boolean]): IntIterator = new AndNotIt(a, b, thisAndOther)
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala
new file mode 100644
index 0000000000..bb5c6e3820
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala
@@ -0,0 +1,48 @@
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
+import scala.tools.asm.tree.analysis.{Frame, BasicInterpreter, Analyzer, Value}
+import scala.tools.nsc.backend.jvm.BTypes._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+/**
+ * This component hosts tools for running ASM analyzers that require access to a `BTypes` instance.
+ * In particular, the AsmAnalyzer class runs `computeMaxLocalsMaxStack` on the methodNode to be
+ * analyzed. This method in turn lives inside the BTypes assembly because it queries the per-run
+ * cache `maxLocalsMaxStackComputed` defined in there.
+ */
+class Analyzers[BT <: BTypes](val btypes: BT) {
+ import btypes._
+
+ /**
+ * A wrapper to make ASM's Analyzer a bit easier to use.
+ */
+ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) {
+ localOpt.computeMaxLocalsMaxStack(methodNode)
+ analyzer.analyze(classInternalName, methodNode)
+ def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
+ }
+
+ /**
+ * See the doc comment on package object `analysis` for a discussion on performance.
+ */
+ object AsmAnalyzer {
+ // jvm limit is 65535 for both number of instructions and number of locals
+
+ private def size(method: MethodNode) = method.instructions.size.toLong * method.maxLocals * method.maxLocals
+
+ // with the limits below, analysis should not take more than one second
+
+ private val nullnessSizeLimit = 5000l * 600l * 600l // 5000 insns, 600 locals
+ private val basicValueSizeLimit = 9000l * 1000l * 1000l
+ private val sourceValueSizeLimit = 8000l * 950l * 950l
+
+ def sizeOKForNullness(method: MethodNode): Boolean = size(method) < nullnessSizeLimit
+ def sizeOKForBasicValue(method: MethodNode): Boolean = size(method) < basicValueSizeLimit
+ def sizeOKForSourceValue(method: MethodNode): Boolean = size(method) < sourceValueSizeLimit
+ }
+
+ class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
index 31b62f747e..f9ac12eb4d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
@@ -7,66 +7,12 @@ import java.util
import scala.annotation.switch
import scala.tools.asm.{Type, Opcodes}
import scala.tools.asm.tree.{MethodInsnNode, LdcInsnNode, AbstractInsnNode}
-import scala.tools.asm.tree.analysis.{Frame, Analyzer, Interpreter, Value}
+import scala.tools.asm.tree.analysis._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils
import BytecodeUtils._
/**
- * Some notes on the ASM analyzer framework.
- *
- * Value
- * - Abstract, needs to be implemented for each analysis.
- * - Represents the desired information about local variables and stack values, for example:
- * - Is this value known to be null / not null?
- * - What are the instructions that could potentially have produced this value?
- *
- * Interpreter
- * - Abstract, needs to be implemented for each analysis. Sometimes one can subclass an existing
- * interpreter, e.g., SourceInterpreter or BasicInterpreter.
- * - Multiple abstract methods that receive an instruction and the instruction's input values, and
- * return a value representing the result of that instruction.
- * - Note: due to control flow, the interpreter can be invoked multiple times for the same
- * instruction, until reaching a fixed point.
- * - Abstract `merge` function that computes the least upper bound of two values. Used by
- * Frame.merge (see below).
- *
- * Frame
- * - Can be used directly for many analyses, no subclass required.
- * - Every frame has an array of values: one for each local variable and for each stack slot.
- * - A `top` index stores the index of the current stack top
- * - NOTE: for a size-2 local variable at index i, the local variable at i+1 is set to an empty
- * value. However, for a size-2 value at index i on the stack, the value at i+1 holds the next
- * stack value.
- * - Defines the `execute(instruction)` method.
- * - executing mutates the state of the frame according to the effect of the instruction
- * - pop consumed values from the stack
- * - pass them to the interpreter together with the instruction
- * - if applicable, push the resulting value on the stack
- * - Defines the `merge(otherFrame)` method
- * - called by the analyzer when multiple control flow paths lead to an instruction
- * - the frame at the branching instruction is merged into the current frame of the
- * instruction (held by the analyzer)
- * - mutates the values of the current frame, merges all values using interpreter.merge.
- *
- * Analyzer
- * - Stores a frame for each instruction
- * - `merge` function takes an instruction and a frame, merges the existing frame for that instr
- * (from the frames array) with the new frame passed as argument.
- * if the frame changed, puts the instruction on the work queue (fixpiont).
- * - initial frame: initialized for first instr by calling interpreter.new[...]Value
- * for each slot (locals and params), stored in frames[firstInstr] by calling `merge`
- * - work queue of instructions (`queue` array, `top` index for next instruction to analyze)
- * - analyze(method): simulate control flow. while work queue non-empty:
- * - copy the state of `frames[instr]` into a local frame `current`
- * - call `current.execute(instr, interpreter)`, mutating the `current` frame
- * - if it's a branching instruction
- * - for all potential destination instructions
- * - merge the destination instruction frame with the `current` frame
- * (this enqueues the destination instr if its frame changed)
- * - invoke `newControlFlowEdge` (see below)
- * - the analyzer also tracks active exception handlers at each instruction
- * - the empty method `newControlFlowEdge` can be overridden to track control flow if required
- *
+ * See the package object `analysis` for details on the ASM analysis framework.
*
* Some notes on nullness analysis.
*
@@ -87,56 +33,34 @@ import BytecodeUtils._
*/
/**
- * Type to represent nullness of values.
- */
-sealed trait Nullness {
- final def merge(other: Nullness) = if (this == other) this else Unknown
-}
-case object NotNull extends Nullness
-case object Unknown extends Nullness
-case object Null extends Nullness
-
-/**
* Represents the nullness state for a local variable or stack value.
*
- * Note that nullness of primitive values is not tracked, it will be always [[Unknown]].
+ * Note that nullness of primitive values is not tracked, it will be always unknown.
*/
-sealed trait NullnessValue extends Value {
- /**
- * The nullness of this value.
- */
- def nullness: Nullness
-
- /**
- * True if this value is a long or double. The Analyzer framework needs to know
- * the size of each value when interpreting instructions, see `Frame.execute`.
- */
- def isSize2: Boolean
+sealed abstract class NullnessValue(final val isSize2: Boolean) extends Value {
/**
* The size of the slot described by this value. Cannot be 0 because no values are allocated
* for void-typed slots, see NullnessInterpreter.newValue.
**/
def getSize: Int = if (isSize2) 2 else 1
- def merge(other: NullnessValue) = NullnessValue(nullness merge other.nullness, isSize2)
+ def merge(other: NullnessValue) = {
+ if (this eq other) this
+ else if (this eq UnknownValue2) this // the only possible value of size two
+ else UnknownValue1
+ }
+
+ final override def equals(other: Any) = this eq other.asInstanceOf[Object]
}
-object NullValue extends NullnessValue { def nullness = Null; def isSize2 = false; override def toString = "Null" }
-object UnknownValue1 extends NullnessValue { def nullness = Unknown; def isSize2 = false; override def toString = "Unknown1" }
-object UnknownValue2 extends NullnessValue { def nullness = Unknown; def isSize2 = true; override def toString = "Unknown2" }
-object NotNullValue extends NullnessValue { def nullness = NotNull; def isSize2 = false; override def toString = "NotNull" }
+object NullValue extends NullnessValue(isSize2 = false) { override def toString = "Null" }
+object UnknownValue1 extends NullnessValue(isSize2 = false) { override def toString = "Unknown1" }
+object UnknownValue2 extends NullnessValue(isSize2 = true ) { override def toString = "Unknown2" }
+object NotNullValue extends NullnessValue(isSize2 = false) { override def toString = "NotNull" }
object NullnessValue {
- def apply(nullness: Nullness, isSize2: Boolean): NullnessValue = {
- if (nullness == Null) NullValue
- else if (nullness == NotNull) NotNullValue
- else if (isSize2) UnknownValue2
- else UnknownValue1
- }
-
- def apply(nullness: Nullness, insn: AbstractInsnNode): NullnessValue = {
- apply(nullness, isSize2 = BytecodeUtils.instructionResultSize(insn) == 2)
- }
+ def unknown(isSize2: Boolean) = if (isSize2) UnknownValue2 else UnknownValue1
+ def unknown(insn: AbstractInsnNode) = if (BytecodeUtils.instructionResultSize(insn) == 2) UnknownValue2 else UnknownValue1
}
final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5) {
@@ -151,29 +75,25 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
// (2) `tp` may also be `null`. When creating the initial frame, the analyzer invokes
// `newValue(null)` for each local variable. We have to return a value of size 1.
if (tp == Type.VOID_TYPE) null // (1)
- else NullnessValue(Unknown, isSize2 = tp != null /*(2)*/ && tp.getSize == 2 )
+ else NullnessValue.unknown(isSize2 = tp != null /*(2)*/ && tp.getSize == 2 )
}
override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): NullnessValue = {
// For instance methods, the `this` parameter is known to be not null.
- if (isInstanceMethod && local == 0) NullnessValue(NotNull, isSize2 = false)
+ if (isInstanceMethod && local == 0) NotNullValue
else super.newParameterValue(isInstanceMethod, local, tp)
}
- def newOperation(insn: AbstractInsnNode): NullnessValue = {
- val nullness = (insn.getOpcode: @switch) match {
- case Opcodes.ACONST_NULL => Null
+ def newOperation(insn: AbstractInsnNode): NullnessValue = (insn.getOpcode: @switch) match {
+ case Opcodes.ACONST_NULL => NullValue
- case Opcodes.LDC => insn.asInstanceOf[LdcInsnNode].cst match {
- case _: String | _: Type => NotNull
- case _ => Unknown
- }
-
- case _ => Unknown
+ case Opcodes.LDC => insn.asInstanceOf[LdcInsnNode].cst match {
+ case _: String | _: Type => NotNullValue
+ case _ => NullnessValue.unknown(insn)
}
// for Opcodes.NEW, we use Unknown. The value will become NotNull after the constructor call.
- NullnessValue(nullness, insn)
+ case _ => NullnessValue.unknown(insn)
}
def copyOperation(insn: AbstractInsnNode, value: NullnessValue): NullnessValue = value
@@ -182,26 +102,24 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
case Opcodes.CHECKCAST => value
case Opcodes.NEWARRAY |
- Opcodes.ANEWARRAY => NullnessValue(NotNull, isSize2 = false)
+ Opcodes.ANEWARRAY => NotNullValue
- case _ => NullnessValue(Unknown, insn)
+ case _ => NullnessValue.unknown(insn)
}
def binaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue): NullnessValue = {
- NullnessValue(Unknown, insn)
+ NullnessValue.unknown(insn)
}
- def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = {
- NullnessValue(Unknown, isSize2 = false)
- }
+ def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = UnknownValue1
def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = (insn.getOpcode: @switch) match {
case Opcodes.MULTIANEWARRAY =>
- NullnessValue(NotNull, isSize2 = false)
+ NotNullValue
case _ =>
// TODO: use a list of methods that are known to return non-null values
- NullnessValue(Unknown, insn)
+ NullnessValue.unknown(insn)
}
def returnOperation(insn: AbstractInsnNode, value: NullnessValue, expected: NullnessValue): Unit = ()
@@ -219,8 +137,10 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
override def execute(insn: AbstractInsnNode, interpreter: Interpreter[NullnessValue]): Unit = {
import Opcodes._
- // get the object id of the object that is known to be not-null after this operation
- val nullCheckedAliasId: Long = (insn.getOpcode: @switch) match {
+ // get the alias set the object that is known to be not-null after this operation.
+ // alias sets are mutable / mutated, so after super.execute, this set contains the remaining
+ // aliases of the value that becomes not-null.
+ val nullCheckedAliases: AliasSet = (insn.getOpcode: @switch) match {
case IALOAD |
LALOAD |
FALOAD |
@@ -229,7 +149,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
BALOAD |
CALOAD |
SALOAD =>
- aliasId(this.stackTop - 1)
+ aliasesOf(this.stackTop - 1)
case IASTORE |
FASTORE |
@@ -239,35 +159,36 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
SASTORE |
LASTORE |
DASTORE =>
- aliasId(this.stackTop - 2)
+ aliasesOf(this.stackTop - 2)
case GETFIELD =>
- aliasId(this.stackTop)
+ aliasesOf(this.stackTop)
case PUTFIELD =>
- aliasId(this.stackTop - 1)
+ aliasesOf(this.stackTop - 1)
case INVOKEVIRTUAL |
INVOKESPECIAL |
INVOKEINTERFACE =>
val desc = insn.asInstanceOf[MethodInsnNode].desc
val numArgs = Type.getArgumentTypes(desc).length
- aliasId(this.stackTop - numArgs)
+ aliasesOf(this.stackTop - numArgs)
case ARRAYLENGTH |
MONITORENTER |
MONITOREXIT =>
- aliasId(this.stackTop)
+ aliasesOf(this.stackTop)
case _ =>
- -1
+ null
}
super.execute(insn, interpreter)
- if (nullCheckedAliasId != -1) {
- for (i <- valuesWithAliasId(nullCheckedAliasId))
- this.setValue(i, NotNullValue)
+ if (nullCheckedAliases != null) {
+ val it = nullCheckedAliases.iterator
+ while (it.hasNext)
+ this.setValue(it.next(), NotNullValue)
}
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala
index 700b2f2f6c..ce2fe943e4 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala
@@ -55,24 +55,16 @@ import scala.collection.convert.decorateAsScala._
*
* If ever needed, we could introduce a mode where primitive conversions (l2i) are considered as
* copying operations.
+ *
+ * Note on performance: thee data flow analysis (SourceValue / SourceInterpreter, provided by ASM)
+ * is roughly 2-3x slower than a simple analysis (like BasicValue). The reason is that the merge
+ * function (merging producer sets) is more complex than merging simple basic values.
+ * See also the doc comment in the package object `analysis`.
*/
-class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) {
-
- /* Timers for benchmarking ProdCons
- import scala.reflect.internal.util.Statistics._
- import ProdConsAnalyzer._
- val analyzerTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - analysis", prodConsAnalyzerTimer)
- val consumersTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - consumers", prodConsAnalyzerTimer)
- */
-
- val analyzer = new Analyzer(new InitialProducerSourceInterpreter)
+trait ProdConsAnalyzerImpl {
+ val methodNode: MethodNode
-// val start = analyzerTimer.start()
- analyzer.analyze(classInternalName, methodNode)
-// analyzerTimer.stop(start)
-// println(analyzerTimer.line)
-
- def frameAt(insn: AbstractInsnNode) = analyzer.frameAt(insn, methodNode)
+ def frameAt(insn: AbstractInsnNode): Frame[SourceValue]
/**
* Returns the potential producer instructions of a (local or stack) value in the frame of `insn`.
@@ -404,7 +396,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
/** For each instruction, a set of potential consumers of the produced values. */
private lazy val _consumersOfOutputsFrom: Map[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] = {
-// val start = consumersTimer.start()
var res = Map.empty[AbstractInsnNode, Vector[Set[AbstractInsnNode]]]
for {
insn <- methodNode.instructions.iterator.asScala
@@ -417,8 +408,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
val outputIndex = producedSlots.indexOf(i)
res = res.updated(producer, currentConsumers.updated(outputIndex, currentConsumers(outputIndex) + insn))
}
-// consumersTimer.stop(start)
-// println(consumersTimer.line)
res
}
@@ -426,11 +415,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
private val _ultimateConsumersCache: mutable.AnyRefMap[(AbstractInsnNode, Int), Set[AbstractInsnNode]] = mutable.AnyRefMap.empty
}
-object ProdConsAnalyzer {
- import scala.reflect.internal.util.Statistics._
- val prodConsAnalyzerTimer = newTimer("Time in ProdConsAnalyzer", "jvm")
-}
-
/**
* A class for pseudo-instructions representing the initial producers of local values that have
* no producer instruction in the method:
@@ -459,9 +443,9 @@ abstract class InitialProducer extends AbstractInsnNode(-1) {
override def accept(cv: MethodVisitor): Unit = throw new UnsupportedOperationException
}
-case class ParameterProducer(local: Int) extends InitialProducer
-case class UninitializedLocalProducer(local: Int) extends InitialProducer
-case class ExceptionProducer(handlerFrame: Frame[_ <: Value]) extends InitialProducer
+case class ParameterProducer(local: Int) extends InitialProducer
+case class UninitializedLocalProducer(local: Int) extends InitialProducer
+case class ExceptionProducer[V <: Value](handlerFrame: Frame[V]) extends InitialProducer
class InitialProducerSourceInterpreter extends SourceInterpreter {
override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): SourceValue = {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala
new file mode 100644
index 0000000000..f1ac703532
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala
@@ -0,0 +1,374 @@
+package scala.tools.nsc.backend.jvm
+
+/**
+ * Summary on the ASM analyzer framework
+ * --------------------------------------
+ *
+ * Value
+ * - Abstract, needs to be implemented for each analysis.
+ * - Represents the desired information about local variables and stack values, for example:
+ * - Is this value known to be null / not null?
+ * - What are the instructions that could potentially have produced this value?
+ *
+ * Interpreter
+ * - Abstract, needs to be implemented for each analysis. Sometimes one can subclass an existing
+ * interpreter, e.g., SourceInterpreter or BasicInterpreter.
+ * - Multiple abstract methods that receive an instruction and the instruction's input values, and
+ * return a value representing the result of that instruction.
+ * - Note: due to control flow, the interpreter can be invoked multiple times for the same
+ * instruction, until reaching a fixed point.
+ * - Abstract `merge` function that computes the least upper bound of two values. Used by
+ * Frame.merge (see below).
+ *
+ * Frame
+ * - Can be used directly for many analyses, no subclass required.
+ * - Every frame has an array of values: one for each local variable and for each stack slot.
+ * - A `top` index stores the index of the current stack top
+ * - NOTE: for a size-2 local variable at index i, the local variable at i+1 is set to an empty
+ * value. However, for a size-2 value at index i on the stack, the value at i+1 holds the next
+ * stack value. IMPORTANT: this is only the case in ASM's analysis framework, not in bytecode.
+ * See comment below.
+ * - Defines the `execute(instruction)` method.
+ * - executing mutates the state of the frame according to the effect of the instruction
+ * - pop consumed values from the stack
+ * - pass them to the interpreter together with the instruction
+ * - if applicable, push the resulting value on the stack
+ * - Defines the `merge(otherFrame)` method
+ * - called by the analyzer when multiple control flow paths lead to an instruction
+ * - the frame at the branching instruction is merged into the current frame of the
+ * instruction (held by the analyzer)
+ * - mutates the values of the current frame, merges all values using interpreter.merge.
+ *
+ * Analyzer
+ * - Stores a frame for each instruction
+ * - `merge` function takes an instruction and a frame, merges the existing frame for that instr
+ * (from the frames array) with the new frame passed as argument.
+ * if the frame changed, puts the instruction on the work queue (fixpiont).
+ * - initial frame: initialized for first instr by calling interpreter.new[...]Value
+ * for each slot (locals and params), stored in frames[firstInstr] by calling `merge`
+ * - work queue of instructions (`queue` array, `top` index for next instruction to analyze)
+ * - analyze(method): simulate control flow. while work queue non-empty:
+ * - copy the state of `frames[instr]` into a local frame `current`
+ * - call `current.execute(instr, interpreter)`, mutating the `current` frame
+ * - if it's a branching instruction
+ * - for all potential destination instructions
+ * - merge the destination instruction frame with the `current` frame
+ * (this enqueues the destination instr if its frame changed)
+ * - invoke `newControlFlowEdge` (see below)
+ * - the analyzer also tracks active exception handlers at each instruction
+ * - the empty method `newControlFlowEdge` can be overridden to track control flow if required
+ *
+ *
+ * MaxLocals and MaxStack
+ * ----------------------
+ *
+ * At the JVM level, long and double values occupy two slots, both as local variables and on the
+ * stack, as specified in the JVM spec 2.6.2:
+ * "At any point in time, an operand stack has an associated depth, where a value of type long or
+ * double contributes two units to the depth and a value of any other type contributes one unit."
+ *
+ * For example, a method
+ * class A { def f(a: Long, b: Long) = a + b }
+ * has MAXSTACK=4 in the classfile. This value is computed by the ClassWriter / MethodWriter when
+ * generating the classfile (we always pass COMPUTE_MAXS to the ClassWriter).
+ *
+ * For running an ASM Analyzer, long and double values occupy two local variable slots, but only
+ * a single slot on the call stack, as shown by the following snippet:
+ *
+ * import scala.tools.nsc.backend.jvm._
+ * import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+ * import scala.collection.convert.decorateAsScala._
+ * import scala.tools.asm.tree.analysis._
+ *
+ * val cn = AsmUtils.readClass("/Users/luc/scala/scala/sandbox/A.class")
+ * val m = cn.methods.iterator.asScala.find(_.name == "f").head
+ *
+ * // the value is read from the classfile, so it's 4
+ * println(s"maxLocals: ${m.maxLocals}, maxStack: ${m.maxStack}") // maxLocals: 5, maxStack: 4
+ *
+ * // we can safely set it to 2 for running the analyzer.
+ * m.maxStack = 2
+ *
+ * val a = new Analyzer(new BasicInterpreter)
+ * a.analyze(cn.name, m)
+ * val addInsn = m.instructions.iterator.asScala.find(_.getOpcode == 97).get // LADD Opcode
+ * val addFrame = a.frameAt(addInsn, m)
+ *
+ * addFrame.getStackSize // 2: the two long values only take one slot each
+ * addFrame.getLocals // 5: this takes one slot, the two long parameters take 2 slots each
+ *
+ *
+ * While running the optimizer, we need to make sure that the `maxStack` value of a method is
+ * large enough for running an ASM analyzer. We don't need to worry if the value is incorrect in
+ * the JVM perspective: the value will be re-computed and overwritten in the ClassWriter.
+ *
+ *
+ * Lessons learnt while benchmarking the alias tracking analysis
+ * -------------------------------------------------------------
+ *
+ * Profiling
+ * - Use YourKit for finding hotspots (cpu profiling). when it comes to drilling down into the details
+ * of a hotspot, don't pay too much attention to the percentages / time counts.
+ * - Should also try other profilers.
+ * - Use timers. When a method showed up as a hotspot, i added a timer around that method, and a
+ * second one within the method to measure specific parts. The timers slow things down, but the
+ * relative numbers show what parts of a method are slow.
+ *
+ * ASM analyzer insights
+ * - The time for running an analysis depends on the number of locals and the number of instructions.
+ * Reducing the number of locals helps speeding up the analysis: there are less values to
+ * merge when merging to frames.
+ * See also https://github.com/scala/scala-dev/issues/47
+ * - The common hot spot of an ASM analysis is Frame.merge, for example in producers / consumers.
+ * - For nullness analysis the time is spent as follows
+ * - 20% merging nullness values. this is as expected: for example, the same absolute amount of
+ * time is spent in merging BasicValues when running a BasicInterpreter.
+ * - 50% merging alias sets. i tried to optimize what i could out of this.
+ * - 20% is spent creating new frames from existing ones, see comment on AliasingFrame.init.
+ * - The implementation of Frame.merge (the main hot spot) contains a megamorphic callsite to
+ * `interpreter.merge`. This can be observed easily by running a test program that either runs
+ * a BasicValue analysis only, versus a program that first runs a nullness analysis and then
+ * a BasicValue. In an example, the time for the BasicValue analysis goes from 519ms to 1963ms,
+ * a 3.8x slowdown.
+ * - I added counters to the Frame.merge methods for nullness and BasicValue analysis. In the
+ * examples I benchmarked, the number of merge invocations was always exactly the same.
+ * It would probably be possible to come up with an example where alias set merging forces
+ * additional analysis rounds until reaching the fixpoint, but I did not observe such cases.
+ *
+ * To benchmark an analysis, instead of benchmarking analysis while it runs in the compiler
+ * backend, one can easily run it from a separate program (or the repl). The bytecode to analyze
+ * can simply be parsed from a classfile. See example at the end of this comment.
+ *
+ *
+ * Nullness Analysis in Miguel's Optimizer
+ * ---------------------------------------
+ *
+ * Miguel implemented alias tracking for nullness analysis differently [1]. Remember that every
+ * frame has an array of values. Miguel's idea was to represent aliasing using reference equality
+ * in the values array: if two entries in the array point to the same value object, the two entries
+ * are aliases in the frame of the given instruction.
+ *
+ * While this idea seems elegant at first sight, Miguel's implementation does not merge frames
+ * correctly when it comes to aliasing. Assume in frame 1, values (a, b, c) are aliases, while in
+ * frame 2 (a, b) are aliases. When merging the second into the first, we have to make sure that
+ * c is removed as an alias of (a, b).
+ *
+ * It would be possible to implement correct alias set merging in Miguel's approach. However, frame
+ * merging is the main hot spot of analysis. The computational complexity of implementing alias set
+ * merging by traversing the values array and comparing references is too high. The concrete
+ * alias set representation that is used in the current implementation (see class AliasingFrame)
+ * makes alias set merging more efficient.
+ *
+ * [1] https://github.com/scala-opt/scala/blob/opt/rebase/src/compiler/scala/tools/nsc/backend/bcode/NullnessPropagator.java
+ *
+ *
+ * Complexity and scaling of analysis
+ * ----------------------------------
+ *
+ * The time complexity of a data flow analysis depends on:
+ *
+ * - The size of the method. The complexity factor is linear (assuming the number of locals and
+ * branching instructions remains constant). The main analysis loop runs through all
+ * instructions of a method once. Instructions are only re-enqueued if a control flow merge
+ * changes the frame at some instruction.
+ *
+ * - The branching instructions. When a second (third, ..) control flow edge arrives at an
+ * instruction, the existing frame at the instruction is merged with the one computed on the
+ * new branch. If the merge function changes the existing frame, the instruction is enqueued
+ * for another analysis. This results in a merge operation for the successors of the
+ * instruction.
+ *
+ * - The number of local variables. The hot spot of analysis is frame merging. The merge function
+ * iterates through the values in the frame (locals and stack values) and merges them.
+ *
+ * I measured the running time of an analysis for two examples:
+ * - Keep the number of locals and branching instructions constant, increase the number of
+ * instructions. The running time grows linearly with the method size.
+ * - Increase the size and number of locals in a method. The method size and number of locals
+ * grow in the same pace. Here, the running time increase is polynomial. It looks like the
+ * complexity is be #instructions * #locals^2 (see below).
+ *
+ * I measured nullness analysis (which tracks aliases) and a SimpleValue analysis. Nullness runs
+ * roughly 5x slower (because of alias tracking) at every problem size - this factor doesn't change.
+ *
+ * The numbers below are for nullness. Note that the the last column is constant, i.e., the running
+ * time is proportional to #ins * #loc^2. Therefore we use this factor when limiting the maximal
+ * method size for running an analysis.
+ *
+ * #insns #locals time (ms) time / #ins * #loc^2 * 10^6
+ * 1305 156 34 1.07
+ * 2610 311 165 0.65
+ * 3915 466 490 0.57
+ * 5220 621 1200 0.59
+ * 6525 776 2220 0.56
+ * 7830 931 3830 0.56
+ * 9135 1086 6570 0.60
+ * 10440 1241 9700 0.60
+ * 11745 1396 13800 0.60
+ *
+ * As a second experiment, nullness analysis was run with varying #insns but constant #locals.
+ * The last column shows linear complexity with respect to the method size (linearOffset = 2279):
+ *
+ * #insns #locals time (ms) (time + linearOffset) / #insns
+ * 5220 621 1090 0.645
+ * 6224 621 1690 0.637
+ * 7226 621 2280 0.630
+ * 8228 621 2870 0.625
+ * 9230 621 3530 0.629
+ * 10232 621 4130 0.626
+ * 11234 621 4770 0.627
+ * 12236 621 5520 0.637
+ * 13238 621 6170 0.638
+ *
+ *
+ * When running a BasicValue analysis, the complexity observation is the same (time is proportional
+ * to #ins * #loc^2).
+ *
+ *
+ * Measuring analysis execution time
+ * ---------------------------------
+ *
+ * See code below.
+ */
+
+/*
+object Test {
+ val overwrite: Option[String] = null
+
+ @noinline def serialize(o: AnyRef): String = null
+
+ @noinline def deserialize(string: String): AnyRef = null
+
+ @inline def checkRoundTrip[T <: AnyRef](instance: T)(f: T => AnyRef) {
+ val result = serialize(instance)
+ val reconstituted = deserialize(result).asInstanceOf[T]
+ assert(f(instance) == f(reconstituted), (f(instance), f(reconstituted)))
+ }
+
+ @inline def check[T <: AnyRef](instance: => T)(prevResult: String, f: T => AnyRef = (x: T) => x) {
+ // pattern match to introduce a lot of control flow, i.e., a lot of frame merges
+ overwrite match {
+ case Some(f) =>
+ case None =>
+ checkRoundTrip(instance)(f)
+ assert(f(deserialize(prevResult).asInstanceOf[T]) == f(instance), instance)
+ assert(prevResult == "res", instance)
+ }
+ }
+
+ // @inline def fun[T <: AnyRef](instance: => T) = (x: T) => x
+
+ def testMain(): Unit = {
+ // every call to check creates quite a number of locals, and also quite a number of aliases
+ // of the same value (x1). First of all, the default argument call is expanded as below. Then
+ // method check is inlined, and within the body of check, checkRoundTrip and assert have
+ // already been inlined as well.
+
+ // {
+ // val x1 = () => ""
+ // val x2 = fun(x1()) // the compiler optimizes this: instead of passing `() => x1()`, it just passes x1
+ // check(x1())("", x2) // same here for x1
+ // }
+
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 5
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 10
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 15
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 20
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 25
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 30
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 35
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("")
+ check("")("") // 40
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("") // 45
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("") // 50
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("")
+ // check("")("") // 55
+
+ // 1000 bytecode instructions, 0 locals
+ // println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10));
+ }
+
+ def timed[T](f: => T): T = {
+ val start = System.nanoTime()
+ val r = f
+ val nanos = System.nanoTime() - start
+ println(s"took ${nanos/1000000}ms")
+ r
+ }
+
+ def main(args: Array[String]): Unit = {
+ import scala.tools.nsc.backend.jvm._
+ val cn = AsmUtils.readClass("/Users/luc/scala/scala/sandbox/Test$.class")
+ import scala.collection.convert.decorateAsScala._
+ val m = cn.methods.iterator.asScala.find(_.name == "testMain").head
+
+ println(s"${m.instructions.size} instructions - ${m.maxLocals} locals")
+
+ val a = new analysis.NullnessAnalyzer
+ a.analyze(cn.name, m) // warm up
+
+ analysis.AliasingFrame.reset()
+ timed(a.analyze(cn.name, m))
+ analysis.AliasingFrame.timers foreach println
+
+ println("---")
+
+ // NOTE: if we don't run nullness analysis above (comment it out), then the BasicValue
+ // analysis runs 3.5x faster. Most likely because the call to Interpreter.merge inside
+ // Frame.merge is no longer megamorphic.
+
+ import scala.tools.asm.tree.analysis._
+ val ba = new Analyzer(new BasicInterpreter)
+ ba.analyze(cn.name, m) // warm up
+
+ timed(ba.analyze(cn.name, m))
+
+ println("---")
+
+ timed(a.analyze(cn.name, m))
+ }
+}
+*/
+package object analysis
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 a5b85e54e7..4492d0baf5 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -10,6 +10,7 @@ package opt
import scala.tools.asm
import asm.tree._
import scala.collection.convert.decorateAsScala._
+import scala.collection.concurrent
import scala.tools.asm.Attribute
import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.io.AbstractFile
@@ -24,39 +25,52 @@ import java.util.concurrent.atomic.AtomicLong
* classpath. Parsed classes are cached in the `classes` map.
*
* @param classPath The compiler classpath where classfiles are searched and read from.
- * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode:
- * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode
- * 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 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 isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) {
+class ByteCodeRepository[BT <: BTypes](val classPath: ClassFileLookup[AbstractFile], val btypes: BT) {
+ import btypes._
+
+ /**
+ * ClassNodes for classes being compiled in the current compilation run.
+ */
+ val compilingClasses: concurrent.Map[InternalName, ClassNode] = recordPerRunCache(concurrent.TrieMap.empty)
+
+ /**
+ * Cache for parsed ClassNodes.
+ * The `Long` field encodes the age of the node in the map, which allows removing old entries when
+ * the map grows too large (see limitCacheSize).
+ * 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.
+ */
+ val parsedClasses: concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Long)]] = recordPerRunCache(concurrent.TrieMap.empty)
private val maxCacheSize = 1500
private val targetSize = 500
- private val idCounter = new AtomicLong(0)
+ private object lruCounter extends AtomicLong(0l) with collection.generic.Clearable {
+ def clear(): Unit = { this.set(0l) }
+ }
+ recordPerRunCache(lruCounter)
/**
* Prevent the code repository from growing too large. Profiling reveals that the average size
* of a ClassNode is about 30 kb. I observed having 17k+ classes in the cache, i.e., 500 mb.
- *
- * 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.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) {
- val removeId = idCounter.get - targetSize
- val toRemove = classes.iterator.collect({
- case (name, Right((_, Classfile, id))) if id < removeId => name
- }).toList
- toRemove foreach classes.remove
+ if (parsedClasses.size > maxCacheSize) {
+ // OK if multiple threads get here
+ val minimalLRU = parsedClasses.valuesIterator.collect({
+ case Right((_, lru)) => lru
+ }).toList.sorted(Ordering.Long.reverse).drop(targetSize).headOption.getOrElse(Long.MaxValue)
+ parsedClasses retain {
+ case (_, Right((_, lru))) => lru > minimalLRU
+ case _ => false
+ }
}
}
def add(classNode: ClassNode, source: Source) = {
- classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet()))
+ if (source == CompilationUnit) compilingClasses(classNode.name) = classNode
+ else parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet()))
}
/**
@@ -64,18 +78,32 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
* parsed from the classfile on the compile classpath.
*/
def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = {
- val r = classes.getOrElseUpdate(internalName, {
- limitCacheSize()
- parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
+ classNode(internalName) map (n => {
+ val source = if (compilingClasses contains internalName) CompilationUnit else Classfile
+ (n, source)
})
- r.map(v => (v._1, v._2))
}
/**
* 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): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1)
+ def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
+ compilingClasses.get(internalName).map(Right(_)) getOrElse {
+ val r = parsedClasses.get(internalName) match {
+ case Some(l @ Left(_)) => l
+ case Some(r @ Right((classNode, _))) =>
+ parsedClasses(internalName) = Right((classNode, lruCounter.incrementAndGet()))
+ r
+ case None =>
+ limitCacheSize()
+ val res = parseClass(internalName).map((_, lruCounter.incrementAndGet()))
+ parsedClasses(internalName) = res
+ res
+ }
+ r.map(_._1)
+ }
+ }
/**
* The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
@@ -86,7 +114,6 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
*/
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) =>
@@ -105,6 +132,11 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
* The method node for a method matching `name` and `descriptor`, accessed in class `ownerInternalNameOrArrayDescriptor`.
* The declaration of the method may be in one of the parents.
*
+ * TODO: make sure we always return the right method, the one being invoked. write tests.
+ * - if there's an abstract and a concrete one. could possibly somehow the abstract be returned?
+ * - with traits and default methods, if there is more than one default method inherited and
+ * no override: what should be returned? We should not just inline one of the two.
+ *
* @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.
*/
@@ -157,7 +189,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
classNode
} match {
case Some(node) => Right(node)
- case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName)))
+ case None => Left(ClassNotFound(internalName, javaDefinedClasses(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 df8dcc690a..ea186f9a1b 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -173,6 +173,11 @@ object BytecodeUtils {
case Opcodes.IFNONNULL => Opcodes.IFNULL
}
+ def isSize2LoadOrStore(opcode: Int): Boolean = (opcode: @switch) match {
+ case Opcodes.LLOAD | Opcodes.DLOAD | Opcodes.LSTORE | Opcodes.DSTORE => true
+ case _ => false
+ }
+
def getPop(size: Int): InsnNode = {
val op = if (size == 1) Opcodes.POP else Opcodes.POP2
new InsnNode(op)
@@ -222,29 +227,6 @@ object BytecodeUtils {
}
}
- /**
- * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
- * framework only computes these values during bytecode generation.
- *
- * Since there's currently no better way, we run a bytecode generator on the method and extract
- * the computed values. This required changes to the ASM codebase:
- * - the [[MethodWriter]] class was made public
- * - accessors for maxLocals / maxStack were added to the MethodWriter class
- *
- * We could probably make this faster (and allocate less memory) by hacking the ASM framework
- * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be
- * to create a separate visitor for computing those values, duplicating the functionality from the
- * MethodWriter.
- */
- def computeMaxLocalsMaxStack(method: MethodNode): Unit = {
- val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
- val excs = method.exceptions.asScala.toArray
- val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter]
- method.accept(mw)
- method.maxLocals = mw.getMaxLocals
- method.maxStack = mw.getMaxStack
- }
-
def codeSizeOKForInlining(caller: MethodNode, callee: MethodNode): Boolean = {
// Looking at the implementation of CodeSizeEvaluator, all instructions except tableswitch and
// lookupswitch are <= 8 bytes. These should be rare enough for 8 to be an OK rough upper bound.
@@ -308,14 +290,14 @@ object BytecodeUtils {
* Clone the local variable descriptors of `methodNode` and map their `start` and `end` labels
* according to the `labelMap`.
*/
- def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String): List[LocalVariableNode] = {
+ def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String, shift: Int): List[LocalVariableNode] = {
methodNode.localVariables.iterator().asScala.map(localVariable => new LocalVariableNode(
prefix + localVariable.name,
localVariable.desc,
localVariable.signature,
labelMap(localVariable.start),
labelMap(localVariable.end),
- localVariable.index
+ localVariable.index + shift
)).toList
}
@@ -352,15 +334,6 @@ object BytecodeUtils {
}
}
- /**
- * A wrapper to make ASM's Analyzer a bit easier to use.
- */
- class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) {
- val analyzer = new Analyzer(interpreter)
- analyzer.analyze(classInternalName, methodNode)
- def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
- }
-
implicit class AnalyzerExtensions[V <: Value](val analyzer: Analyzer[V]) extends AnyVal {
def frameAt(instruction: AbstractInsnNode, methodNode: MethodNode): Frame[V] = analyzer.getFrames()(methodNode.instructions.indexOf(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 96455c0e38..b9788c3f56 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -7,104 +7,92 @@ package scala.tools.nsc
package backend.jvm
package opt
+import scala.collection.immutable.IntMap
import scala.reflect.internal.util.{NoPosition, Position}
-import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter}
import scala.tools.asm.{Opcodes, Type, Handle}
import scala.tools.asm.tree._
-import scala.collection.concurrent
+import scala.collection.{concurrent, mutable}
import scala.collection.convert.decorateAsScala._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.BackendReporting._
-import scala.tools.nsc.backend.jvm.analysis.{NotNull, NullnessAnalyzer}
+import scala.tools.nsc.backend.jvm.analysis._
import ByteCodeRepository.{Source, CompilationUnit}
import BytecodeUtils._
class CallGraph[BT <: BTypes](val btypes: BT) {
import btypes._
+ import analyzers._
- val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty)
-
- val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, ClosureInstantiation] = recordPerRunCache(concurrent.TrieMap.empty)
-
- def addClass(classNode: ClassNode): Unit = {
- val classType = classBTypeFromClassNode(classNode)
- for {
- m <- classNode.methods.asScala
- (calls, closureInits) = analyzeCallsites(m, classType)
- } {
- calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite)
- closureInits foreach (lmf => closureInstantiations(lmf.indy) = ClosureInstantiation(lmf, m, classType))
- }
- }
+ /**
+ * The call graph contains the callsites in the program being compiled.
+ *
+ * Indexing the call graph by the containing MethodNode and the invocation MethodInsnNode allows
+ * finding callsites efficiently. For example, an inlining heuristic might want to know all
+ * callsites withing a callee method.
+ *
+ * Note that the call graph is not guaranteed to be complete: callsites may be missing. In
+ * particular, if a method is very large, all of its callsites might not be in the hash map.
+ * The reason is that adding a method to the call graph requires running an ASM analyzer, which
+ * can be too slow.
+ *
+ * Note that call graph entries (Callsite instances) keep a reference to the invocation
+ * MethodInsnNode, which keeps all AbstractInsnNodes of the method reachable. Adding classes
+ * from the classpath to the call graph (in addition to classes being compiled) may prevent
+ * method instruction nodes from being GCd. The ByteCodeRepository has a fixed size cache for
+ * parsed ClassNodes - keeping all ClassNodes alive consumed too much memory.
+ * The call graph is less problematic because only methods being called are kept alive, not entire
+ * classes. But we should keep an eye on this.
+ */
+ val callsites: mutable.Map[MethodNode, Map[MethodInsnNode, Callsite]] = recordPerRunCache(concurrent.TrieMap.empty withDefaultValue Map.empty)
/**
- * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
+ * Closure instantiations in the program being compiled.
+ *
+ * Indexing closure instantiations by the containing MethodNode is beneficial for the closure
+ * optimizer: finding callsites to re-write requires running a producers-consumers analysis on
+ * the method. Here the closure instantiations are already grouped by method.
*/
- def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[LambdaMetaFactoryCall]) = {
+ val closureInstantiations: mutable.Map[MethodNode, Map[InvokeDynamicInsnNode, ClosureInstantiation]] = recordPerRunCache(concurrent.TrieMap.empty withDefaultValue Map.empty)
+
+ def removeCallsite(invocation: MethodInsnNode, methodNode: MethodNode): Option[Callsite] = {
+ val methodCallsites = callsites(methodNode)
+ val newCallsites = methodCallsites - invocation
+ if (newCallsites.isEmpty) callsites.remove(methodNode)
+ else callsites(methodNode) = newCallsites
+ methodCallsites.get(invocation)
+ }
- case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
- annotatedInline: Boolean, annotatedNoInline: Boolean,
- warning: Option[CalleeInfoWarning])
+ def addCallsite(callsite: Callsite): Unit = {
+ val methodCallsites = callsites(callsite.callsiteMethod)
+ callsites(callsite.callsiteMethod) = methodCallsites + (callsite.callsiteInstruction -> callsite)
+ }
- /**
- * Analyze a callsite and gather meta-data that can be used for inlining decisions.
- */
- def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = {
- val methodSignature = calleeMethodNode.name + calleeMethodNode.desc
-
- 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 = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit
-
- val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
-
- // (1) A non-final method can be safe to 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
- //
- // 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.
- val isStaticallyResolved: Boolean = {
- methodInlineInfo.effectivelyFinal ||
- classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal // (1)
- }
+ def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = {
+ val methodClosureInits = closureInstantiations(methodNode)
+ val newClosureInits = methodClosureInits - indy
+ if (newClosureInits.isEmpty) closureInstantiations.remove(methodNode)
+ else closureInstantiations(methodNode) = newClosureInits
+ methodClosureInits.get(indy)
+ }
- val isRewritableTraitCall = isStaticallyResolved && methodInlineInfo.traitMethodWithStaticImplementation
+ def addClosureInstantiation(closureInit: ClosureInstantiation) = {
+ val methodClosureInits = closureInstantiations(closureInit.ownerMethod)
+ closureInstantiations(closureInit.ownerMethod) = methodClosureInits + (closureInit.lambdaMetaFactoryCall.indy -> closureInit)
+ }
- val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map(
- MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _))
+ def addClass(classNode: ClassNode): Unit = {
+ val classType = classBTypeFromClassNode(classNode)
+ classNode.methods.asScala.foreach(addMethod(_, classType))
+ }
- // (1) For invocations of final trait methods, the callee isStaticallyResolved but also
- // abstract. Such a callee is not safe to inline - it needs to be re-written to the
- // static impl method first (safeToRewrite).
- // (2) Final trait methods can be rewritten from the interface to the static implementation
- // method to enable inlining.
- CallsiteInfo(
- safeToInline =
- canInlineFromSource &&
- isStaticallyResolved && // (1)
- !isAbstract &&
- !BytecodeUtils.isConstructor(calleeMethodNode) &&
- !BytecodeUtils.isNativeMethod(calleeMethodNode),
- safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2)
- annotatedInline = methodInlineInfo.annotatedInline,
- annotatedNoInline = methodInlineInfo.annotatedNoInline,
- warning = warning)
-
- case None =>
- val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning)
- CallsiteInfo(false, false, false, false, Some(warning))
- }
- } catch {
- case Invalid(noInfo: NoClassBTypeInfo) =>
- val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
- CallsiteInfo(false, false, false, false, Some(warning))
- }
- }
+ def addIfMissing(methodNode: MethodNode, definingClass: ClassBType): Unit = {
+ if (!callsites.contains(methodNode)) addMethod(methodNode, definingClass)
+ }
+ /**
+ * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
+ */
+ def addMethod(methodNode: MethodNode, definingClass: ClassBType): Unit = {
// TODO: run dataflow analyses to make the call graph more precise
// - producers to get forwarded parameters (ForwardedParam)
// - typeAnalysis for more precise argument types, more precise callee
@@ -112,77 +100,236 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
// For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance
// call is known to be not-null, in which case we don't have to emit a null check when inlining.
// It is also used to get the stack height at the call site.
- localOpt.minimalRemoveUnreachableCode(methodNode, definingClass.internalName)
- val analyzer: Analyzer[_ <: Value] = {
- if (compilerSettings.YoptNullnessTracking) new NullnessAnalyzer
- else new Analyzer(new BasicInterpreter)
+ val analyzer = {
+ if (compilerSettings.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) {
+ Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer))
+ } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) {
+ Some(new AsmAnalyzer(methodNode, definingClass.internalName))
+ } else None
}
- analyzer.analyze(definingClass.internalName, methodNode)
- def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = analyzer match {
- case nullnessAnalyzer: NullnessAnalyzer =>
- val frame = nullnessAnalyzer.frameAt(call, methodNode)
- frame.getStack(frame.getStackSize - 1 - numArgs).nullness == NotNull
+ // if the method is too large to run an analyzer, it is not added to the call graph
+ if (analyzer.nonEmpty) {
+ val Some(a) = analyzer
+ def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = a.analyzer match {
+ case nullnessAnalyzer: NullnessAnalyzer =>
+ val frame = nullnessAnalyzer.frameAt(call, methodNode)
+ frame.getStack(frame.getStackSize - 1 - numArgs) eq NotNullValue
+ case _ => false
+ }
+
+ var methodCallsites = Map.empty[MethodInsnNode, Callsite]
+ var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation]
+
+ // lazy so it is only computed if actually used by computeArgInfos
+ lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName)
+
+ methodNode.instructions.iterator.asScala foreach {
+ case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code
+ 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, safeToRewrite, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source)
+ Callee(
+ callee = method,
+ calleeDeclarationClass = declarationClassBType,
+ safeToInline = safeToInline,
+ safeToRewrite = safeToRewrite,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ samParamTypes = samParamTypes,
+ calleeInfoWarning = warning)
+ }
- case _ => false
+ val argInfos = computeArgInfos(callee, call, prodCons)
+
+ val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || {
+ val numArgs = Type.getArgumentTypes(call.desc).length
+ receiverNotNullByAnalysis(call, numArgs)
+ }
+
+ methodCallsites += call -> Callsite(
+ callsiteInstruction = call,
+ callsiteMethod = methodNode,
+ callsiteClass = definingClass,
+ callee = callee,
+ argInfos = argInfos,
+ callsiteStackHeight = a.frameAt(call).getStackSize,
+ receiverKnownNotNull = receiverNotNull,
+ callsitePosition = callsitePositions.getOrElse(call, NoPosition)
+ )
+
+ case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null =>
+ val lmf = LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)
+ val capturedArgInfos = computeCapturedArgInfos(lmf, prodCons)
+ methodClosureInstantiations += indy -> ClosureInstantiation(
+ lmf,
+ methodNode,
+ definingClass,
+ capturedArgInfos)
+
+ case _ =>
+ }
+
+ callsites(methodNode) = methodCallsites
+ closureInstantiations(methodNode) = methodClosureInstantiations
}
+ }
- val callsites = new collection.mutable.ListBuffer[Callsite]
- val closureInstantiations = new collection.mutable.ListBuffer[LambdaMetaFactoryCall]
-
- methodNode.instructions.iterator.asScala foreach {
- case call: MethodInsnNode =>
- 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, safeToRewrite, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source)
- Callee(
- callee = method,
- calleeDeclarationClass = declarationClassBType,
- safeToInline = safeToInline,
- safeToRewrite = safeToRewrite,
- annotatedInline = annotatedInline,
- annotatedNoInline = annotatedNoInline,
- calleeInfoWarning = warning)
- }
+ def computeArgInfos(callee: Either[OptimizerWarning, Callee], callsiteInsn: MethodInsnNode, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = {
+ if (callee.isLeft) IntMap.empty
+ else {
+ lazy val numArgs = Type.getArgumentTypes(callsiteInsn.desc).length + (if (callsiteInsn.getOpcode == Opcodes.INVOKESTATIC) 0 else 1)
+ argInfosForSams(callee.get.samParamTypes, callsiteInsn, numArgs, prodCons)
+ }
+ }
- 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
- // new duplicated callsites, see Inliner.inline
- Nil
- }
+ def computeCapturedArgInfos(lmf: LambdaMetaFactoryCall, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = {
+ val capturedSams = capturedSamTypes(lmf)
+ val numCaptures = Type.getArgumentTypes(lmf.indy.desc).length
+ argInfosForSams(capturedSams, lmf.indy, numCaptures, prodCons)
+ }
- val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || {
- val numArgs = Type.getArgumentTypes(call.desc).length
- receiverNotNullByAnalysis(call, numArgs)
+ private def argInfosForSams(sams: IntMap[ClassBType], consumerInsn: AbstractInsnNode, numConsumed: => Int, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = {
+ // TODO: use type analysis instead of ProdCons - should be more efficient
+ // some random thoughts:
+ // - assign special types to parameters and indy-lambda-functions to track them
+ // - upcast should not change type flow analysis: don't lose information.
+ // - can we do something about factory calls? Foo(x) for case class foo gives a Foo.
+ // inline the factory? analysis across method boundary?
+
+ // assign to a lazy val to prevent repeated evaluation of the by-name arg
+ lazy val prodConsI = prodCons
+ lazy val firstConsumedSlot = {
+ val consumerFrame = prodConsI.frameAt(consumerInsn)
+ consumerFrame.stackTop - numConsumed + 1
+ }
+ sams flatMap {
+ case (index, _) =>
+ val prods = prodConsI.initialProducersForValueAt(consumerInsn, firstConsumedSlot + index)
+ if (prods.size != 1) None
+ else {
+ val argInfo = prods.head match {
+ case LambdaMetaFactoryCall(_, _, _, _) => Some(FunctionLiteral)
+ case ParameterProducer(local) => Some(ForwardedParam(local))
+ case _ => None
+ }
+ argInfo.map((index, _))
}
+ }
+ }
- callsites += Callsite(
- callsiteInstruction = call,
- callsiteMethod = methodNode,
- callsiteClass = definingClass,
- callee = callee,
- argInfos = argInfos,
- callsiteStackHeight = analyzer.frameAt(call, methodNode).getStackSize,
- receiverKnownNotNull = receiverNotNull,
- callsitePosition = callsitePositions.getOrElse(call, NoPosition)
- )
-
- case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) =>
- closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)
-
- case _ =>
+ def samParamTypes(methodNode: MethodNode, receiverType: ClassBType): IntMap[ClassBType] = {
+ val paramTypes = {
+ val params = Type.getMethodType(methodNode.desc).getArgumentTypes.map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor))
+ val isStatic = BytecodeUtils.isStaticMethod(methodNode)
+ if (isStatic) params else receiverType +: params
}
+ samTypes(paramTypes)
+ }
- (callsites.toList, closureInstantiations.toList)
+ def capturedSamTypes(lmf: LambdaMetaFactoryCall): IntMap[ClassBType] = {
+ val capturedTypes = Type.getArgumentTypes(lmf.indy.desc).map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor))
+ samTypes(capturedTypes)
+ }
+
+ private def samTypes(types: Array[BType]): IntMap[ClassBType] = {
+ var res = IntMap.empty[ClassBType]
+ for (i <- types.indices) {
+ types(i) match {
+ case c: ClassBType =>
+ if (c.info.get.inlineInfo.sam.isDefined) res = res.updated(i, c)
+
+ case _ =>
+ }
+ }
+ res
}
/**
+ * Just a named tuple used as return type of `analyzeCallsite`.
+ */
+ private case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
+ annotatedInline: Boolean, annotatedNoInline: Boolean,
+ samParamTypes: IntMap[ClassBType],
+ warning: Option[CalleeInfoWarning])
+
+ /**
+ * Analyze a callsite and gather meta-data that can be used for inlining decisions.
+ */
+ private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = {
+ val methodSignature = calleeMethodNode.name + calleeMethodNode.desc
+
+ 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 = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit
+
+ val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
+
+ val receiverType = classBTypeFromParsedClassfile(receiverTypeInternalName)
+ // (1) A non-final method can be safe to 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
+ //
+ // TODO: (1) doesn't cover the following example:
+ // trait TravLike { def map = ... }
+ // sealed trait List extends TravLike { ... } // assume map is not overridden
+ // final case class :: / final case object Nil
+ // (l: List).map // can be inlined
+ // we need to know that
+ // - the recevier is sealed
+ // - what are the children of the receiver
+ // - all children are final
+ // - none of the children overrides map
+ //
+ // 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.
+ val isStaticallyResolved: Boolean = {
+ methodInlineInfo.effectivelyFinal ||
+ receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1)
+ }
+
+ val isRewritableTraitCall = isStaticallyResolved && methodInlineInfo.traitMethodWithStaticImplementation
+
+ val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map(
+ MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _))
+
+ // (1) For invocations of final trait methods, the callee isStaticallyResolved but also
+ // abstract. Such a callee is not safe to inline - it needs to be re-written to the
+ // static impl method first (safeToRewrite).
+ // (2) Final trait methods can be rewritten from the interface to the static implementation
+ // method to enable inlining.
+ CallsiteInfo(
+ safeToInline =
+ canInlineFromSource &&
+ isStaticallyResolved && // (1)
+ !isAbstract &&
+ !BytecodeUtils.isConstructor(calleeMethodNode) &&
+ !BytecodeUtils.isNativeMethod(calleeMethodNode),
+ safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2)
+ annotatedInline = methodInlineInfo.annotatedInline,
+ annotatedNoInline = methodInlineInfo.annotatedNoInline,
+ samParamTypes = samParamTypes(calleeMethodNode, receiverType),
+ warning = warning)
+
+ case None =>
+ val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning)
+ CallsiteInfo(false, false, false, false, IntMap.empty, Some(warning))
+ }
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) =>
+ val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
+ CallsiteInfo(false, false, false, false, IntMap.empty, Some(warning))
+ }
+ }
+
+ /**
* A callsite in the call graph.
*
* @param callsiteInstruction The invocation instruction
@@ -197,7 +344,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* @param callsitePosition The source position of the callsite, used for inliner warnings.
*/
final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType,
- callee: Either[OptimizerWarning, Callee], argInfos: List[ArgInfo],
+ callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo],
callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position) {
override def toString =
"Invocation of" +
@@ -210,8 +357,9 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* Information about invocation arguments, obtained through data flow analysis of the callsite method.
*/
sealed trait ArgInfo
- final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo
+ case object FunctionLiteral extends ArgInfo
final case class ForwardedParam(index: Int) extends ArgInfo
+ // final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo
// can be extended, e.g., with constant types
/**
@@ -227,17 +375,29 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* that can be safely re-written to the static implementation method.
* @param annotatedInline True if the callee is annotated @inline
* @param annotatedNoInline True if the callee is annotated @noinline
+ * @param samParamTypes A map from parameter positions to SAM parameter types
* @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, safeToRewrite: Boolean,
annotatedInline: Boolean, annotatedNoInline: Boolean,
+ samParamTypes: IntMap[ClassBType],
calleeInfoWarning: Option[CalleeInfoWarning]) {
assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.")
+ override def toString = s"Callee($calleeDeclarationClass.${callee.name})"
}
- final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType) {
+ /**
+ * Metadata about a closure instantiation, stored in the call graph
+ *
+ * @param lambdaMetaFactoryCall the InvokeDynamic instruction
+ * @param ownerMethod the method where the closure is allocated
+ * @param ownerClass the class containing the above method
+ * @param capturedArgInfos information about captured arguments. Used for updating the call
+ * graph when re-writing a closure invocation to the body method.
+ */
+ final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType, capturedArgInfos: IntMap[ArgInfo]) {
override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)"
}
final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
index b0dc6ead1b..fb7dd16909 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
@@ -9,11 +9,11 @@ package opt
import scala.annotation.switch
import scala.collection.immutable
+import scala.collection.immutable.IntMap
import scala.reflect.internal.util.NoPosition
import scala.tools.asm.{Type, Opcodes}
import scala.tools.asm.tree._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
-import scala.tools.nsc.backend.jvm.analysis.ProdConsAnalyzer
import BytecodeUtils._
import BackendReporting._
import Opcodes._
@@ -23,6 +23,7 @@ import scala.collection.convert.decorateAsScala._
class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
import btypes._
import callGraph._
+ import analyzers._
/**
* If a closure is allocated and invoked within the same method, re-write the invocation to the
@@ -70,24 +71,19 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
}
}
- // Grouping the closure instantiations by method allows running the ProdConsAnalyzer only once per
- // method. Also sort the instantiations: If there are multiple closure instantiations in a method,
- // closure invocations need to be re-written in a consistent order for bytecode stability. The local
- // variable slots for storing captured values depends on the order of rewriting.
- val closureInstantiationsByMethod: Map[MethodNode, immutable.TreeSet[ClosureInstantiation]] = {
- closureInstantiations.values.groupBy(_.ownerMethod).mapValues(immutable.TreeSet.empty ++ _)
- }
-
// For each closure instantiation, a list of callsites of the closure that can be re-written
// If a callsite cannot be rewritten, for example because the lambda body method is not accessible,
// a warning is returned instead.
val callsitesToRewrite: List[(ClosureInstantiation, List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]])] = {
- closureInstantiationsByMethod.iterator.flatMap({
+ closureInstantiations.iterator.flatMap({
case (methodNode, closureInits) =>
// A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`)
- lazy val prodCons = new ProdConsAnalyzer(methodNode, closureInits.head.ownerClass.internalName)
- closureInits.iterator.map(init => (init, closureCallsites(init, prodCons)))
- }).toList // mapping to a list (not a map) to keep the sorting of closureInstantiationsByMethod
+ // We don't need to worry about the method being too large for running an analysis: large
+ // methods are not added to the call graph / closureInstantiations map.
+ lazy val prodCons = new ProdConsAnalyzer(methodNode, closureInits.valuesIterator.next().ownerClass.internalName)
+ val sortedInits = immutable.TreeSet.empty ++ closureInits.values
+ sortedInits.iterator.map(init => (init, closureCallsites(init, prodCons))).filter(_._2.nonEmpty)
+ }).toList // mapping to a list (not a map) to keep the sorting
}
// Rewrite all closure callsites (or issue inliner warnings for those that cannot be rewritten)
@@ -162,7 +158,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
isAccessible
}
- def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition)
+ def pos = callGraph.callsites(ownerMethod).get(invocation).map(_.callsitePosition).getOrElse(NoPosition)
val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match {
case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w))
case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName))
@@ -210,8 +206,9 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
insertLoadOps(invocation, ownerMethod, argumentLocalsList)
// update maxStack
- val capturesStackSize = localsForCapturedValues.size
- val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone
+ // One slot per value is correct for long / double, see comment in the `analysis` package object.
+ val numCapturedValues = localsForCapturedValues.locals.length
+ val invocationStackHeight = stackHeight + numCapturedValues - 1 // -1 because the closure is gone
if (invocationStackHeight > ownerMethod.maxStack)
ownerMethod.maxStack = invocationStackHeight
@@ -237,26 +234,33 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
ownerMethod.instructions.remove(invocation)
// update the call graph
- val originalCallsite = callGraph.callsites.remove(invocation)
+ val originalCallsite = callGraph.removeCallsite(invocation, ownerMethod)
// the method node is needed for building the call graph entry
val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc)
def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false)
- val bodyMethodCallsite = Callsite(
- callsiteInstruction = bodyInvocation,
- callsiteMethod = ownerMethod,
- callsiteClass = closureInit.ownerClass,
- callee = bodyMethod.map({
- case (bodyMethodNode, bodyMethodDeclClass) => Callee(
+ val callee = bodyMethod.map({
+ case (bodyMethodNode, bodyMethodDeclClass) =>
+ val bodyDeclClassType = classBTypeFromParsedClassfile(bodyMethodDeclClass)
+ Callee(
callee = bodyMethodNode,
- calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass),
+ calleeDeclarationClass = bodyDeclClassType,
safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled,
safeToRewrite = false, // the lambda body method is not a trait interface method
annotatedInline = false,
annotatedNoInline = false,
+ samParamTypes = callGraph.samParamTypes(bodyMethodNode, bodyDeclClassType),
calleeInfoWarning = None)
- }),
- argInfos = Nil,
+ })
+ val argInfos = closureInit.capturedArgInfos ++ originalCallsite.map(cs => cs.argInfos map {
+ case (index, info) => (index + numCapturedValues, info)
+ }).getOrElse(IntMap.empty)
+ val bodyMethodCallsite = Callsite(
+ callsiteInstruction = bodyInvocation,
+ callsiteMethod = ownerMethod,
+ callsiteClass = closureInit.ownerClass,
+ callee = callee,
+ argInfos = argInfos,
callsiteStackHeight = invocationStackHeight,
receiverKnownNotNull = true, // see below (*)
callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition)
@@ -266,7 +270,11 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
// (corresponding to the receiver) must be non-null"
// Explanation: If the lambda body method is non-static, the receiver is a captured
// value. It can only be captured within some instance method, so we know it's non-null.
- callGraph.callsites(bodyInvocation) = bodyMethodCallsite
+ callGraph.addCallsite(bodyMethodCallsite)
+
+ // Rewriting a closure invocation may render code unreachable. For example, the body method of
+ // (x: T) => ??? has return type Nothing$, and an ATHROW is added (see fixLoadedNothingOrNullValue).
+ unreachableCodeEliminated -= ownerMethod
}
/**
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 e7dd5abc57..c885a29e16 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -47,15 +47,22 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
result.putByte(InlineInfoAttribute.VERSION)
- var hasSelfIsFinal = 0
- if (inlineInfo.isEffectivelyFinal) hasSelfIsFinal |= 1
- if (inlineInfo.traitImplClassSelfType.isDefined) hasSelfIsFinal |= 2
- result.putByte(hasSelfIsFinal)
+ var finalSelfSam = 0
+ if (inlineInfo.isEffectivelyFinal) finalSelfSam |= 1
+ if (inlineInfo.traitImplClassSelfType.isDefined) finalSelfSam |= 2
+ if (inlineInfo.sam.isDefined) finalSelfSam |= 4
+ result.putByte(finalSelfSam)
for (selfInternalName <- inlineInfo.traitImplClassSelfType) {
result.putShort(cw.newUTF8(selfInternalName))
}
+ for (samNameDesc <- inlineInfo.sam) {
+ val (name, desc) = samNameDesc.span(_ != '(')
+ result.putShort(cw.newUTF8(name))
+ result.putShort(cw.newUTF8(desc))
+ }
+
// The method count fits in a short (the methods_count in a classfile is also a short)
result.putShort(inlineInfo.methodInfos.size)
@@ -94,15 +101,20 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
val version = nextByte()
if (version == 1) {
- val hasSelfIsFinal = nextByte()
- val isFinal = (hasSelfIsFinal & 1) != 0
- val hasSelf = (hasSelfIsFinal & 2) != 0
+ val finalSelfSam = nextByte()
+ val isFinal = (finalSelfSam & 1) != 0
+ val hasSelf = (finalSelfSam & 2) != 0
+ val hasSam = (finalSelfSam & 4) != 0
- val self = if (hasSelf) {
+ val self = if (!hasSelf) None else {
val selfName = nextUTF8()
Some(selfName)
- } else {
- None
+ }
+
+ val sam = if (!hasSam) None else {
+ val name = nextUTF8()
+ val desc = nextUTF8()
+ Some(name + desc)
}
val numEntries = nextShort()
@@ -118,7 +130,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
(name + desc, MethodInlineInfo(isFinal, traitMethodWithStaticImplementation, isInline, isNoInline))
}).toMap
- InlineInfoAttribute(InlineInfo(self, isFinal, infos, None))
+ InlineInfoAttribute(InlineInfo(self, isFinal, sam, infos, None))
} else {
val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg)))
@@ -129,8 +141,10 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
object InlineInfoAttribute {
/**
* [u1] version
- * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1)
+ * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1), hasSam (<< 2)
* [u2]? traitImplClassSelfType (reference)
+ * [u2]? samName (reference)
+ * [u2]? samDescriptor (reference)
* [u2] numMethodEntries
* [u2] name (reference)
* [u2] descriptor (reference)
@@ -145,4 +159,4 @@ object InlineInfoAttribute {
* In order to instruct the ASM framework to de-serialize the ScalaInlineInfo attribute, we need
* to pass a prototype instance when running the class reader.
*/
-object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(null, false, null, null))
+object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(null, false, null, null, null))
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 6b2786c1a3..baa747492f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -9,7 +9,6 @@ package opt
import scala.annotation.tailrec
import scala.tools.asm
-import asm.Handle
import asm.Opcodes._
import asm.tree._
import scala.collection.convert.decorateAsScala._
@@ -17,18 +16,20 @@ import scala.collection.convert.decorateAsJava._
import AsmUtils._
import BytecodeUtils._
import collection.mutable
-import scala.tools.asm.tree.analysis.SourceInterpreter
+import scala.tools.asm.tree.analysis.{Analyzer, SourceInterpreter}
import BackendReporting._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
class Inliner[BT <: BTypes](val btypes: BT) {
import btypes._
import callGraph._
+ import inlinerHeuristics._
+ import analyzers._
def eliminateUnreachableCodeAndUpdateCallGraph(methodNode: MethodNode, definingClass: InternalName): Unit = {
localOpt.minimalRemoveUnreachableCode(methodNode, definingClass) foreach {
- case invocation: MethodInsnNode => callGraph.callsites.remove(invocation)
- case indy: InvokeDynamicInsnNode => callGraph.closureInstantiations.remove(indy)
+ case invocation: MethodInsnNode => callGraph.removeCallsite(invocation, methodNode)
+ case indy: InvokeDynamicInsnNode => callGraph.removeClosureInstantiation(indy, methodNode)
case _ =>
}
}
@@ -37,7 +38,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
rewriteFinalTraitMethodInvocations()
for (request <- collectAndOrderInlineRequests) {
- val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee
+ val callsite = request.callsite
+ val Right(callee) = callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee
// Inlining a method can create unreachable code. Example:
// def f = throw e
@@ -48,16 +50,14 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// DCE above removes unreachable callsites from the call graph. If the inlining request denotes
// such an eliminated callsite, do nothing.
- if (callGraph.callsites contains request.callsiteInstruction) {
- val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass,
- callee.callee, callee.calleeDeclarationClass,
- request.receiverKnownNotNull, keepLineNumbers = false)
+ if (callGraph.callsites(callsite.callsiteMethod).contains(callsite.callsiteInstruction)) {
+ val warnings = inline(request)
- for (warning <- r) {
+ for (warning <- warnings) {
if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) {
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)
+ backendReporting.inlinerWarning(callsite.callsitePosition, msg)
}
}
}
@@ -69,81 +69,36 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* - Always remove the same request when breaking inlining cycles
* - Perform inlinings in a consistent order
*/
- object callsiteOrdering extends Ordering[Callsite] {
- override def compare(x: Callsite, y: Callsite): Int = {
- val cls = x.callsiteClass.internalName compareTo y.callsiteClass.internalName
+ object callsiteOrdering extends Ordering[InlineRequest] {
+ override def compare(x: InlineRequest, y: InlineRequest): Int = {
+ val xCs = x.callsite
+ val yCs = y.callsite
+ val cls = xCs.callsiteClass.internalName compareTo yCs.callsiteClass.internalName
if (cls != 0) return cls
- val name = x.callsiteMethod.name compareTo y.callsiteMethod.name
+ val name = xCs.callsiteMethod.name compareTo yCs.callsiteMethod.name
if (name != 0) return name
- val desc = x.callsiteMethod.desc compareTo y.callsiteMethod.desc
+ val desc = xCs.callsiteMethod.desc compareTo yCs.callsiteMethod.desc
if (desc != 0) return desc
def pos(c: Callsite) = c.callsiteMethod.instructions.indexOf(c.callsiteInstruction)
- pos(x) - pos(y)
+ pos(xCs) - pos(yCs)
}
}
- /**
- * Select callsites from the call graph that should be inlined. The resulting list of inlining
- * requests is allowed to have cycles, and the callsites can appear in any order.
- */
- def selectCallsitesForInlining: List[Callsite] = {
- callsites.valuesIterator.filter({
- case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, _, pos) =>
- val res = doInlineCallsite(callsite)
-
- if (!res) {
- if (annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) {
- // 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 (doRewriteTraitCallsite(callsite))
- backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg)
- else if (!safeToInline)
- backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg)
- else
- backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg)
- } else if (warning.isDefined && warning.get.emitWarning(compilerSettings)) {
- // 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(compilerSettings))
- 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) =>
- if (compilerSettings.YoptInlineHeuristics.value == "everything") safeToInline
- else annotatedInline && safeToInline
-
- 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
// map. Therefore we first copy the values to a list.
- callsites.values.toList.foreach(rewriteFinalTraitMethodInvocation)
+ callsites.valuesIterator.flatMap(_.valuesIterator).toList.foreach(rewriteFinalTraitMethodInvocation)
}
/**
* 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, safeToInline, true, annotatedInline, annotatedNoInline, infoWarning)) => true
+ case Right(Callee(_, _, _, safeToRewrite, _, _, _, _)) => safeToRewrite
case _ => false
}
@@ -156,7 +111,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
*/
def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = {
if (doRewriteTraitCallsite(callsite)) {
- val Right(Callee(callee, calleeDeclarationClass, _, _, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee
+ val Right(Callee(callee, calleeDeclarationClass, _, _, annotatedInline, annotatedNoInline, samParamTypes, infoWarning)) = callsite.callee
val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc)
@@ -188,9 +143,10 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// 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 (!selfTypeOk) {
- // there's no need to run eliminateUnreachableCode here. building the call graph does that
- // already, no code can become unreachable in the meantime.
- val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter)
+ // We don't need to worry about the method being too large for running an analysis.
+ // Callsites of large methods are not added to the call graph.
+ localOpt.minimalRemoveUnreachableCode(callsite.callsiteMethod, callsite.callsiteClass.internalName)
+ val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new Analyzer(new SourceInterpreter))
val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekStack(traitMethodArgumentTypes.length)
for (i <- receiverValue.insns.asScala) {
val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName)
@@ -202,7 +158,11 @@ class Inliner[BT <: BTypes](val btypes: BT) {
callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction)
callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction)
- callGraph.callsites.remove(callsite.callsiteInstruction)
+ callGraph.removeCallsite(callsite.callsiteInstruction, callsite.callsiteMethod)
+ val staticCallSamParamTypes = {
+ if (selfParamType.info.get.inlineInfo.sam.isEmpty) samParamTypes - 0
+ else samParamTypes.updated(0, selfParamType)
+ }
val staticCallsite = Callsite(
callsiteInstruction = newCallsiteInstruction,
callsiteMethod = callsite.callsiteMethod,
@@ -214,19 +174,20 @@ class Inliner[BT <: BTypes](val btypes: BT) {
safeToRewrite = false,
annotatedInline = annotatedInline,
annotatedNoInline = annotatedNoInline,
+ samParamTypes = staticCallSamParamTypes,
calleeInfoWarning = infoWarning)),
- argInfos = Nil,
+ argInfos = callsite.argInfos,
callsiteStackHeight = callsite.callsiteStackHeight,
receiverKnownNotNull = callsite.receiverKnownNotNull,
callsitePosition = callsite.callsitePosition
)
- callGraph.callsites(newCallsiteInstruction) = staticCallsite
+ callGraph.addCallsite(staticCallsite)
}
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))
+ callGraph.addCallsite(callsite.copy(callee = Right(newCallee)))
}
}
}
@@ -238,15 +199,11 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* The resulting list is sorted such that the leaves of the inline request graph are on the left.
* Once these leaves are inlined, the successive elements will be leaves, etc.
*/
- private def collectAndOrderInlineRequests: List[Callsite] = {
- val requests = selectCallsitesForInlining
+ private def collectAndOrderInlineRequests: List[InlineRequest] = {
+ val requestsByMethod = selectCallsitesForInlining withDefaultValue Set.empty
- // This map is an index to look up the inlining requests for a method. The value sets are mutable
- // to allow removing elided requests (to break inlining cycles). The map itself is mutable to
- // allow efficient building: requests.groupBy would build values as List[Callsite] that need to
- // be transformed to mutable sets.
- val inlineRequestsForMethod: mutable.Map[MethodNode, mutable.Set[Callsite]] = mutable.HashMap.empty.withDefaultValue(mutable.HashSet.empty)
- for (r <- requests) inlineRequestsForMethod.getOrElseUpdate(r.callsiteMethod, mutable.HashSet.empty) += r
+ val elided = mutable.Set.empty[InlineRequest]
+ def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided
/**
* Break cycles in the inline request graph by removing callsites.
@@ -254,7 +211,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* The list `requests` is traversed left-to-right, removing those callsites that are part of a
* cycle. Elided callsites are also removed from the `inlineRequestsForMethod` map.
*/
- def breakInlineCycles(requests: List[Callsite]): List[Callsite] = {
+ def breakInlineCycles: List[InlineRequest] = {
// is there a path of inline requests from start to goal?
def isReachable(start: MethodNode, goal: MethodNode): Boolean = {
@tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match {
@@ -262,7 +219,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
if (x == goal) true
else if (visited(x)) reachableImpl(xs, visited)
else {
- val callees = inlineRequestsForMethod(x).map(_.callee.get.callee)
+ val callees = nonElidedRequests(x).map(_.callsite.callee.get.callee)
reachableImpl(xs ::: callees.toList, visited + x)
}
@@ -272,12 +229,14 @@ class Inliner[BT <: BTypes](val btypes: BT) {
reachableImpl(List(start), Set.empty)
}
- val result = new mutable.ListBuffer[Callsite]()
+ val result = new mutable.ListBuffer[InlineRequest]()
+ val requests = requestsByMethod.valuesIterator.flatten.toArray
// sort the inline requests to ensure that removing requests is deterministic
- for (r <- requests.sorted(callsiteOrdering)) {
+ java.util.Arrays.sort(requests, callsiteOrdering)
+ for (r <- requests) {
// is there a chain of inlining requests that would inline the callsite method into the callee?
- if (isReachable(r.callee.get.callee, r.callsiteMethod))
- inlineRequestsForMethod(r.callsiteMethod) -= r
+ if (isReachable(r.callsite.callee.get.callee, r.callsite.callsiteMethod))
+ elided += r
else
result += r
}
@@ -286,11 +245,11 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// sort the remaining inline requests such that the leaves appear first, then those requests
// that become leaves, etc.
- def leavesFirst(requests: List[Callsite], visited: Set[Callsite] = Set.empty): List[Callsite] = {
+ def leavesFirst(requests: List[InlineRequest], visited: Set[InlineRequest] = Set.empty): List[InlineRequest] = {
if (requests.isEmpty) Nil
else {
val (leaves, others) = requests.partition(r => {
- val inlineRequestsForCallee = inlineRequestsForMethod(r.callee.get.callee)
+ val inlineRequestsForCallee = nonElidedRequests(r.callsite.callee.get.callee)
inlineRequestsForCallee.forall(visited)
})
assert(leaves.nonEmpty, requests)
@@ -298,192 +257,232 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
}
- leavesFirst(breakInlineCycles(requests))
+ leavesFirst(breakInlineCycles)
}
+ /**
+ * Inline the callsites of an inlining request and its post-inlining requests.
+ *
+ * @return An inliner warning for each callsite that could not be inlined.
+ */
+ def inline(request: InlineRequest): List[CannotInlineWarning] = canInline(request.callsite) match {
+ case Some(warning) => List(warning)
+ case None =>
+ val instructionsMap = inlineCallsite(request.callsite)
+ val postRequests = request.post.flatMap(post => {
+ // the post-request invocation instruction might not exist anymore: it might have been
+ // inlined itself, or eliminated by DCE.
+ for {
+ inlinedInvocationInstr <- instructionsMap.get(post.callsiteInstruction).map(_.asInstanceOf[MethodInsnNode])
+ inlinedCallsite <- callGraph.callsites(request.callsite.callsiteMethod).get(inlinedInvocationInstr)
+ } yield InlineRequest(inlinedCallsite, post.post)
+ })
+ postRequests flatMap inline
+ }
/**
* Copy and adapt the instructions of a method to a callsite.
*
* Preconditions:
+ * - The callsite can safely be inlined (canInline is true)
* - The maxLocals and maxStack values of the callsite method are correctly computed
- * - The callsite method contains no unreachable basic blocks, i.e., running an [[Analyzer]]
- * does not produce any `null` frames
+ * - The callsite method contains no unreachable basic blocks, i.e., running an Analyzer does
+ * not produce any `null` frames
*
- * @param callsiteInstruction The invocation instruction
- * @param callsiteStackHeight The stack height at the callsite
- * @param callsiteMethod The method in which the invocation occurs
- * @param callsiteClass The class in which the callsite method is defined
- * @param callee The invoked method
- * @param calleeDeclarationClass The class in which the invoked method is defined
- * @param receiverKnownNotNull `true` if the receiver is known to be non-null
- * @param keepLineNumbers `true` if LineNumberNodes should be copied to the call site
- * @return `Some(message)` if inlining cannot be performed, `None` otherwise
+ * @return A map associating instruction nodes of the callee with the corresponding cloned
+ * instruction in the callsite method.
*/
- def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType,
- callee: MethodNode, calleeDeclarationClass: ClassBType,
- receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[CannotInlineWarning] = {
- canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse {
- // New labels for the cloned instructions
- val labelsMap = cloneLabels(callee)
- val (clonedInstructions, instructionMap) = cloneInstructions(callee, labelsMap)
- if (!keepLineNumbers) {
- removeLineNumberNodes(clonedInstructions)
- }
+ def inlineCallsite(callsite: Callsite): Map[AbstractInsnNode, AbstractInsnNode] = {
+ import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass}
+
+ // New labels for the cloned instructions
+ val labelsMap = cloneLabels(callee)
+ val (clonedInstructions, instructionMap) = cloneInstructions(callee, labelsMap)
+ val keepLineNumbers = callsiteClass == calleeDeclarationClass
+ if (!keepLineNumbers) {
+ removeLineNumberNodes(clonedInstructions)
+ }
- // local vars in the callee are shifted by the number of locals at the callsite
- val localVarShift = callsiteMethod.maxLocals
- clonedInstructions.iterator.asScala foreach {
- case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift
- case iinc: IincInsnNode => iinc.`var` += localVarShift
- case _ => ()
- }
+ // local vars in the callee are shifted by the number of locals at the callsite
+ val localVarShift = callsiteMethod.maxLocals
+ clonedInstructions.iterator.asScala foreach {
+ case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift
+ case iinc: IincInsnNode => iinc.`var` += localVarShift
+ case _ => ()
+ }
- // add a STORE instruction for each expected argument, including for THIS instance if any
- val argStores = new InsnList
- var nextLocalIndex = callsiteMethod.maxLocals
- if (!isStaticMethod(callee)) {
- if (!receiverKnownNotNull) {
- argStores.add(new InsnNode(DUP))
- val nonNullLabel = newLabelNode
- argStores.add(new JumpInsnNode(IFNONNULL, nonNullLabel))
- argStores.add(new InsnNode(ACONST_NULL))
- argStores.add(new InsnNode(ATHROW))
- argStores.add(nonNullLabel)
- }
- argStores.add(new VarInsnNode(ASTORE, nextLocalIndex))
- nextLocalIndex += 1
+ // add a STORE instruction for each expected argument, including for THIS instance if any
+ val argStores = new InsnList
+ var nextLocalIndex = callsiteMethod.maxLocals
+ if (!isStaticMethod(callee)) {
+ if (!receiverKnownNotNull) {
+ argStores.add(new InsnNode(DUP))
+ val nonNullLabel = newLabelNode
+ argStores.add(new JumpInsnNode(IFNONNULL, nonNullLabel))
+ argStores.add(new InsnNode(ACONST_NULL))
+ argStores.add(new InsnNode(ATHROW))
+ argStores.add(nonNullLabel)
}
+ argStores.add(new VarInsnNode(ASTORE, nextLocalIndex))
+ nextLocalIndex += 1
+ }
- // We just use an asm.Type here, no need to create the MethodBType.
- val calleAsmType = asm.Type.getMethodType(callee.desc)
+ // We just use an asm.Type here, no need to create the MethodBType.
+ val calleAsmType = asm.Type.getMethodType(callee.desc)
+ val calleeParamTypes = calleAsmType.getArgumentTypes
- for(argTp <- calleAsmType.getArgumentTypes) {
- val opc = argTp.getOpcode(ISTORE) // returns the correct xSTORE instruction for argTp
- argStores.insert(new VarInsnNode(opc, nextLocalIndex)) // "insert" is "prepend" - the last argument is on the top of the stack
- nextLocalIndex += argTp.getSize
- }
+ for(argTp <- calleeParamTypes) {
+ val opc = argTp.getOpcode(ISTORE) // returns the correct xSTORE instruction for argTp
+ argStores.insert(new VarInsnNode(opc, nextLocalIndex)) // "insert" is "prepend" - the last argument is on the top of the stack
+ nextLocalIndex += argTp.getSize
+ }
- clonedInstructions.insert(argStores)
-
- // label for the exit of the inlined functions. xRETURNs are replaced by GOTOs to this label.
- val postCallLabel = newLabelNode
- clonedInstructions.add(postCallLabel)
-
- // replace xRETURNs:
- // - store the return value (if any)
- // - clear the stack of the inlined method (insert DROPs)
- // - load the return value
- // - GOTO postCallLabel
-
- val returnType = calleAsmType.getReturnType
- val hasReturnValue = returnType.getSort != asm.Type.VOID
- val returnValueIndex = callsiteMethod.maxLocals + callee.maxLocals
- nextLocalIndex += returnType.getSize
-
- def returnValueStore(returnInstruction: AbstractInsnNode) = {
- val opc = returnInstruction.getOpcode match {
- case IRETURN => ISTORE
- case LRETURN => LSTORE
- case FRETURN => FSTORE
- case DRETURN => DSTORE
- case ARETURN => ASTORE
- }
- new VarInsnNode(opc, returnValueIndex)
+ clonedInstructions.insert(argStores)
+
+ // label for the exit of the inlined functions. xRETURNs are replaced by GOTOs to this label.
+ val postCallLabel = newLabelNode
+ clonedInstructions.add(postCallLabel)
+
+ // replace xRETURNs:
+ // - store the return value (if any)
+ // - clear the stack of the inlined method (insert DROPs)
+ // - load the return value
+ // - GOTO postCallLabel
+
+ val returnType = calleAsmType.getReturnType
+ val hasReturnValue = returnType.getSort != asm.Type.VOID
+ val returnValueIndex = callsiteMethod.maxLocals + callee.maxLocals
+ nextLocalIndex += returnType.getSize
+
+ def returnValueStore(returnInstruction: AbstractInsnNode) = {
+ val opc = returnInstruction.getOpcode match {
+ case IRETURN => ISTORE
+ case LRETURN => LSTORE
+ case FRETURN => FSTORE
+ case DRETURN => DSTORE
+ case ARETURN => ASTORE
}
+ new VarInsnNode(opc, returnValueIndex)
+ }
- // We run an interpreter to know the stack height at each xRETURN instruction and the sizes
- // of the values on the stack.
- val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName)
+ // We run an interpreter to know the stack height at each xRETURN instruction and the sizes
+ // of the values on the stack.
+ // We don't need to worry about the method being too large for running an analysis. Callsites of
+ // large methods are not added to the call graph.
+ val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName)
- for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
- val frame = analyzer.frameAt(originalReturn)
- var stackHeight = frame.getStackSize
+ for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
+ val frame = analyzer.frameAt(originalReturn)
+ var stackHeight = frame.getStackSize
- val inlinedReturn = instructionMap(originalReturn)
- val returnReplacement = new InsnList
+ val inlinedReturn = instructionMap(originalReturn)
+ val returnReplacement = new InsnList
- def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize)
+ def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize)
- // for non-void methods, store the stack top into the return local variable
- if (hasReturnValue) {
- returnReplacement add returnValueStore(originalReturn)
- stackHeight -= 1
- }
+ // for non-void methods, store the stack top into the return local variable
+ if (hasReturnValue) {
+ returnReplacement add returnValueStore(originalReturn)
+ stackHeight -= 1
+ }
- // drop the rest of the stack
- for (i <- 0 until stackHeight) drop(i)
+ // drop the rest of the stack
+ for (i <- 0 until stackHeight) drop(i)
- returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
- clonedInstructions.insert(inlinedReturn, returnReplacement)
- clonedInstructions.remove(inlinedReturn)
- }
+ returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
+ clonedInstructions.insert(inlinedReturn, returnReplacement)
+ clonedInstructions.remove(inlinedReturn)
+ }
- // Load instruction for the return value
- if (hasReturnValue) {
- val retVarLoad = {
- val opc = returnType.getOpcode(ILOAD)
- new VarInsnNode(opc, returnValueIndex)
- }
- clonedInstructions.insert(postCallLabel, retVarLoad)
+ // Load instruction for the return value
+ if (hasReturnValue) {
+ val retVarLoad = {
+ val opc = returnType.getOpcode(ILOAD)
+ new VarInsnNode(opc, returnValueIndex)
}
+ clonedInstructions.insert(postCallLabel, retVarLoad)
+ }
- callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions)
- callsiteMethod.instructions.remove(callsiteInstruction)
-
- callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_").asJava)
- callsiteMethod.tryCatchBlocks.addAll(cloneTryCatchBlockNodes(callee, labelsMap).asJava)
-
- // Add all invocation instructions and closure instantiations that were inlined to the call graph
- callee.instructions.iterator().asScala foreach {
- case originalCallsiteIns: MethodInsnNode =>
- callGraph.callsites.get(originalCallsiteIns) match {
- case Some(originalCallsite) =>
- val newCallsiteIns = instructionMap(originalCallsiteIns).asInstanceOf[MethodInsnNode]
- callGraph.callsites(newCallsiteIns) = Callsite(
- callsiteInstruction = newCallsiteIns,
- callsiteMethod = callsiteMethod,
- callsiteClass = callsiteClass,
- callee = originalCallsite.callee,
- argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them)
- callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight,
- receiverKnownNotNull = originalCallsite.receiverKnownNotNull,
- callsitePosition = originalCallsite.callsitePosition
- )
-
- case None =>
- }
+ callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions)
+ callsiteMethod.instructions.remove(callsiteInstruction)
+
+ callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_", localVarShift).asJava)
+ // prepend the handlers of the callee. the order of handlers matters: when an exception is thrown
+ // at some instruction, the first handler guarding that instruction and having a matching exception
+ // type is executed. prepending the callee's handlers makes sure to test those handlers first if
+ // an exception is thrown in the inlined code.
+ callsiteMethod.tryCatchBlocks.addAll(0, cloneTryCatchBlockNodes(callee, labelsMap).asJava)
+
+ callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals
+ val maxStackOfInlinedCode = {
+ // One slot per value is correct for long / double, see comment in the `analysis` package object.
+ val numStoredArgs = calleeParamTypes.length + (if (isStaticMethod(callee)) 0 else 1)
+ callee.maxStack + callsiteStackHeight - numStoredArgs
+ }
+ val stackHeightAtNullCheck = {
+ // When adding a null check for the receiver, a DUP is inserted, which might cause a new maxStack.
+ // If the callsite has other argument values than the receiver on the stack, these are pop'ed
+ // and stored into locals before the null check, so in that case the maxStack doesn't grow.
+ val stackSlotForNullCheck = if (!isStaticMethod(callee) && !receiverKnownNotNull && calleeParamTypes.isEmpty) 1 else 0
+ callsiteStackHeight + stackSlotForNullCheck
+ }
- case indy: InvokeDynamicInsnNode =>
- callGraph.closureInstantiations.get(indy) match {
- case Some(closureInit) =>
- val newIndy = instructionMap(indy).asInstanceOf[InvokeDynamicInsnNode]
- callGraph.closureInstantiations(newIndy) = ClosureInstantiation(closureInit.lambdaMetaFactoryCall.copy(indy = newIndy), callsiteMethod, callsiteClass)
+ callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode))
- case None =>
- }
+ callGraph.addIfMissing(callee, calleeDeclarationClass)
- case _ =>
- }
- // Remove the elided invocation from the call graph
- callGraph.callsites.remove(callsiteInstruction)
-
- // Inlining a method body can render some code unreachable, see example above (in runInliner).
- unreachableCodeEliminated -= callsiteMethod
+ def mapArgInfo(argInfo: (Int, ArgInfo)): Option[(Int, ArgInfo)] = argInfo match {
+ case lit @ (_, FunctionLiteral) => Some(lit)
+ case (argIndex, ForwardedParam(paramIndex)) => callsite.argInfos.get(paramIndex).map((argIndex, _))
+ }
- callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals
- callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight)
+ // Add all invocation instructions and closure instantiations that were inlined to the call graph
+ callGraph.callsites(callee).valuesIterator foreach { originalCallsite =>
+ val newCallsiteIns = instructionMap(originalCallsite.callsiteInstruction).asInstanceOf[MethodInsnNode]
+ val argInfos = originalCallsite.argInfos flatMap mapArgInfo
+ callGraph.addCallsite(Callsite(
+ callsiteInstruction = newCallsiteIns,
+ callsiteMethod = callsiteMethod,
+ callsiteClass = callsiteClass,
+ callee = originalCallsite.callee,
+ argInfos = argInfos,
+ callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight,
+ receiverKnownNotNull = originalCallsite.receiverKnownNotNull,
+ callsitePosition = originalCallsite.callsitePosition
+ ))
+ }
- None
+ callGraph.closureInstantiations(callee).valuesIterator foreach { originalClosureInit =>
+ val newIndy = instructionMap(originalClosureInit.lambdaMetaFactoryCall.indy).asInstanceOf[InvokeDynamicInsnNode]
+ val capturedArgInfos = originalClosureInit.capturedArgInfos flatMap mapArgInfo
+ callGraph.addClosureInstantiation(
+ ClosureInstantiation(
+ originalClosureInit.lambdaMetaFactoryCall.copy(indy = newIndy),
+ callsiteMethod,
+ callsiteClass,
+ capturedArgInfos)
+ )
}
+
+ // Remove the elided invocation from the call graph
+ callGraph.removeCallsite(callsiteInstruction, callsiteMethod)
+
+ // Inlining a method body can render some code unreachable, see example above (in runInliner).
+ unreachableCodeEliminated -= callsiteMethod
+
+ instructionMap
}
/**
- * Check whether an inling can be performed. Parmeters are described in method [[inline]].
+ * Check whether an inling can be performed.
* @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[CannotInlineWarning] = {
+ def canInline(callsite: Callsite): Option[CannotInlineWarning] = {
+ import callsite.{callsiteInstruction, callsiteMethod, callsiteClass, callsiteStackHeight}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass}
def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}"
def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc"
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
new file mode 100644
index 0000000000..e559b63c09
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
@@ -0,0 +1,230 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.collection.immutable.IntMap
+import scala.tools.asm.Type
+import scala.tools.asm.tree.{MethodNode, MethodInsnNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.collection.convert.decorateAsScala._
+
+class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
+ import bTypes._
+ import inliner._
+ import callGraph._
+
+ case class InlineRequest(callsite: Callsite, post: List[PostInlineRequest])
+ case class PostInlineRequest(callsiteInstruction: MethodInsnNode, post: List[PostInlineRequest])
+
+ /**
+ * Select callsites from the call graph that should be inlined, grouped by the containing method.
+ * Cyclic inlining requests are allowed, the inliner will eliminate requests to break cycles.
+ */
+ def selectCallsitesForInlining: Map[MethodNode, Set[InlineRequest]] = {
+ // We should only create inlining requests for callsites being compiled (not for callsites in
+ // classes on the classpath). The call graph may contain callsites of classes parsed from the
+ // classpath. In order to get only the callsites being compiled, we start at the map of
+ // compilingClasses in the byteCodeRepository.
+ val compilingMethods = for {
+ classNode <- byteCodeRepository.compilingClasses.valuesIterator
+ methodNode <- classNode.methods.iterator.asScala
+ } yield methodNode
+
+ compilingMethods.map(methodNode => {
+ var requests = Set.empty[InlineRequest]
+ callGraph.callsites(methodNode).valuesIterator foreach {
+ case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, _, warning)), _, _, _, pos) =>
+ val request = inlineRequest(callsite)
+ requests ++= request
+ if (request.isEmpty) {
+ if (annotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) {
+ // 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 (doRewriteTraitCallsite(callsite))
+ backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg)
+ else if (!safeToInline)
+ backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg)
+ else
+ backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg)
+ } else if (warning.isDefined && warning.get.emitWarning(compilerSettings)) {
+ // 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)
+ }
+ }
+
+ case Callsite(ins, _, _, Left(warning), _, _, _, pos) =>
+ if (warning.emitWarning(compilerSettings))
+ backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning")
+ }
+ (methodNode, requests)
+ }).filterNot(_._2.isEmpty).toMap
+ }
+
+ /**
+ * Returns the inline request for a callsite if the callsite should be inlined according to the
+ * current heuristics (`-Yopt-inline-heuristics`).
+ *
+ * The resulting inline request may contain post-inlining requests of callsites that in turn are
+ * also selected as individual inlining requests.
+ */
+ def inlineRequest(callsite: Callsite): Option[InlineRequest] = compilerSettings.YoptInlineHeuristics.value match {
+ case "everything" =>
+ if (callsite.callee.get.safeToInline) Some(InlineRequest(callsite, Nil))
+ else None
+
+ case "at-inline-annotated" =>
+ val callee = callsite.callee.get
+ if (callee.safeToInline && callee.annotatedInline) Some(InlineRequest(callsite, Nil))
+ else None
+
+ case "default" =>
+ val callee = callsite.callee.get
+ if (callee.safeToInline && !callee.annotatedNoInline) {
+ val shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists {
+ case (index, _) => callsite.argInfos.contains(index)
+ })
+
+ if (shouldInlineHO || callee.annotatedInline) Some(InlineRequest(callsite, Nil))
+ else None
+ } else None
+ }
+
+ /*
+ // using http://lihaoyi.github.io/Ammonite/
+
+ load.ivy("com.google.guava" % "guava" % "18.0")
+ val javaUtilFunctionClasses = {
+ val rt = System.getProperty("sun.boot.class.path").split(":").find(_.endsWith("lib/rt.jar")).get
+ val u = new java.io.File(rt).toURL
+ val l = new java.net.URLClassLoader(Array(u))
+ val cp = com.google.common.reflect.ClassPath.from(l)
+ cp.getTopLevelClasses("java.util.function").toArray.map(_.toString).toList
+ }
+
+ // found using IntelliJ's "Find Usages" on the @FunctionalInterface annotation
+ val otherClasses = List(
+ "com.sun.javafx.css.parser.Recognizer",
+ "java.awt.KeyEventDispatcher",
+ "java.awt.KeyEventPostProcessor",
+ "java.io.FileFilter",
+ "java.io.FilenameFilter",
+ "java.lang.Runnable",
+ "java.lang.Thread$UncaughtExceptionHandler",
+ "java.nio.file.DirectoryStream$Filter",
+ "java.nio.file.PathMatcher",
+ "java.time.temporal.TemporalAdjuster",
+ "java.time.temporal.TemporalQuery",
+ "java.util.Comparator",
+ "java.util.concurrent.Callable",
+ "java.util.logging.Filter",
+ "java.util.prefs.PreferenceChangeListener",
+ "javafx.animation.Interpolatable",
+ "javafx.beans.InvalidationListener",
+ "javafx.beans.value.ChangeListener",
+ "javafx.collections.ListChangeListener",
+ "javafx.collections.MapChangeListener",
+ "javafx.collections.SetChangeListener",
+ "javafx.event.EventHandler",
+ "javafx.util.Builder",
+ "javafx.util.BuilderFactory",
+ "javafx.util.Callback"
+ )
+
+ val allClasses = javaUtilFunctionClasses ::: otherClasses
+
+ load.ivy("org.ow2.asm" % "asm" % "5.0.4")
+ val classesAndSamNameDesc = allClasses.map(c => {
+ val cls = Class.forName(c)
+ val internalName = org.objectweb.asm.Type.getDescriptor(cls).drop(1).dropRight(1) // drop L and ;
+ val sams = cls.getMethods.filter(m => {
+ (m.getModifiers & java.lang.reflect.Modifier.ABSTRACT) != 0 &&
+ m.getName != "equals" // Comparator has an abstract override of "equals" for adding Javadoc
+ })
+ assert(sams.size == 1, internalName + sams.map(_.getName))
+ val sam = sams.head
+ val samDesc = org.objectweb.asm.Type.getMethodDescriptor(sam)
+ (internalName, sam.getName, samDesc)
+ })
+ println(classesAndSamNameDesc map {
+ case (cls, nme, desc) => s"""("$cls", "$nme$desc")"""
+ } mkString ("", ",\n", "\n"))
+ */
+ private val javaSams: Map[String, String] = Map(
+ ("java/util/function/BiConsumer", "accept(Ljava/lang/Object;Ljava/lang/Object;)V"),
+ ("java/util/function/BiFunction", "apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("java/util/function/BiPredicate", "test(Ljava/lang/Object;Ljava/lang/Object;)Z"),
+ ("java/util/function/BinaryOperator", "apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("java/util/function/BooleanSupplier", "getAsBoolean()Z"),
+ ("java/util/function/Consumer", "accept(Ljava/lang/Object;)V"),
+ ("java/util/function/DoubleBinaryOperator", "applyAsDouble(DD)D"),
+ ("java/util/function/DoubleConsumer", "accept(D)V"),
+ ("java/util/function/DoubleFunction", "apply(D)Ljava/lang/Object;"),
+ ("java/util/function/DoublePredicate", "test(D)Z"),
+ ("java/util/function/DoubleSupplier", "getAsDouble()D"),
+ ("java/util/function/DoubleToIntFunction", "applyAsInt(D)I"),
+ ("java/util/function/DoubleToLongFunction", "applyAsLong(D)J"),
+ ("java/util/function/DoubleUnaryOperator", "applyAsDouble(D)D"),
+ ("java/util/function/Function", "apply(Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("java/util/function/IntBinaryOperator", "applyAsInt(II)I"),
+ ("java/util/function/IntConsumer", "accept(I)V"),
+ ("java/util/function/IntFunction", "apply(I)Ljava/lang/Object;"),
+ ("java/util/function/IntPredicate", "test(I)Z"),
+ ("java/util/function/IntSupplier", "getAsInt()I"),
+ ("java/util/function/IntToDoubleFunction", "applyAsDouble(I)D"),
+ ("java/util/function/IntToLongFunction", "applyAsLong(I)J"),
+ ("java/util/function/IntUnaryOperator", "applyAsInt(I)I"),
+ ("java/util/function/LongBinaryOperator", "applyAsLong(JJ)J"),
+ ("java/util/function/LongConsumer", "accept(J)V"),
+ ("java/util/function/LongFunction", "apply(J)Ljava/lang/Object;"),
+ ("java/util/function/LongPredicate", "test(J)Z"),
+ ("java/util/function/LongSupplier", "getAsLong()J"),
+ ("java/util/function/LongToDoubleFunction", "applyAsDouble(J)D"),
+ ("java/util/function/LongToIntFunction", "applyAsInt(J)I"),
+ ("java/util/function/LongUnaryOperator", "applyAsLong(J)J"),
+ ("java/util/function/ObjDoubleConsumer", "accept(Ljava/lang/Object;D)V"),
+ ("java/util/function/ObjIntConsumer", "accept(Ljava/lang/Object;I)V"),
+ ("java/util/function/ObjLongConsumer", "accept(Ljava/lang/Object;J)V"),
+ ("java/util/function/Predicate", "test(Ljava/lang/Object;)Z"),
+ ("java/util/function/Supplier", "get()Ljava/lang/Object;"),
+ ("java/util/function/ToDoubleBiFunction", "applyAsDouble(Ljava/lang/Object;Ljava/lang/Object;)D"),
+ ("java/util/function/ToDoubleFunction", "applyAsDouble(Ljava/lang/Object;)D"),
+ ("java/util/function/ToIntBiFunction", "applyAsInt(Ljava/lang/Object;Ljava/lang/Object;)I"),
+ ("java/util/function/ToIntFunction", "applyAsInt(Ljava/lang/Object;)I"),
+ ("java/util/function/ToLongBiFunction", "applyAsLong(Ljava/lang/Object;Ljava/lang/Object;)J"),
+ ("java/util/function/ToLongFunction", "applyAsLong(Ljava/lang/Object;)J"),
+ ("java/util/function/UnaryOperator", "apply(Ljava/lang/Object;)Ljava/lang/Object;"),
+ ("com/sun/javafx/css/parser/Recognizer", "recognize(I)Z"),
+ ("java/awt/KeyEventDispatcher", "dispatchKeyEvent(Ljava/awt/event/KeyEvent;)Z"),
+ ("java/awt/KeyEventPostProcessor", "postProcessKeyEvent(Ljava/awt/event/KeyEvent;)Z"),
+ ("java/io/FileFilter", "accept(Ljava/io/File;)Z"),
+ ("java/io/FilenameFilter", "accept(Ljava/io/File;Ljava/lang/String;)Z"),
+ ("java/lang/Runnable", "run()V"),
+ ("java/lang/Thread$UncaughtExceptionHandler", "uncaughtException(Ljava/lang/Thread;Ljava/lang/Throwable;)V"),
+ ("java/nio/file/DirectoryStream$Filter", "accept(Ljava/lang/Object;)Z"),
+ ("java/nio/file/PathMatcher", "matches(Ljava/nio/file/Path;)Z"),
+ ("java/time/temporal/TemporalAdjuster", "adjustInto(Ljava/time/temporal/Temporal;)Ljava/time/temporal/Temporal;"),
+ ("java/time/temporal/TemporalQuery", "queryFrom(Ljava/time/temporal/TemporalAccessor;)Ljava/lang/Object;"),
+ ("java/util/Comparator", "compare(Ljava/lang/Object;Ljava/lang/Object;)I"),
+ ("java/util/concurrent/Callable", "call()Ljava/lang/Object;"),
+ ("java/util/logging/Filter", "isLoggable(Ljava/util/logging/LogRecord;)Z"),
+ ("java/util/prefs/PreferenceChangeListener", "preferenceChange(Ljava/util/prefs/PreferenceChangeEvent;)V"),
+ ("javafx/animation/Interpolatable", "interpolate(Ljava/lang/Object;D)Ljava/lang/Object;"),
+ ("javafx/beans/InvalidationListener", "invalidated(Ljavafx/beans/Observable;)V"),
+ ("javafx/beans/value/ChangeListener", "changed(Ljavafx/beans/value/ObservableValue;Ljava/lang/Object;Ljava/lang/Object;)V"),
+ ("javafx/collections/ListChangeListener", "onChanged(Ljavafx/collections/ListChangeListener$Change;)V"),
+ ("javafx/collections/MapChangeListener", "onChanged(Ljavafx/collections/MapChangeListener$Change;)V"),
+ ("javafx/collections/SetChangeListener", "onChanged(Ljavafx/collections/SetChangeListener$Change;)V"),
+ ("javafx/event/EventHandler", "handle(Ljavafx/event/Event;)V"),
+ ("javafx/util/Builder", "build()Ljava/lang/Object;"),
+ ("javafx/util/BuilderFactory", "getBuilder(Ljava/lang/Class;)Ljavafx/util/Builder;"),
+ ("javafx/util/Callback", "call(Ljava/lang/Object;)Ljava/lang/Object;")
+ )
+ def javaSam(internalName: InternalName): Option[String] = javaSams.get(internalName)
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
index 4132710a96..1e7b46012e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -8,8 +8,7 @@ package backend.jvm
package opt
import scala.annotation.switch
-import scala.tools.asm.Opcodes
-import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter}
+import scala.tools.asm.{Type, ClassWriter, MethodWriter, Opcodes}
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
@@ -49,19 +48,53 @@ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
class LocalOpt[BT <: BTypes](val btypes: BT) {
import LocalOptImpls._
import btypes._
+ import analyzers._
+
+ /**
+ * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
+ * framework only computes these values during bytecode generation.
+ *
+ * Since there's currently no better way, we run a bytecode generator on the method and extract
+ * the computed values. This required changes to the ASM codebase:
+ * - the [[MethodWriter]] class was made public
+ * - accessors for maxLocals / maxStack were added to the MethodWriter class
+ *
+ * We could probably make this faster (and allocate less memory) by hacking the ASM framework
+ * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be
+ * to create a separate visitor for computing those values, duplicating the functionality from the
+ * MethodWriter.
+ *
+ * NOTE: the maxStack value computed by this method allocates two slots for long / double values,
+ * as required by the JVM spec. For running an Analyzer, one slot per long / double would be fine.
+ * See comment in `analysis` package object.
+ */
+ def computeMaxLocalsMaxStack(method: MethodNode): Unit = {
+ if (!maxLocalsMaxStackComputed(method)) {
+ method.maxLocals = 0
+ method.maxStack = 0
+ val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
+ val excs = method.exceptions.asScala.toArray
+ val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter]
+ method.accept(mw)
+ method.maxLocals = mw.getMaxLocals
+ method.maxStack = mw.getMaxStack
+ maxLocalsMaxStackComputed += method
+ }
+ }
/**
* Remove unreachable code from a method.
*
* This implementation only removes instructions that are unreachable for an ASM analyzer /
* interpreter. This ensures that future analyses will not produce `null` frames. The inliner
- * and call graph builder depend on this property.
+ * depends on this property.
*
* @return A set containing the eliminated instructions
*/
def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Set[AbstractInsnNode] = {
if (method.instructions.size == 0) return Set.empty // fast path for abstract methods
if (unreachableCodeEliminated(method)) return Set.empty // we know there is no unreachable code
+ if (!AsmAnalyzer.sizeOKForBasicValue(method)) return Set.empty // the method is too large for running an analyzer
// For correctness, after removing unreachable code, we have to eliminate empty exception
// handlers, see scaladoc of def methodOptimizations. Removing an live handler may render more
@@ -137,9 +170,10 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
// This triggers "ClassFormatError: Illegal exception table range in class file C". Similar
// for local variables in dead blocks. Maybe that's a bug in the ASM framework.
+ def canRunDCE = AsmAnalyzer.sizeOKForBasicValue(method)
def removalRound(): Boolean = {
// unreachable-code, empty-handlers and simplify-jumps run until reaching a fixpoint (see doc on class LocalOpt)
- val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (compilerSettings.YoptUnreachableCode) {
+ val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (compilerSettings.YoptUnreachableCode && canRunDCE) {
val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
val removedHandlers = removeEmptyExceptionHandlers(method)
(removedInstructions.nonEmpty, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start)))
@@ -179,47 +213,55 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
}
-}
-
-object LocalOptImpls {
/**
* Removes unreachable basic blocks.
*
- * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow.
- *
* @return A set containing eliminated instructions, and a set containing all live label nodes.
*/
def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Set[AbstractInsnNode], Set[LabelNode]) = {
- // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed.
- computeMaxLocalsMaxStack(method)
- val a = new Analyzer(new BasicInterpreter)
- a.analyze(ownerClassName, method)
- val frames = a.getFrames
+ val a = new AsmAnalyzer(method, ownerClassName)
+ val frames = a.analyzer.getFrames
- val initialSize = method.instructions.size
var i = 0
var liveLabels = Set.empty[LabelNode]
var removedInstructions = Set.empty[AbstractInsnNode]
+ var maxLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2 - (if (BytecodeUtils.isStaticMethod(method)) 1 else 0)
+ var maxStack = 0
val itr = method.instructions.iterator()
while (itr.hasNext) {
- itr.next() match {
- case l: LabelNode =>
- if (frames(i) != null) liveLabels += l
+ val insn = itr.next()
+ val isLive = frames(i) != null
+ if (isLive) maxStack = math.max(maxStack, frames(i).getStackSize)
- case ins =>
+ insn match {
+ case l: LabelNode =>
// label nodes are not removed: they might be referenced for example in a LocalVariableNode
- if (frames(i) == null || ins.getOpcode == Opcodes.NOP) {
+ if (isLive) liveLabels += l
+
+ case v: VarInsnNode if isLive =>
+ val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0
+ maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 becauase local numbers are 0-based
+
+ case i: IincInsnNode if isLive =>
+ maxLocals = math.max(maxLocals, i.`var` + 1)
+
+ case _ =>
+ if (!isLive || insn.getOpcode == Opcodes.NOP) {
// Instruction iterators allow removing during iteration.
// Removing is O(1): instructions are doubly linked list elements.
itr.remove()
- removedInstructions += ins
+ removedInstructions += insn
}
}
i += 1
}
+ method.maxLocals = maxLocals
+ method.maxStack = maxStack
(removedInstructions, liveLabels)
}
+}
+object LocalOptImpls {
/**
* Remove exception handlers that cover empty code blocks. A block is considered empty if it
* consist only of labels, frames, line numbers, nops and gotos.
@@ -312,10 +354,7 @@ object LocalOptImpls {
// Add the index of the local variable used by `varIns` to the `renumber` array.
def addVar(varIns: VarInsnNode): Unit = {
val index = varIns.`var`
- val isWide = (varIns.getOpcode: @switch) match {
- case Opcodes.LLOAD | Opcodes.DLOAD | Opcodes.LSTORE | Opcodes.DSTORE => true
- case _ => false
- }
+ val isWide = isSize2LoadOrStore(varIns.getOpcode)
// Ensure the length of `renumber`. Unused variable indices are mapped to -1.
val minLength = if (isWide) index + 2 else index + 1
diff --git a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala
index 5bf611a7b0..4bf92fd1fb 100644
--- a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala
+++ b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala
@@ -11,8 +11,7 @@ import java.io.{ BufferedReader, IOException, PrintWriter }
import scala.reflect.internal.util._
import StringOps._
-/**
- * This class implements a Reporter that displays messages on a text console.
+/** This class implements a Reporter that displays messages on a text console.
*/
class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: PrintWriter) extends AbstractReporter {
def this(settings: Settings) = this(settings, Console.in, new PrintWriter(Console.err, true))
@@ -85,5 +84,7 @@ class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: Pr
}
}
- override def flush() { writer.flush() }
+ override def flush() = writer.flush()
+
+ override def finish() = printSummary()
}
diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
index 6b339b2a6d..8386722b63 100644
--- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
@@ -31,6 +31,7 @@ trait AbsScalaSettings {
def BooleanSetting(name: String, descr: String): BooleanSetting
def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting
+ def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting
def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]): IntSetting
def MultiStringSetting(name: String, helpArg: String, descr: String): MultiStringSetting
def MultiChoiceSetting[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]]): MultiChoiceSetting[E]
diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
index b4987e1240..9cc8faf8c2 100644
--- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
@@ -221,6 +221,13 @@ class MutableSettings(val errorFn: String => Unit)
def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr))
def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) =
add(new ChoiceSetting(name, helpArg, descr, choices, default))
+ def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String) =
+ ChoiceSetting(name, helpArg, descr, choices, default).withPostSetHook(sett =>
+ if (sett.value != default) {
+ sett.withDeprecationMessage(s"${name}:${sett.value} is deprecated, forcing use of $default")
+ sett.value = default
+ }
+ )
def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]) =
add(new IntSetting(name, descr, default, range, parser))
def MultiStringSetting(name: String, arg: String, descr: String) = add(new MultiStringSetting(name, arg, descr))
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 1817cfa25a..70170d9e97 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -22,13 +22,9 @@ trait ScalaSettings extends AbsScalaSettings
/** Set of settings */
protected[scala] lazy val allSettings = mutable.HashSet[Setting]()
- /** Against my better judgment, giving in to martin here and allowing
- * CLASSPATH to be used automatically. So for the user-specified part
- * of the classpath:
- *
- * - If -classpath or -cp is given, it is that
- * - Otherwise, if CLASSPATH is set, it is that
- * - If neither of those, then "." is used.
+ /** The user class path, specified by `-classpath` or `-cp`,
+ * defaults to the value of CLASSPATH env var if it is set, as in Java,
+ * or else to `"."` for the current user directory.
*/
protected def defaultClasspath = sys.env.getOrElse("CLASSPATH", ".")
@@ -134,8 +130,9 @@ trait ScalaSettings extends AbsScalaSettings
val Xshowobj = StringSetting ("-Xshow-object", "object", "Show internal representation of object.", "")
val showPhases = BooleanSetting ("-Xshow-phases", "Print a synopsis of compiler phases.")
val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files.", "")
+ val reporter = StringSetting ("-Xreporter", "classname", "Specify a custom reporter for compiler messages.", "scala.tools.nsc.reporters.ConsoleReporter")
val strictInference = BooleanSetting ("-Xstrict-inference", "Don't infer known-unsound types")
- val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.11"))
+ val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.12"))
val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.")
val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.")
@@ -143,7 +140,7 @@ trait ScalaSettings extends AbsScalaSettings
// XML parsing options
object XxmlSettings extends MultiChoiceEnumeration {
val coalescing = Choice("coalescing", "Convert PCData to Text and coalesce sibling nodes")
- def isCoalescing = (Xxml contains coalescing) || (!isScala212 && !Xxml.isSetByUser)
+ def isCoalescing = Xxml contains coalescing
}
val Xxml = MultiChoiceSetting(
name = "-Xxml",
@@ -223,9 +220,8 @@ trait ScalaSettings extends AbsScalaSettings
val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.")
val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.")
- val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
- // the current standard is "inline" but we are moving towards "method"
- val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline")
+ val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
+ val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method")
val YskipInlineInfoAttribute = BooleanSetting("-Yskip-inline-info-attribute", "Do not add the ScalaInlineInfo attribute to classfiles generated by -Ybackend:GenASM")
@@ -275,19 +271,20 @@ trait ScalaSettings extends AbsScalaSettings
def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal
def YoptBuildCallGraph = YoptInlinerEnabled || YoptClosureElimination
- def YoptAddToBytecodeRepository = YoptInlinerEnabled || YoptClosureElimination
+ def YoptAddToBytecodeRepository = YoptBuildCallGraph || YoptInlinerEnabled || YoptClosureElimination
val YoptInlineHeuristics = ChoiceSetting(
name = "-Yopt-inline-heuristics",
helpArg = "strategy",
descr = "Set the heuristics for inlining decisions.",
- choices = List("at-inline-annotated", "everything"),
- default = "at-inline-annotated")
+ choices = List("at-inline-annotated", "everything", "default"),
+ default = "default")
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 anyInlineFailed = Choice("any-inline-failed" , "A detailed warning for every callsite that was chosen for inlining by the heuristics, but 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.")
@@ -306,7 +303,8 @@ trait ScalaSettings extends AbsScalaSettings
def YoptWarningEmitAtInlineFailed =
!YoptWarnings.isSetByUser ||
YoptWarnings.contains(YoptWarningsChoices.atInlineFailedSummary) ||
- YoptWarnings.contains(YoptWarningsChoices.atInlineFailed)
+ YoptWarnings.contains(YoptWarningsChoices.atInlineFailed) ||
+ YoptWarnings.contains(YoptWarningsChoices.anyInlineFailed)
def YoptWarningNoInlineMixed = YoptWarnings.contains(YoptWarningsChoices.noInlineMixed)
def YoptWarningNoInlineMissingBytecode = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingBytecode)
@@ -358,7 +356,7 @@ trait ScalaSettings extends AbsScalaSettings
*/
val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.",
List("GenASM", "GenBCode"),
- "GenASM")
+ "GenBCode")
// Feature extensions
val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.")
@@ -389,6 +387,23 @@ trait ScalaSettings extends AbsScalaSettings
val Normal = "normal"
val Discard = "discard"
}
+
+ def conflictWarning: Option[String] = {
+ def oldOptimiseFlagsInGenBCode: Option[String] = {
+ val optFlags: List[Setting] = if (optimise.value) List(optimise) else optimiseSettings.filter(_.value)
+ if (isBCodeActive && optFlags.nonEmpty) {
+ val msg = s"""Compiler settings for the 2.11 optimizer (${optFlags.map(_.name).mkString(", ")}) are incompatible with -Ybackend:GenBCode (which is the default in 2.12).
+ |The optimizer settings are ignored. See -Yopt:help for enabling the new optimizer in 2.12.""".stripMargin
+ Some(msg)
+ } else
+ None
+ }
+
+ List(oldOptimiseFlagsInGenBCode /*, moreToCome */).flatten match {
+ case Nil => None
+ case warnings => Some("Conflicting compiler settings were detected. Some settings will be ignored.\n" + warnings.mkString("\n"))
+ }
+ }
}
object ClassPathRepresentationType {
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
index 43bdad5882..0b051ef89d 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
@@ -68,45 +68,37 @@ case object AnyScalaVersion extends ScalaVersion {
* Factory methods for producing ScalaVersions
*/
object ScalaVersion {
- private val dot = "\\."
- private val dash = "\\-"
- private def not(s:String) = s"[^${s}]"
- private val R = s"((${not(dot)}*)(${dot}(${not(dot)}*)(${dot}(${not(dash)}*)(${dash}(.*))?)?)?)".r
-
- def apply(versionString : String, errorHandler: String => Unit): ScalaVersion = {
- def errorAndValue() = {
- errorHandler(
- s"There was a problem parsing ${versionString}. " +
- "Versions should be in the form major[.minor[.revision]] " +
- "where each part is a positive number, as in 2.10.1. " +
- "The minor and revision parts are optional."
- )
- AnyScalaVersion
- }
+ private val dot = """\."""
+ private val dash = "-"
+ private val vchar = """\d""" //"[^-+.]"
+ private val vpat = s"(?s)($vchar+)(?:$dot($vchar+)(?:$dot($vchar+)(?:$dash(.*))?)?)?".r
+ private val rcpat = """(?i)rc(\d*)""".r
+ private val mspat = """(?i)m(\d*)""".r
+
+ def apply(versionString: String, errorHandler: String => Unit): ScalaVersion = {
+ def error() = errorHandler(
+ s"Bad version (${versionString}) not major[.minor[.revision[-suffix]]]"
+ )
def toInt(s: String) = s match {
case null | "" => 0
- case _ => s.toInt
+ case _ => s.toInt
}
- def isInt(s: String) = util.Try(toInt(s)).isSuccess
-
def toBuild(s: String) = s match {
case null | "FINAL" => Final
- case s if (s.toUpperCase.startsWith("RC") && isInt(s.substring(2))) => RC(toInt(s.substring(2)))
- case s if (s.toUpperCase.startsWith("M") && isInt(s.substring(1))) => Milestone(toInt(s.substring(1)))
- case _ => Development(s)
+ case rcpat(i) => RC(toInt(i))
+ case mspat(i) => Milestone(toInt(i))
+ case _ /* | "" */ => Development(s)
}
- try versionString match {
+ versionString match {
case "none" => NoScalaVersion
- case "any" => AnyScalaVersion
- case R(_, majorS, _, minorS, _, revS, _, buildS) =>
+ case "" => NoScalaVersion
+ case "any" => AnyScalaVersion
+ case vpat(majorS, minorS, revS, buildS) =>
SpecificScalaVersion(toInt(majorS), toInt(minorS), toInt(revS), toBuild(buildS))
- case _ =>
- errorAndValue()
- } catch {
- case e: NumberFormatException => errorAndValue()
+ case _ => error() ; AnyScalaVersion
}
}
diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
index d42c0dd730..f197a4930d 100644
--- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
@@ -38,8 +38,8 @@ trait StandardScalaSettings {
val nowarn = BooleanSetting ("-nowarn", "Generate no warnings.")
val optimise: BooleanSetting // depends on post hook which mutates other settings
val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.")
- val target = ChoiceSetting ("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.",
- List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.6")
+ val target = ChoiceSettingForcedDefault ("-target", "target", "Target platform for object files. All JVM 1.5 - 1.7 targets are deprecated.",
+ List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.8")
val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions.")
val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.")
val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.")
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index 6a46c65267..48eb878e2a 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -165,11 +165,19 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
return
}
+ // Note: elision of outer reference is based on a class-wise analysis, if a class might have subclasses,
+ // it doesn't work. For example, `LocalParent` retains the outer reference in:
+ //
+ // class Outer { def test = {class LocalParent; class LocalChild extends LocalParent } }
+ //
+ // See run/t9408.scala for related test cases.
+ val isEffectivelyFinal = clazz.isEffectivelyFinal
def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal)
- def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && sym.owner.isEffectivelyFinal && !sym.isOverridingSymbol)
+ def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && isEffectivelyFinal && !sym.isOverridingSymbol)
- val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (clazz.info.decls.toSet filter isParamCandidateForElision)
- val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (clazz.info.decls.toSet filter isOuterCandidateForElision)
+ val decls = clazz.info.decls.toSet
+ val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (decls filter isParamCandidateForElision)
+ val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (decls filter isOuterCandidateForElision)
omittables ++= paramCandidatesForElision
omittables ++= outerCandidatesForElision
@@ -388,9 +396,9 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
}
if (stat1 eq stat) {
- assert(ctorParams(genericClazz).length == constrInfo.constrParams.length)
+ assert(ctorParams(genericClazz).length == primaryConstrParams.length)
// this is just to make private fields public
- (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), constrInfo.constrParams, null, true))(stat1)
+ (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), primaryConstrParams, null, true))(stat1)
val stat2 = rewriteArrayUpdate(stat1)
// statements coming from the original class need retyping in the current context
@@ -427,16 +435,16 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// postfix = postfix.tail
// }
- if (shouldGuard && usesSpecializedField && stats.nonEmpty) {
+ if (guardSpecializedFieldInit && intoConstructor.usesSpecializedField && stats.nonEmpty) {
// save them for duplication in the specialized subclass
guardedCtorStats(clazz) = stats
- ctorParams(clazz) = constrInfo.constrParams
+ ctorParams(clazz) = primaryConstrParams
val tree =
If(
Apply(
CODE.NOT (
- Apply(gen.mkAttributedRef(specializedFlag), List())),
+ Apply(gen.mkAttributedRef(hasSpecializedFieldsSym), List())),
List()),
Block(stats, Literal(Constant(()))),
EmptyTree)
@@ -468,27 +476,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
val stats = impl.body // the transformed template body
val localTyper = typer.atOwner(impl, clazz)
- val specializedFlag: Symbol = clazz.info.decl(nme.SPECIALIZED_INSTANCE)
- val shouldGuard = (specializedFlag != NoSymbol) && !clazz.hasFlag(SPECIALIZED)
-
- val isDelayedInitSubclass = (clazz isSubClass DelayedInitClass)
-
- case class ConstrInfo(
- constr: DefDef, // The primary constructor
- constrParams: List[Symbol], // ... and its parameters
- constrBody: Block // ... and its body
- )
- // decompose primary constructor into the three entities above.
- val constrInfo: ConstrInfo = {
- val ddef = (stats find (_.symbol.isPrimaryConstructor))
- ddef match {
- case Some(ddef @ DefDef(_, _, _, List(vparams), _, rhs @ Block(_, _))) =>
- ConstrInfo(ddef, vparams map (_.symbol), rhs)
- case x =>
- abort("no constructor in template: impl = " + impl)
- }
- }
- import constrInfo._
+ val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE)
+ // The constructor of a non-specialized class that has specialized subclasses
+ // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields.
+ val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED)
+
+ val isDelayedInitSubclass = clazz isSubClass DelayedInitClass
+
+ // find and dissect primary constructor
+ val (primaryConstr, primaryConstrParams, primaryConstrBody) = stats collectFirst {
+ case dd@DefDef(_, _, _, vps :: Nil, _, rhs: Block) if dd.symbol.isPrimaryConstructor => (dd, vps map (_.symbol), rhs)
+ } getOrElse abort("no constructor in template: impl = " + impl)
// The parameter accessor fields which are members of the class
val paramAccessors = clazz.constrParamAccessors
@@ -501,27 +499,26 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
def parameterNamed(name: Name): Symbol = {
def matchesName(param: Symbol) = param.name == name || param.name.startsWith(name + nme.NAME_JOIN_STRING)
- (constrParams filter matchesName) match {
- case Nil => abort(name + " not in " + constrParams)
+ primaryConstrParams filter matchesName match {
+ case Nil => abort(name + " not in " + primaryConstrParams)
case p :: _ => p
}
}
- /*
- * `usesSpecializedField` makes a difference in deciding whether constructor-statements
- * should be guarded in a `shouldGuard` class, ie in a class that's the generic super-class of
- * one or more specialized sub-classes.
- *
- * Given that `usesSpecializedField` isn't read for any other purpose than the one described above,
- * we skip setting `usesSpecializedField` in case the current class isn't `shouldGuard` to start with.
- * That way, trips to a map in `specializeTypes` are saved.
- */
- var usesSpecializedField: Boolean = false
-
// A transformer for expressions that go into the constructor
- private class IntoCtorTransformer extends Transformer {
-
- private def isParamRef(sym: Symbol) = (sym.isParamAccessor && sym.owner == clazz)
+ object intoConstructor extends Transformer {
+ /*
+ * `usesSpecializedField` makes a difference in deciding whether constructor-statements
+ * should be guarded in a `guardSpecializedFieldInit` class, ie in a class that's the generic super-class of
+ * one or more specialized sub-classes.
+ *
+ * Given that `usesSpecializedField` isn't read for any other purpose than the one described above,
+ * we skip setting `usesSpecializedField` in case the current class isn't `guardSpecializedFieldInit` to start with.
+ * That way, trips to a map in `specializeTypes` are saved.
+ */
+ var usesSpecializedField: Boolean = false
+
+ private def isParamRef(sym: Symbol) = sym.isParamAccessor && sym.owner == clazz
// Terminology: a stationary location is never written after being read.
private def isStationaryParamRef(sym: Symbol) = (
@@ -540,10 +537,9 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* (b.2) the constructor in the specialized (sub-)class.
* (c) isn't part of a DelayedInit subclass.
*/
- private def canBeSupplanted(sym: Symbol) = (!isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym))
+ private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym)
override def transform(tree: Tree): Tree = tree match {
-
case Apply(Select(This(_), _), List()) =>
// references to parameter accessor methods of own class become references to parameters
// outer accessors become references to $outer parameter
@@ -558,7 +554,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// references to parameter accessor field of own class become references to parameters
gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos
- case Select(_, _) if shouldGuard => // reasoning behind this guard in the docu of `usesSpecializedField`
+ case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField`
if (possiblySpecialized(tree.symbol)) {
usesSpecializedField = true
}
@@ -568,18 +564,10 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
super.transform(tree)
}
- }
-
- private val intoConstructorTransformer = new IntoCtorTransformer
-
- // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol
- def intoConstructor(oldowner: Symbol, tree: Tree) =
- intoConstructorTransformer transform tree.changeOwner(oldowner -> constr.symbol)
-
- // Should tree be moved in front of super constructor call?
- def canBeMoved(tree: Tree) = tree match {
- case ValDef(mods, _, _, _) => (mods hasFlag PRESUPER | PARAMACCESSOR)
- case _ => false
+ // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol
+ def apply(oldowner: Symbol, tree: Tree) =
+ if (tree eq EmptyTree) tree
+ else transform(tree.changeOwner(oldowner -> primaryConstr.symbol))
}
// Create an assignment to class field `to` with rhs `from`
@@ -621,10 +609,10 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
val classInitStatBuf = new ListBuffer[Tree]
// generate code to copy pre-initialized fields
- for (stat <- constrBody.stats) {
+ for (stat <- primaryConstrBody.stats) {
constrStatBuf += stat
stat match {
- case ValDef(mods, name, _, _) if (mods hasFlag PRESUPER) =>
+ case ValDef(mods, name, _, _) if mods hasFlag PRESUPER =>
// stat is the constructor-local definition of the field value
val fields = presupers filter (_.getterName == name)
assert(fields.length == 1)
@@ -635,45 +623,48 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
}
}
- // Triage all template definitions to go into defBuf/auxConstructorBuf, constrStatBuf, or constrPrefixBuf.
- for (stat <- stats) stat match {
- case DefDef(_,_,_,_,_,rhs) =>
- // methods with constant result type get literals as their body
- // all methods except the primary constructor go into template
- stat.symbol.tpe match {
- case MethodType(List(), tp @ ConstantType(c)) =>
- defBuf += deriveDefDef(stat)(Literal(c) setPos _.pos setType tp)
- case _ =>
- if (stat.symbol.isPrimaryConstructor) ()
- else if (stat.symbol.isConstructor) auxConstructorBuf += stat
- else defBuf += stat
- }
- case ValDef(mods, _, _, rhs) if !mods.hasStaticFlag =>
+
+ for (stat <- stats) {
+ val statSym = stat.symbol
+
+ stat match {
+ // recurse on class definition, store in defBuf
+ case _: ClassDef => defBuf += new ConstructorTransformer(unit).transform(stat)
+
+ // methods (except primary constructor) go into template
+ // (non-primary ctors --> auxConstructorBuf / regular defs --> defBuf)
+ case _: DefDef if statSym.isPrimaryConstructor => ()
+ case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat
+ case _: DefDef => defBuf += stat
+
// val defs with constant right-hand sides are eliminated.
- // for all other val defs, an empty valdef goes into the template and
- // the initializer goes as an assignment into the constructor
- // if the val def is an early initialized or a parameter accessor, it goes
- // before the superclass constructor call, otherwise it goes after.
- // Lazy vals don't get the assignment in the constructor.
- if (!stat.symbol.tpe.isInstanceOf[ConstantType]) {
- if (rhs != EmptyTree && !stat.symbol.isLazy) {
- val rhs1 = intoConstructor(stat.symbol, rhs)
- (if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign(
- stat.symbol, rhs1)
+ case _: ValDef if statSym.info.isInstanceOf[ConstantType] => ()
+
+ // For all other val defs, an empty valdef goes into the template.
+ // Additionally, non-lazy vals are initialized by an assignment in:
+ // - the class initializer (static),
+ // - the constructor, before the super call (early initialized or a parameter accessor),
+ // - the constructor, after the super call (regular val).
+ case ValDef(mods, _, _, rhs) =>
+ val initializingRhs =
+ if (statSym.isLazy) EmptyTree
+ else if (!mods.hasStaticFlag) intoConstructor(statSym, rhs)
+ else rhs
+
+ if (initializingRhs ne EmptyTree) {
+ val initPhase =
+ if (mods hasFlag STATIC) classInitStatBuf
+ else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf
+ else constrStatBuf
+
+ initPhase += mkAssign(statSym, initializingRhs)
}
+
defBuf += deriveValDef(stat)(_ => EmptyTree)
- }
- case ValDef(_, _, _, rhs) =>
- // Add static initializer statements to classInitStatBuf and remove the rhs from the val def.
- classInitStatBuf += mkAssign(stat.symbol, rhs)
- defBuf += deriveValDef(stat)(_ => EmptyTree)
-
- case ClassDef(_, _, _, _) =>
- // classes are treated recursively, and left in the template
- defBuf += new ConstructorTransformer(unit).transform(stat)
- case _ =>
+
// all other statements go into the constructor
- constrStatBuf += intoConstructor(impl.symbol, stat)
+ case _ => constrStatBuf += intoConstructor(impl.symbol, stat)
+ }
}
populateOmittables()
@@ -708,12 +699,12 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
rewriteDelayedInit()
// Assemble final constructor
- defBuf += deriveDefDef(constr)(_ =>
+ defBuf += deriveDefDef(primaryConstr)(_ =>
treeCopy.Block(
- constrBody,
+ primaryConstrBody,
paramInits ::: constrPrefixBuf.toList ::: uptoSuperStats :::
guardSpecializedInitializer(remainingConstrStats),
- constrBody.expr))
+ primaryConstrBody.expr))
// Followed by any auxiliary constructors
defBuf ++= auxConstructorBuf
@@ -724,6 +715,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// Eliminate all field definitions that can be dropped from template
val templateWithoutOmittables: Template = deriveTemplate(impl)(_ => defBuf.toList filter (stat => mustBeKept(stat.symbol)))
+
// Add the static initializers
val transformed: Template = addStaticInits(templateWithoutOmittables, classInitStatBuf, localTyper)
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index ea8c1cbaf6..33fd5d0461 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -18,7 +18,7 @@ import scala.collection.mutable.LinkedHashMap
*
* From a lambda, Delambdafy will create:
*
- * Under -target:jvm-1.7 and below:
+ * Under GenASM
*
* 1) a new top level class that
a) has fields and a constructor taking the captured environment (including possibly the "this"
@@ -27,7 +27,7 @@ import scala.collection.mutable.LinkedHashMap
* c) if needed a bridge method for the apply method
* 2) an instantiation of the newly created class which replaces the lambda
*
- * Under -target:jvm-1.8 with GenBCode:
+ * Under GenBCode:
*
* 1) An application of the captured arguments to a fictional symbol representing the lambda factory.
* This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
@@ -571,10 +571,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
// The functional interface that can be used to adapt the lambda target method `target` to the
// given function type. Returns `NoSymbol` if the compiler settings are unsuitable.
private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = {
- val canUseLambdaMetafactory: Boolean = {
- val isTarget18 = settings.target.value.contains("jvm-1.8")
- settings.isBCodeActive && isTarget18
- }
+ val canUseLambdaMetafactory = settings.isBCodeActive
val sym = functionType.typeSymbol
val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index a04625c9c5..833f25537c 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -341,8 +341,8 @@ abstract class Erasure extends AddInterfaces
buf append (if (restpe.typeSymbol == UnitClass || sym0.isConstructor) VOID_TAG.toString else jsig(restpe))
buf.toString
- case RefinedType(parent :: _, decls) =>
- boxedSig(parent)
+ case RefinedType(parents, decls) =>
+ boxedSig(intersectionDominator(parents))
case ClassInfoType(parents, _, _) =>
superSig(parents)
case AnnotatedType(_, atp) =>
diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
index 540de2cfe1..f12f6c4e18 100644
--- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
@@ -446,8 +446,10 @@ abstract class ExplicitOuter extends InfoTransform
//
// See SI-6552 for an example of why `sym.owner.enclMethod hasAnnotation ScalaInlineClass`
// is not suitable; if we make a method-local class non-private, it mangles outer pointer names.
- if (currentClass != sym.owner ||
- (closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass))
+ def enclMethodIsInline = closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass
+ // SI-8710 The extension method condition reflects our knowledge that a call to `new Meter(12).privateMethod`
+ // with later be rewritten (in erasure) to `Meter.privateMethod$extension(12)`.
+ if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension)
sym.makeNotPrivate(sym.owner)
val qsym = qual.tpe.widen.typeSymbol
diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
index 116047a2ad..6349fc3fb9 100644
--- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
@@ -208,7 +208,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers {
def makeExtensionMethodSymbol = {
val extensionName = extensionNames(origMeth).head.toTermName
val extensionMeth = (
- companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~LOCAL | FINAL)
+ companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~PRIVATE & ~LOCAL | FINAL)
setAnnotations origMeth.annotations
)
origMeth.removeAnnotation(TailrecClass) // it's on the extension method, now.
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index a079a76ce7..00a994fe87 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -778,7 +778,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, PRIVATE)
val params = defSym newSyntheticValueParams args.map(_.symbol.tpe)
defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType)
- val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym)
+ val rhs: Tree = gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats).changeOwner(currentOwner -> defSym)
val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params)
addDef(position(defSym), DefDef(defSym, strictSubst(BLOCK(rhs, retVal))))
defSym
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 53a1347a48..998f0b22cb 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -697,7 +697,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
else
debuglog("conflicting env for " + m + " env: " + env)
}
- else if (m.isDeferred) { // abstract methods
+ else if (m.isDeferred && m.isSpecialized) { // abstract methods
val specMember = enterMember(cloneInSpecializedClass(m, _ | DEFERRED))
// debuglog("deferred " + specMember.fullName + " remains abstract")
@@ -705,11 +705,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
// was: new Forward(specMember) {
// override def target = m.owner.info.member(specializedName(m, env))
// }
- } else if (m.isMethod && !m.hasAccessorFlag) { // other concrete methods
+ } else if (!sClass.isTrait && m.isMethod && !m.hasAccessorFlag) { // other concrete methods
// log("other concrete " + m)
forwardToOverload(m)
- } else if (m.isMethod && m.hasFlag(LAZY)) {
+ } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) {
forwardToOverload(m)
} else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 163c44822e..57639a94c7 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -237,7 +237,7 @@ abstract class UnCurry extends InfoTransform
def canUseDelamdafyMethod = (
(inConstructorFlag == 0) // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation
- && (!isSpecialized || (settings.isBCodeActive && settings.target.value == "jvm-1.8")) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
+ && (!isSpecialized || settings.isBCodeActive) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime
)
if (inlineFunctionExpansion || !canUseDelamdafyMethod) {
val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe))
@@ -582,6 +582,7 @@ abstract class UnCurry extends InfoTransform
}
case dd @ DefDef(_, _, _, vparamss0, _, rhs0) =>
+ val ddSym = dd.symbol
val (newParamss, newRhs): (List[List[ValDef]], Tree) =
if (dependentParamTypeErasure isDependent dd)
dependentParamTypeErasure erase dd
@@ -593,11 +594,22 @@ abstract class UnCurry extends InfoTransform
(vparamss1, rhs0)
}
+ // A no-arg method with ConstantType result type can safely be reduced to the corresponding Literal
+ // (only pure methods are typed as ConstantType). We could also do this for methods with arguments,
+ // after ensuring the arguments are not referenced.
+ val literalRhsIfConst =
+ if (newParamss.head.isEmpty) { // We know newParamss.length == 1 from above
+ ddSym.info.resultType match {
+ case tp@ConstantType(value) => Literal(value) setType tp setPos newRhs.pos // inlining of gen.mkAttributedQualifier(tp)
+ case _ => newRhs
+ }
+ } else newRhs
+
val flatdd = copyDefDef(dd)(
vparamss = newParamss,
- rhs = nonLocalReturnKeys get dd.symbol match {
- case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(newRhs, k, dd.symbol))
- case None => newRhs
+ rhs = nonLocalReturnKeys get ddSym match {
+ case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(literalRhsIfConst, k, ddSym))
+ case None => literalRhsIfConst
}
)
addJavaVarargsForwarders(dd, flatdd)
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
index 1331eb6993..f259283e48 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
@@ -138,7 +138,7 @@ trait TreeAndTypeAnalysis extends Debugging {
if(grouped) {
def enumerateChildren(sym: Symbol) = {
- sym.children.toList
+ sym.sealedChildren.toList
.sortBy(_.sealedSortName)
.filterNot(x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
index 309b80f9ba..2b6a4c763a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
@@ -212,8 +212,8 @@ trait Checkable {
)
/** Are all children of these symbols pairwise irreconcilable? */
def allChildrenAreIrreconcilable(sym1: Symbol, sym2: Symbol) = (
- sym1.children.toList forall (c1 =>
- sym2.children.toList forall (c2 =>
+ sym1.sealedChildren.toList forall (c1 =>
+ sym2.sealedChildren.toList forall (c2 =>
areIrreconcilableAsParents(c1, c2)
)
)
diff --git a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
index 56ed0ee16c..2f4771e9d4 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
@@ -75,7 +75,7 @@ abstract class ConstantFolder {
case nme.AND => Constant(x.booleanValue & y.booleanValue)
case nme.EQ => Constant(x.booleanValue == y.booleanValue)
case nme.NE => Constant(x.booleanValue != y.booleanValue)
- case _ => null
+ case _ => null
}
private def foldSubrangeOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.OR => Constant(x.intValue | y.intValue)
@@ -95,14 +95,20 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.intValue * y.intValue)
case nme.DIV => Constant(x.intValue / y.intValue)
case nme.MOD => Constant(x.intValue % y.intValue)
- case _ => null
+ case _ => null
}
private def foldLongOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.OR => Constant(x.longValue | y.longValue)
case nme.XOR => Constant(x.longValue ^ y.longValue)
case nme.AND => Constant(x.longValue & y.longValue)
- case nme.LSL => Constant(x.longValue << y.longValue)
+ case nme.LSL if x.tag <= IntTag
+ => Constant(x.intValue << y.longValue)
+ case nme.LSL => Constant(x.longValue << y.longValue)
+ case nme.LSR if x.tag <= IntTag
+ => Constant(x.intValue >>> y.longValue)
case nme.LSR => Constant(x.longValue >>> y.longValue)
+ case nme.ASR if x.tag <= IntTag
+ => Constant(x.intValue >> y.longValue)
case nme.ASR => Constant(x.longValue >> y.longValue)
case nme.EQ => Constant(x.longValue == y.longValue)
case nme.NE => Constant(x.longValue != y.longValue)
@@ -115,7 +121,7 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.longValue * y.longValue)
case nme.DIV => Constant(x.longValue / y.longValue)
case nme.MOD => Constant(x.longValue % y.longValue)
- case _ => null
+ case _ => null
}
private def foldFloatOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.EQ => Constant(x.floatValue == y.floatValue)
@@ -129,7 +135,7 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.floatValue * y.floatValue)
case nme.DIV => Constant(x.floatValue / y.floatValue)
case nme.MOD => Constant(x.floatValue % y.floatValue)
- case _ => null
+ case _ => null
}
private def foldDoubleOp(op: Name, x: Constant, y: Constant): Constant = op match {
case nme.EQ => Constant(x.doubleValue == y.doubleValue)
@@ -143,7 +149,7 @@ abstract class ConstantFolder {
case nme.MUL => Constant(x.doubleValue * y.doubleValue)
case nme.DIV => Constant(x.doubleValue / y.doubleValue)
case nme.MOD => Constant(x.doubleValue % y.doubleValue)
- case _ => null
+ case _ => null
}
private def foldBinop(op: Name, x: Constant, y: Constant): Constant = {
@@ -162,7 +168,7 @@ abstract class ConstantFolder {
case _ => null
}
catch {
- case ex: ArithmeticException => null
+ case _: ArithmeticException => null
}
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
index b0bd9977a8..c9d3b3da96 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
@@ -1105,7 +1105,7 @@ trait ContextErrors {
def GetterDefinedTwiceError(getter: Symbol) =
issueSymbolTypeError(getter, getter+" is defined twice")
- def ValOrValWithSetterSuffixError(tree: Tree) =
+ def ValOrVarWithSetterSuffixError(tree: Tree) =
issueNormalTypeError(tree, "Names of vals or vars may not end in `_='")
def PrivateThisCaseClassParameterError(tree: Tree) =
@@ -1212,7 +1212,8 @@ trait ContextErrors {
import definitions._
- def AmbiguousImplicitError(info1: ImplicitInfo, info2: ImplicitInfo,
+ def AmbiguousImplicitError(info1: ImplicitInfo, tree1: Tree,
+ info2: ImplicitInfo, tree2: Tree,
pre1: String, pre2: String, trailer: String)
(isView: Boolean, pt: Type, tree: Tree)(implicit context0: Context) = {
if (!info1.tpe.isErroneous && !info2.tpe.isErroneous) {
@@ -1248,10 +1249,20 @@ trait ContextErrors {
if (explanation == "") "" else "\n" + explanation
)
}
+
+ def treeTypeArgs(annotatedTree: Tree) = annotatedTree match {
+ case TypeApply(_, args) => args.map(_.toString)
+ case _ => Nil
+ }
+
context.issueAmbiguousError(AmbiguousImplicitTypeError(tree,
- if (isView) viewMsg
- else s"ambiguous implicit values:\n${coreMsg}match expected type $pt")
- )
+ (tree1.symbol, tree2.symbol) match {
+ case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1))
+ case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2))
+ case (_, _) if isView => viewMsg
+ case (_, _) => s"ambiguous implicit values:\n${coreMsg}match expected type $pt"
+ }
+ ))
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
index c46bc7444a..3fe2f24818 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -24,7 +24,8 @@ trait Contexts { self: Analyzer =>
object NoContext
extends Context(EmptyTree, NoSymbol, EmptyScope, NoCompilationUnit,
- null) { // We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer`
+ // We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer`
+ null) {
enclClass = this
enclMethod = this
@@ -48,12 +49,11 @@ trait Contexts { self: Analyzer =>
def ambiguousDefnAndImport(owner: Symbol, imp: ImportInfo) =
LookupAmbiguous(s"it is both defined in $owner and imported subsequently by \n$imp")
- private lazy val startContext = {
- NoContext.make(
+ private lazy val startContext = NoContext.make(
Template(List(), noSelfType, List()) setSymbol global.NoSymbol setType global.NoType,
rootMirror.RootClass,
- rootMirror.RootClass.info.decls)
- }
+ rootMirror.RootClass.info.decls
+ )
private lazy val allUsedSelectors =
mutable.Map[ImportInfo, Set[ImportSelector]]() withDefaultValue Set()
@@ -806,6 +806,14 @@ trait Contexts { self: Analyzer =>
(e ne null) && (e.owner == scope) && (!settings.isScala212 || e.sym.exists)
})
+ /** Do something with the symbols with name `name` imported via the import in `imp`,
+ * if any such symbol is accessible from this context and is a qualifying implicit.
+ */
+ private def withQualifyingImplicitAlternatives(imp: ImportInfo, name: Name, pre: Type)(f: Symbol => Unit) = for {
+ sym <- importedAccessibleSymbol(imp, name, requireExplicit = false, record = false).alternatives
+ if isQualifyingImplicit(name, sym, pre, imported = true)
+ } f(sym)
+
private def collectImplicits(syms: Scope, pre: Type, imported: Boolean = false): List[ImplicitInfo] =
for (sym <- syms.toList if isQualifyingImplicit(sym.name, sym, pre, imported)) yield
new ImplicitInfo(sym.name, pre, sym)
@@ -813,12 +821,8 @@ trait Contexts { self: Analyzer =>
private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = {
val qual = imp.qual
- val pre =
- if (qual.tpe.typeSymbol.isPackageClass)
- // SI-6225 important if the imported symbol is inherited by the the package object.
- singleType(qual.tpe, qual.tpe member nme.PACKAGE)
- else
- qual.tpe
+ val qualSym = qual.tpe.typeSymbol
+ val pre = qual.tpe
def collect(sels: List[ImportSelector]): List[ImplicitInfo] = sels match {
case List() =>
List()
@@ -827,9 +831,9 @@ trait Contexts { self: Analyzer =>
case ImportSelector(from, _, to, _) :: sels1 =>
var impls = collect(sels1) filter (info => info.name != from)
if (to != nme.WILDCARD) {
- for (sym <- importedAccessibleSymbol(imp, to).alternatives)
- if (isQualifyingImplicit(to, sym, pre, imported = true))
- impls = new ImplicitInfo(to, pre, sym) :: impls
+ withQualifyingImplicitAlternatives(imp, to, pre) { sym =>
+ impls = new ImplicitInfo(to, pre, sym) :: impls
+ }
}
impls
}
@@ -889,7 +893,8 @@ trait Contexts { self: Analyzer =>
Some(collectImplicitImports(imports.head))
} else if (owner.isPackageClass) {
// the corresponding package object may contain implicit members.
- Some(collectImplicits(owner.tpe.implicitMembers, owner.tpe))
+ val pre = owner.packageObject.typeOfThis
+ Some(collectImplicits(pre.implicitMembers, pre))
} else Some(Nil)
}
@@ -953,57 +958,22 @@ trait Contexts { self: Analyzer =>
/** The symbol with name `name` imported via the import in `imp`,
* if any such symbol is accessible from this context.
*/
- def importedAccessibleSymbol(imp: ImportInfo, name: Name): Symbol =
- importedAccessibleSymbol(imp, name, requireExplicit = false)
-
- private def importedAccessibleSymbol(imp: ImportInfo, name: Name, requireExplicit: Boolean): Symbol =
- imp.importedSymbol(name, requireExplicit) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))
-
- /** Is `sym` defined in package object of package `pkg`?
- * Since sym may be defined in some parent of the package object,
- * we cannot inspect its owner only; we have to go through the
- * info of the package object. However to avoid cycles we'll check
- * what other ways we can before pushing that way.
+ private def importedAccessibleSymbol(imp: ImportInfo, name: Name, requireExplicit: Boolean, record: Boolean): Symbol =
+ imp.importedSymbol(name, requireExplicit, record) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))
+
+ private def requiresQualifier(s: Symbol): Boolean = (
+ s.owner.isClass
+ && !s.owner.isPackageClass
+ && !s.isTypeParameterOrSkolem
+ && !s.isExistentiallyBound
+ )
+
+ /** Must `sym` defined in package object of package `pkg`, if
+ * it selected from a prefix with `pkg` as its type symbol?
*/
def isInPackageObject(sym: Symbol, pkg: Symbol): Boolean = {
- def uninitialized(what: String) = {
- log(s"Cannot look for $sym in package object of $pkg; $what is not initialized.")
- false
- }
- def pkgClass = if (pkg.isTerm) pkg.moduleClass else pkg
- def matchesInfo = (
- // need to be careful here to not get a cyclic reference during bootstrap
- if (pkg.isInitialized) {
- val module = pkg.info member nme.PACKAGEkw
- if (module.isInitialized)
- module.info.member(sym.name).alternatives contains sym
- else
- uninitialized("" + module)
- }
- else uninitialized("" + pkg)
- )
- def inPackageObject(sym: Symbol) = (
- // To be in the package object, one of these must be true:
- // 1) sym.owner is a package object class, and sym.owner.owner is the package class for `pkg`
- // 2) sym.owner is inherited by the correct package object class
- // We try to establish 1) by inspecting the owners directly, and then we try
- // to rule out 2), and only if both those fail do we resort to looking in the info.
- !sym.hasPackageFlag && sym.owner.exists && (
- if (sym.owner.isPackageObjectClass)
- sym.owner.owner == pkgClass
- else
- !sym.owner.isPackageClass && matchesInfo
- )
- )
-
- // An overloaded symbol might not have the expected owner!
- // The alternatives must be inspected directly.
- pkgClass.isPackageClass && (
- if (sym.isOverloaded)
- sym.alternatives forall (isInPackageObject(_, pkg))
- else
- inPackageObject(sym)
- )
+ if (sym.isOverloaded) sym.alternatives.exists(alt => isInPackageObject(alt, pkg))
+ else pkg.isPackage && sym.owner != pkg && requiresQualifier(sym)
}
def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess
@@ -1040,11 +1010,6 @@ trait Contexts { self: Analyzer =>
|| unit.exists && s.sourceFile != unit.source.file
)
)
- def requiresQualifier(s: Symbol) = (
- s.owner.isClass
- && !s.owner.isPackageClass
- && !s.isTypeParameterOrSkolem
- )
def lookupInPrefix(name: Name) = pre member name filter qualifies
def accessibleInPrefix(s: Symbol) = isAccessible(s, pre, superAccess = false)
@@ -1103,7 +1068,7 @@ trait Contexts { self: Analyzer =>
def imp2Explicit = imp2 isExplicitImport name
def lookupImport(imp: ImportInfo, requireExplicit: Boolean) =
- importedAccessibleSymbol(imp, name, requireExplicit) filter qualifies
+ importedAccessibleSymbol(imp, name, requireExplicit, record = true) filter qualifies
// Java: A single-type-import declaration d in a compilation unit c of package p
// that imports a type named n shadows, throughout c, the declarations of:
@@ -1399,7 +1364,6 @@ trait Contexts { self: Analyzer =>
protected def handleError(pos: Position, msg: String): Unit = onTreeCheckerError(pos, msg)
}
-
class ImportInfo(val tree: Import, val depth: Int) {
def pos = tree.pos
def posOf(sel: ImportSelector) = tree.pos withPoint sel.namePos
@@ -1415,19 +1379,20 @@ trait Contexts { self: Analyzer =>
def isExplicitImport(name: Name): Boolean =
tree.selectors exists (_.rename == name.toTermName)
- /** The symbol with name `name` imported from import clause `tree`.
- */
- def importedSymbol(name: Name): Symbol = importedSymbol(name, requireExplicit = false)
+ /** The symbol with name `name` imported from import clause `tree`. */
+ def importedSymbol(name: Name): Symbol = importedSymbol(name, requireExplicit = false, record = true)
- private def recordUsage(sel: ImportSelector, result: Symbol) {
- def posstr = pos.source.file.name + ":" + posOf(sel).line
- def resstr = if (tree.symbol.hasCompleteInfo) s"(qual=$qual, $result)" else s"(expr=${tree.expr}, ${result.fullLocationString})"
- debuglog(s"In $this at $posstr, selector '${selectorString(sel)}' resolved to $resstr")
+ private def recordUsage(sel: ImportSelector, result: Symbol): Unit = {
+ debuglog(s"In $this at ${ pos.source.file.name }:${ posOf(sel).line }, selector '${ selectorString(sel)
+ }' resolved to ${
+ if (tree.symbol.hasCompleteInfo) s"(qual=$qual, $result)"
+ else s"(expr=${tree.expr}, ${result.fullLocationString})"
+ }")
allUsedSelectors(this) += sel
}
/** If requireExplicit is true, wildcard imports are not considered. */
- def importedSymbol(name: Name, requireExplicit: Boolean): Symbol = {
+ def importedSymbol(name: Name, requireExplicit: Boolean, record: Boolean): Symbol = {
var result: Symbol = NoSymbol
var renamed = false
var selectors = tree.selectors
@@ -1444,7 +1409,7 @@ trait Contexts { self: Analyzer =>
if (result == NoSymbol)
selectors = selectors.tail
}
- if (settings.warnUnusedImport && selectors.nonEmpty && result != NoSymbol && pos != NoPosition)
+ if (record && settings.warnUnusedImport && selectors.nonEmpty && result != NoSymbol && pos != NoPosition)
recordUsage(current, result)
// Harden against the fallout from bugs like SI-6745
diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
index 494e1e49b7..383baad2ad 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -887,7 +887,7 @@ trait Implicits {
* - find the most likely one
* - if it matches, forget about all others it improves upon
*/
- @tailrec private def rankImplicits(pending: Infos, acc: Infos): Infos = pending match {
+ @tailrec private def rankImplicits(pending: Infos, acc: List[(SearchResult, ImplicitInfo)]): List[(SearchResult, ImplicitInfo)] = pending match {
case Nil => acc
case firstPending :: otherPending =>
def firstPendingImproves(alt: ImplicitInfo) =
@@ -914,7 +914,7 @@ trait Implicits {
val pendingImprovingBest = undoLog undo {
otherPending filterNot firstPendingImproves
}
- rankImplicits(pendingImprovingBest, firstPending :: acc)
+ rankImplicits(pendingImprovingBest, (newBest, firstPending) :: acc)
}
}
@@ -930,14 +930,14 @@ trait Implicits {
// So if there is any element not improved upon by the first it is an error.
rankImplicits(eligible, Nil) match {
case Nil => ()
- case chosen :: rest =>
- rest find (alt => !improves(chosen, alt)) match {
- case Some(competing) =>
- AmbiguousImplicitError(chosen, competing, "both", "and", "")(isView, pt, tree)(context)
+ case (chosenResult, chosenInfo) :: rest =>
+ rest find { case (_, alt) => !improves(chosenInfo, alt) } match {
+ case Some((competingResult, competingInfo)) =>
+ AmbiguousImplicitError(chosenInfo, chosenResult.tree, competingInfo, competingResult.tree, "both", "and", "")(isView, pt, tree)(context)
return AmbiguousSearchFailure // Stop the search once ambiguity is encountered, see t4457_2.scala
case _ =>
- if (isView) chosen.useCountView += 1
- else chosen.useCountArg += 1
+ if (isView) chosenInfo.useCountView += 1
+ else chosenInfo.useCountArg += 1
}
}
@@ -1014,15 +1014,12 @@ trait Implicits {
}
case None =>
if (pre.isStable && !pre.typeSymbol.isExistentiallyBound) {
- val companion = companionSymbolOf(sym, context)
- companion.moduleClass match {
- case mc: ModuleClassSymbol =>
- val infos =
- for (im <- mc.implicitMembers.toList) yield new ImplicitInfo(im.name, singleType(pre, companion), im)
- if (infos.nonEmpty)
- infoMap += (sym -> infos)
- case _ =>
- }
+ val pre1 =
+ if (sym.isPackageClass) sym.packageObject.typeOfThis
+ else singleType(pre, companionSymbolOf(sym, context))
+ val infos = pre1.implicitMembers.iterator.map(mem => new ImplicitInfo(mem.name, pre1, mem)).toList
+ if (infos.nonEmpty)
+ infoMap += (sym -> infos)
}
val bts = tp.baseTypeSeq
var i = 1
@@ -1413,7 +1410,7 @@ trait Implicits {
}
if (result.isFailure && settings.debug) // debuglog is not inlined for some reason
- log("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+implicitsOfExpectedType)
+ log(s"no implicits found for ${pt} ${pt.typeSymbol.info.baseClasses} ${implicitsOfExpectedType}")
result
}
@@ -1450,9 +1447,9 @@ trait Implicits {
}
}
- object ImplicitNotFoundMsg {
- def unapply(sym: Symbol): Option[(Message)] = sym.implicitNotFoundMsg match {
- case Some(m) => Some(new Message(sym, m))
+ class ImplicitAnnotationMsg(f: Symbol => Option[String], clazz: Symbol, annotationName: String) {
+ def unapply(sym: Symbol): Option[(Message)] = f(sym) match {
+ case Some(m) => Some(new Message(sym, m, annotationName))
case None if sym.isAliasType =>
// perform exactly one step of dealiasing
// this is necessary because ClassManifests are now aliased to ClassTags
@@ -1464,41 +1461,45 @@ trait Implicits {
// check the message's syntax: should be a string literal that may contain occurrences of the string "${X}",
// where `X` refers to a type parameter of `sym`
def check(sym: Symbol): Option[String] =
- sym.getAnnotation(ImplicitNotFoundClass).flatMap(_.stringArg(0) match {
- case Some(m) => new Message(sym, m).validate
- case None => Some("Missing argument `msg` on implicitNotFound annotation.")
+ sym.getAnnotation(clazz).flatMap(_.stringArg(0) match {
+ case Some(m) => new Message(sym, m, annotationName).validate
+ case None => Some(s"Missing argument `msg` on $annotationName annotation.")
})
+ }
+
+ object ImplicitNotFoundMsg extends ImplicitAnnotationMsg(_.implicitNotFoundMsg, ImplicitNotFoundClass, "implicitNotFound")
+ object ImplicitAmbiguousMsg extends ImplicitAnnotationMsg(_.implicitAmbiguousMsg, ImplicitAmbiguousClass, "implicitAmbiguous")
+
+ class Message(sym: Symbol, msg: String, annotationName: String) {
// http://dcsobral.blogspot.com/2010/01/string-interpolation-in-scala-with.html
private val Intersobralator = """\$\{\s*([^}\s]+)\s*\}""".r
- class Message(sym: Symbol, msg: String) {
- private def interpolate(text: String, vars: Map[String, String]) =
- Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
- case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
+ private def interpolate(text: String, vars: Map[String, String]) =
+ Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
+ case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
// #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw)
- })
+ })
- private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
- private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs
+ private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
+ private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs
- def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString))
+ def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString))
- def format(typeArgs: List[String]): String =
- interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
+ def format(typeArgs: List[String]): String =
+ interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
- def validate: Option[String] = {
- val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet
- val decls = typeParamNames.toSet
+ def validate: Option[String] = {
+ val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet
+ val decls = typeParamNames.toSet
- (refs &~ decls) match {
- case s if s.isEmpty => None
- case unboundNames =>
- val singular = unboundNames.size == 1
- val ess = if (singular) "" else "s"
- val bee = if (singular) "is" else "are"
- Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @implicitNotFound annotation $bee not defined by $sym.")
- }
+ (refs &~ decls) match {
+ case s if s.isEmpty => None
+ case unboundNames =>
+ val singular = unboundNames.size == 1
+ val ess = if (singular) "" else "s"
+ val bee = if (singular) "is" else "are"
+ Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not defined by $sym.")
}
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index 9f7bdf7aff..a5fdbb5148 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -1446,7 +1446,7 @@ trait Infer extends Checkable {
log(s"Attaching AntiPolyType-carrying overloaded type to $sym")
// Multiple alternatives which are within bounds; spin up an
// overloaded type which carries an "AntiPolyType" as a prefix.
- val tparams = newAsSeenFromMap(pre, hd.owner) mapOver hd.typeParams
+ val tparams = new AsSeenFromMap(pre, hd.owner) mapOver hd.typeParams
val bounds = tparams map (_.tpeHK) // see e.g., #1236
val tpe = PolyType(tparams, OverloadedType(AntiPolyType(pre, bounds), alts))
finish(sym setInfo tpe, tpe)
diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
index f3856db552..f3632b144d 100644
--- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala
@@ -134,28 +134,28 @@ trait MethodSynthesis {
ImplicitClassWrapper(tree).createAndEnterSymbol()
}
- def enterGetterSetter(tree: ValDef) {
- val ValDef(mods, name, _, _) = tree
- if (nme.isSetterName(name))
- ValOrValWithSetterSuffixError(tree)
-
- tree.symbol = (
- if (mods.isLazy) {
+ // TODO: see if we can link symbol creation & tree derivation by sharing the Field/Getter/Setter factories
+ def enterGetterSetter(tree: ValDef): Unit = {
+ tree.symbol =
+ if (tree.mods.isLazy) {
val lazyValGetter = LazyValGetter(tree).createAndEnterSymbol()
enterLazyVal(tree, lazyValGetter)
} else {
- if (mods.isPrivateLocal)
- PrivateThisCaseClassParameterError(tree)
- val getter = Getter(tree).createAndEnterSymbol()
+ val getter = Getter(tree)
+ val getterSym = getter.createAndEnterSymbol()
+
// Create the setter if necessary.
- if (mods.isMutable)
+ if (getter.needsSetter)
Setter(tree).createAndEnterSymbol()
- // If abstract, the tree gets the getter's symbol. Otherwise, create a field.
- if (mods.isDeferred) getter setPos tree.pos
+ // If the getter's abstract the tree gets the getter's symbol,
+ // otherwise, create a field (assume the getter requires storage).
+ // NOTE: we cannot look at symbol info, since we're in the process of deriving them
+ // (luckily, they only matter for lazy vals, which we've ruled out in this else branch,
+ // and `doNotDeriveField` will skip them if `!mods.isLazy`)
+ if (Field.noFieldFor(tree)) getterSym setPos tree.pos
else enterStrictVal(tree)
}
- )
enterBeans(tree)
}
@@ -177,11 +177,11 @@ trait MethodSynthesis {
}
def addDerivedTrees(typer: Typer, stat: Tree): List[Tree] = stat match {
- case vd @ ValDef(mods, name, tpt, rhs) if !noFinishGetterSetter(vd) =>
+ case vd @ ValDef(mods, name, tpt, rhs) if deriveAccessors(vd) && !vd.symbol.isModuleVar =>
// If we don't save the annotations, they seem to wander off.
val annotations = stat.symbol.initialize.annotations
val trees = (
- allValDefDerived(vd)
+ (field(vd) ::: standardAccessors(vd) ::: beanAccessors(vd))
map (acc => atPos(vd.pos.focus)(acc derive annotations))
filterNot (_ eq EmptyTree)
)
@@ -221,11 +221,14 @@ trait MethodSynthesis {
stat :: Nil
}
- def standardAccessors(vd: ValDef): List[DerivedFromValDef] = (
- if (vd.mods.isMutable && !vd.mods.isLazy) List(Getter(vd), Setter(vd))
- else if (vd.mods.isLazy) List(LazyValGetter(vd))
- else List(Getter(vd))
- )
+ def standardAccessors(vd: ValDef): List[DerivedFromValDef] =
+ if (vd.mods.isLazy) List(LazyValGetter(vd))
+ else {
+ val getter = Getter(vd)
+ if (getter.needsSetter) List(getter, Setter(vd))
+ else List(getter)
+ }
+
def beanAccessors(vd: ValDef): List[DerivedFromValDef] = {
val setter = if (vd.mods.isMutable) List(BeanSetter(vd)) else Nil
if (vd.symbol hasAnnotation BeanPropertyAttr)
@@ -234,15 +237,8 @@ trait MethodSynthesis {
BooleanBeanGetter(vd) :: setter
else Nil
}
- def allValDefDerived(vd: ValDef) = {
- val field = if (vd.mods.isDeferred || (vd.mods.isLazy && hasUnitType(vd.symbol))) Nil
- else List(Field(vd))
- field ::: standardAccessors(vd) ::: beanAccessors(vd)
- }
- // Take into account annotations so that we keep annotated unit lazy val
- // to get better error message already from the cps plugin itself
- def hasUnitType(sym: Symbol) = (sym.tpe.typeSymbol == UnitClass) && sym.tpe.annotations.isEmpty
+ def field(vd: ValDef): List[Field] = if (Field.noFieldFor(vd)) Nil else List(Field(vd))
/** This trait assembles what's needed for synthesizing derived methods.
* Important: Typically, instances of this trait are created TWICE for each derived
@@ -260,7 +256,6 @@ trait MethodSynthesis {
def name: TermName
/** The flags that are retained from the original symbol */
-
def flagsMask: Long
/** The flags that the derived symbol has in addition to those retained from
@@ -284,8 +279,9 @@ trait MethodSynthesis {
def enclClass: Symbol
// Final methods to make the rest easier to reason about.
- final def mods = tree.mods
- final def basisSym = tree.symbol
+ final def mods = tree.mods
+ final def basisSym = tree.symbol
+ final def derivedMods = mods & flagsMask | flagsExtra
}
sealed trait DerivedFromClassDef extends DerivedFromMemberDef {
@@ -305,7 +301,6 @@ trait MethodSynthesis {
/* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */
final def completer(sym: Symbol) = namerOf(sym).accessorTypeCompleter(tree, isSetter)
final def fieldSelection = Select(This(enclClass), basisSym)
- final def derivedMods: Modifiers = mods & flagsMask | flagsExtra mapAnnotations (_ => Nil)
def derivedSym: Symbol = tree.symbol
def derivedTree: Tree = EmptyTree
@@ -314,8 +309,8 @@ trait MethodSynthesis {
def isDeferred = mods.isDeferred
def keepClean = false // whether annotations whose definitions are not meta-annotated should be kept.
def validate() { }
- def createAndEnterSymbol(): Symbol = {
- val sym = owner.newMethod(name, tree.pos.focus, (tree.mods.flags & flagsMask) | flagsExtra)
+ def createAndEnterSymbol(): MethodSymbol = {
+ val sym = owner.newMethod(name, tree.pos.focus, derivedMods.flags)
setPrivateWithin(tree, sym)
enterInScope(sym)
sym setInfo completer(sym)
@@ -333,7 +328,8 @@ trait MethodSynthesis {
}
}
sealed trait DerivedGetter extends DerivedFromValDef {
- // TODO
+ // A getter must be accompanied by a setter if the ValDef is mutable.
+ def needsSetter = mods.isMutable
}
sealed trait DerivedSetter extends DerivedFromValDef {
override def isSetter = true
@@ -341,10 +337,13 @@ trait MethodSynthesis {
case (p :: Nil) :: _ => p
case _ => NoSymbol
}
- private def setterRhs = (
- if (mods.isDeferred || derivedSym.isOverloaded) EmptyTree
+
+ private def setterRhs = {
+ assert(!derivedSym.isOverloaded, s"Unexpected overloaded setter $derivedSym for $basisSym in $enclClass")
+ if (Field.noFieldFor(tree) || derivedSym.isOverloaded) EmptyTree
else Assign(fieldSelection, Ident(setterParam))
- )
+ }
+
private def setterDef = DefDef(derivedSym, setterRhs)
override def derivedTree: Tree = if (setterParam == NoSymbol) EmptyTree else setterDef
}
@@ -363,8 +362,7 @@ trait MethodSynthesis {
context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $name in $enclClass / ${enclClass.info.decls}")
result
}
- def derivedTree: DefDef =
- factoryMeth(mods & flagsMask | flagsExtra, name, tree)
+ def derivedTree: DefDef = factoryMeth(derivedMods, name, tree)
def flagsExtra: Long = METHOD | IMPLICIT | SYNTHETIC
def flagsMask: Long = AccessFlags
def name: TermName = tree.name.toTermName
@@ -385,8 +383,9 @@ trait MethodSynthesis {
}
}
case class Getter(tree: ValDef) extends BaseGetter(tree) {
- override def derivedSym = if (mods.isDeferred) basisSym else basisSym.getterIn(enclClass)
- private def derivedRhs = if (mods.isDeferred) EmptyTree else fieldSelection
+ override def derivedSym = if (Field.noFieldFor(tree)) basisSym else basisSym.getterIn(enclClass)
+ private def derivedRhs = if (Field.noFieldFor(tree)) tree.rhs else fieldSelection
+
private def derivedTpt = {
// For existentials, don't specify a type for the getter, even one derived
// from the symbol! This leads to incompatible existentials for the field and
@@ -400,19 +399,21 @@ trait MethodSynthesis {
// Range position errors ensue if we don't duplicate this in some
// circumstances (at least: concrete vals with existential types.)
case ExistentialType(_, _) => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus)
- case _ if mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field
+ case _ if isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field
case tp => TypeTree(tp)
}
tpt setPos tree.tpt.pos.focus
}
override def derivedTree: DefDef = newDefDef(derivedSym, derivedRhs)(tpt = derivedTpt)
}
+
/** Implements lazy value accessors:
- * - for lazy values of type Unit and all lazy fields inside traits,
- * the rhs is the initializer itself
- * - for all other lazy values z the accessor is a block of this form:
- * { z = <rhs>; z } where z can be an identifier or a field.
- */
+ * - for lazy values of type Unit and all lazy fields inside traits,
+ * the rhs is the initializer itself, because we'll just "compute" the result on every access
+ * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap)
+ * - for all other lazy values z the accessor is a block of this form:
+ * { z = <rhs>; z } where z can be an identifier or a field.
+ */
case class LazyValGetter(tree: ValDef) extends BaseGetter(tree) {
class ChangeOwnerAndModuleClassTraverser(oldowner: Symbol, newowner: Symbol)
extends ChangeOwnerTraverser(oldowner, newowner) {
@@ -432,10 +433,10 @@ trait MethodSynthesis {
override def derivedTree: DefDef = {
val ValDef(_, _, tpt0, rhs0) = tree
val rhs1 = context.unit.transformed.getOrElse(rhs0, rhs0)
- val body = (
- if (tree.symbol.owner.isTrait || hasUnitType(basisSym)) rhs1
+ val body =
+ if (tree.symbol.owner.isTrait || Field.noFieldFor(tree)) rhs1 // TODO move tree.symbol.owner.isTrait into noFieldFor
else gen.mkAssignAndReturn(basisSym, rhs1)
- )
+
derivedSym setPos tree.pos // cannot set it at createAndEnterSymbol because basisSym can possibly still have NoPosition
val ddefRes = DefDef(derivedSym, new ChangeOwnerAndModuleClassTraverser(basisSym, derivedSym)(body))
// ValDef will have its position focused whereas DefDef will have original correct rangepos
@@ -454,6 +455,24 @@ trait MethodSynthesis {
override def derivedSym = basisSym.setterIn(enclClass)
}
+
+ object Field {
+ // No field for these vals (either never emitted or eliminated later on):
+ // - abstract vals have no value we could store (until they become concrete, potentially)
+ // - lazy vals of type Unit
+ // - [Emitted, later removed during AddInterfaces/Mixins] concrete vals in traits can't have a field
+ // - [Emitted, later removed during Constructors] a concrete val with a statically known value (Unit / ConstantType)
+ // performs its side effect according to lazy/strict semantics, but doesn't need to store its value
+ // each access will "evaluate" the RHS (a literal) again
+ // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer.
+ // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does.
+ // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`),
+ // as the symbol info is in the process of being created then.
+ // TODO: harmonize tree & symbol creation
+ // TODO: the `def field` call-site does not tollerate including `|| vd.symbol.owner.isTrait` --> tests break
+ def noFieldFor(vd: ValDef) = vd.mods.isDeferred || (vd.mods.isLazy && isUnitType(vd.symbol.info)) // || vd.symbol.owner.isTrait))
+ }
+
case class Field(tree: ValDef) extends DerivedFromValDef {
def name = tree.localName
def category = FieldTargetClass
@@ -462,11 +481,13 @@ trait MethodSynthesis {
// By default annotations go to the field, except if the field is
// generated for a class parameter (PARAMACCESSOR).
override def keepClean = !mods.isParamAccessor
- override def derivedTree = (
- if (mods.isDeferred) EmptyTree
- else if (mods.isLazy) copyValDef(tree)(mods = mods | flagsExtra, name = this.name, rhs = EmptyTree).setPos(tree.pos.focus)
+
+ // handle lazy val first for now (we emit a Field even though we probably shouldn't...)
+ override def derivedTree =
+ if (mods.isLazy) copyValDef(tree)(mods = mods | flagsExtra, name = this.name, rhs = EmptyTree).setPos(tree.pos.focus)
+ else if (Field.noFieldFor(tree)) EmptyTree
else copyValDef(tree)(mods = mods | flagsExtra, name = this.name)
- )
+
}
case class Param(tree: ValDef) extends DerivedFromValDef {
def name = tree.name
@@ -501,12 +522,12 @@ trait MethodSynthesis {
// Derives a tree without attempting to use the original tree's symbol.
override def derivedTree = {
atPos(tree.pos.focus) {
- DefDef(derivedMods, name, Nil, ListOfNil, tree.tpt.duplicate,
+ DefDef(derivedMods mapAnnotations (_ => Nil), name, Nil, ListOfNil, tree.tpt.duplicate,
if (isDeferred) EmptyTree else Select(This(owner), tree.name)
)
}
}
- override def createAndEnterSymbol(): Symbol = enterSyntheticSym(derivedTree)
+ override def createAndEnterSymbol(): MethodSymbol = enterSyntheticSym(derivedTree).asInstanceOf[MethodSymbol]
}
case class BooleanBeanGetter(tree: ValDef) extends BeanAccessor("is") with AnyBeanGetter { }
case class BeanGetter(tree: ValDef) extends BeanAccessor("get") with AnyBeanGetter { }
diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
index 4ad81b60ae..254cb7111c 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -115,21 +115,14 @@ trait Namers extends MethodSynthesis {
TypeSigError(tree, ex)
alt
}
- // PRIVATE | LOCAL are fields generated for primary constructor arguments
- // @PP: ...or fields declared as private[this]. PARAMACCESSOR marks constructor arguments.
- // Neither gets accessors so the code is as far as I know still correct.
- def noEnterGetterSetter(vd: ValDef) = !vd.mods.isLazy && (
- !owner.isClass
- || (vd.mods.isPrivateLocal && !vd.mods.isCaseAccessor)
- || (vd.name startsWith nme.OUTER)
- || (context.unit.isJava)
- || isEnumConstant(vd)
- )
- def noFinishGetterSetter(vd: ValDef) = (
- (vd.mods.isPrivateLocal && !vd.mods.isLazy) // all lazy vals need accessors, even private[this]
- || vd.symbol.isModuleVar
- || isEnumConstant(vd))
+ // All lazy vals need accessors, including those owned by terms (e.g., in method) or private[this] in a class
+ def deriveAccessors(vd: ValDef) = vd.mods.isLazy || (owner.isClass && deriveAccessorsInClass(vd))
+
+ private def deriveAccessorsInClass(vd: ValDef) =
+ !vd.mods.isPrivateLocal && // note, private[this] lazy vals do get accessors -- see outer disjunction of deriveAccessors
+ !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter
+ !isEnumConstant(vd) // enums can only occur in classes, so only check here
/** Determines whether this field holds an enum constant.
* To qualify, the following conditions must be met:
@@ -655,11 +648,15 @@ trait Namers extends MethodSynthesis {
}
}
- def enterValDef(tree: ValDef) {
- if (noEnterGetterSetter(tree))
- assignAndEnterFinishedSymbol(tree)
- else
- enterGetterSetter(tree)
+ def enterValDef(tree: ValDef): Unit = {
+ val isScala = !context.unit.isJava
+ if (isScala) {
+ if (nme.isSetterName(tree.name)) ValOrVarWithSetterSuffixError(tree)
+ if (tree.mods.isPrivateLocal && tree.mods.isCaseAccessor) PrivateThisCaseClassParameterError(tree)
+ }
+
+ if (isScala && deriveAccessors(tree)) enterGetterSetter(tree)
+ else assignAndEnterFinishedSymbol(tree)
if (isEnumConstant(tree))
tree.symbol setInfo ConstantType(Constant(tree.symbol))
diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
index 39cd610b1c..1bc5daac65 100644
--- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
@@ -9,6 +9,7 @@ package typechecker
import symtab.Flags._
import scala.collection.mutable
import scala.reflect.ClassTag
+import PartialFunction.{ cond => when }
/**
* @author Lukas Rytz
@@ -551,64 +552,73 @@ trait NamesDefaults { self: Analyzer =>
}
}
- /**
- * Removes name assignments from args. Additionally, returns an array mapping
- * argument indices from call-site-order to definition-site-order.
+ /** Removes name assignments from args. Additionally, returns an array mapping
+ * argument indices from call-site-order to definition-site-order.
*
- * Verifies that names are not specified twice, positional args don't appear
- * after named ones.
+ * Verifies that names are not specified twice, and positional args don't appear after named ones.
*/
def removeNames(typer: Typer)(args: List[Tree], params: List[Symbol]): (List[Tree], Array[Int]) = {
implicit val context0 = typer.context
- // maps indices from (order written by user) to (order of definition)
- val argPos = Array.fill(args.length)(-1)
- var positionalAllowed = true
- val namelessArgs = mapWithIndex(args) { (arg, argIndex) =>
- arg match {
- case arg @ AssignOrNamedArg(Ident(name), rhs) =>
- def matchesName(param: Symbol) = !param.isSynthetic && (
- (param.name == name) || (param.deprecatedParamName match {
- case Some(`name`) =>
- context0.deprecationWarning(arg.pos, param,
- s"the parameter name $name has been deprecated. Use ${param.name} instead.")
- true
- case _ => false
- })
- )
- val paramPos = params indexWhere matchesName
- if (paramPos == -1) {
- if (positionalAllowed) {
- argPos(argIndex) = argIndex
- // prevent isNamed from being true when calling doTypedApply recursively,
- // treat the arg as an assignment of type Unit
- Assign(arg.lhs, rhs) setPos arg.pos
- }
- else UnknownParameterNameNamesDefaultError(arg, name)
- }
- else if (argPos contains paramPos) {
+ def matchesName(param: Symbol, name: Name, argIndex: Int) = {
+ def warn(w: String) = context0.deprecationWarning(args(argIndex).pos, param, w)
+ def checkDeprecation(anonOK: Boolean) =
+ when (param.deprecatedParamName) {
+ case Some(`name`) => true
+ case Some(nme.NO_NAME) => anonOK
+ }
+ def checkName = {
+ val res = param.name == name
+ if (res && checkDeprecation(true)) warn(s"naming parameter $name has been deprecated.")
+ res
+ }
+ def checkAltName = {
+ val res = checkDeprecation(false)
+ if (res) warn(s"the parameter name $name has been deprecated. Use ${param.name} instead.")
+ res
+ }
+ !param.isSynthetic && (checkName || checkAltName)
+ }
+ // argPos maps indices from (order written by user) to (order of definition)
+ val argPos = Array.fill(args.length)(-1)
+ val namelessArgs = {
+ var positionalAllowed = true
+ def stripNamedArg(arg: AssignOrNamedArg, argIndex: Int): Tree = {
+ val AssignOrNamedArg(Ident(name), rhs) = arg
+ params indexWhere (p => matchesName(p, name, argIndex)) match {
+ case -1 if positionalAllowed =>
+ // prevent isNamed from being true when calling doTypedApply recursively,
+ // treat the arg as an assignment of type Unit
+ Assign(arg.lhs, rhs) setPos arg.pos
+ case -1 =>
+ UnknownParameterNameNamesDefaultError(arg, name)
+ case paramPos if argPos contains paramPos =>
val existingArgIndex = argPos.indexWhere(_ == paramPos)
- val otherName = args(paramPos) match {
- case AssignOrNamedArg(Ident(oName), rhs) if oName != name => Some(oName)
- case _ => None
+ val otherName = Some(args(paramPos)) collect {
+ case AssignOrNamedArg(Ident(oName), _) if oName != name => oName
}
DoubleParamNamesDefaultError(arg, name, existingArgIndex+1, otherName)
- } else if (isAmbiguousAssignment(typer, params(paramPos), arg))
+ case paramPos if isAmbiguousAssignment(typer, params(paramPos), arg) =>
AmbiguousReferenceInNamesDefaultError(arg, name)
- else {
- // if the named argument is on the original parameter
- // position, positional after named is allowed.
- if (argIndex != paramPos)
- positionalAllowed = false
- argPos(argIndex) = paramPos
+ case paramPos if paramPos != argIndex =>
+ positionalAllowed = false // named arg is not in original parameter order: require names after this
+ argPos(argIndex) = paramPos // fix up the arg position
rhs
- }
- case _ =>
- argPos(argIndex) = argIndex
- if (positionalAllowed) arg
- else PositionalAfterNamedNamesDefaultError(arg)
+ case _ => rhs
+ }
+ }
+ mapWithIndex(args) {
+ case (arg: AssignOrNamedArg, argIndex) =>
+ val t = stripNamedArg(arg, argIndex)
+ if (!t.isErroneous && argPos(argIndex) < 0) argPos(argIndex) = argIndex
+ t
+ case (arg, argIndex) =>
+ if (positionalAllowed) {
+ argPos(argIndex) = argIndex
+ arg
+ } else
+ PositionalAfterNamedNamesDefaultError(arg)
}
}
-
(namelessArgs, argPos)
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index 90ac1f466d..7252cae0b5 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -1188,20 +1188,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
// set NoType so it will be ignored.
val cdef = ClassDef(module.moduleClass, impl) setType NoType
- // Create the module var unless the immediate owner is a class and
- // the module var already exists there. See SI-5012, SI-6712.
- def findOrCreateModuleVar() = {
- val vsym = (
- if (site.isTerm) NoSymbol
- else site.info decl nme.moduleVarName(moduleName)
- )
- vsym orElse (site newModuleVarSymbol module)
- }
def newInnerObject() = {
- // Create the module var unless it is already in the module owner's scope.
- // The lookup is on module.enclClass and not module.owner lest there be a
- // nullary method between us and the class; see SI-5012.
- val moduleVar = findOrCreateModuleVar()
+ val moduleVar = site newModuleVarSymbol module
val rhs = gen.newModule(module, moduleVar.tpe)
val body = if (site.isTrait) rhs else gen.mkAssignAndReturn(moduleVar, rhs)
val accessor = DefDef(module, body.changeOwner(moduleVar -> module))
@@ -1217,6 +1205,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
val newTrees = cdef :: (
if (module.isStatic)
+ // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O.
if (module.isOverridingSymbol) matchingInnerObject() else Nil
else
newInnerObject()
@@ -1468,10 +1457,13 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
case m: MemberDef =>
val sym = m.symbol
applyChecks(sym.annotations)
- // validate implicitNotFoundMessage
- analyzer.ImplicitNotFoundMsg.check(sym) foreach { warn =>
- reporter.warning(tree.pos, f"Invalid implicitNotFound message for ${sym}%s${sym.locationString}%s:%n$warn")
- }
+
+ def messageWarning(name: String)(warn: String) =
+ reporter.warning(tree.pos, f"Invalid $name message for ${sym}%s${sym.locationString}%s:%n$warn")
+
+ // validate implicitNotFoundMessage and implicitAmbiguousMessage
+ analyzer.ImplicitNotFoundMsg.check(sym) foreach messageWarning("implicitNotFound")
+ analyzer.ImplicitAmbiguousMsg.check(sym) foreach messageWarning("implicitAmbiguous")
case tpt@TypeTree() =>
if(tpt.original != null) {
diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
index 966e8f1abe..4ccc183334 100644
--- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala
@@ -87,24 +87,6 @@ trait SyntheticMethods extends ast.TreeDSL {
def accessors = clazz.caseFieldAccessors
val arity = accessors.size
- // If this is ProductN[T1, T2, ...], accessorLub is the lub of T1, T2, ..., .
- // !!! Hidden behind -Xexperimental due to bummer type inference bugs.
- // Refining from Iterator[Any] leads to types like
- //
- // Option[Int] { def productIterator: Iterator[String] }
- //
- // appearing legitimately, but this breaks invariant places
- // like Tags and Arrays which are not robust and infer things
- // which they shouldn't.
- val accessorLub = (
- if (settings.Xexperimental) {
- global.lub(accessors map (_.tpe.finalResultType)) match {
- case RefinedType(parents, decls) if !decls.isEmpty => intersectionType(parents)
- case tp => tp
- }
- }
- else AnyTpe
- )
def forwardToRuntime(method: Symbol): Tree =
forwardMethod(method, getMember(ScalaRunTimeModule, (method.name prepend "_")))(mkThis :: _)
@@ -125,8 +107,8 @@ trait SyntheticMethods extends ast.TreeDSL {
}
}
def productIteratorMethod = {
- createMethod(nme.productIterator, iteratorOfType(accessorLub))(_ =>
- gen.mkMethodCall(ScalaRunTimeModule, nme.typedProductIterator, List(accessorLub), List(mkThis))
+ createMethod(nme.productIterator, iteratorOfType(AnyTpe))(_ =>
+ gen.mkMethodCall(ScalaRunTimeModule, nme.typedProductIterator, List(AnyTpe), List(mkThis))
)
}
@@ -246,7 +228,7 @@ trait SyntheticMethods extends ast.TreeDSL {
List(
Product_productPrefix -> (() => constantNullary(nme.productPrefix, clazz.name.decode)),
Product_productArity -> (() => constantNullary(nme.productArity, arity)),
- Product_productElement -> (() => perElementMethod(nme.productElement, accessorLub)(mkThisSelect)),
+ Product_productElement -> (() => perElementMethod(nme.productElement, AnyTpe)(mkThisSelect)),
Product_iterator -> (() => productIteratorMethod),
Product_canEqual -> (() => canEqualMethod)
// This is disabled pending a reimplementation which doesn't add any
@@ -322,6 +304,7 @@ trait SyntheticMethods extends ast.TreeDSL {
clazz.isModuleClass
&& clazz.isSerializable
&& !hasConcreteImpl(nme.readResolve)
+ && clazz.isStatic
)
def synthesize(): List[Tree] = {
@@ -353,16 +336,18 @@ trait SyntheticMethods extends ast.TreeDSL {
}
for ((m, impl) <- methods ; if shouldGenerate(m)) yield impl()
}
- def extras = (
+ def extras = {
if (needsReadResolve) {
// Aha, I finally decoded the original comment.
// This method should be generated as private, but apparently if it is, then
// it is name mangled afterward. (Wonder why that is.) So it's only protected.
// For sure special methods like "readResolve" should not be mangled.
- List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => { m setFlag PRIVATE ; REF(clazz.sourceModule) }))
+ List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => {
+ m setFlag PRIVATE; REF(clazz.sourceModule)
+ }))
}
else Nil
- )
+ }
try impls ++ extras
catch { case _: TypeError if reporter.hasErrors => Nil }
@@ -380,7 +365,14 @@ trait SyntheticMethods extends ast.TreeDSL {
for (ddef @ DefDef(_, _, _, _, _, _) <- templ.body ; if isRewrite(ddef.symbol)) {
val original = ddef.symbol
- val newAcc = deriveMethod(ddef.symbol, name => context.unit.freshTermName(name + "$")) { newAcc =>
+ val i = original.owner.caseFieldAccessors.indexOf(original)
+ def freshAccessorName = {
+ devWarning(s"Unable to find $original among case accessors of ${original.owner}: ${original.owner.caseFieldAccessors}")
+ context.unit.freshTermName(original.name + "$")
+ }
+ def nameSuffixedByParamIndex = original.name.append(nme.CASE_ACCESSOR + "$" + i).toTermName
+ val newName = if (i < 0) freshAccessorName else nameSuffixedByParamIndex
+ val newAcc = deriveMethod(ddef.symbol, name => newName) { newAcc =>
newAcc.makePublic
newAcc resetFlag (ACCESSOR | PARAMACCESSOR | OVERRIDE)
ddef.rhs.duplicate
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 8113cd9b96..533ef13574 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -542,7 +542,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
val qual = typedQualifier { atPos(tree.pos.makeTransparent) {
tree match {
- case Ident(_) => Ident(rootMirror.getPackageObjectWithMember(pre, sym))
+ case Ident(_) =>
+ val packageObject =
+ if (!sym.isOverloaded && sym.owner.isModuleClass) sym.owner.sourceModule // historical optimization, perhaps no longer needed
+ else pre.typeSymbol.packageObject
+ Ident(packageObject)
case Select(qual, _) => Select(qual, nme.PACKAGEkw)
case SelectFromTypeTree(qual, _) => Select(qual, nme.PACKAGEkw)
}
@@ -928,24 +932,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
def insertApply(): Tree = {
assert(!context.inTypeConstructorAllowed, mode) //@M
val adapted = adaptToName(tree, nme.apply)
- def stabilize0(pre: Type): Tree = stabilize(adapted, pre, MonoQualifierModes, WildcardType)
-
- // TODO reconcile the overlap between Typers#stablize and TreeGen.stabilize
- val qual = adapted match {
- case This(_) =>
- gen.stabilize(adapted)
- case Ident(_) =>
- val owner = adapted.symbol.owner
- val pre =
- if (owner.isPackageClass) owner.thisType
- else if (owner.isClass) context.enclosingSubClassContext(owner).prefix
- else NoPrefix
- stabilize0(pre)
- case Select(qualqual, _) =>
- stabilize0(qualqual.tpe)
- case other =>
- other
- }
+ val qual = gen.stabilize(adapted)
typedPos(tree.pos, mode, pt) {
Select(qual setPos tree.pos.makeTransparent, nme.apply)
}
@@ -1707,6 +1694,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
psym addChild context.owner
else
pending += ParentSealedInheritanceError(parent, psym)
+ if (psym.isLocalToBlock && !phase.erasedTypes)
+ psym addChild context.owner
val parentTypeOfThis = parent.tpe.dealias.typeOfThis
if (!(selfType <:< parentTypeOfThis) &&
@@ -2228,7 +2217,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val allParams = meth.paramss.flatten
for (p <- allParams) {
for (n <- p.deprecatedParamName) {
- if (allParams.exists(p1 => p1.name == n || (p != p1 && p1.deprecatedParamName.exists(_ == n))))
+ if (allParams.exists(p1 => p != p1 && (p1.name == n || p1.deprecatedParamName.exists(_ == n))))
DeprecatedParamNameError(p, n)
}
}
@@ -5228,7 +5217,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (refTyped.isErrorTyped) {
setError(tree)
} else {
- tree setType refTyped.tpe.resultType
+ tree setType refTyped.tpe.resultType.deconst
if (refTyped.isErrorTyped || treeInfo.admitsTypeSelection(refTyped)) tree
else UnstableTreeError(tree)
}
diff --git a/src/compiler/scala/tools/reflect/WrappedProperties.scala b/src/compiler/scala/tools/reflect/WrappedProperties.scala
index 523287fc66..348d000d15 100644
--- a/src/compiler/scala/tools/reflect/WrappedProperties.scala
+++ b/src/compiler/scala/tools/reflect/WrappedProperties.scala
@@ -30,9 +30,10 @@ trait WrappedProperties extends PropertiesTrait {
def systemProperties: List[(String, String)] = {
import scala.collection.JavaConverters._
wrap {
+ // SI-7269,7775 Avoid `ConcurrentModificationException` and nulls if another thread modifies properties
val props = System.getProperties
- // SI-7269 Be careful to avoid `ConcurrentModificationException` if another thread modifies the properties map
- props.stringPropertyNames().asScala.toList.map(k => (k, props.get(k).asInstanceOf[String]))
+ val it = props.stringPropertyNames().asScala.iterator map (k => (k, props getProperty k)) filter (_._2 ne null)
+ it.toList
} getOrElse Nil
}
}
diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala
index 8e5b1e0a5c..09c6c9d744 100644
--- a/src/compiler/scala/tools/util/PathResolver.scala
+++ b/src/compiler/scala/tools/util/PathResolver.scala
@@ -52,7 +52,7 @@ object PathResolver {
*/
object Environment {
private def searchForBootClasspath =
- systemProperties find (_._1 endsWith ".boot.class.path") map (_._2) getOrElse ""
+ systemProperties collectFirst { case (k, v) if k endsWith ".boot.class.path" => v } getOrElse ""
/** Environment variables which java pays attention to so it
* seems we do as well.
@@ -254,17 +254,7 @@ abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFil
* TODO: we should refactor this as a separate -bootstrap option to have a clean implementation, no? */
def sourcePath = if (!settings.isScaladoc) cmdLineOrElse("sourcepath", Defaults.scalaSourcePath) else ""
- /** Against my better judgment, giving in to martin here and allowing
- * CLASSPATH to be used automatically. So for the user-specified part
- * of the classpath:
- *
- * - If -classpath or -cp is given, it is that
- * - Otherwise, if CLASSPATH is set, it is that
- * - If neither of those, then "." is used.
- */
- def userClassPath =
- if (!settings.classpath.isDefault) settings.classpath.value
- else sys.env.getOrElse("CLASSPATH", ".")
+ def userClassPath = settings.classpath.value // default is specified by settings and can be overridden there
import classPathFactory._