summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala2
-rw-r--r--src/compiler/scala/reflect/macros/compiler/Errors.scala1
-rw-r--r--src/compiler/scala/reflect/macros/compiler/Resolvers.scala6
-rw-r--r--src/compiler/scala/reflect/macros/compiler/Validators.scala2
-rw-r--r--src/compiler/scala/reflect/macros/util/Helpers.scala6
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Parsers.scala2
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Reifiers.scala1
-rw-r--r--src/compiler/scala/reflect/reify/Reifier.scala1
-rw-r--r--src/compiler/scala/reflect/reify/Taggers.scala3
-rw-r--r--src/compiler/scala/reflect/reify/codegen/GenUtils.scala4
-rw-r--r--src/compiler/scala/tools/nsc/CompilationUnits.scala1
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala7
-rw-r--r--src/compiler/scala/tools/nsc/Parsing.scala1
-rw-r--r--src/compiler/scala/tools/nsc/Properties.scala4
-rw-r--r--src/compiler/scala/tools/nsc/Reporting.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala18
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala21
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala20
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala13
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala184
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala510
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala1
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala26
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala33
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaVersion.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala1
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala48
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/OverridingPairs.scala1
-rw-r--r--src/compiler/scala/tools/nsc/transform/Statics.scala3
-rw-r--r--src/compiler/scala/tools/nsc/transform/TailCalls.scala41
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala1
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala1
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Tags.scala3
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Unapplies.scala25
-rw-r--r--src/compiler/scala/tools/util/Javap.scala32
43 files changed, 795 insertions, 255 deletions
diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala
index a13a778b2f..b8384851da 100644
--- a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala
+++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala
@@ -12,7 +12,7 @@ abstract class DefaultMacroCompiler extends Resolvers
import treeInfo._
import definitions._
val runDefinitions = currentRun.runDefinitions
- import runDefinitions.{Predef_???, _}
+ import runDefinitions.Predef_???
val typer: global.analyzer.Typer
val context = typer.context
diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala
index cc4508e696..98fd091e9c 100644
--- a/src/compiler/scala/reflect/macros/compiler/Errors.scala
+++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala
@@ -11,7 +11,6 @@ trait Errors extends Traces {
import analyzer._
import definitions._
import treeInfo._
- import typer.TyperErrorGen._
import typer.infer.InferErrorGen._
import runDefinitions._
def globalSettings = global.settings
diff --git a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala
index 4484c234aa..d3f49390ea 100644
--- a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala
+++ b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala
@@ -1,18 +1,12 @@
package scala.reflect.macros
package compiler
-import scala.reflect.internal.Flags._
-import scala.reflect.macros.TypecheckException
-
trait Resolvers {
self: DefaultMacroCompiler =>
import global._
import analyzer._
- import definitions._
import treeInfo._
- import gen._
- import runDefinitions._
trait Resolver {
self: MacroImplRefCompiler =>
diff --git a/src/compiler/scala/reflect/macros/compiler/Validators.scala b/src/compiler/scala/reflect/macros/compiler/Validators.scala
index a146818ae3..fc932f2b18 100644
--- a/src/compiler/scala/reflect/macros/compiler/Validators.scala
+++ b/src/compiler/scala/reflect/macros/compiler/Validators.scala
@@ -9,7 +9,7 @@ trait Validators {
import global._
import analyzer._
import definitions._
- import runDefinitions.{Predef_???, _}
+ import runDefinitions.Predef_???
trait Validator {
self: MacroImplRefCompiler =>
diff --git a/src/compiler/scala/reflect/macros/util/Helpers.scala b/src/compiler/scala/reflect/macros/util/Helpers.scala
index bddc42d1f9..961c41dab5 100644
--- a/src/compiler/scala/reflect/macros/util/Helpers.scala
+++ b/src/compiler/scala/reflect/macros/util/Helpers.scala
@@ -54,14 +54,10 @@ trait Helpers {
*
* @see Metalevels.scala for more information and examples about metalevels
*/
- def increaseMetalevel(pre: Type, tp: Type): Type = {
- val runDefinitions = currentRun.runDefinitions
- import runDefinitions._
-
+ def increaseMetalevel(pre: Type, tp: Type): Type =
transparentShallowTransform(RepeatedParamClass, tp) {
case tp => typeRef(pre, MacroContextExprClass, List(tp))
}
- }
/** Transforms c.Expr[T] types into c.Tree and leaves the rest unchanged.
*/
diff --git a/src/compiler/scala/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/reflect/quasiquotes/Parsers.scala
index 007bac27da..97ec7dbfc3 100644
--- a/src/compiler/scala/reflect/quasiquotes/Parsers.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Parsers.scala
@@ -90,7 +90,7 @@ trait Parsers { self: Quasiquotes =>
case _ => super.makePatDef(mods, pat, rhs)
}
}
- import treeBuilder.{global => _, unit => _, _}
+ import treeBuilder.{global => _, unit => _}
// q"def foo($x)"
override def param(owner: Name, implicitmod: Int, caseParam: Boolean): ValDef =
diff --git a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
index 07becdc3c6..cc98717c4e 100644
--- a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
@@ -8,7 +8,6 @@ import scala.reflect.internal.Flags._
trait Reifiers { self: Quasiquotes =>
import global._
import global.build._
- import global.treeInfo._
import global.definitions._
import Rank._
import universeTypes._
diff --git a/src/compiler/scala/reflect/reify/Reifier.scala b/src/compiler/scala/reflect/reify/Reifier.scala
index b1cc797389..a3e0f02dcc 100644
--- a/src/compiler/scala/reflect/reify/Reifier.scala
+++ b/src/compiler/scala/reflect/reify/Reifier.scala
@@ -21,7 +21,6 @@ abstract class Reifier extends States
import global._
import definitions._
private val runDefinitions = currentRun.runDefinitions
- import runDefinitions._
val typer: global.analyzer.Typer
val universe: Tree
diff --git a/src/compiler/scala/reflect/reify/Taggers.scala b/src/compiler/scala/reflect/reify/Taggers.scala
index 093c2bee22..0863ee38f9 100644
--- a/src/compiler/scala/reflect/reify/Taggers.scala
+++ b/src/compiler/scala/reflect/reify/Taggers.scala
@@ -79,8 +79,7 @@ abstract class Taggers {
try materializer
catch {
case ReificationException(pos, msg) =>
- c.error(pos.asInstanceOf[c.Position], msg) // this cast is a very small price for the sanity of exception handling
- EmptyTree
+ c.abort(pos.asInstanceOf[c.Position], msg) // this cast is a very small price for the sanity of exception handling
case UnexpectedReificationException(pos, err, cause) if cause != null =>
throw cause
}
diff --git a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
index 4512b2cb6f..de9fec0df5 100644
--- a/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
+++ b/src/compiler/scala/reflect/reify/codegen/GenUtils.scala
@@ -5,10 +5,6 @@ trait GenUtils {
self: Reifier =>
import global._
- import treeInfo._
- import definitions._
- private val runDefinitions = currentRun.runDefinitions
- import runDefinitions._
def reifyList(xs: List[Any]): Tree =
mkList(xs map reify)
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala
index 0a356ed7b6..1a6843a249 100644
--- a/src/compiler/scala/tools/nsc/CompilationUnits.scala
+++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala
@@ -8,7 +8,6 @@ package scala.tools.nsc
import scala.reflect.internal.util.{ SourceFile, NoSourceFile, FreshNameCreator }
import scala.collection.mutable
import scala.collection.mutable.{ LinkedHashSet, ListBuffer }
-import scala.tools.nsc.reporters.Reporter
trait CompilationUnits { global: Global =>
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 76d8b7e5ef..9cc9712b44 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -9,17 +9,16 @@ package nsc
import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException }
import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException }
-import java.util.UUID._
import scala.compat.Platform.currentTime
import scala.collection.{ mutable, immutable }
import io.{ SourceReader, AbstractFile, Path }
import reporters.{ Reporter, ConsoleReporter }
-import util.{ ClassPath, MergedClassPath, StatisticsInfo, returning, stackTraceString }
+import util.{ ClassPath, StatisticsInfo, returning, stackTraceString }
import scala.reflect.ClassTag
import scala.reflect.internal.util.{ OffsetPosition, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile }
import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat }
import scala.reflect.io.VirtualFile
-import symtab.{ Flags, SymbolTable, SymbolLoaders, SymbolTrackers }
+import symtab.{ Flags, SymbolTable, SymbolTrackers }
import symtab.classfile.Pickler
import plugins.Plugins
import ast._
@@ -28,7 +27,7 @@ import typechecker._
import transform.patmat.PatternMatching
import transform._
import backend.icode.{ ICodes, GenICode, ICodeCheckers }
-import backend.{ ScalaPrimitives, Platform, JavaPlatform }
+import backend.{ ScalaPrimitives, JavaPlatform }
import backend.jvm.GenBCode
import backend.jvm.GenASM
import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination }
diff --git a/src/compiler/scala/tools/nsc/Parsing.scala b/src/compiler/scala/tools/nsc/Parsing.scala
index 4dd3c3f378..9e5999ce4f 100644
--- a/src/compiler/scala/tools/nsc/Parsing.scala
+++ b/src/compiler/scala/tools/nsc/Parsing.scala
@@ -7,7 +7,6 @@ package scala
package tools.nsc
import scala.reflect.internal.Positions
-import scala.tools.nsc.reporters.Reporter
/** Similar to Reporting: gather global functionality specific to parsing.
*/
diff --git a/src/compiler/scala/tools/nsc/Properties.scala b/src/compiler/scala/tools/nsc/Properties.scala
index bec686ec05..9f160e2485 100644
--- a/src/compiler/scala/tools/nsc/Properties.scala
+++ b/src/compiler/scala/tools/nsc/Properties.scala
@@ -14,7 +14,9 @@ object Properties extends scala.util.PropertiesTrait {
// settings based on jar properties, falling back to System prefixed by "scala."
def residentPromptString = scalaPropOrElse("resident.prompt", "\nnsc> ")
def shellPromptString = scalaPropOrElse("shell.prompt", "\nscala> ")
- def shellInterruptedString = scalaPropOrElse("shell.interrupted", ":quit\n")
+ // message to display at EOF (which by default ends with
+ // a newline so as not to break the user's terminal)
+ def shellInterruptedString = scalaPropOrElse("shell.interrupted", f":quit$lineSeparator")
// derived values
def isEmacsShell = propOrEmpty("env.emacs") != ""
diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala
index c9782de7c8..4d7e9e753f 100644
--- a/src/compiler/scala/tools/nsc/Reporting.scala
+++ b/src/compiler/scala/tools/nsc/Reporting.scala
@@ -7,7 +7,6 @@ package scala
package tools
package nsc
-import reporters.{ Reporter, ConsoleReporter }
import scala.collection.{ mutable, immutable }
import scala.reflect.internal.util.StringOps.countElementsAsString
diff --git a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
index f9551697d2..ad1975ef23 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
@@ -300,14 +300,16 @@ trait BasicBlocks {
if (!closed)
instructionList = instructionList map (x => map.getOrElse(x, x))
else
- instrs.zipWithIndex collect {
- case (oldInstr, i) if map contains oldInstr =>
- // SI-6288 clone important here because `replaceInstruction` assigns
- // a position to `newInstr`. Without this, a single instruction can
- // be added twice, and the position last position assigned clobbers
- // all previous positions in other usages.
- val newInstr = map(oldInstr).clone()
- code.touched |= replaceInstruction(i, newInstr)
+ instrs.iterator.zipWithIndex foreach {
+ case (oldInstr, i) =>
+ if (map contains oldInstr) {
+ // SI-6288 clone important here because `replaceInstruction` assigns
+ // a position to `newInstr`. Without this, a single instruction can
+ // be added twice, and the position last position assigned clobbers
+ // all previous positions in other usages.
+ val newInstr = map(oldInstr).clone()
+ code.touched |= replaceInstruction(i, newInstr)
+ }
}
////////////////////// Emit //////////////////////
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
index 2af2037fec..7269910af6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
@@ -5,7 +5,7 @@
package scala.tools.nsc.backend.jvm
-import scala.tools.asm.tree.{ClassNode, MethodNode}
+import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode}
import java.io.PrintWriter
import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier}
import scala.tools.asm.ClassReader
@@ -58,4 +58,9 @@ object AsmUtils {
new ClassReader(bytes).accept(node, 0)
node
}
+
+ def instructionString(instruction: AbstractInsnNode): String = instruction.getOpcode match {
+ case -1 => instruction.toString
+ case op => scala.tools.asm.util.Printer.OPCODES(op)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
index 54d4a30553..328ec8a033 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
@@ -6,7 +6,6 @@
package scala.tools.nsc.backend.jvm
import scala.tools.nsc.Global
-import PartialFunction._
/**
* This trait contains code shared between GenBCode and GenASM that depends on types defined in
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 0e2f938602..3b7cbd6392 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -195,32 +195,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
case _ => NoSymbol
}
- /**
- * Drop redundant interfaces (which are implemented by some other parent) from the immediate
- * parents. In other words, no two interfaces in the result are related by subtyping.
- */
- def dropRedundantInterfaces(lstIfaces: List[Symbol]): List[Symbol] = {
- var rest = lstIfaces
- var leaves = List.empty[Symbol]
- while (!rest.isEmpty) {
- val candidate = rest.head
- val nonLeaf = leaves exists { lsym => lsym isSubClass candidate }
- if (!nonLeaf) {
- leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym })
- }
- rest = rest.tail
- }
-
- leaves
- }
-
val superInterfaces0: List[Symbol] = classSym.mixinClasses
val superInterfaces = existingSymbols(superInterfaces0 ++ classSym.annotations.map(newParentForAnnotation)).distinct
assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString(", ")}")
assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString(", ")}")
- dropRedundantInterfaces(superInterfaces)
+ erasure.minimizeInterfaces(superInterfaces.map(_.info)).map(_.typeSymbol)
}
private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
index 4b9383c67c..03306f30aa 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
@@ -14,7 +14,7 @@ object BackendStats {
val bcodeInitTimer = newSubTimer("bcode initialization", bcodeTimer)
val bcodeGenStat = newSubTimer("code generation", bcodeTimer)
- val bcodeDceTimer = newSubTimer("dead code elimination", bcodeTimer)
+ val methodOptTimer = newSubTimer("intra-method optimizations", bcodeTimer)
val bcodeWriteTimer = newSubTimer("classfile writing", bcodeTimer)
def timed[T](timer: Statistics.Timer)(body: => T): T = {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
index d0a12c32e5..e56a20c2e7 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -1214,30 +1214,12 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
case _ => NoSymbol
}
- /* Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents.
- * This is important on Android because there is otherwise an interface explosion.
- */
- def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = {
- var rest = lstIfaces
- var leaves = List.empty[Symbol]
- while(!rest.isEmpty) {
- val candidate = rest.head
- val nonLeaf = leaves exists { lsym => lsym isSubClass candidate }
- if(!nonLeaf) {
- leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym })
- }
- rest = rest.tail
- }
-
- leaves
- }
-
val ps = c.symbol.info.parents
val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses
val superInterfaces = existingSymbols(superInterfaces0 ++ c.symbol.annotations.map(newParentForAttr)).distinct
if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY
- else mkArray(minimizeInterfaces(superInterfaces) map javaName)
+ else mkArray(erasure.minimizeInterfaces(superInterfaces.map(_.info)).map(t => javaName(t.typeSymbol)))
}
var clasz: IClass = _ // this var must be assigned only by genClass()
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index ba94a9c44c..a45f586666 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -9,12 +9,12 @@ package tools.nsc
package backend
package jvm
-import scala.collection.{ mutable, immutable }
-import scala.annotation.switch
+import scala.collection.mutable
import scala.reflect.internal.util.Statistics
import scala.tools.asm
import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.opt.LocalOpt
/*
* Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk.
@@ -215,13 +215,10 @@ abstract class GenBCode extends BCodeSyncAndTry {
* - converting the plain ClassNode to byte array and placing it on queue-3
*/
class Worker2 {
- def localOptimizations(classNode: ClassNode): Unit = {
- def dce(): Boolean = BackendStats.timed(BackendStats.bcodeDceTimer) {
- if (settings.YoptUnreachableCode) opt.LocalOpt.removeUnreachableCode(classNode)
- else false
- }
+ lazy val localOpt = new LocalOpt(settings)
- dce()
+ def localOptimizations(classNode: ClassNode): Unit = {
+ BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode))
}
def run() {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
new file mode 100644
index 0000000000..6b4047c0a7
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -0,0 +1,184 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.annotation.{tailrec, switch}
+import scala.collection.mutable
+import scala.reflect.internal.util.Collections._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree._
+import scala.collection.convert.decorateAsScala._
+
+object BytecodeUtils {
+
+ object Goto {
+ def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
+ if (instruction.getOpcode == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode])
+ else None
+ }
+ }
+
+ object JumpNonJsr {
+ def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
+ if (isJumpNonJsr(instruction)) Some(instruction.asInstanceOf[JumpInsnNode])
+ else None
+ }
+ }
+
+ object ConditionalJump {
+ def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
+ if (isConditionalJump(instruction)) Some(instruction.asInstanceOf[JumpInsnNode])
+ else None
+ }
+ }
+
+ object VarInstruction {
+ def unapply(instruction: AbstractInsnNode): Option[VarInsnNode] = {
+ if (isVarInstruction(instruction)) Some(instruction.asInstanceOf[VarInsnNode])
+ else None
+ }
+
+ }
+
+ def isJumpNonJsr(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ // JSR is deprecated in classfile version 50, disallowed in 51. historically, it was used to implement finally.
+ op == Opcodes.GOTO || isConditionalJump(instruction)
+ }
+
+ def isConditionalJump(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ (op >= Opcodes.IFEQ && op <= Opcodes.IF_ACMPNE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL
+ }
+
+ def isReturn(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op >= Opcodes.IRETURN && op <= Opcodes.RETURN
+ }
+
+ def isVarInstruction(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ (op >= Opcodes.ILOAD && op <= Opcodes.ALOAD) || (op >= Opcodes.ISTORE && op <= Opcodes.ASTORE)
+ }
+
+ def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
+
+ def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
+ var result = instruction
+ do { result = result.getNext }
+ while (result != null && !isExecutable(result) && !alsoKeep(result))
+ Option(result)
+ }
+
+ def sameTargetExecutableInstruction(a: JumpInsnNode, b: JumpInsnNode): Boolean = {
+ // Compare next executable instead of the the labels. Identifies a, b as the same target:
+ // LabelNode(a)
+ // LabelNode(b)
+ // Instr
+ nextExecutableInstruction(a.label) == nextExecutableInstruction(b.label)
+ }
+
+ def removeJumpAndAdjustStack(method: MethodNode, jump: JumpInsnNode) {
+ val instructions = method.instructions
+ val op = jump.getOpcode
+ if ((op >= Opcodes.IFEQ && op <= Opcodes.IFGE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL) {
+ instructions.insert(jump, getPop(1))
+ } else if ((op >= Opcodes.IF_ICMPEQ && op <= Opcodes.IF_ICMPLE) || op == Opcodes.IF_ACMPEQ || op == Opcodes.IF_ACMPNE) {
+ instructions.insert(jump, getPop(1))
+ instructions.insert(jump, getPop(1))
+ } else {
+ // we can't remove JSR: its execution does not only jump, it also adds a return address to the stack
+ assert(jump.getOpcode == Opcodes.GOTO)
+ }
+ instructions.remove(jump)
+ }
+
+ def finalJumpTarget(source: JumpInsnNode): LabelNode = {
+ @tailrec def followGoto(label: LabelNode, seenLabels: Set[LabelNode]): LabelNode = nextExecutableInstruction(label) match {
+ case Some(Goto(dest)) =>
+ if (seenLabels(dest.label)) dest.label
+ else followGoto(dest.label, seenLabels + dest.label)
+
+ case _ => label
+ }
+ followGoto(source.label, Set(source.label))
+ }
+
+ def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match {
+ case Opcodes.IFEQ => Opcodes.IFNE
+ case Opcodes.IFNE => Opcodes.IFEQ
+
+ case Opcodes.IFLT => Opcodes.IFGE
+ case Opcodes.IFGE => Opcodes.IFLT
+
+ case Opcodes.IFGT => Opcodes.IFLE
+ case Opcodes.IFLE => Opcodes.IFGT
+
+ case Opcodes.IF_ICMPEQ => Opcodes.IF_ICMPNE
+ case Opcodes.IF_ICMPNE => Opcodes.IF_ICMPEQ
+
+ case Opcodes.IF_ICMPLT => Opcodes.IF_ICMPGE
+ case Opcodes.IF_ICMPGE => Opcodes.IF_ICMPLT
+
+ case Opcodes.IF_ICMPGT => Opcodes.IF_ICMPLE
+ case Opcodes.IF_ICMPLE => Opcodes.IF_ICMPGT
+
+ case Opcodes.IF_ACMPEQ => Opcodes.IF_ACMPNE
+ case Opcodes.IF_ACMPNE => Opcodes.IF_ACMPEQ
+
+ case Opcodes.IFNULL => Opcodes.IFNONNULL
+ case Opcodes.IFNONNULL => Opcodes.IFNULL
+ }
+
+ def getPop(size: Int): InsnNode = {
+ val op = if (size == 1) Opcodes.POP else Opcodes.POP2
+ new InsnNode(op)
+ }
+
+ def labelReferences(method: MethodNode): Map[LabelNode, Set[AnyRef]] = {
+ val res = mutable.Map.empty[LabelNode, Set[AnyRef]]
+ def add(l: LabelNode, ref: AnyRef) = if (res contains l) res(l) = res(l) + ref else res(l) = Set(ref)
+
+ method.instructions.iterator().asScala foreach {
+ case jump: JumpInsnNode => add(jump.label, jump)
+ case line: LineNumberNode => add(line.start, line)
+ case switch: LookupSwitchInsnNode => switch.labels.asScala.foreach(add(_, switch)); add(switch.dflt, switch)
+ case switch: TableSwitchInsnNode => switch.labels.asScala.foreach(add(_, switch)); add(switch.dflt, switch)
+ case _ =>
+ }
+ if (method.localVariables != null) {
+ method.localVariables.iterator().asScala.foreach(l => { add(l.start, l); add(l.end, l) })
+ }
+ if (method.tryCatchBlocks != null) {
+ method.tryCatchBlocks.iterator().asScala.foreach(l => { add(l.start, l); add(l.handler, l); add(l.end, l) })
+ }
+
+ res.toMap
+ }
+
+ def substituteLabel(reference: AnyRef, from: LabelNode, to: LabelNode): Unit = {
+ def substList(list: java.util.List[LabelNode]) = {
+ foreachWithIndex(list.asScala.toList) { case (l, i) =>
+ if (l == from) list.set(i, to)
+ }
+ }
+ reference match {
+ case jump: JumpInsnNode => jump.label = to
+ case line: LineNumberNode => line.start = to
+ case switch: LookupSwitchInsnNode => substList(switch.labels); if (switch.dflt == from) switch.dflt = to
+ case switch: TableSwitchInsnNode => substList(switch.labels); if (switch.dflt == from) switch.dflt = to
+ case local: LocalVariableNode =>
+ if (local.start == from) local.start = to
+ if (local.end == from) local.end = to
+ case handler: TryCatchBlockNode =>
+ if (handler.start == from) handler.start = to
+ if (handler.handler == from) handler.handler = to
+ if (handler.end == from) handler.end = to
+ }
+ }
+}
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 3acd2d6154..273112b93c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -7,125 +7,206 @@ package scala.tools.nsc
package backend.jvm
package opt
+import scala.annotation.switch
import scala.tools.asm.{Opcodes, MethodWriter, ClassWriter}
import scala.tools.asm.tree.analysis.{Analyzer, BasicValue, BasicInterpreter}
import scala.tools.asm.tree._
import scala.collection.convert.decorateAsScala._
-import scala.collection.{ mutable => m }
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+import scala.tools.nsc.settings.ScalaSettings
/**
- * Intra-Method optimizations.
+ * Optimizations within a single method.
+ *
+ * unreachable code
+ * - removes instrucions of basic blocks to which no branch instruction points
+ * + enables eliminating some exception handlers and local variable descriptors
+ * > eliminating them is required for correctness, as explained in `removeUnreachableCode`
+ *
+ * empty exception handlers
+ * - removes exception handlers whose try block is empty
+ * + eliminating a handler where the try block is empty and reachable will turn the catch block
+ * unreachble. in this case "unreachable code" is invoked recursively until reaching a fixpiont.
+ * > for try blocks that are unreachable, "unreachable code" removes also the instructions of the
+ * catch block, and the recrusive invocation is not necessary.
+ *
+ * simplify jumps
+ * - various simplifications, see doc domments of individual optimizations
+ * + changing or eliminating jumps may render some code unreachable, therefore "simplify jumps" is
+ * executed in a loop with "unreachable code"
+ *
+ * empty local variable descriptors
+ * - removes entries from the local variable table where the variable is not actually used
+ * + enables eliminating labels that the entry points to (if they are not otherwise referenced)
+ *
+ * empty line numbers
+ * - eliminates line number nodes that describe no executable instructions
+ * + enables eliminating the label of the line number node (if it's not otherwise referenced)
+ *
+ * stale labels
+ * - eliminate labels that are not referenced, merge sequences of label definitions.
*/
-object LocalOpt {
+class LocalOpt(settings: ScalaSettings) {
/**
- * Remove unreachable instructions from all (non-abstract) methods.
+ * Remove unreachable instructions from all (non-abstract) methods and apply various other
+ * cleanups to the bytecode.
*
* @param clazz The class whose methods are optimized
* @return `true` if unreachable code was elminated in some method, `false` otherwise.
*/
- def removeUnreachableCode(clazz: ClassNode): Boolean = {
- clazz.methods.asScala.foldLeft(false) {
- case (changed, method) => removeUnreachableCode(method, clazz.name) || changed
+ def methodOptimizations(clazz: ClassNode): Boolean = {
+ settings.Yopt.value.nonEmpty && clazz.methods.asScala.foldLeft(false) {
+ case (changed, method) => methodOptimizations(method, clazz.name) || changed
}
}
/**
* Remove unreachable code from a method.
+ *
* We rely on dead code elimination provided by the ASM framework, as described in the ASM User
* Guide (http://asm.ow2.org/index.html), Section 8.2.1. It runs a data flow analysis, which only
* computes Frame information for reachable instructions. Instructions for which no Frame data is
* available after the analyis are unreachable.
*
- * TODO doc: it also removes empty handlers, unused local vars
+ * Also simplifies branching instructions, removes unused local variable descriptors, empty
+ * exception handlers, unnecessary label declarations and empty line number nodes.
*
- * Returns `true` if dead code in `method` has been eliminated.
+ * Returns `true` if the bytecode of `method` was changed.
*/
- private def removeUnreachableCode(method: MethodNode, ownerClassName: String): Boolean = {
+ private def methodOptimizations(method: MethodNode, ownerClassName: String): Boolean = {
if (method.instructions.size == 0) return false // fast path for abstract methods
- val codeRemoved = removeUnreachableCodeImpl(method, ownerClassName)
-
// unreachable-code also removes unused local variable nodes and empty exception handlers.
- // This is required for correctness: such nodes are not allowed to refer to instruction offsets
- // that don't exist (because they have been eliminated).
- val localsRemoved = removeUnusedLocalVariableNodes(method)
- val handlersRemoved = removeEmptyExceptionHandlers(method)
-
- // When eliminating a handler, the catch block becomes unreachable. The recursive invocation
- // removes these blocks.
- // Note that invoking removeUnreachableCode*Impl* a second time is not enough: removing the dead
- // catch block can render other handlers empty, which also have to be removed in turn.
- if (handlersRemoved) removeUnreachableCode(method, ownerClassName)
-
- // assert that we can leave local variable annotations as-is
+ // This is required for correctness, for example:
+ //
+ // def f = { return 0; try { 1 } catch { case _ => 2 } }
+ //
+ // The result after removeUnreachableCodeImpl:
+ //
+ // TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
+ // L4
+ // ICONST_0
+ // IRETURN
+ // L0
+ // L1
+ // L2
+ //
+ // If we don't eliminate the handler, the ClassWriter emits:
+ //
+ // TRYCATCHBLOCK L0 L0 L0 java/lang/Exception
+ // L1
+ // ICONST_0
+ // IRETURN
+ // L0
+ //
+ // 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.
+
+ var recurse = true
+ var codeHandlersOrJumpsChanged = false
+ while (recurse) {
+ // unreachable-code, empty-handlers and simplify-jumps run until reaching a fixpoint (see doc on class LocalOpt)
+ val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (settings.YoptUnreachableCode) {
+ val (codeRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
+ val removedHandlers = removeEmptyExceptionHandlers(method)
+ (codeRemoved, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start)))
+ } else {
+ (false, false, false)
+ }
+
+ val jumpsChanged = if (settings.YoptSimplifyJumps) simplifyJumps(method) else false
+
+ codeHandlersOrJumpsChanged ||= (codeRemoved || handlersRemoved || jumpsChanged)
+
+ // The doc comment of class LocalOpt explains why we recurse if jumpsChanged || liveHandlerRemoved
+ recurse = settings.YoptRecurseUnreachableJumps && (jumpsChanged || liveHandlerRemoved)
+ }
+
+ // (*) Removing stale local variable descriptors is required for correctness of unreachable-code
+ val localsRemoved =
+ if (settings.YoptCompactLocals) compactLocalVariables(method)
+ else if (settings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*)
+ else false
+
+ val lineNumbersRemoved = if (settings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false
+
+ val labelsRemoved = if (settings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false
+
+ // assert that local variable annotations are empty (we don't emit them) - otherwise we'd have
+ // to eliminate those covering an empty range, similar to removeUnusedLocalVariableNodes.
def nullOrEmpty[T](l: java.util.List[T]) = l == null || l.isEmpty
assert(nullOrEmpty(method.visibleLocalVariableAnnotations), method.visibleLocalVariableAnnotations)
assert(nullOrEmpty(method.invisibleLocalVariableAnnotations), method.invisibleLocalVariableAnnotations)
- codeRemoved || localsRemoved || handlersRemoved
+ codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
}
- private def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): Boolean = {
- val initialSize = method.instructions.size
- if (initialSize == 0) return false
-
+ /**
+ * Removes unreachable basic blocks.
+ *
+ * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow.
+ */
+ def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: String): (Boolean, Set[LabelNode]) = {
// The data flow analysis requires the maxLocals / maxStack fields of the method to be computed.
computeMaxLocalsMaxStack(method)
val a = new Analyzer[BasicValue](new BasicInterpreter)
a.analyze(ownerClassName, method)
val frames = a.getFrames
+ val initialSize = method.instructions.size
var i = 0
+ var liveLabels = Set.empty[LabelNode]
val itr = method.instructions.iterator()
while (itr.hasNext) {
- val ins = itr.next()
- // Don't remove label nodes: they might be referenced for example in a LocalVariableNode
- if (frames(i) == null && !ins.isInstanceOf[LabelNode]) {
- // Instruction iterators allow removing during iteration.
- // Removing is O(1): instructions are doubly linked list elements.
- itr.remove()
+ itr.next() match {
+ case l: LabelNode =>
+ if (frames(i) != null) liveLabels += l
+
+ case ins =>
+ // label nodes are not removed: they might be referenced for example in a LocalVariableNode
+ if (frames(i) == null || ins.getOpcode == Opcodes.NOP) {
+ // Instruction iterators allow removing during iteration.
+ // Removing is O(1): instructions are doubly linked list elements.
+ itr.remove()
+ }
}
i += 1
}
-
- method.instructions.size != initialSize
- }
-
- /**
- * Remove exception handlers that cover empty code blocks from all methods of `clazz`.
- * Returns `true` if any exception handler was eliminated.
- */
- def removeEmptyExceptionHandlers(clazz: ClassNode): Boolean = {
- clazz.methods.asScala.foldLeft(false) {
- case (changed, method) => removeEmptyExceptionHandlers(method) || changed
- }
+ (method.instructions.size != initialSize, liveLabels)
}
/**
* 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.
*
+ * There are no executable instructions that we can assume don't throw (eg ILOAD). The JVM spec
+ * basically says that a VirtualMachineError may be thrown at any time:
+ * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.3
+ *
* Note that no instructions are eliminated.
*
- * @return `true` if some exception handler was eliminated.
+ * @return the set of removed handlers
*/
- def removeEmptyExceptionHandlers(method: MethodNode): Boolean = {
+ def removeEmptyExceptionHandlers(method: MethodNode): Set[TryCatchBlockNode] = {
/** True if there exists code between start and end. */
def containsExecutableCode(start: AbstractInsnNode, end: LabelNode): Boolean = {
- start != end && (start.getOpcode match {
+ start != end && ((start.getOpcode : @switch) match {
// FrameNode, LabelNode and LineNumberNode have opcode == -1.
- case -1 | Opcodes.NOP | Opcodes.GOTO => containsExecutableCode(start.getNext, end)
+ case -1 | Opcodes.GOTO => containsExecutableCode(start.getNext, end)
case _ => true
})
}
- val initialNumberHandlers = method.tryCatchBlocks.size
+ var removedHandlers = Set.empty[TryCatchBlockNode]
val handlersIter = method.tryCatchBlocks.iterator()
while(handlersIter.hasNext) {
val handler = handlersIter.next()
- if (!containsExecutableCode(handler.start, handler.end)) handlersIter.remove()
+ if (!containsExecutableCode(handler.start, handler.end)) {
+ removedHandlers += handler
+ handlersIter.remove()
+ }
}
- method.tryCatchBlocks.size != initialNumberHandlers
+ removedHandlers
}
/**
@@ -135,35 +216,107 @@ object LocalOpt {
* Note that each entry in the local variable table has a start, end and index. Two entries with
* the same index, but distinct start / end ranges are different variables, they may have not the
* same type or name.
- *
- * TODO: also re-allocate locals to occupy fewer slots after eliminating unused ones
*/
- def removeUnusedLocalVariableNodes(method: MethodNode): Boolean = {
+ def removeUnusedLocalVariableNodes(method: MethodNode)(fistLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = {
def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
start != end && (start match {
- case v: VarInsnNode => v.`var` == varIndex
+ case v: VarInsnNode if v.`var` == varIndex => true
case _ => variableIsUsed(start.getNext, end, varIndex)
})
}
val initialNumVars = method.localVariables.size
val localsIter = method.localVariables.iterator()
- // The parameters and `this` (for instance methods) have the lowest indices in the local variables
- // table. Note that double / long fields occupy two slots, so we sum up the sizes. Since getSize
- // returns 0 for void, we have to add `max 1`.
- val paramsSize = scala.tools.asm.Type.getArgumentTypes(method.desc).map(_.getSize max 1).sum
- val thisSize = if ((method.access & Opcodes.ACC_STATIC) == 0) 1 else 0
- val endParamIndex = paramsSize + thisSize
while (localsIter.hasNext) {
val local = localsIter.next()
- // parameters and `this` have the lowest indices, starting at 0
- val used = local.index < endParamIndex || variableIsUsed(local.start, local.end, local.index)
- if (!used)
- localsIter.remove()
+ val index = local.index
+ // parameters and `this` (the lowest indices, starting at 0) are never removed or renumbered
+ if (index >= fistLocalIndex) {
+ if (!variableIsUsed(local.start, local.end, index)) localsIter.remove()
+ else if (renumber(index) != index) local.index = renumber(index)
+ }
}
- method.localVariables.size == initialNumVars
+ method.localVariables.size != initialNumVars
}
+ /**
+ * The number of local varialbe slots used for parameters and for the `this` reference.
+ */
+ private def parametersSize(method: MethodNode): Int = {
+ // Double / long fields occupy two slots, so we sum up the sizes. Since getSize returns 0 for
+ // void, we have to add `max 1`.
+ val paramsSize = scala.tools.asm.Type.getArgumentTypes(method.desc).iterator.map(_.getSize max 1).sum
+ val thisSize = if ((method.access & Opcodes.ACC_STATIC) == 0) 1 else 0
+ paramsSize + thisSize
+ }
+
+ /**
+ * Compact the local variable slots used in the method's implementation. This prevents having
+ * unused slots for example after eliminating unreachable code.
+ *
+ * This transformation reduces the size of the frame for invoking the method. For example, if the
+ * method has an ISTORE instruction to the local variable 3, the maxLocals of the method is at
+ * least 4, even if some local variable slots below 3 are not used by any instruction.
+ *
+ * This could be improved by doing proper register allocation.
+ */
+ def compactLocalVariables(method: MethodNode): Boolean = {
+ // This array is built up to map local variable indices from old to new.
+ val renumber = collection.mutable.ArrayBuffer.empty[Int]
+
+ // 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
+ }
+
+ // Ensure the length of `renumber`. Unused variable indices are mapped to -1.
+ val minLength = if (isWide) index + 2 else index + 1
+ for (i <- renumber.length until minLength) renumber += -1
+
+ renumber(index) = index
+ if (isWide) renumber(index + 1) = index
+ }
+
+ // first phase: collect all used local variables. if the variable at index x is used, set
+ // renumber(x) = x, otherwise renumber(x) = -1. if the variable is wide (long or double), set
+ // renumber(x+1) = x.
+
+ val firstLocalIndex = parametersSize(method)
+ for (i <- 0 until firstLocalIndex) renumber += i // parameters and `this` are always used.
+ method.instructions.iterator().asScala foreach {
+ case VarInstruction(varIns) => addVar(varIns)
+ case _ =>
+ }
+
+ // assign the next free slot to each used local variable.
+ // for example, rewrite (0, 1, -1, 3, -1, 5) to (0, 1, -1, 2, -1, 3).
+
+ var nextIndex = firstLocalIndex
+ for (i <- firstLocalIndex until renumber.length if renumber(i) != -1) {
+ renumber(i) = nextIndex
+ nextIndex += 1
+ }
+
+ // Update the local variable descriptors according to the renumber table, and eliminate stale entries
+ val removedLocalVariableDescriptors = removeUnusedLocalVariableNodes(method)(firstLocalIndex, renumber)
+
+ if (nextIndex == renumber.length) removedLocalVariableDescriptors
+ else {
+ // update variable instructions according to the renumber table
+ method.maxLocals = nextIndex
+ method.instructions.iterator().asScala.foreach {
+ case VarInstruction(varIns) =>
+ val oldIndex = varIns.`var`
+ if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex)
+ varIns.`var` = renumber(varIns.`var`)
+ case _ =>
+ }
+ true
+ }
+ }
/**
* In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
@@ -187,4 +340,223 @@ object LocalOpt {
method.maxLocals = mw.getMaxLocals
method.maxStack = mw.getMaxStack
}
+
+ /**
+ * Removes LineNumberNodes that don't describe any executable instructions.
+ *
+ * This method expects (and asserts) that the `start` label of each LineNumberNode is the
+ * lexically preceeding label declaration.
+ */
+ def removeEmptyLineNumbers(method: MethodNode): Boolean = {
+ def isEmpty(node: AbstractInsnNode): Boolean = node.getNext match {
+ case null => true
+ case l: LineNumberNode => true
+ case n if n.getOpcode >= 0 => false
+ case n => isEmpty(n)
+ }
+
+ val initialSize = method.instructions.size
+ val iterator = method.instructions.iterator()
+ var previousLabel: LabelNode = null
+ while (iterator.hasNext) {
+ iterator.next match {
+ case label: LabelNode => previousLabel = label
+ case line: LineNumberNode if isEmpty(line) =>
+ assert(line.start == previousLabel)
+ iterator.remove()
+ case _ =>
+ }
+ }
+ method.instructions.size != initialSize
+ }
+
+ /**
+ * Removes unreferenced label declarations, also squashes sequences of label definitions.
+ *
+ * [ops]; Label(a); Label(b); [ops];
+ * => subs([ops], b, a); Label(a); subs([ops], b, a);
+ */
+ def removeEmptyLabelNodes(method: MethodNode): Boolean = {
+ val references = labelReferences(method)
+
+ val initialSize = method.instructions.size
+ val iterator = method.instructions.iterator()
+ var prev: LabelNode = null
+ while (iterator.hasNext) {
+ iterator.next match {
+ case label: LabelNode =>
+ if (!references.contains(label)) iterator.remove()
+ else if (prev != null) {
+ references(label).foreach(substituteLabel(_, label, prev))
+ iterator.remove()
+ } else prev = label
+
+ case instruction =>
+ if (instruction.getOpcode >= 0) prev = null
+ }
+ }
+ method.instructions.size != initialSize
+ }
+
+ /**
+ * Apply various simplifications to branching instructions.
+ */
+ def simplifyJumps(method: MethodNode): Boolean = {
+ var changed = false
+
+ val allHandlers = method.tryCatchBlocks.asScala.toSet
+
+ // A set of all exception handlers that guard the current instruction, required for simplifyGotoReturn
+ var activeHandlers = Set.empty[TryCatchBlockNode]
+
+ // Instructions that need to be removed. simplifyBranchOverGoto returns an instruction to be
+ // removed. It cannot remove it itself because the instruction may be the successor of the current
+ // instruction of the iterator, which is not supported in ASM.
+ var instructionsToRemove = Set.empty[AbstractInsnNode]
+
+ val iterator = method.instructions.iterator()
+ while (iterator.hasNext) {
+ val instruction = iterator.next()
+
+ instruction match {
+ case l: LabelNode =>
+ activeHandlers ++= allHandlers.filter(_.start == l)
+ activeHandlers = activeHandlers.filter(_.end != l)
+ case _ =>
+ }
+
+ if (instructionsToRemove(instruction)) {
+ iterator.remove()
+ instructionsToRemove -= instruction
+ } else if (isJumpNonJsr(instruction)) { // fast path - all of the below only treat jumps
+ var jumpRemoved = simplifyThenElseSameTarget(method, instruction)
+
+ if (!jumpRemoved) {
+ changed = collapseJumpChains(instruction) || changed
+ jumpRemoved = removeJumpToSuccessor(method, instruction)
+
+ if (!jumpRemoved) {
+ val staleGoto = simplifyBranchOverGoto(method, instruction)
+ instructionsToRemove ++= staleGoto
+ changed ||= staleGoto.nonEmpty
+ changed = simplifyGotoReturn(method, instruction, inTryBlock = activeHandlers.nonEmpty) || changed
+ }
+ }
+ changed ||= jumpRemoved
+ }
+ }
+ assert(instructionsToRemove.isEmpty, "some optimization required removing a previously traversed instruction. add `instructionsToRemove.foreach(method.instructions.remove)`")
+ changed
+ }
+
+ /**
+ * Removes a conditional jump if it is followed by a GOTO to the same destination.
+ *
+ * CondJump l; [nops]; GOTO l; [...]
+ * POP*; [nops]; GOTO l; [...]
+ *
+ * Introduces 1 or 2 POP instructions, depending on the number of values consumed by the CondJump.
+ */
+ private def simplifyThenElseSameTarget(method: MethodNode, instruction: AbstractInsnNode): Boolean = instruction match {
+ case ConditionalJump(jump) =>
+ nextExecutableInstruction(instruction) match {
+ case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) =>
+ removeJumpAndAdjustStack(method, jump)
+ true
+
+ case _ => false
+ }
+ case _ => false
+ }
+
+ /**
+ * Replace jumps to a sequence of GOTO instructions by a jump to the final destination.
+ *
+ * Jump l; [any ops]; l: GOTO m; [any ops]; m: GOTO n; [any ops]; n: NotGOTO; [...]
+ * => Jump n; [rest unchaned]
+ *
+ * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop.
+ */
+ private def collapseJumpChains(instruction: AbstractInsnNode): Boolean = instruction match {
+ case JumpNonJsr(jump) =>
+ val target = finalJumpTarget(jump)
+ if (jump.label == target) false else {
+ jump.label = target
+ true
+ }
+
+ case _ => false
+ }
+
+ /**
+ * Eliminates unnecessary jump instructions
+ *
+ * Jump l; [nops]; l: [...]
+ * => POP*; [nops]; l: [...]
+ *
+ * Introduces 0, 1 or 2 POP instructions, depending on the number of values consumed by the Jump.
+ */
+ private def removeJumpToSuccessor(method: MethodNode, instruction: AbstractInsnNode) = instruction match {
+ case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) == Some(jump.label) =>
+ removeJumpAndAdjustStack(method, jump)
+ true
+ case _ => false
+ }
+
+ /**
+ * If the "else" part of a conditional branch is a simple GOTO, negates the conditional branch
+ * and eliminates the GOTO.
+ *
+ * CondJump l; [nops, no labels]; GOTO m; [nops]; l: [...]
+ * => NegatedCondJump m; [nops, no labels]; [nops]; l: [...]
+ *
+ * Note that no label definitions are allowed in the first [nops] section. Otherwsie, there could
+ * be some other jump to the GOTO, and eliminating it would change behavior.
+ *
+ * For technical reasons, we cannot remove the GOTO here (*).Instead this method returns an Option
+ * containing the GOTO that needs to be eliminated.
+ *
+ * (*) The ASM instruction iterator (used in the caller [[simplifyJumps]]) has an undefined
+ * behavior if the successor of the current instruction is removed, which may be the case here
+ */
+ private def simplifyBranchOverGoto(method: MethodNode, instruction: AbstractInsnNode): Option[JumpInsnNode] = instruction match {
+ case ConditionalJump(jump) =>
+ // don't skip over labels, see doc comment
+ nextExecutableInstruction(jump, alsoKeep = _.isInstanceOf[LabelNode]) match {
+ case Some(Goto(goto)) =>
+ if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) == Some(jump.label)) {
+ val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label)
+ method.instructions.set(jump, newJump)
+ Some(goto)
+ } else None
+
+ case _ => None
+ }
+ case _ => None
+ }
+
+ /**
+ * Inlines xRETURN and ATHROW
+ *
+ * GOTO l; [any ops]; l: xRETURN/ATHROW
+ * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW
+ *
+ * inlining is only done if the GOTO instruction is not part of a try block, otherwise the
+ * rewrite might change the behavior. For xRETURN, the reason is that return insructions may throw
+ * an IllegalMonitorStateException, as described here:
+ * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return
+ */
+ private def simplifyGotoReturn(method: MethodNode, instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match {
+ case Goto(jump) =>
+ nextExecutableInstruction(jump.label) match {
+ case Some(target) =>
+ if (isReturn(target) || target.getOpcode == Opcodes.ATHROW) {
+ method.instructions.set(jump, target.clone(null))
+ true
+ } else false
+
+ case _ => false
+ }
+ case _ => false
+ })
}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
index 1fadcb8920..c6e699373b 100644
--- a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
+++ b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
@@ -7,7 +7,6 @@ package scala
package tools.nsc
package backend.opt
-import scala.tools.nsc.backend.icode.analysis.LubException
import scala.annotation.tailrec
/**
diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
index 23611bb629..b4987e1240 100644
--- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
@@ -229,7 +229,8 @@ class MutableSettings(val errorFn: String => Unit)
def OutputSetting(outputDirs: OutputDirs, default: String) = add(new OutputSetting(outputDirs, default))
def PhasesSetting(name: String, descr: String, default: String = "") = add(new PhasesSetting(name, descr, default))
def StringSetting(name: String, arg: String, descr: String, default: String) = add(new StringSetting(name, arg, descr, default))
- def ScalaVersionSetting(name: String, arg: String, descr: String, default: ScalaVersion) = add(new ScalaVersionSetting(name, arg, descr, default))
+ def ScalaVersionSetting(name: String, arg: String, descr: String, initial: ScalaVersion, default: Option[ScalaVersion] = None) =
+ add(new ScalaVersionSetting(name, arg, descr, initial, default))
def PathSetting(name: String, descr: String, default: String): PathSetting = {
val prepend = StringSetting(name + "/p", "", "", "").internalOnly()
val append = StringSetting(name + "/a", "", "", "").internalOnly()
@@ -506,28 +507,35 @@ class MutableSettings(val errorFn: String => Unit)
withHelpSyntax(name + " <" + arg + ">")
}
- /** A setting represented by a Scala version, (`default` unless set) */
+ /** A setting represented by a Scala version.
+ * The `initial` value is used if the setting is not specified.
+ * The `default` value is used if the option is specified without argument (e.g., `-Xmigration`).
+ */
class ScalaVersionSetting private[nsc](
name: String,
val arg: String,
descr: String,
- default: ScalaVersion)
+ initial: ScalaVersion,
+ default: Option[ScalaVersion])
extends Setting(name, descr) {
type T = ScalaVersion
- protected var v: T = NoScalaVersion
+ protected var v: T = initial
+ // This method is invoked if there are no colonated args. In this case the default value is
+ // used. No arguments are consumed.
override def tryToSet(args: List[String]) = {
- value = default
+ default match {
+ case Some(d) => value = d
+ case None => errorFn(s"$name requires an argument, the syntax is $helpSyntax")
+ }
Some(args)
}
override def tryToSetColon(args: List[String]) = args match {
- case Nil => value = default; Some(Nil)
- case x :: xs => value = ScalaVersion(x, errorFn) ; Some(xs)
+ case x :: xs => value = ScalaVersion(x, errorFn); Some(xs)
+ case nil => Some(nil)
}
- override def tryToSetFromPropertyValue(s: String) = tryToSet(List(s))
-
def unparse: List[String] = if (value == NoScalaVersion) Nil else List(s"${name}:${value.unparse}")
withHelpSyntax(s"${name}:<${arg}>")
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 850534f2cc..d6650595eb 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -92,7 +92,8 @@ trait ScalaSettings extends AbsScalaSettings
* The previous "-source" option is intended to be used mainly
* though this helper.
*/
- lazy val isScala211: Boolean = (source.value >= ScalaVersion("2.11.0"))
+ def isScala211: Boolean = source.value >= ScalaVersion("2.11.0")
+ def isScala212: Boolean = source.value >= ScalaVersion("2.12.0")
/**
* -X "Advanced" settings
@@ -111,7 +112,7 @@ trait ScalaSettings extends AbsScalaSettings
val logFreeTerms = BooleanSetting ("-Xlog-free-terms", "Print a message when reification creates a free term.")
val logFreeTypes = BooleanSetting ("-Xlog-free-types", "Print a message when reification resorts to generating a free type.")
val maxClassfileName = IntSetting ("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, Some((72, 255)), _ => None)
- val Xmigration = ScalaVersionSetting ("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", AnyScalaVersion)
+ val Xmigration = ScalaVersionSetting ("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", initial = NoScalaVersion, default = Some(AnyScalaVersion))
val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.")
val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.")
val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)")
@@ -133,7 +134,7 @@ trait ScalaSettings extends AbsScalaSettings
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 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.", ScalaVersion("2.11")) withPostSetHook ( _ => isScala211)
+ val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.11"))
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.")
@@ -210,21 +211,26 @@ trait ScalaSettings extends AbsScalaSettings
val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline")
object YoptChoices extends MultiChoiceEnumeration {
- val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code")
+ val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.")
+ val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessery ones.")
+ val recurseUnreachableJumps = Choice("recurse-unreachable-jumps", "Recursively apply unreachable-code and simplify-jumps (if enabled) until reaching a fixpoint.")
+ val emptyLineNumbers = Choice("empty-line-numbers", "Eliminate unnecessary line number information.")
+ val emptyLabels = Choice("empty-labels", "Eliminate and collapse redundant labels in the bytecode.")
+ val compactLocals = Choice("compact-locals", "Eliminate empty slots in the sequence of local variables.")
- val lNone = Choice("l:none", "Don't enable any optimizations")
+ val lNone = Choice("l:none", "Don't enable any optimizations.")
private val defaultChoices = List(unreachableCode)
- val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices)
+ val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices)
- private val methodChoices = List(lDefault)
- val lMethod = Choice("l:method", "Intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices)
+ private val methodChoices = List(unreachableCode, simplifyJumps, recurseUnreachableJumps, emptyLineNumbers, emptyLabels, compactLocals)
+ val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices)
private val projectChoices = List(lMethod)
- val lProject = Choice("l:project", "Cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices)
+ val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices)
private val classpathChoices = List(lProject)
- val lClasspath = Choice("l:classpath", "Cross-method optmizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices)
+ val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices)
}
val Yopt = MultiChoiceSetting(
@@ -233,7 +239,12 @@ trait ScalaSettings extends AbsScalaSettings
descr = "Enable optimizations",
domain = YoptChoices)
- def YoptUnreachableCode: Boolean = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode)
+ def YoptUnreachableCode = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode)
+ def YoptSimplifyJumps = Yopt.contains(YoptChoices.simplifyJumps)
+ def YoptRecurseUnreachableJumps = Yopt.contains(YoptChoices.recurseUnreachableJumps)
+ def YoptEmptyLineNumbers = Yopt.contains(YoptChoices.emptyLineNumbers)
+ def YoptEmptyLabels = Yopt.contains(YoptChoices.emptyLabels)
+ def YoptCompactLocals = Yopt.contains(YoptChoices.compactLocals)
private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug."
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
index 4f45043c5e..43bdad5882 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
@@ -34,7 +34,7 @@ case object NoScalaVersion extends ScalaVersion {
* to segregate builds
*/
case class SpecificScalaVersion(major: Int, minor: Int, rev: Int, build: ScalaBuild) extends ScalaVersion {
- def unparse = s"${major}.${minor}.${rev}.${build.unparse}"
+ def unparse = s"${major}.${minor}.${rev}${build.unparse}"
def compare(that: ScalaVersion): Int = that match {
case SpecificScalaVersion(thatMajor, thatMinor, thatRev, thatBuild) =>
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index 1664fe0e0d..c29826551b 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -520,7 +520,9 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
* And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler
* have little in common.
*/
- case Apply(fn, (arg @ Literal(Constant(symname: String))) :: Nil) if fn.symbol == Symbol_apply =>
+ case Apply(fn @ Select(qual, _), (arg @ Literal(Constant(symname: String))) :: Nil)
+ if treeInfo.isQualifierSafeToElide(qual) && fn.symbol == Symbol_apply && !currentClass.isTrait =>
+
def transformApply = {
// add the symbol name to a map if it's not there already
val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil)
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 835d338ab3..f7b1021ea2 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -30,7 +30,6 @@ import scala.collection.mutable.LinkedHashMap
abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer {
import global._
import definitions._
- import CODE._
val analyzer: global.analyzer.type = global.analyzer
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index 3d8b2f02f3..b6af19250e 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -98,7 +98,7 @@ abstract class Erasure extends AddInterfaces
val len = sig.length
val copy: Array[Char] = sig.toCharArray
var changed = false
- while (i < sig.length) {
+ while (i < len) {
val ch = copy(i)
if (ch == '.' && last != '>') {
copy(i) = '$'
@@ -185,6 +185,25 @@ abstract class Erasure extends AddInterfaces
private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType]
+ /* Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents.
+ * This is important on Android because there is otherwise an interface explosion.
+ */
+ def minimizeInterfaces(lstIfaces: List[Type]): List[Type] = {
+ var rest = lstIfaces
+ var leaves = List.empty[Type]
+ while(!rest.isEmpty) {
+ val candidate = rest.head
+ val nonLeaf = leaves exists { t => t.typeSymbol isSubClass candidate.typeSymbol }
+ if(!nonLeaf) {
+ leaves = candidate :: (leaves filterNot { t => candidate.typeSymbol isSubClass t.typeSymbol })
+ }
+ rest = rest.tail
+ }
+
+ leaves.reverse
+ }
+
+
/** The Java signature of type 'info', for symbol sym. The symbol is used to give the right return
* type for constructors.
*/
@@ -192,16 +211,24 @@ abstract class Erasure extends AddInterfaces
val isTraitSignature = sym0.enclClass.isTrait
def superSig(parents: List[Type]) = {
- val ps = (
- if (isTraitSignature) {
+ def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait
+
+ // a signature should always start with a class
+ def ensureClassAsFirstParent(tps: List[Type]) = tps match {
+ case Nil => ObjectTpe :: Nil
+ case head :: tail if isInterfaceOrTrait(head.typeSymbol) => ObjectTpe :: tps
+ case _ => tps
+ }
+
+ val minParents = minimizeInterfaces(parents)
+ val validParents =
+ if (isTraitSignature)
// java is unthrilled about seeing interfaces inherit from classes
- val ok = parents filter (p => p.typeSymbol.isTrait || p.typeSymbol.isInterface)
- // traits should always list Object.
- if (ok.isEmpty || ok.head.typeSymbol != ObjectClass) ObjectTpe :: ok
- else ok
- }
- else parents
- )
+ minParents filter (p => isInterfaceOrTrait(p.typeSymbol))
+ else minParents
+
+ val ps = ensureClassAsFirstParent(validParents)
+
(ps map boxedSig).mkString
}
def boxedSig(tp: Type) = jsig(tp, primitiveOK = false)
@@ -410,7 +437,6 @@ abstract class Erasure extends AddInterfaces
def fulldef(sym: Symbol) =
if (sym == NoSymbol) sym.toString
else s"$sym: ${sym.tpe} in ${sym.owner}"
- var noclash = true
val clashErrors = mutable.Buffer[(Position, String)]()
def clashError(what: String) = {
val pos = if (member.owner == root) member.pos else root.pos
diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
index 228c9da624..116047a2ad 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 | FINAL)
+ companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~LOCAL | FINAL)
setAnnotations origMeth.annotations
)
origMeth.removeAnnotation(TailrecClass) // it's on the extension method, now.
diff --git a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
index bbd11efa7e..c1c025ad48 100644
--- a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
+++ b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
@@ -6,7 +6,6 @@
package scala.tools.nsc
package transform
-import symtab.Flags._
import scala.reflect.internal.SymbolPairs
/** A class that yields a kind of iterator (`Cursor`),
diff --git a/src/compiler/scala/tools/nsc/transform/Statics.scala b/src/compiler/scala/tools/nsc/transform/Statics.scala
index e2508b8d08..4673be6de7 100644
--- a/src/compiler/scala/tools/nsc/transform/Statics.scala
+++ b/src/compiler/scala/tools/nsc/transform/Statics.scala
@@ -1,9 +1,6 @@
package scala.tools.nsc
package transform
-import symtab._
-import Flags._
-
import collection.mutable.Buffer
abstract class Statics extends Transform with ast.TreeDSL {
diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala
index ef534f70fd..16ea3ea90f 100644
--- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala
+++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala
@@ -129,6 +129,13 @@ abstract class TailCalls extends Transform {
}
override def toString = s"${method.name} tparams=$tparams tailPos=$tailPos label=$label label info=${label.info}"
+ final def noTailContext() = clonedTailContext(false)
+ final def yesTailContext() = clonedTailContext(true)
+ protected def clonedTailContext(tailPos: Boolean): TailContext = this match {
+ case _ if this.tailPos == tailPos => this
+ case clone: ClonedTailContext => clone.that.clonedTailContext(tailPos)
+ case _ => new ClonedTailContext(this, tailPos)
+ }
}
object EmptyTailContext extends TailContext {
@@ -174,7 +181,7 @@ abstract class TailCalls extends Transform {
}
def containsRecursiveCall(t: Tree) = t exists isRecursiveCall
}
- class ClonedTailContext(that: TailContext, override val tailPos: Boolean) extends TailContext {
+ class ClonedTailContext(val that: TailContext, override val tailPos: Boolean) extends TailContext {
def method = that.method
def tparams = that.tparams
def methodPos = that.methodPos
@@ -183,9 +190,6 @@ abstract class TailCalls extends Transform {
}
private var ctx: TailContext = EmptyTailContext
- private def noTailContext() = new ClonedTailContext(ctx, tailPos = false)
- private def yesTailContext() = new ClonedTailContext(ctx, tailPos = true)
-
override def transformUnit(unit: CompilationUnit): Unit = {
try {
@@ -206,16 +210,16 @@ abstract class TailCalls extends Transform {
finally this.ctx = saved
}
- def yesTailTransform(tree: Tree): Tree = transform(tree, yesTailContext())
- def noTailTransform(tree: Tree): Tree = transform(tree, noTailContext())
+ def yesTailTransform(tree: Tree): Tree = transform(tree, ctx.yesTailContext())
+ def noTailTransform(tree: Tree): Tree = transform(tree, ctx.noTailContext())
def noTailTransforms(trees: List[Tree]) = {
- val nctx = noTailContext()
- trees map (t => transform(t, nctx))
+ val nctx = ctx.noTailContext()
+ trees mapConserve (t => transform(t, nctx))
}
override def transform(tree: Tree): Tree = {
/* A possibly polymorphic apply to be considered for tail call transformation. */
- def rewriteApply(target: Tree, fun: Tree, targs: List[Tree], args: List[Tree]) = {
+ def rewriteApply(target: Tree, fun: Tree, targs: List[Tree], args: List[Tree], mustTransformArgs: Boolean = true) = {
val receiver: Tree = fun match {
case Select(qual, _) => qual
case _ => EmptyTree
@@ -223,7 +227,7 @@ abstract class TailCalls extends Transform {
def receiverIsSame = ctx.enclosingType.widen =:= receiver.tpe.widen
def receiverIsSuper = ctx.enclosingType.widen <:< receiver.tpe.widen
def isRecursiveCall = (ctx.method eq fun.symbol) && ctx.tailPos
- def transformArgs = noTailTransforms(args)
+ def transformArgs = if (mustTransformArgs) noTailTransforms(args) else args
def matchesTypeArgs = ctx.tparams sameElements (targs map (_.tpe.typeSymbol))
/* Records failure reason in Context for reporting.
@@ -265,6 +269,10 @@ abstract class TailCalls extends Transform {
!(sym.hasAccessorFlag || sym.isConstructor)
}
+ // intentionally shadowing imports from definitions for performance
+ val runDefinitions = currentRun.runDefinitions
+ import runDefinitions.{Boolean_or, Boolean_and}
+
tree match {
case ValDef(_, _, _, _) =>
if (tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass))
@@ -312,8 +320,13 @@ abstract class TailCalls extends Transform {
// the assumption is once we encounter a case, the remainder of the block will consist of cases
// the prologue may be empty, usually it is the valdef that stores the scrut
val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef])
+ val transformedPrologue = noTailTransforms(prologue)
+ val transformedCases = transformTrees(cases)
+ val transformedStats =
+ if ((prologue eq transformedPrologue) && (cases eq transformedCases)) stats // allow reuse of `tree` if the subtransform was an identity
+ else transformedPrologue ++ transformedCases
treeCopy.Block(tree,
- noTailTransforms(prologue) ++ transformTrees(cases),
+ transformedStats,
transform(expr)
)
@@ -380,7 +393,7 @@ abstract class TailCalls extends Transform {
if (res ne arg)
treeCopy.Apply(tree, fun, res :: Nil)
else
- rewriteApply(fun, fun, Nil, args)
+ rewriteApply(fun, fun, Nil, args, mustTransformArgs = false)
case Apply(fun, args) =>
rewriteApply(fun, fun, Nil, args)
@@ -421,6 +434,10 @@ abstract class TailCalls extends Transform {
def traverseNoTail(tree: Tree) = traverse(tree, maybeTailNew = false)
def traverseTreesNoTail(trees: List[Tree]) = trees foreach traverseNoTail
+ // intentionally shadowing imports from definitions for performance
+ private val runDefinitions = currentRun.runDefinitions
+ import runDefinitions.{Boolean_or, Boolean_and}
+
override def traverse(tree: Tree) = tree match {
// we're looking for label(x){x} in tail position, since that means `a` is in tail position in a call `label(a)`
case LabelDef(_, List(arg), body@Ident(_)) if arg.symbol == body.symbol =>
diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
index f83b6f857e..3b23306386 100644
--- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
@@ -1,7 +1,6 @@
package scala.tools.nsc
package transform
-import scala.reflect.internal._
import scala.tools.nsc.ast.TreeDSL
import scala.tools.nsc.Global
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
index 7fcfdf4868..8924394b72 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
@@ -122,7 +122,6 @@ trait ScalacPatternExpanders {
case _ => sel
}
val patterns = newPatterns(args)
- val isSeq = sel.symbol.name == nme.unapplySeq
val isUnapply = sel.symbol.name == nme.unapply
val extractor = sel.symbol.name match {
diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
index eb29ccf4e1..e278130437 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -330,7 +330,7 @@ trait Contexts { self: Analyzer =>
// if set, errors will not be reporter/thrown
def bufferErrors = reporter.isBuffering
- def reportErrors = !bufferErrors
+ def reportErrors = !(bufferErrors || reporter.isThrowing)
// whether to *report* (which is separate from buffering/throwing) ambiguity errors
def ambiguousErrors = this(AmbiguousErrors)
@@ -1247,6 +1247,7 @@ trait Contexts { self: Analyzer =>
def makeImmediate: ContextReporter = this
def makeBuffering: ContextReporter = this
def isBuffering: Boolean = false
+ def isThrowing: Boolean = false
/** Emit an ambiguous error according to context.ambiguousErrors
*
@@ -1384,6 +1385,7 @@ trait Contexts { self: Analyzer =>
* TODO: get rid of it, use ImmediateReporter and a check for reporter.hasErrors where necessary
*/
private[typechecker] class ThrowingReporter extends ContextReporter {
+ override def isThrowing = true
protected def handleError(pos: Position, msg: String): Unit = throw new TypeError(pos, msg)
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala
index 57f27a05fd..ea44b9dc39 100644
--- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala
@@ -61,7 +61,7 @@ trait StdAttachments {
val metadata = MacroExpansionAttachment(expandee, expanded)
expandee updateAttachment metadata
expanded match {
- case expanded: Tree => expanded updateAttachment metadata
+ case expanded: Tree if !expanded.isEmpty => expanded updateAttachment metadata
case _ => // do nothing
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Tags.scala b/src/compiler/scala/tools/nsc/typechecker/Tags.scala
index 90ec3a89b8..57dc74d2a0 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Tags.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Tags.scala
@@ -11,7 +11,6 @@ trait Tags {
self: Typer =>
private val runDefinitions = currentRun.runDefinitions
- import runDefinitions._
private def resolveTag(pos: Position, taggedTp: Type, allowMaterialization: Boolean) = enteringTyper {
def wrapper (tree: => Tree): Tree = if (allowMaterialization) (context.withMacrosEnabled[Tree](tree)) else (context.withMacrosDisabled[Tree](tree))
@@ -66,7 +65,7 @@ trait Tags {
// if someone requests a type tag, but scala-reflect.jar isn't on the library classpath, then bail
if (pre == NoType && ApiUniverseClass == NoSymbol) EmptyTree
else {
- val tagSym = if (concrete) TypeTagClass else WeakTypeTagClass
+ val tagSym = if (concrete) runDefinitions.TypeTagClass else runDefinitions.WeakTypeTagClass
val tagTp = if (pre == NoType) TypeRef(ApiUniverseClass.toTypeConstructor, tagSym, List(tp)) else singleType(pre, pre member tagSym.name)
val taggedTp = appliedType(tagTp, List(tp))
resolveTag(pos, taggedTp, allowMaterialization)
diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
index 1dac27639c..0f90c6a478 100644
--- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
@@ -607,7 +607,7 @@ trait TypeDiagnostics {
if (!c.owner.exists || c.owner.isClass || c.owner.isMethod || (c.owner.isType && !c.owner.isParameter)) c
else enclClassOrMethodOrTypeMember(c.outer)
- val tt = tparams.filter(_.name != typeNames.WILDCARD).foreach { tp =>
+ tparams.filter(_.name != typeNames.WILDCARD).foreach { tp =>
// we don't care about type params shadowing other type params in the same declaration
enclClassOrMethodOrTypeMember(context).outer.lookupSymbol(tp.name, s => s != tp.symbol && s.hasRawInfo && reallyExists(s)) match {
case LookupSucceeded(_, sym2) => context.warning(tp.pos,
diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
index cc2d9141ce..fc1f45e358 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
@@ -142,17 +142,30 @@ trait Unapplies extends ast.TreeDSL {
/** The unapply method corresponding to a case class
*/
def caseModuleUnapplyMeth(cdef: ClassDef): DefDef = {
- val tparams = constrTparamsInvariant(cdef)
- val method = constrParamss(cdef) match {
+ val tparams = constrTparamsInvariant(cdef)
+ val method = constrParamss(cdef) match {
case xs :: _ if xs.nonEmpty && isRepeatedParamType(xs.last.tpt) => nme.unapplySeq
case _ => nme.unapply
}
- val cparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), unapplyParamName, classType(cdef, tparams), EmptyTree))
- val ifNull = if (constrParamss(cdef).head.isEmpty) FALSE else REF(NoneModule)
- val body = nullSafe({ case Ident(x) => caseClassUnapplyReturnValue(x, cdef) }, ifNull)(Ident(unapplyParamName))
+ val cparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), unapplyParamName, classType(cdef, tparams), EmptyTree))
+ val resultType = if (!settings.isScala212) TypeTree() else { // fix for SI-6541 under -Xsource:2.12
+ def repeatedToSeq(tp: Tree) = tp match {
+ case AppliedTypeTree(Select(_, tpnme.REPEATED_PARAM_CLASS_NAME), tps) => AppliedTypeTree(gen.rootScalaDot(tpnme.Seq), tps)
+ case _ => tp
+ }
+ constrParamss(cdef) match {
+ case Nil | Nil :: _ =>
+ gen.rootScalaDot(tpnme.Boolean)
+ case params :: _ =>
+ val constrParamTypes = params.map(param => repeatedToSeq(param.tpt))
+ AppliedTypeTree(gen.rootScalaDot(tpnme.Option), List(treeBuilder.makeTupleType(constrParamTypes)))
+ }
+ }
+ val ifNull = if (constrParamss(cdef).head.isEmpty) FALSE else REF(NoneModule)
+ val body = nullSafe({ case Ident(x) => caseClassUnapplyReturnValue(x, cdef) }, ifNull)(Ident(unapplyParamName))
atPos(cdef.pos.focus)(
- DefDef(caseMods, method, tparams, List(cparams), TypeTree(), body)
+ DefDef(caseMods, method, tparams, List(cparams), resultType, body)
)
}
diff --git a/src/compiler/scala/tools/util/Javap.scala b/src/compiler/scala/tools/util/Javap.scala
deleted file mode 100644
index 3cfc1eb2a1..0000000000
--- a/src/compiler/scala/tools/util/Javap.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Paul Phillips
- */
-
-package scala.tools
-package util
-
-import scala.tools.nsc.util.ScalaClassLoader
-import java.io.PrintWriter
-
-trait JpResult {
- def isError: Boolean
- def value: Any
- def show(): Unit
-}
-
-trait Javap {
- def loader: ScalaClassLoader
- def printWriter: PrintWriter
- def apply(args: Seq[String]): List[JpResult]
- def tryFile(path: String): Option[Array[Byte]]
- def tryClass(path: String): Array[Byte]
-}
-
-object NoJavap extends Javap {
- def loader: ScalaClassLoader = getClass.getClassLoader
- def printWriter: PrintWriter = new PrintWriter(System.err, true)
- def apply(args: Seq[String]): List[JpResult] = Nil
- def tryFile(path: String): Option[Array[Byte]] = None
- def tryClass(path: String): Array[Byte] = Array()
-}