summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend')
-rw-r--r--src/compiler/scala/tools/nsc/backend/JavaPlatform.scala35
-rw-r--r--src/compiler/scala/tools/nsc/backend/Platform.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala56
-rw-r--r--src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala51
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala553
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala71
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala2239
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala711
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/ICodes.scala129
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala201
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Members.scala296
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala767
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Primitives.scala247
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Printers.scala126
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/Repository.scala47
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala438
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala82
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala553
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala92
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala102
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala12
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala18
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala250
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala49
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala725
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala62
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala465
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala784
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala696
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala25
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala110
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala152
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala60
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala174
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala370
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala144
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala445
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala3350
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala62
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala556
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala514
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala273
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala191
-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)80
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala36
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala374
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala907
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala247
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala243
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala492
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala351
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala635
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala75
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala936
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala339
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala240
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala871
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala235
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala626
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala450
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala392
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/Inliners.scala1075
65 files changed, 7596 insertions, 17316 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
index 6bd123c51f..dc63b335cc 100644
--- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
+++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
@@ -7,11 +7,9 @@ package scala.tools.nsc
package backend
import io.AbstractFile
-import scala.tools.nsc.classpath.FlatClassPath
-import scala.tools.nsc.settings.ClassPathRepresentationType
-import scala.tools.nsc.util.{ ClassPath, DeltaClassPath, MergedClassPath }
-import scala.tools.util.FlatClassPathResolver
+import scala.tools.nsc.classpath.AggregateClassPath
import scala.tools.util.PathResolver
+import scala.tools.nsc.util.ClassPath
trait JavaPlatform extends Platform {
val global: Global
@@ -19,34 +17,25 @@ trait JavaPlatform extends Platform {
import global._
import definitions._
- private[nsc] var currentClassPath: Option[MergedClassPath[AbstractFile]] = None
-
- def classPath: ClassPath[AbstractFile] = {
- assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive,
- "To use recursive classpath representation you must enable it with -YclasspathImpl:recursive compiler option.")
+ private[nsc] var currentClassPath: Option[ClassPath] = None
+ private[nsc] def classPath: ClassPath = {
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result)
currentClassPath.get
}
- private[nsc] lazy val flatClassPath: FlatClassPath = {
- assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat,
- "To use flat classpath representation you must enable it with -YclasspathImpl:flat compiler option.")
-
- new FlatClassPathResolver(settings).result
- }
-
/** Update classpath with a substituted subentry */
- def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) =
- currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst))
+ def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = global.classPath match {
+ case AggregateClassPath(entries) =>
+ currentClassPath = Some(AggregateClassPath(entries map (e => subst.getOrElse(e, e))))
- private def classEmitPhase =
- if (settings.isBCodeActive) genBCode
- else genASM
+ case cp: ClassPath =>
+ currentClassPath = Some(subst.getOrElse(cp, cp))
+ }
def platformPhases = List(
- flatten, // get rid of inner classes
- classEmitPhase // generate .class files
+ flatten, // get rid of inner classes
+ genBCode // generate .class files
)
lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_)
diff --git a/src/compiler/scala/tools/nsc/backend/Platform.scala b/src/compiler/scala/tools/nsc/backend/Platform.scala
index c3bc213be1..e464768bb3 100644
--- a/src/compiler/scala/tools/nsc/backend/Platform.scala
+++ b/src/compiler/scala/tools/nsc/backend/Platform.scala
@@ -6,9 +6,8 @@
package scala.tools.nsc
package backend
-import util.ClassPath
import io.AbstractFile
-import scala.tools.nsc.classpath.FlatClassPath
+import scala.tools.nsc.util.ClassPath
/** The platform dependent pieces of Global.
*/
@@ -16,14 +15,11 @@ trait Platform {
val symbolTable: symtab.SymbolTable
import symbolTable._
- /** The old, recursive implementation of compiler classpath. */
- def classPath: ClassPath[AbstractFile]
-
/** The new implementation of compiler classpath. */
- private[nsc] def flatClassPath: FlatClassPath
+ private[nsc] def classPath: ClassPath
/** Update classpath with a substitution that maps entries to entries */
- def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]])
+ def updateClassPath(subst: Map[ClassPath, ClassPath])
/** Any platform-specific phases. */
def platformPhases: List[SubComponent]
diff --git a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala
index b8ddb65de9..c18f220d95 100644
--- a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala
+++ b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala
@@ -7,7 +7,7 @@ package scala
package tools.nsc
package backend
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
/** Scala primitive operations are represented as methods in `Any` and
* `AnyVal` subclasses. Here we demultiplex them by providing a mapping
@@ -31,7 +31,6 @@ abstract class ScalaPrimitives {
import global._
import definitions._
- import global.icodes._
// Arithmetic unary operations
final val POS = 1 // +x
@@ -62,8 +61,8 @@ abstract class ScalaPrimitives {
final val NE = 43 // x != y
final val LT = 44 // x < y
final val LE = 45 // x <= y
- final val GE = 46 // x > y
- final val GT = 47 // x >= y
+ final val GT = 46 // x > y
+ final val GE = 47 // x >= y
// Boolean unary operations
final val ZNOT = 50 // !x
@@ -447,9 +446,10 @@ abstract class ScalaPrimitives {
inform(s"Unknown primitive method $cls.$method")
else alts foreach (s =>
addPrimitive(s,
- s.info.paramTypes match {
- case tp :: _ if code == ADD && tp =:= StringTpe => CONCAT
- case _ => code
+ if (code != ADD) code
+ else exitingTyper(s.info).paramTypes match {
+ case tp :: _ if tp =:= StringTpe => CONCAT
+ case _ => code
}
)
)
@@ -457,18 +457,6 @@ abstract class ScalaPrimitives {
def isCoercion(code: Int): Boolean = (code >= B2B) && (code <= D2D)
- final val typeOfArrayOp: Map[Int, TypeKind] = Map(
- (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++
- (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++
- (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++
- (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++
- (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++
- (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++
- (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++
- (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++
- (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> REFERENCE(AnyRefClass))) : _*
- )
-
/** Check whether the given operation code is an array operation. */
def isArrayOp(code: Int): Boolean =
isArrayNew(code) | isArrayLength(code) | isArrayGet(code) | isArraySet(code)
@@ -535,24 +523,11 @@ abstract class ScalaPrimitives {
case _ => false
}
- /** If code is a coercion primitive, the result type */
- def generatedKind(code: Int): TypeKind = code match {
- case B2B | C2B | S2B | I2B | L2B | F2B | D2B => BYTE
- case B2C | C2C | S2C | I2C | L2C | F2C | D2C => CHAR
- case B2S | C2S | S2S | I2S | L2S | F2S | D2S => SHORT
- case B2I | C2I | S2I | I2I | L2I | F2I | D2I => INT
- case B2L | C2L | S2L | I2L | L2L | F2L | D2L => LONG
- case B2F | C2F | S2F | I2F | L2F | F2F | D2F => FLOAT
- case B2D | C2D | S2D | I2D | L2D | F2D | D2D => DOUBLE
- }
-
def isPrimitive(sym: Symbol): Boolean = primitives contains sym
/** Return the code for the given symbol. */
- def getPrimitive(sym: Symbol): Int = {
- assert(isPrimitive(sym), "Unknown primitive " + sym)
- primitives(sym)
- }
+ def getPrimitive(sym: Symbol): Int =
+ primitives.getOrElse(sym, throw new AssertionError(s"Unknown primitive $sym"))
/**
* Return the primitive code of the given operation. If the
@@ -565,6 +540,7 @@ abstract class ScalaPrimitives {
*/
def getPrimitive(fun: Symbol, tpe: Type): Int = {
import definitions._
+ import genBCode.bTypes._
val code = getPrimitive(fun)
def elementType = enteringTyper {
@@ -577,7 +553,7 @@ abstract class ScalaPrimitives {
code match {
case APPLY =>
- toTypeKind(elementType) match {
+ typeToBType(elementType) match {
case BOOL => ZARRAY_GET
case BYTE => BARRAY_GET
case SHORT => SARRAY_GET
@@ -586,13 +562,13 @@ abstract class ScalaPrimitives {
case LONG => LARRAY_GET
case FLOAT => FARRAY_GET
case DOUBLE => DARRAY_GET
- case REFERENCE(_) | ARRAY(_) => OARRAY_GET
+ case _: ClassBType | _: ArrayBType => OARRAY_GET
case _ =>
abort("Unexpected array element type: " + elementType)
}
case UPDATE =>
- toTypeKind(elementType) match {
+ typeToBType(elementType) match {
case BOOL => ZARRAY_SET
case BYTE => BARRAY_SET
case SHORT => SARRAY_SET
@@ -601,13 +577,13 @@ abstract class ScalaPrimitives {
case LONG => LARRAY_SET
case FLOAT => FARRAY_SET
case DOUBLE => DARRAY_SET
- case REFERENCE(_) | ARRAY(_) => OARRAY_SET
+ case _: ClassBType | _: ArrayBType => OARRAY_SET
case _ =>
abort("Unexpected array element type: " + elementType)
}
case LENGTH =>
- toTypeKind(elementType) match {
+ typeToBType(elementType) match {
case BOOL => ZARRAY_LENGTH
case BYTE => BARRAY_LENGTH
case SHORT => SARRAY_LENGTH
@@ -616,7 +592,7 @@ abstract class ScalaPrimitives {
case LONG => LARRAY_LENGTH
case FLOAT => FARRAY_LENGTH
case DOUBLE => DARRAY_LENGTH
- case REFERENCE(_) | ARRAY(_) => OARRAY_LENGTH
+ case _: ClassBType | _: ArrayBType => OARRAY_LENGTH
case _ =>
abort("Unexpected array element type: " + elementType)
}
diff --git a/src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala b/src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala
deleted file mode 100644
index 45ca39fee4..0000000000
--- a/src/compiler/scala/tools/nsc/backend/WorklistAlgorithm.scala
+++ /dev/null
@@ -1,51 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-
-import scala.collection.mutable
-
-/**
- * Simple implementation of a worklist algorithm. A processing
- * function is applied repeatedly to the first element in the
- * worklist, as long as the stack is not empty.
- *
- * The client class should mix-in this class and initialize the worklist
- * field and define the `processElement` method. Then call the `run` method
- * providing a function that initializes the worklist.
- *
- * @author Martin Odersky
- * @version 1.0
- * @see [[scala.tools.nsc.backend.icode.Linearizers]]
- */
-trait WorklistAlgorithm {
- type Elem
- type WList = mutable.Stack[Elem]
-
- val worklist: WList
-
- /**
- * Run the iterative algorithm until the worklist remains empty.
- * The initializer is run once before the loop starts and should
- * initialize the worklist.
- */
- def run(initWorklist: => Unit) = {
- initWorklist
-
- while (worklist.nonEmpty)
- processElement(dequeue)
- }
-
- /**
- * Process the current element from the worklist.
- */
- def processElement(e: Elem): Unit
-
- /**
- * Remove and return the first element to be processed from the worklist.
- */
- def dequeue: Elem
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
deleted file mode 100644
index ad1975ef23..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala
+++ /dev/null
@@ -1,553 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import mutable.ListBuffer
-import backend.icode.analysis.ProgramPoint
-import scala.language.postfixOps
-
-trait BasicBlocks {
- self: ICodes =>
-
- import opcodes._
- import global._
-
- /** Override Array creation for efficiency (to not go through reflection). */
- private implicit val instructionTag: scala.reflect.ClassTag[Instruction] = new scala.reflect.ClassTag[Instruction] {
- def runtimeClass: java.lang.Class[Instruction] = classOf[Instruction]
- final override def newArray(len: Int): Array[Instruction] = new Array[Instruction](len)
- }
-
- object NoBasicBlock extends BasicBlock(-1, null)
-
- /** This class represents a basic block. Each
- * basic block contains a list of instructions that are
- * either executed all, or none. No jumps
- * to/from the "middle" of the basic block are allowed (modulo exceptions).
- */
- class BasicBlock(val label: Int, val method: IMethod) extends ProgramPoint[BasicBlock] {
- outer =>
-
- import BBFlags._
-
- def code = if (method eq null) NoCode else method.code
-
- private final class SuccessorList() {
- private var successors: List[BasicBlock] = Nil
- /** This method is very hot! Handle with care. */
- private def updateConserve() {
- var lb: ListBuffer[BasicBlock] = null
- var matches = 0
- var remaining = successors
- val direct = directSuccessors
- var scratchHandlers: List[ExceptionHandler] = method.exh
- var scratchBlocks: List[BasicBlock] = direct
-
- def addBlock(bb: BasicBlock) {
- if (matches < 0)
- lb += bb
- else if (remaining.isEmpty || bb != remaining.head) {
- lb = ListBuffer[BasicBlock]() ++= (successors take matches) += bb
- matches = -1
- }
- else {
- matches += 1
- remaining = remaining.tail
- }
- }
-
- while (scratchBlocks ne Nil) {
- addBlock(scratchBlocks.head)
- scratchBlocks = scratchBlocks.tail
- }
- /* Return a list of successors for 'b' that come from exception handlers
- * covering b's (non-exceptional) successors. These exception handlers
- * might not cover 'b' itself. This situation corresponds to an
- * exception being thrown as the first thing of one of b's successors.
- */
- while (scratchHandlers ne Nil) {
- val handler = scratchHandlers.head
- if (handler covers outer)
- addBlock(handler.startBlock)
-
- scratchBlocks = direct
- while (scratchBlocks ne Nil) {
- if (handler covers scratchBlocks.head)
- addBlock(handler.startBlock)
- scratchBlocks = scratchBlocks.tail
- }
- scratchHandlers = scratchHandlers.tail
- }
- // Blocks did not align: create a new list.
- if (matches < 0)
- successors = lb.toList
- // Blocks aligned, but more blocks remain. Take a prefix of the list.
- else if (remaining.nonEmpty)
- successors = successors take matches
- // Otherwise the list is unchanged, leave it alone.
- }
-
- /** This is called millions of times: it is performance sensitive. */
- def updateSuccs() {
- if (isEmpty) {
- if (successors.nonEmpty)
- successors = Nil
- }
- else updateConserve()
- }
- def toList = successors
- }
-
- /** Flags of this basic block. */
- private[this] var flags: Int = 0
-
- /** Does this block have the given flag? */
- def hasFlag(flag: Int): Boolean = (flags & flag) != 0
-
- /** Set the given flag. */
- private def setFlag(flag: Int): Unit = flags |= flag
- private def resetFlag(flag: Int) {
- flags &= ~flag
- }
-
- /** Is this block closed? */
- def closed: Boolean = hasFlag(CLOSED)
- def closed_=(b: Boolean) = if (b) setFlag(CLOSED) else resetFlag(CLOSED)
-
- /** When set, the `emit` methods will be ignored. */
- def ignore: Boolean = hasFlag(IGNORING)
- def ignore_=(b: Boolean) = if (b) setFlag(IGNORING) else resetFlag(IGNORING)
-
- /** Is this block the head of a while? */
- def loopHeader = hasFlag(LOOP_HEADER)
- def loopHeader_=(b: Boolean) =
- if (b) setFlag(LOOP_HEADER) else resetFlag(LOOP_HEADER)
-
- /** Is this block the start block of an exception handler? */
- def exceptionHandlerStart = hasFlag(EX_HEADER)
- def exceptionHandlerStart_=(b: Boolean) =
- if (b) setFlag(EX_HEADER) else resetFlag(EX_HEADER)
-
- /** Has this basic block been modified since the last call to 'successors'? */
- def touched = hasFlag(DIRTYSUCCS)
- def touched_=(b: Boolean) = if (b) {
- setFlag(DIRTYSUCCS | DIRTYPREDS)
- } else {
- resetFlag(DIRTYSUCCS | DIRTYPREDS)
- }
-
- // basic blocks start in a dirty state
- setFlag(DIRTYSUCCS | DIRTYPREDS)
-
- /** Cached predecessors. */
- var preds: List[BasicBlock] = Nil
-
- /** Local variables that are in scope at entry of this basic block. Used
- * for debugging information.
- */
- val varsInScope: mutable.Set[Local] = new mutable.LinkedHashSet()
-
- /** ICode instructions, used as temporary storage while emitting code.
- * Once closed is called, only the `instrs` array should be used.
- */
- private var instructionList: List[Instruction] = Nil
- private var instrs: Array[Instruction] = _
-
- def take(n: Int): Seq[Instruction] =
- if (closed) instrs take n else instructionList takeRight n reverse
-
- def toList: List[Instruction] =
- if (closed) instrs.toList else instructionList.reverse
-
- /** Return an iterator over the instructions in this basic block. */
- def iterator: Iterator[Instruction] =
- if (closed) instrs.iterator else instructionList.reverseIterator
-
- /** return the underlying array of instructions */
- def getArray: Array[Instruction] = {
- assert(closed, this)
- instrs
- }
-
- def fromList(is: List[Instruction]) {
- code.touched = true
- instrs = is.toArray
- closed = true
- }
-
- /** Return the index of inst. Uses reference equality.
- * Returns -1 if not found.
- */
- def indexOf(inst: Instruction): Int = {
- assert(closed, this)
- instrs indexWhere (_ eq inst)
- }
-
- /** Apply a function to all the instructions of the block. */
- final def foreach[U](f: Instruction => U) = {
- if (!closed) dumpMethodAndAbort(method, this)
- else instrs foreach f
-
- // !!! If I replace "instrs foreach f" with the following:
- // var i = 0
- // val len = instrs.length
- // while (i < len) {
- // f(instrs(i))
- // i += 1
- // }
- //
- // Then when compiling under -optimise, quick.plugins fails as follows:
- //
- // quick.plugins:
- // [mkdir] Created dir: /scratch/trunk6/build/quick/classes/continuations-plugin
- // [scalacfork] Compiling 5 files to /scratch/trunk6/build/quick/classes/continuations-plugin
- // [scalacfork] error: java.lang.VerifyError: (class: scala/tools/nsc/typechecker/Implicits$ImplicitSearch, method: typedImplicit0 signature: (Lscala/tools/nsc/typechecker/Implicits$ImplicitInfo;Z)Lscala/tools/nsc/typechecker/Implicits$SearchResult;) Incompatible object argument for function call
- // [scalacfork] at scala.tools.nsc.typechecker.Implicits$class.inferImplicit(Implicits.scala:67)
- // [scalacfork] at scala.tools.nsc.Global$$anon$1.inferImplicit(Global.scala:419)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.wrapImplicit$1(Typers.scala:170)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.inferView(Typers.scala:174)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.adapt(Typers.scala:963)
- // [scalacfork] at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4378)
- //
- // This is bad and should be understood/eliminated.
- }
-
- /** The number of instructions in this basic block so far. */
- def length = if (closed) instrs.length else instructionList.length
- def size = length
-
- /** Return the n-th instruction. */
- def apply(n: Int): Instruction =
- if (closed) instrs(n) else instructionList.reverse(n)
-
- ///////////////////// Substitutions ///////////////////////
-
- /**
- * Replace the instruction at the given position. Used by labels when they are anchored.
- * The replacing instruction is given the nsc.util.Position of the instruction it replaces.
- */
- def replaceInstruction(pos: Int, instr: Instruction): Boolean = {
- assert(closed, "Instructions can be replaced only after the basic block is closed")
- instr.setPos(instrs(pos).pos)
- instrs(pos) = instr
- code.touched = true
- true
- }
-
- /**
- * Replace the given instruction with the new one.
- * Returns `true` if it actually changed something.
- * The replacing instruction is given the nsc.util.Position of the instruction it replaces.
- */
- def replaceInstruction(oldInstr: Instruction, newInstr: Instruction): Boolean = {
- assert(closed, "Instructions can be replaced only after the basic block is closed")
-
- indexOf(oldInstr) match {
- case -1 => false
- case idx =>
- newInstr setPos oldInstr.pos
- instrs(idx) = newInstr
- code.touched = true
- true
- }
- }
-
- /** Replaces `oldInstr` with `is`. It does not update
- * the position field in the newly inserted instructions, so it behaves
- * differently than the one-instruction versions of this function.
- */
- def replaceInstruction(oldInstr: Instruction, is: List[Instruction]): Boolean = {
- assert(closed, "Instructions can be replaced only after the basic block is closed")
-
- indexOf(oldInstr) match {
- case -1 => false
- case idx =>
- instrs = instrs.patch(idx, is, 1)
- code.touched = true
- true
- }
- }
-
- /** Removes instructions found at the given positions.
- */
- def removeInstructionsAt(positions: Int*) {
- assert(closed, this)
- instrs = instrs.indices.toArray filterNot positions.toSet map instrs
- code.touched = true
- }
-
- /** Remove the last instruction of this basic block. It is
- * fast for an open block, but slower when the block is closed.
- */
- def removeLastInstruction() {
- if (closed)
- removeInstructionsAt(length)
- else {
- instructionList = instructionList.tail
- code.touched = true
- }
- }
-
- /** Replaces all instructions found in the map.
- */
- def subst(map: Map[Instruction, Instruction]): Unit =
- if (!closed)
- instructionList = instructionList map (x => map.getOrElse(x, x))
- else
- 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 //////////////////////
-
-
- /** Add a new instruction at the end of the block,
- * using the same source position as the last emitted instruction
- */
- def emit(instr: Instruction) {
- val pos = if (instructionList.isEmpty) NoPosition else instructionList.head.pos
- emit(instr, pos)
- }
-
- /** Emitting does not set touched to true. During code generation this is a hotspot and
- * setting the flag for each emit is a waste. Caching should happen only after a block
- * is closed, which sets the DIRTYSUCCS flag.
- */
- def emit(instr: Instruction, pos: Position) {
- assert(!closed || ignore, this)
-
- if (ignore) {
- if (settings.debug) {
- /* Trying to pin down what it's likely to see after a block has been
- * put into ignore mode so we hear about it if there's a problem.
- */
- instr match {
- case JUMP(_) | RETURN(_) | THROW(_) | SCOPE_EXIT(_) => // ok
- case STORE_LOCAL(local) if nme.isExceptionResultName(local.sym.name) => // ok
- case x => log("Ignoring instruction, possibly at our peril, at " + pos + ": " + x)
- }
- }
- }
- else {
- instr.setPos(pos)
- instructionList ::= instr
- }
- }
-
- def emit(is: Seq[Instruction]) {
- is foreach (i => emit(i, i.pos))
- }
-
- /** The semantics of this are a little odd but it's designed to work
- * seamlessly with the existing code. It emits each supplied instruction,
- * then closes the block. The odd part is that if the instruction has
- * pos == NoPosition, it calls the 1-arg emit, but otherwise it calls
- * the 2-arg emit. This way I could retain existing behavior exactly by
- * calling setPos on any instruction using the two arg version which
- * I wanted to include in a call to emitOnly.
- */
- def emitOnly(is: Instruction*) {
- is foreach (i => if (i.pos == NoPosition) emit(i) else emit(i, i.pos))
- this.close()
- }
-
- /** do nothing if block is already closed */
- def closeWith(instr: Instruction) {
- if (!closed) {
- emit(instr)
- close()
- }
- }
-
- def closeWith(instr: Instruction, pos: Position) {
- if (!closed) {
- emit(instr, pos)
- close()
- }
- }
-
- /** Close the block */
- def close() {
- assert(!closed || ignore, this)
- if (ignore && closed) { // redundant `ignore &&` for clarity -- we should never be in state `!ignore && closed`
- // not doing anything to this block is important...
- // because the else branch reverses innocent blocks, which is wrong when they're in ignore mode (and closed)
- // reversing the instructions when (closed && ignore) wreaks havoc for nested label jumps (see comments in genLoad)
- } else {
- closed = true
- setFlag(DIRTYSUCCS)
- instructionList = instructionList.reverse
- instrs = instructionList.toArray
- if (instructionList.isEmpty) {
- debuglog(s"Removing empty block $this")
- code removeBlock this
- }
- }
- }
-
- /**
- * if cond is true, closes this block, entersIgnoreMode, and removes the block from
- * its list of blocks. Used to allow a block to be started and then cancelled when it
- * is discovered to be unreachable.
- */
- def killIf(cond: Boolean) {
- if (!settings.YdisableUnreachablePrevention && cond) {
- debuglog(s"Killing block $this")
- assert(instructionList.isEmpty, s"Killing a non empty block $this")
- // only checked under debug because fetching predecessor list is moderately expensive
- if (settings.debug)
- assert(predecessors.isEmpty, s"Killing block $this which is referred to from ${predecessors.mkString}")
-
- close()
- enterIgnoreMode()
- }
- }
-
- /**
- * Same as killIf but with the logic of the condition reversed
- */
- def killUnless(cond: Boolean) {
- this killIf !cond
- }
-
- def open() {
- assert(closed, this)
- closed = false
- ignore = false
- touched = true
- instructionList = instructionList.reverse // prepare for appending to the head
- }
-
- def clear() {
- instructionList = Nil
- instrs = null
- preds = Nil
- }
-
- final def isEmpty = instructionList.isEmpty
- final def nonEmpty = !isEmpty
-
- /** Enter ignore mode: new 'emit'ted instructions will not be
- * added to this basic block. It makes the generation of THROW
- * and RETURNs easier.
- */
- def enterIgnoreMode() = {
- ignore = true
- }
-
- /** Return the last instruction of this basic block. */
- def lastInstruction =
- if (closed) instrs(instrs.length - 1)
- else instructionList.head
-
- def exceptionSuccessors: List[BasicBlock] =
- exceptionSuccessorsForBlock(this)
-
- def exceptionSuccessorsForBlock(block: BasicBlock): List[BasicBlock] =
- method.exh collect { case x if x covers block => x.startBlock }
-
- /** Cached value of successors. Must be recomputed whenever a block in the current method is changed. */
- private val succs = new SuccessorList
-
- def successors: List[BasicBlock] = {
- if (touched) {
- succs.updateSuccs()
- resetFlag(DIRTYSUCCS)
- }
- succs.toList
- }
-
- def directSuccessors: List[BasicBlock] =
- if (isEmpty) Nil else lastInstruction match {
- case JUMP(whereto) => whereto :: Nil
- case CJUMP(succ, fail, _, _) => fail :: succ :: Nil
- case CZJUMP(succ, fail, _, _) => fail :: succ :: Nil
- case SWITCH(_, labels) => labels
- case RETURN(_) => Nil
- case THROW(_) => Nil
- case _ =>
- if (closed)
- devWarning(s"$lastInstruction/${lastInstruction.getClass.getName} is not a control flow instruction")
-
- Nil
- }
-
- /** Returns the predecessors of this block. */
- def predecessors: List[BasicBlock] = {
- if (hasFlag(DIRTYPREDS)) {
- resetFlag(DIRTYPREDS)
- preds = code.blocks.iterator filter (_.successors contains this) toList
- }
- preds
- }
-
- override def equals(other: Any): Boolean = other match {
- case that: BasicBlock => (that.label == label) && (that.code == code)
- case _ => false
- }
-
- override def hashCode = label * 41 + code.hashCode
-
- private def succString = if (successors.isEmpty) "[S: N/A]" else successors.distinct.mkString("[S: ", ", ", "]")
- private def predString = if (predecessors.isEmpty) "[P: N/A]" else predecessors.distinct.mkString("[P: ", ", ", "]")
-
- override def toString(): String = "" + label
-
- def blockContents = {
- def posStr(p: Position) = if (p.isDefined) p.line.toString else "<??>"
- val xs = this.toList map (instr => posStr(instr.pos) + "\t" + instr)
- xs.mkString(fullString + " {\n ", "\n ", "\n}")
- }
- def predContents = predecessors.map(_.blockContents).mkString(predecessors.size + " preds:\n", "\n", "\n")
- def succContents = successors.map(_.blockContents).mkString(successors.size + " succs:\n", "\n", "\n")
-
- def fullString: String = List("Block", label, succString, predString, flagsString) mkString " "
- def flagsString: String = BBFlags.flagsToString(flags)
- }
-}
-
-object BBFlags {
- /** This block is a loop header (was translated from a while). */
- final val LOOP_HEADER = (1 << 0)
-
- /** Ignoring mode: emit instructions are dropped. */
- final val IGNORING = (1 << 1)
-
- /** This block is the header of an exception handler. */
- final val EX_HEADER = (1 << 2)
-
- /** This block is closed. No new instructions can be added. */
- final val CLOSED = (1 << 3)
-
- /** Code has been changed, recompute successors. */
- final val DIRTYSUCCS = (1 << 4)
-
- /** Code has been changed, recompute predecessors. */
- final val DIRTYPREDS = (1 << 5)
-
- val flagMap = Map[Int, String](
- LOOP_HEADER -> "loopheader",
- IGNORING -> "ignore",
- EX_HEADER -> "exheader",
- CLOSED -> "closed",
- DIRTYSUCCS -> "dirtysuccs",
- DIRTYPREDS -> "dirtypreds"
- )
- def flagsToString(flags: Int) = {
- flagMap collect { case (bit, name) if (bit & flags) != 0 => "<" + name + ">" } mkString " "
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala b/src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala
deleted file mode 100644
index 8bcdb6dbd2..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/CheckerException.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-class CheckerException(s: String) extends Exception(s)
diff --git a/src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala b/src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala
deleted file mode 100644
index 7243264773..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/ExceptionHandlers.scala
+++ /dev/null
@@ -1,71 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection.immutable
-
-/**
- * Exception handlers are pieces of code that `handle` exceptions on
- * the covered basic blocks. Since Scala's exception handling uses
- * pattern matching instead of just class names to identify handlers,
- * all our handlers will catch `Throwable` and rely on proper ordering
- * in the generated code to preserve nesting.
- */
-trait ExceptionHandlers {
- self: ICodes =>
-
- import global._
- import definitions.{ ThrowableClass }
-
- class ExceptionHandler(val method: IMethod, val label: TermName, val cls: Symbol, val pos: Position) {
- def loadExceptionClass = if (cls == NoSymbol) ThrowableClass else cls
- private var _startBlock: BasicBlock = _
- var finalizer: Finalizer = _
-
- def setStartBlock(b: BasicBlock) = {
- _startBlock = b
- b.exceptionHandlerStart = true
- }
- def startBlock = _startBlock
-
- /** The list of blocks that are covered by this exception handler */
- var covered: immutable.Set[BasicBlock] = immutable.HashSet.empty[BasicBlock]
-
- def addCoveredBlock(b: BasicBlock): this.type = {
- covered = covered + b
- this
- }
-
- /** Is `b` covered by this exception handler? */
- def covers(b: BasicBlock): Boolean = covered(b)
-
- /** The body of this exception handler. May contain 'dead' blocks (which will not
- * make it into generated code because linearizers may not include them) */
- var blocks: List[BasicBlock] = Nil
-
- def addBlock(b: BasicBlock): Unit = blocks = b :: blocks
-
- override def toString() = "exh_" + label + "(" + cls.simpleName + ")"
-
- /** A standard copy constructor */
- def this(other: ExceptionHandler) = {
- this(other.method, other.label, other.cls, other.pos)
-
- covered = other.covered
- setStartBlock(other.startBlock)
- finalizer = other.finalizer
- }
-
- def dup: ExceptionHandler = new ExceptionHandler(this)
- }
-
- class Finalizer(method: IMethod, label: TermName, pos: Position) extends ExceptionHandler(method, label, NoSymbol, pos) {
- override def toString() = "finalizer_" + label
- override def dup: Finalizer = new Finalizer(method, label, pos)
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
deleted file mode 100644
index b6f9bcc9ab..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ /dev/null
@@ -1,2239 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import scala.collection.mutable.{ ListBuffer, Buffer }
-import scala.tools.nsc.symtab._
-import scala.annotation.switch
-
-/**
- * @author Iulian Dragos
- * @version 1.0
- */
-abstract class GenICode extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions._
- import scalaPrimitives.{
- isArrayOp, isComparisonOp, isLogicalOp,
- isUniversalEqualityOp, isReferenceEqualityOp
- }
- import platform.isMaybeBoxed
-
- private val bCodeICodeCommon: jvm.BCodeICodeCommon[global.type] = new jvm.BCodeICodeCommon(global)
- import bCodeICodeCommon._
-
- val phaseName = "icode"
-
- override def newPhase(prev: Phase) = new ICodePhase(prev)
-
- @inline private def debugassert(cond: => Boolean, msg: => Any) {
- if (settings.debug)
- assert(cond, msg)
- }
-
- class ICodePhase(prev: Phase) extends StdPhase(prev) {
-
- override def description = "Generate ICode from the AST"
-
- var unit: CompilationUnit = NoCompilationUnit
-
- override def run() {
- if (!settings.isBCodeActive) {
- scalaPrimitives.init()
- classes.clear()
- }
- super.run()
- }
-
- override def apply(unit: CompilationUnit): Unit = {
- if (settings.isBCodeActive) { return }
- this.unit = unit
- unit.icode.clear()
- informProgress("Generating icode for " + unit)
- gen(unit.body)
- this.unit = NoCompilationUnit
- }
-
- def gen(tree: Tree): Context = gen(tree, new Context())
-
- def gen(trees: List[Tree], ctx: Context): Context = {
- var ctx1 = ctx
- for (t <- trees) ctx1 = gen(t, ctx1)
- ctx1
- }
-
- /** If the selector type has a member with the right name,
- * it is the host class; otherwise the symbol's owner.
- */
- def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match {
- case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner
- case _ => selector.typeSymbol
- }
-
- /////////////////// Code generation ///////////////////////
-
- def gen(tree: Tree, ctx: Context): Context = tree match {
- case EmptyTree => ctx
-
- case PackageDef(pid, stats) =>
- gen(stats, ctx setPackage pid.name)
-
- case ClassDef(mods, name, _, impl) =>
- debuglog("Generating class: " + tree.symbol.fullName)
- val outerClass = ctx.clazz
- ctx setClass (new IClass(tree.symbol) setCompilationUnit unit)
- addClassFields(ctx, tree.symbol)
- classes += (tree.symbol -> ctx.clazz)
- unit.icode += ctx.clazz
- gen(impl, ctx)
- ctx.clazz.methods = ctx.clazz.methods.reverse // preserve textual order
- ctx.clazz.fields = ctx.clazz.fields.reverse // preserve textual order
- ctx setClass outerClass
-
- // !! modules should be eliminated by refcheck... or not?
- case ModuleDef(mods, name, impl) =>
- abort("Modules should not reach backend! " + tree)
-
- case ValDef(mods, name, tpt, rhs) =>
- ctx // we use the symbol to add fields
-
- case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
- debuglog("Entering method " + name)
- val m = new IMethod(tree.symbol)
- m.sourceFile = unit.source
- m.returnType = if (tree.symbol.isConstructor) UNIT
- else toTypeKind(tree.symbol.info.resultType)
- ctx.clazz.addMethod(m)
-
- var ctx1 = ctx.enterMethod(m, tree.asInstanceOf[DefDef])
- addMethodParams(ctx1, vparamss)
- m.native = m.symbol.hasAnnotation(definitions.NativeAttr)
-
- if (!m.isAbstractMethod && !m.native) {
- ctx1 = genLoad(rhs, ctx1, m.returnType)
-
- // reverse the order of the local variables, to match the source-order
- m.locals = m.locals.reverse
-
- rhs match {
- case Block(_, Return(_)) => ()
- case Return(_) => ()
- case EmptyTree =>
- globalError("Concrete method has no definition: " + tree + (
- if (settings.debug) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")"
- else "")
- )
- case _ => if (ctx1.bb.isEmpty)
- ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos)
- else
- ctx1.bb.closeWith(RETURN(m.returnType))
- }
- if (!ctx1.bb.closed) ctx1.bb.close()
- prune(ctx1.method)
- } else
- ctx1.method.setCode(NoCode)
- ctx1
-
- case Template(_, _, body) =>
- gen(body, ctx)
-
- case _ =>
- abort("Illegal tree in gen: " + tree)
- }
-
- private def genStat(trees: List[Tree], ctx: Context): Context =
- trees.foldLeft(ctx)((currentCtx, t) => genStat(t, currentCtx))
-
- /**
- * Generate code for the given tree. The trees should contain statements
- * and not produce any value. Use genLoad for expressions which leave
- * a value on top of the stack.
- *
- * @return a new context. This is necessary for control flow instructions
- * which may change the current basic block.
- */
- private def genStat(tree: Tree, ctx: Context): Context = tree match {
- case Assign(lhs @ Select(_, _), rhs) =>
- val isStatic = lhs.symbol.isStaticMember
- var ctx1 = if (isStatic) ctx else genLoadQualifier(lhs, ctx)
-
- ctx1 = genLoad(rhs, ctx1, toTypeKind(lhs.symbol.info))
- ctx1.bb.emit(STORE_FIELD(lhs.symbol, isStatic), tree.pos)
- ctx1
-
- case Assign(lhs, rhs) =>
- val ctx1 = genLoad(rhs, ctx, toTypeKind(lhs.symbol.info))
- val Some(l) = ctx.method.lookupLocal(lhs.symbol)
- ctx1.bb.emit(STORE_LOCAL(l), tree.pos)
- ctx1
-
- case _ =>
- genLoad(tree, ctx, UNIT)
- }
-
- private def genThrow(expr: Tree, ctx: Context): (Context, TypeKind) = {
- require(expr.tpe <:< ThrowableTpe, expr.tpe)
-
- val thrownKind = toTypeKind(expr.tpe)
- val ctx1 = genLoad(expr, ctx, thrownKind)
- ctx1.bb.emit(THROW(expr.tpe.typeSymbol), expr.pos)
- ctx1.bb.enterIgnoreMode()
-
- (ctx1, NothingReference)
- }
-
- /**
- * Generate code for primitive arithmetic operations.
- * Returns (Context, Generated Type)
- */
- private def genArithmeticOp(tree: Tree, ctx: Context, code: Int): (Context, TypeKind) = {
- val Apply(fun @ Select(larg, _), args) = tree
- var ctx1 = ctx
- var resKind = toTypeKind(larg.tpe)
-
- debugassert(args.length <= 1,
- "Too many arguments for primitive function: " + fun.symbol)
- debugassert(resKind.isNumericType | resKind == BOOL,
- resKind.toString() + " is not a numeric or boolean type " +
- "[operation: " + fun.symbol + "]")
-
- args match {
- // unary operation
- case Nil =>
- ctx1 = genLoad(larg, ctx1, resKind)
- code match {
- case scalaPrimitives.POS =>
- () // nothing
- case scalaPrimitives.NEG =>
- ctx1.bb.emit(CALL_PRIMITIVE(Negation(resKind)), larg.pos)
- case scalaPrimitives.NOT =>
- ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(NOT, resKind)), larg.pos)
- case _ =>
- abort("Unknown unary operation: " + fun.symbol.fullName +
- " code: " + code)
- }
-
- // binary operation
- case rarg :: Nil =>
- resKind = getMaxType(larg.tpe :: rarg.tpe :: Nil)
- if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code))
- assert(resKind.isIntegralType | resKind == BOOL,
- resKind.toString() + " incompatible with arithmetic modulo operation: " + ctx1)
-
- ctx1 = genLoad(larg, ctx1, resKind)
- ctx1 = genLoad(rarg,
- ctx1, // check .NET size of shift arguments!
- if (scalaPrimitives.isShiftOp(code)) INT else resKind)
-
- val primitiveOp = code match {
- case scalaPrimitives.ADD => Arithmetic(ADD, resKind)
- case scalaPrimitives.SUB => Arithmetic(SUB, resKind)
- case scalaPrimitives.MUL => Arithmetic(MUL, resKind)
- case scalaPrimitives.DIV => Arithmetic(DIV, resKind)
- case scalaPrimitives.MOD => Arithmetic(REM, resKind)
- case scalaPrimitives.OR => Logical(OR, resKind)
- case scalaPrimitives.XOR => Logical(XOR, resKind)
- case scalaPrimitives.AND => Logical(AND, resKind)
- case scalaPrimitives.LSL => Shift(LSL, resKind)
- case scalaPrimitives.LSR => Shift(LSR, resKind)
- case scalaPrimitives.ASR => Shift(ASR, resKind)
- case _ => abort("Unknown primitive: " + fun.symbol + "[" + code + "]")
- }
- ctx1.bb.emit(CALL_PRIMITIVE(primitiveOp), tree.pos)
-
- case _ =>
- abort("Too many arguments for primitive function: " + tree)
- }
- (ctx1, resKind)
- }
-
- /** Generate primitive array operations.
- */
- private def genArrayOp(tree: Tree, ctx: Context, code: Int, expectedType: TypeKind): (Context, TypeKind) = {
- import scalaPrimitives._
- val Apply(Select(arrayObj, _), args) = tree
- val k = toTypeKind(arrayObj.tpe)
- val ARRAY(elem) = k
- var ctx1 = genLoad(arrayObj, ctx, k)
- val elementType = typeOfArrayOp.getOrElse(code, abort("Unknown operation on arrays: " + tree + " code: " + code))
-
- var generatedType = expectedType
-
- if (scalaPrimitives.isArrayGet(code)) {
- // load argument on stack
- debugassert(args.length == 1,
- "Too many arguments for array get operation: " + tree)
- ctx1 = genLoad(args.head, ctx1, INT)
- generatedType = elem
- ctx1.bb.emit(LOAD_ARRAY_ITEM(elementType), tree.pos)
- // it's tempting to just drop array loads of type Null instead
- // of adapting them but array accesses can cause
- // ArrayIndexOutOfBounds so we can't. Besides, Array[Null]
- // probably isn't common enough to figure out an optimization
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- }
- else if (scalaPrimitives.isArraySet(code)) {
- debugassert(args.length == 2,
- "Too many arguments for array set operation: " + tree)
- ctx1 = genLoad(args.head, ctx1, INT)
- ctx1 = genLoad(args.tail.head, ctx1, toTypeKind(args.tail.head.tpe))
- // the following line should really be here, but because of bugs in erasure
- // we pretend we generate whatever type is expected from us.
- //generatedType = UNIT
-
- ctx1.bb.emit(STORE_ARRAY_ITEM(elementType), tree.pos)
- }
- else {
- generatedType = INT
- ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(elementType)), tree.pos)
- }
-
- (ctx1, generatedType)
- }
- private def genSynchronized(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val Apply(fun, args) = tree
- val monitor = ctx.makeLocal(tree.pos, ObjectTpe, "monitor")
- var monitorResult: Local = null
- val argTpe = args.head.tpe
- val hasResult = expectedType != UNIT
- if (hasResult)
- monitorResult = ctx.makeLocal(tree.pos, argTpe, "monitorResult")
-
- var ctx1 = genLoadQualifier(fun, ctx)
- ctx1.bb.emit(Seq(
- DUP(ObjectReference),
- STORE_LOCAL(monitor),
- MONITOR_ENTER() setPos tree.pos
- ))
- ctx1.enterSynchronized(monitor)
- debuglog("synchronized block start")
-
- ctx1 = ctx1.Try(
- bodyCtx => {
- val ctx2 = genLoad(args.head, bodyCtx, expectedType /* toTypeKind(tree.tpe.resultType) */)
- if (hasResult)
- ctx2.bb.emit(STORE_LOCAL(monitorResult))
- ctx2.bb.emit(Seq(
- LOAD_LOCAL(monitor),
- MONITOR_EXIT() setPos tree.pos
- ))
- ctx2
- }, List(
- // tree.tpe / fun.tpe is object, which is no longer true after this transformation
- (ThrowableClass, expectedType, exhCtx => {
- exhCtx.bb.emit(Seq(
- LOAD_LOCAL(monitor),
- MONITOR_EXIT() setPos tree.pos,
- THROW(ThrowableClass)
- ))
- exhCtx.bb.enterIgnoreMode()
- exhCtx
- })), EmptyTree, tree)
-
- debuglog("synchronized block end with block %s closed=%s".format(ctx1.bb, ctx1.bb.closed))
- ctx1.exitSynchronized(monitor)
- if (hasResult)
- ctx1.bb.emit(LOAD_LOCAL(monitorResult))
- (ctx1, expectedType)
- }
-
- private def genLoadIf(tree: If, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val If(cond, thenp, elsep) = tree
-
- var thenCtx = ctx.newBlock()
- var elseCtx = ctx.newBlock()
- val contCtx = ctx.newBlock()
-
- genCond(cond, ctx, thenCtx, elseCtx)
-
- val ifKind = toTypeKind(tree.tpe)
- val thenKind = toTypeKind(thenp.tpe)
- val elseKind = if (elsep == EmptyTree) UNIT else toTypeKind(elsep.tpe)
-
- // we need to drop unneeded results, if one branch gives
- // unit and the other gives something on the stack, because
- // the type of 'if' is scala.Any, and its erasure would be Object.
- // But unboxed units are not Objects...
- def hasUnitBranch = thenKind == UNIT || elseKind == UNIT
- val resKind = if (hasUnitBranch) UNIT else ifKind
-
- if (hasUnitBranch)
- debuglog("Will drop result from an if branch")
-
- thenCtx = genLoad(thenp, thenCtx, resKind)
- elseCtx = genLoad(elsep, elseCtx, resKind)
-
- debugassert(!hasUnitBranch || expectedType == UNIT,
- "I produce UNIT in a context where " + expectedType + " is expected!")
-
- // alternatives may be already closed by a tail-recursive jump
- val contReachable = !(thenCtx.bb.ignore && elseCtx.bb.ignore)
- thenCtx.bb.closeWith(JUMP(contCtx.bb))
- elseCtx.bb.closeWith(
- if (elsep == EmptyTree) JUMP(contCtx.bb)
- else JUMP(contCtx.bb) setPos tree.pos
- )
-
- contCtx.bb killUnless contReachable
- (contCtx, resKind)
- }
- private def genLoadTry(tree: Try, ctx: Context, setGeneratedType: TypeKind => Unit): Context = {
- val Try(block, catches, finalizer) = tree
- val kind = toTypeKind(tree.tpe)
-
- val caseHandlers =
- for (CaseDef(pat, _, body) <- catches.reverse) yield {
- def genWildcardHandler(sym: Symbol): (Symbol, TypeKind, Context => Context) =
- (sym, kind, ctx => {
- ctx.bb.emit(DROP(REFERENCE(sym))) // drop the loaded exception
- genLoad(body, ctx, kind)
- })
-
- pat match {
- case Typed(Ident(nme.WILDCARD), tpt) => genWildcardHandler(tpt.tpe.typeSymbol)
- case Ident(nme.WILDCARD) => genWildcardHandler(ThrowableClass)
- case Bind(_, _) =>
- val exception = ctx.method addLocal new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false) // the exception will be loaded and stored into this local
-
- (pat.symbol.tpe.typeSymbol, kind, {
- ctx: Context =>
- ctx.bb.emit(STORE_LOCAL(exception), pat.pos)
- genLoad(body, ctx, kind)
- })
- }
- }
-
- ctx.Try(
- bodyCtx => {
- setGeneratedType(kind)
- genLoad(block, bodyCtx, kind)
- },
- caseHandlers,
- finalizer,
- tree)
- }
-
- private def genPrimitiveOp(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val sym = tree.symbol
- val Apply(fun @ Select(receiver, _), _) = tree
- val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
-
- if (scalaPrimitives.isArithmeticOp(code))
- genArithmeticOp(tree, ctx, code)
- else if (code == scalaPrimitives.CONCAT)
- (genStringConcat(tree, ctx), StringReference)
- else if (code == scalaPrimitives.HASH)
- (genScalaHash(receiver, ctx), INT)
- else if (isArrayOp(code))
- genArrayOp(tree, ctx, code, expectedType)
- else if (isLogicalOp(code) || isComparisonOp(code)) {
- val trueCtx, falseCtx, afterCtx = ctx.newBlock()
-
- genCond(tree, ctx, trueCtx, falseCtx)
- trueCtx.bb.emitOnly(
- CONSTANT(Constant(true)) setPos tree.pos,
- JUMP(afterCtx.bb)
- )
- falseCtx.bb.emitOnly(
- CONSTANT(Constant(false)) setPos tree.pos,
- JUMP(afterCtx.bb)
- )
- (afterCtx, BOOL)
- }
- else if (code == scalaPrimitives.SYNCHRONIZED)
- genSynchronized(tree, ctx, expectedType)
- else if (scalaPrimitives.isCoercion(code)) {
- val ctx1 = genLoad(receiver, ctx, toTypeKind(receiver.tpe))
- genCoercion(tree, ctx1, code)
- (ctx1, scalaPrimitives.generatedKind(code))
- }
- else abort(
- "Primitive operation not handled yet: " + sym.fullName + "(" +
- fun.symbol.simpleName + ") " + " at: " + (tree.pos)
- )
- }
-
- /**
- * Generate code for trees that produce values on the stack
- *
- * @param tree The tree to be translated
- * @param ctx The current context
- * @param expectedType The type of the value to be generated on top of the
- * stack.
- * @return The new context. The only thing that may change is the current
- * basic block (as the labels map is mutable).
- */
- private def genLoad(tree: Tree, ctx: Context, expectedType: TypeKind): Context = {
- var generatedType = expectedType
- debuglog("at line: " + (if (tree.pos.isDefined) tree.pos.line else tree.pos))
-
- val resCtx: Context = tree match {
- case LabelDef(name, params, rhs) =>
- def genLoadLabelDef = {
- val ctx1 = ctx.newBlock() // note: we cannot kill ctx1 if ctx is in ignore mode because
- // label defs can be the target of jumps from other locations.
- // that means label defs can lead to unreachable code without
- // proper reachability analysis
-
- if (nme.isLoopHeaderLabel(name))
- ctx1.bb.loopHeader = true
-
- ctx1.labels.get(tree.symbol) match {
- case Some(label) =>
- debuglog("Found existing label for " + tree.symbol.fullLocationString)
- label.anchor(ctx1.bb)
- label.patch(ctx.method.code)
-
- case None =>
- val pair = (tree.symbol -> (new Label(tree.symbol) anchor ctx1.bb setParams (params map (_.symbol))))
- debuglog("Adding label " + tree.symbol.fullLocationString + " in genLoad.")
- ctx1.labels += pair
- ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)))
- }
-
- ctx.bb.closeWith(JUMP(ctx1.bb), tree.pos)
- genLoad(rhs, ctx1, expectedType /*toTypeKind(tree.symbol.info.resultType)*/)
- }
- genLoadLabelDef
-
- case ValDef(_, name, _, rhs) =>
- def genLoadValDef =
- if (name == nme.THIS) {
- debuglog("skipping trivial assign to _$this: " + tree)
- ctx
- } else {
- val sym = tree.symbol
- val local = ctx.method.addLocal(new Local(sym, toTypeKind(sym.info), false))
-
- if (rhs == EmptyTree) {
- debuglog("Uninitialized variable " + tree + " at: " + (tree.pos))
- ctx.bb.emit(getZeroOf(local.kind))
- }
-
- var ctx1 = ctx
- if (rhs != EmptyTree)
- ctx1 = genLoad(rhs, ctx, local.kind)
-
- ctx1.bb.emit(STORE_LOCAL(local), tree.pos)
- ctx1.scope.add(local)
- ctx1.bb.emit(SCOPE_ENTER(local))
- generatedType = UNIT
- ctx1
- }
- genLoadValDef
-
- case t @ If(cond, thenp, elsep) =>
- val (newCtx, resKind) = genLoadIf(t, ctx, expectedType)
- generatedType = resKind
- newCtx
-
- case Return(expr) =>
- def genLoadReturn = {
- val returnedKind = toTypeKind(expr.tpe)
- debuglog("Return(" + expr + ") with returnedKind = " + returnedKind)
-
- var ctx1 = genLoad(expr, ctx, returnedKind)
- lazy val tmp = ctx1.makeLocal(tree.pos, expr.tpe, "tmp")
- val saved = savingCleanups(ctx1) {
- var savedFinalizer = false
- ctx1.cleanups foreach {
- case MonitorRelease(m) =>
- debuglog("removing " + m + " from cleanups: " + ctx1.cleanups)
- ctx1.bb.emit(Seq(LOAD_LOCAL(m), MONITOR_EXIT()))
- ctx1.exitSynchronized(m)
-
- case Finalizer(f, finalizerCtx) =>
- debuglog("removing " + f + " from cleanups: " + ctx1.cleanups)
- if (returnedKind != UNIT && mayCleanStack(f)) {
- log("Emitting STORE_LOCAL for " + tmp + " to save finalizer.")
- ctx1.bb.emit(STORE_LOCAL(tmp))
- savedFinalizer = true
- }
-
- // duplicate finalizer (takes care of anchored labels)
- val f1 = duplicateFinalizer(Set.empty ++ ctx1.labels.keySet, ctx1, f)
-
- // we have to run this without the same finalizer in
- // the list, otherwise infinite recursion happens for
- // finalizers that contain 'return'
- val fctx = finalizerCtx.newBlock()
- fctx.bb killIf ctx1.bb.ignore
- ctx1.bb.closeWith(JUMP(fctx.bb))
- ctx1 = genLoad(f1, fctx, UNIT)
- }
- savedFinalizer
- }
-
- if (saved) {
- log("Emitting LOAD_LOCAL for " + tmp + " after saving finalizer.")
- ctx1.bb.emit(LOAD_LOCAL(tmp))
- }
- adapt(returnedKind, ctx1.method.returnType, ctx1, tree.pos)
- ctx1.bb.emit(RETURN(ctx.method.returnType), tree.pos)
- ctx1.bb.enterIgnoreMode()
- generatedType = expectedType
- ctx1
- }
- genLoadReturn
-
- case t @ Try(_, _, _) =>
- genLoadTry(t, ctx, generatedType = _)
-
- case Throw(expr) =>
- val (ctx1, expectedType) = genThrow(expr, ctx)
- generatedType = expectedType
- ctx1
-
- case New(tpt) =>
- abort("Unexpected New(" + tpt.summaryString + "/" + tpt + ") received in icode.\n" +
- " Call was genLoad" + ((tree, ctx, expectedType)))
-
- case Apply(TypeApply(fun, targs), _) =>
- def genLoadApply1 = {
- val sym = fun.symbol
- val cast = sym match {
- case Object_isInstanceOf => false
- case Object_asInstanceOf => true
- case _ => abort("Unexpected type application " + fun + "[sym: " + sym.fullName + "]" + " in: " + tree)
- }
-
- val Select(obj, _) = fun
- val l = toTypeKind(obj.tpe)
- val r = toTypeKind(targs.head.tpe)
- val ctx1 = genLoadQualifier(fun, ctx)
-
- if (l.isValueType && r.isValueType)
- genConversion(l, r, ctx1, cast)
- else if (l.isValueType) {
- ctx1.bb.emit(DROP(l), fun.pos)
- if (cast) {
- ctx1.bb.emit(Seq(
- NEW(REFERENCE(definitions.ClassCastExceptionClass)),
- DUP(ObjectReference),
- THROW(definitions.ClassCastExceptionClass)
- ))
- } else
- ctx1.bb.emit(CONSTANT(Constant(false)))
- } else if (r.isValueType && cast) {
- /* Erasure should have added an unboxing operation to prevent that. */
- abort("should have been unboxed by erasure: " + tree)
- } else if (r.isValueType) {
- ctx.bb.emit(IS_INSTANCE(REFERENCE(definitions.boxedClass(r.toType.typeSymbol))))
- } else {
- genCast(l, r, ctx1, cast)
- }
- generatedType = if (cast) r else BOOL
- ctx1
- }
- genLoadApply1
-
- // 'super' call: Note: since constructors are supposed to
- // return an instance of what they construct, we have to take
- // special care. On JVM they are 'void', and Scala forbids (syntactically)
- // to call super constructors explicitly and/or use their 'returned' value.
- // therefore, we can ignore this fact, and generate code that leaves nothing
- // on the stack (contrary to what the type in the AST says).
- case Apply(fun @ Select(Super(_, mix), _), args) =>
- def genLoadApply2 = {
- debuglog("Call to super: " + tree)
- val invokeStyle = SuperCall(mix)
- // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix);
-
- ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos)
- val ctx1 = genLoadArguments(args, fun.symbol.info.paramTypes, ctx)
-
- ctx1.bb.emit(CALL_METHOD(fun.symbol, invokeStyle), tree.pos)
- generatedType =
- if (fun.symbol.isConstructor) UNIT
- else toTypeKind(fun.symbol.info.resultType)
- ctx1
- }
- genLoadApply2
-
- // 'new' constructor call: Note: since constructors are
- // thought to return an instance of what they construct,
- // we have to 'simulate' it by DUPlicating the freshly created
- // instance (on JVM, <init> methods return VOID).
- case Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) =>
- def genLoadApply3 = {
- val ctor = fun.symbol
- debugassert(ctor.isClassConstructor,
- "'new' call to non-constructor: " + ctor.name)
-
- generatedType = toTypeKind(tpt.tpe)
- debugassert(generatedType.isReferenceType || generatedType.isArrayType,
- "Non reference type cannot be instantiated: " + generatedType)
-
- generatedType match {
- case arr @ ARRAY(elem) =>
- val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx)
- val dims = arr.dimensions
- var elemKind = arr.elementKind
- if (args.length > dims)
- reporter.error(tree.pos, "too many arguments for array constructor: found " + args.length +
- " but array has only " + dims + " dimension(s)")
- if (args.length != dims)
- for (i <- args.length until dims) elemKind = ARRAY(elemKind)
- ctx1.bb.emit(CREATE_ARRAY(elemKind, args.length), tree.pos)
- ctx1
-
- case rt @ REFERENCE(cls) =>
- debugassert(ctor.owner == cls,
- "Symbol " + ctor.owner.fullName + " is different than " + tpt)
-
- val nw = NEW(rt)
- ctx.bb.emit(nw, tree.pos)
- ctx.bb.emit(DUP(generatedType))
- val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx)
-
- val init = CALL_METHOD(ctor, Static(onInstance = true))
- nw.init = init
- ctx1.bb.emit(init, tree.pos)
- ctx1
- case _ =>
- abort("Cannot instantiate " + tpt + " of kind: " + generatedType)
- }
- }
- genLoadApply3
-
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
- def genLoadApply4 = {
- debuglog("BOX : " + fun.symbol.fullName)
- val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe))
- val nativeKind = toTypeKind(expr.tpe)
- if (settings.Xdce) {
- // we store this boxed value to a local, even if not really needed.
- // boxing optimization might use it, and dead code elimination will
- // take care of unnecessary stores
- val loc1 = ctx.makeLocal(tree.pos, expr.tpe, "boxed")
- ctx1.bb.emit(STORE_LOCAL(loc1))
- ctx1.bb.emit(LOAD_LOCAL(loc1))
- }
- ctx1.bb.emit(BOX(nativeKind), expr.pos)
- generatedType = toTypeKind(fun.symbol.tpe.resultType)
- ctx1
- }
- genLoadApply4
-
- case Apply(fun @ _, List(expr)) if (currentRun.runDefinitions.isUnbox(fun.symbol)) =>
- debuglog("UNBOX : " + fun.symbol.fullName)
- val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe))
- val boxType = toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
- generatedType = boxType
- ctx1.bb.emit(UNBOX(boxType), expr.pos)
- ctx1
-
- case app @ Apply(fun, args) =>
- def genLoadApply6 = {
- val sym = fun.symbol
-
- if (sym.isLabel) { // jump to a label
- val label = ctx.labels.getOrElse(sym, {
- // it is a forward jump, scan for labels
- resolveForwardLabel(ctx.defdef, ctx, sym)
- ctx.labels.get(sym) match {
- case Some(l) =>
- debuglog("Forward jump for " + sym.fullLocationString + ": scan found label " + l)
- l
- case _ =>
- abort("Unknown label target: " + sym + " at: " + (fun.pos) + ": ctx: " + ctx)
- }
- })
- // note: when one of the args to genLoadLabelArguments is a jump to a label,
- // it will call back into genLoad and arrive at this case, which will then set ctx1.bb.ignore to true,
- // this is okay, since we're jumping unconditionally, so the loads and jumps emitted by the outer
- // call to genLoad (by calling genLoadLabelArguments and emitOnly) can safely be ignored,
- // however, as emitOnly will close the block, which reverses its instructions (when it's still open),
- // we better not reverse when the block has already been closed but is in ignore mode
- // (if it's not in ignore mode, double-closing is an error)
- val ctx1 = genLoadLabelArguments(args, label, ctx)
- ctx1.bb.emitOnly(if (label.anchored) JUMP(label.block) else PJUMP(label))
- ctx1.bb.enterIgnoreMode()
- ctx1
- } else if (isPrimitive(sym)) { // primitive method call
- val (newCtx, resKind) = genPrimitiveOp(app, ctx, expectedType)
- generatedType = resKind
- newCtx
- } else { // normal method call
- debuglog("Gen CALL_METHOD with sym: " + sym + " isStaticSymbol: " + sym.isStaticMember)
- val invokeStyle =
- if (sym.isStaticMember)
- Static(onInstance = false)
- else if (sym.isPrivate || sym.isClassConstructor)
- Static(onInstance = true)
- else
- Dynamic
-
- var ctx1 = if (invokeStyle.hasInstance) genLoadQualifier(fun, ctx) else ctx
- ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx1)
- val cm = CALL_METHOD(sym, invokeStyle)
-
- /* In a couple cases, squirrel away a little extra information in the
- * CALL_METHOD for use by GenASM.
- */
- fun match {
- case Select(qual, _) =>
- val qualSym = findHostClass(qual.tpe, sym)
- if (qualSym == ArrayClass) {
- val kind = toTypeKind(qual.tpe)
- cm setTargetTypeKind kind
- log(s"Stored target type kind for {$sym.fullName} as $kind")
- }
- else {
- cm setHostClass qualSym
- if (qual.tpe.typeSymbol != qualSym)
- log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}")
- }
- case _ =>
- }
- ctx1.bb.emit(cm, tree.pos)
- ctx1.method.updateRecursive(sym)
- generatedType =
- if (sym.isClassConstructor) UNIT
- else toTypeKind(sym.info.resultType)
- // deal with methods that return Null
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- ctx1
- }
- }
- genLoadApply6
-
- case ApplyDynamic(qual, args) =>
- // TODO - this is where we'd catch dynamic applies for invokedynamic.
- sys.error("No invokedynamic support yet.")
- // val ctx1 = genLoad(qual, ctx, ObjectReference)
- // genLoadArguments(args, tree.symbol.info.paramTypes, ctx1)
- // ctx1.bb.emit(CALL_METHOD(tree.symbol, InvokeDynamic), tree.pos)
- // ctx1
-
- case This(qual) =>
- def genLoadThis = {
- assert(tree.symbol == ctx.clazz.symbol || tree.symbol.isModuleClass,
- "Trying to access the this of another class: " +
- "tree.symbol = " + tree.symbol + ", ctx.clazz.symbol = " + ctx.clazz.symbol + " compilation unit:"+unit)
- if (tree.symbol.isModuleClass && tree.symbol != ctx.clazz.symbol) {
- genLoadModule(ctx, tree)
- generatedType = REFERENCE(tree.symbol)
- } else {
- ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos)
- generatedType = REFERENCE(
- if (tree.symbol == ArrayClass) ObjectClass else ctx.clazz.symbol
- )
- }
- ctx
- }
- genLoadThis
-
- case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) =>
- debugassert(tree.symbol.isModule,
- "Selection of non-module from empty package: " + tree +
- " sym: " + tree.symbol + " at: " + (tree.pos)
- )
- genLoadModule(ctx, tree)
-
- case Select(qualifier, selector) =>
- def genLoadSelect = {
- val sym = tree.symbol
- generatedType = toTypeKind(sym.info)
- val hostClass = findHostClass(qualifier.tpe, sym)
- debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass")
- val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier
-
- def genLoadQualUnlessElidable: Context =
- if (qualSafeToElide) ctx else genLoadQualifier(tree, ctx)
-
- if (sym.isModule) {
- genLoadModule(genLoadQualUnlessElidable, tree)
- } else {
- val isStatic = sym.isStaticMember
- val ctx1 = if (isStatic) genLoadQualUnlessElidable
- else genLoadQualifier(tree, ctx)
- ctx1.bb.emit(LOAD_FIELD(sym, isStatic) setHostClass hostClass, tree.pos)
- // it's tempting to drop field accesses of type Null instead of adapting them,
- // but field access can cause static class init so we can't. Besides, fields
- // of type Null probably aren't common enough to figure out an optimization
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- ctx1
- }
- }
- genLoadSelect
-
- case Ident(name) =>
- def genLoadIdent = {
- val sym = tree.symbol
- if (!sym.hasPackageFlag) {
- if (sym.isModule) {
- genLoadModule(ctx, tree)
- generatedType = toTypeKind(sym.info)
- } else {
- ctx.method.lookupLocal(sym) match {
- case Some(l) =>
- ctx.bb.emit(LOAD_LOCAL(l), tree.pos)
- generatedType = l.kind
- case None =>
- val saved = settings.uniqid
- settings.uniqid.value = true
- try {
- val methodCode = unit.body.collect { case dd: DefDef
- if dd.symbol == ctx.method.symbol => showCode(dd);
- }.headOption.getOrElse("<unknown>")
- abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}. \nMethod code: $methodCode")
- }
- finally settings.uniqid.value = saved
- }
- }
- }
- ctx
- }
- genLoadIdent
-
- case Literal(value) =>
- def genLoadLiteral = {
- if (value.tag != UnitTag) (value.tag, expectedType) match {
- case (IntTag, LONG) =>
- ctx.bb.emit(CONSTANT(Constant(value.longValue)), tree.pos)
- generatedType = LONG
- case (FloatTag, DOUBLE) =>
- ctx.bb.emit(CONSTANT(Constant(value.doubleValue)), tree.pos)
- generatedType = DOUBLE
- case (NullTag, _) =>
- ctx.bb.emit(CONSTANT(value), tree.pos)
- generatedType = NullReference
- case _ =>
- ctx.bb.emit(CONSTANT(value), tree.pos)
- generatedType = toTypeKind(tree.tpe)
- }
- ctx
- }
- genLoadLiteral
-
- case Block(stats, expr) =>
- ctx.enterScope()
- var ctx1 = genStat(stats, ctx)
- ctx1 = genLoad(expr, ctx1, expectedType)
- ctx1.exitScope()
- ctx1
-
- case Typed(Super(_, _), _) =>
- genLoad(This(ctx.clazz.symbol), ctx, expectedType)
-
- case Typed(expr, _) =>
- genLoad(expr, ctx, expectedType)
-
- case Assign(_, _) =>
- generatedType = UNIT
- genStat(tree, ctx)
-
- case ArrayValue(tpt @ TypeTree(), _elems) =>
- def genLoadArrayValue = {
- var ctx1 = ctx
- val elmKind = toTypeKind(tpt.tpe)
- generatedType = ARRAY(elmKind)
- val elems = _elems.toIndexedSeq
-
- ctx1.bb.emit(CONSTANT(new Constant(elems.length)), tree.pos)
- ctx1.bb.emit(CREATE_ARRAY(elmKind, 1))
- // inline array literals
- var i = 0
- while (i < elems.length) {
- ctx1.bb.emit(DUP(generatedType), tree.pos)
- ctx1.bb.emit(CONSTANT(new Constant(i)))
- ctx1 = genLoad(elems(i), ctx1, elmKind)
- ctx1.bb.emit(STORE_ARRAY_ITEM(elmKind))
- i = i + 1
- }
- ctx1
- }
- genLoadArrayValue
-
- case Match(selector, cases) =>
- def genLoadMatch = {
- debuglog("Generating SWITCH statement.")
- val ctx1 = genLoad(selector, ctx, INT) // TODO: Java 7 allows strings in switches (so, don't assume INT and don't convert the literals using intValue)
- val afterCtx = ctx1.newBlock()
- afterCtx.bb killIf ctx1.bb.ignore
- var afterCtxReachable = false
- var caseCtx: Context = null
- generatedType = toTypeKind(tree.tpe)
-
- var targets: List[BasicBlock] = Nil
- var tags: List[Int] = Nil
- var default: BasicBlock = afterCtx.bb
-
- for (caze @ CaseDef(pat, guard, body) <- cases) {
- assert(guard == EmptyTree, guard)
- val tmpCtx = ctx1.newBlock()
- tmpCtx.bb killIf ctx1.bb.ignore
- pat match {
- case Literal(value) =>
- tags = value.intValue :: tags
- targets = tmpCtx.bb :: targets
- case Ident(nme.WILDCARD) =>
- default = tmpCtx.bb
- case Alternative(alts) =>
- alts foreach {
- case Literal(value) =>
- tags = value.intValue :: tags
- targets = tmpCtx.bb :: targets
- case _ =>
- abort("Invalid case in alternative in switch-like pattern match: " +
- tree + " at: " + tree.pos)
- }
- case _ =>
- abort("Invalid case statement in switch-like pattern match: " +
- tree + " at: " + (tree.pos))
- }
-
- caseCtx = genLoad(body, tmpCtx, generatedType)
- afterCtxReachable ||= !caseCtx.bb.ignore
- // close the block unless it's already been closed by the body, which closes the block if it ends in a jump (which is emitted to have alternatives share their body)
- caseCtx.bb.closeWith(JUMP(afterCtx.bb) setPos caze.pos)
- }
- afterCtxReachable ||= (default == afterCtx)
- ctx1.bb.emitOnly(
- SWITCH(tags.reverse map (x => List(x)), (default :: targets).reverse) setPos tree.pos
- )
- afterCtx.bb killUnless afterCtxReachable
- afterCtx
- }
- genLoadMatch
-
- case EmptyTree =>
- if (expectedType != UNIT)
- ctx.bb.emit(getZeroOf(expectedType))
- ctx
-
- case _ =>
- abort("Unexpected tree in genLoad: " + tree + "/" + tree.getClass + " at: " + tree.pos)
- }
-
- // emit conversion
- if (generatedType != expectedType) {
- tree match {
- case Literal(Constant(null)) if generatedType == NullReference && expectedType != UNIT =>
- // literal null on the stack (as opposed to a boxed null, see SI-8233),
- // we can bypass `adapt` which would otherwise emit a redundant [DROP, CONSTANT(null)]
- // except one case: when expected type is UNIT (unboxed) where we need to emit just a DROP
- case _ =>
- adapt(generatedType, expectedType, resCtx, tree.pos)
- }
- }
-
- resCtx
- }
-
- /**
- * If we have a method call, field load, or array element load of type Null then
- * we need to convince the JVM that we have a null value because in Scala
- * land Null is a subtype of all ref types, but in JVM land scala.runtime.Null$
- * is not. Note we don't have to adapt loads of locals because the JVM type
- * system for locals does have a null type which it tracks internally. As
- * long as we adapt these other things, the JVM will know that a Scala local of
- * type Null is holding a null.
- */
- private def adaptNullRef(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) {
- debuglog(s"GenICode#adaptNullRef($from, $to, $ctx, $pos)")
-
- // Don't need to adapt null to unit because we'll just drop it anyway. Don't
- // need to adapt to Object or AnyRef because the JVM is happy with
- // upcasting Null to them.
- // We do have to adapt from NullReference to NullReference because we could be storing
- // this value into a local of type Null and we want the JVM to see that it's
- // a null value so we don't have to also adapt local loads.
- if (from == NullReference && to != UNIT && to != ObjectReference && to != AnyRefReference) {
- assert(to.isRefOrArrayType, s"Attempt to adapt a null to a non reference type $to.")
- // adapt by dropping what we've got and pushing a null which
- // will convince the JVM we really do have null
- ctx.bb.emit(DROP(from), pos)
- ctx.bb.emit(CONSTANT(Constant(null)), pos)
- }
- }
-
- private def adapt(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) {
- // An awful lot of bugs explode here - let's leave ourselves more clues.
- // A typical example is an overloaded type assigned after typer.
- debuglog(s"GenICode#adapt($from, $to, $ctx, $pos)")
-
- def coerce(from: TypeKind, to: TypeKind) = ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)), pos)
-
- (from, to) match {
- // The JVM doesn't have a Nothing equivalent, so it doesn't know that a method of type Nothing can't actually return. So for instance, with
- // def f: String = ???
- // we need
- // 0: getstatic #25; //Field scala/Predef$.MODULE$:Lscala/Predef$;
- // 3: invokevirtual #29; //Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
- // 6: athrow
- // So this case tacks on the ahtrow which makes the JVM happy because class Nothing is declared as a subclass of Throwable
- case (NothingReference, _) =>
- ctx.bb.emit(THROW(ThrowableClass))
- ctx.bb.enterIgnoreMode()
- case (NullReference, REFERENCE(_)) =>
- // SI-8223 we can't assume that the stack contains a `null`, it might contain a Null$
- ctx.bb.emit(Seq(DROP(from), CONSTANT(Constant(null))))
- case _ if from isAssignabledTo to =>
- ()
- case (_, UNIT) =>
- ctx.bb.emit(DROP(from), pos)
- // otherwise we'd better be doing a primitive -> primitive coercion or there's a problem
- case _ if !from.isRefOrArrayType && !to.isRefOrArrayType =>
- coerce(from, to)
- case _ =>
- assert(false, s"Can't convert from $from to $to in unit ${unit.source} at $pos")
- }
- }
-
- /** Load the qualifier of `tree` on top of the stack. */
- private def genLoadQualifier(tree: Tree, ctx: Context): Context =
- tree match {
- case Select(qualifier, _) =>
- genLoad(qualifier, ctx, toTypeKind(qualifier.tpe))
- case _ =>
- abort("Unknown qualifier " + tree)
- }
-
- /**
- * Generate code that loads args into label parameters.
- */
- private def genLoadLabelArguments(args: List[Tree], label: Label, ctx: Context): Context = {
- debugassert(
- args.length == label.params.length,
- "Wrong number of arguments in call to label " + label.symbol
- )
- var ctx1 = ctx
-
- def isTrivial(kv: (Tree, Symbol)) = kv match {
- case (This(_), p) if p.name == nme.THIS => true
- case (arg @ Ident(_), p) if arg.symbol == p => true
- case _ => false
- }
-
- val stores = args zip label.params filterNot isTrivial map {
- case (arg, param) =>
- val local = ctx.method.lookupLocal(param).get
- ctx1 = genLoad(arg, ctx1, local.kind)
-
- val store =
- if (param.name == nme.THIS) STORE_THIS(toTypeKind(ctx1.clazz.symbol.tpe))
- else STORE_LOCAL(local)
-
- store setPos arg.pos
- }
-
- // store arguments in reverse order on the stack
- ctx1.bb.emit(stores.reverse)
- ctx1
- }
-
- private def genLoadArguments(args: List[Tree], tpes: List[Type], ctx: Context): Context =
- (args zip tpes).foldLeft(ctx) {
- case (res, (arg, tpe)) =>
- genLoad(arg, res, toTypeKind(tpe))
- }
-
- private def genLoadModule(ctx: Context, tree: Tree): Context = {
- // Working around SI-5604. Rather than failing the compile when we see
- // 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 {
- 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}")
- s.moduleClass
- }
- )
- debuglog("LOAD_MODULE from %s: %s".format(tree.shortClass, sym))
- ctx.bb.emit(LOAD_MODULE(sym), tree.pos)
- ctx
- }
-
- def genConversion(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) = {
- if (cast)
- ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)))
- else {
- ctx.bb.emit(DROP(from))
- ctx.bb.emit(CONSTANT(Constant(from == to)))
- }
- }
-
- def genCast(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) =
- ctx.bb.emit(if (cast) CHECK_CAST(to) else IS_INSTANCE(to))
-
- def getZeroOf(k: TypeKind): Instruction = k match {
- case UNIT => CONSTANT(Constant(()))
- case BOOL => CONSTANT(Constant(false))
- case BYTE => CONSTANT(Constant(0: Byte))
- case SHORT => CONSTANT(Constant(0: Short))
- case CHAR => CONSTANT(Constant(0: Char))
- case INT => CONSTANT(Constant(0: Int))
- case LONG => CONSTANT(Constant(0: Long))
- case FLOAT => CONSTANT(Constant(0.0f))
- case DOUBLE => CONSTANT(Constant(0.0d))
- case REFERENCE(cls) => CONSTANT(Constant(null: Any))
- case ARRAY(elem) => CONSTANT(Constant(null: Any))
- case BOXED(_) => CONSTANT(Constant(null: Any))
- case ConcatClass => abort("no zero of ConcatClass")
- }
-
-
- /** Is the given symbol a primitive operation? */
- def isPrimitive(fun: Symbol): Boolean = scalaPrimitives.isPrimitive(fun)
-
- /** Generate coercion denoted by "code"
- */
- def genCoercion(tree: Tree, ctx: Context, code: Int) = {
- import scalaPrimitives._
- (code: @switch) match {
- case B2B => ()
- case B2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, CHAR)), tree.pos)
- case B2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, SHORT)), tree.pos)
- case B2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, INT)), tree.pos)
- case B2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, LONG)), tree.pos)
- case B2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, FLOAT)), tree.pos)
- case B2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, DOUBLE)), tree.pos)
-
- case S2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, BYTE)), tree.pos)
- case S2S => ()
- case S2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, CHAR)), tree.pos)
- case S2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, INT)), tree.pos)
- case S2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, LONG)), tree.pos)
- case S2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, FLOAT)), tree.pos)
- case S2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, DOUBLE)), tree.pos)
-
- case C2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, BYTE)), tree.pos)
- case C2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, SHORT)), tree.pos)
- case C2C => ()
- case C2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, INT)), tree.pos)
- case C2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, LONG)), tree.pos)
- case C2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, FLOAT)), tree.pos)
- case C2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, DOUBLE)), tree.pos)
-
- case I2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, BYTE)), tree.pos)
- case I2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, SHORT)), tree.pos)
- case I2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, CHAR)), tree.pos)
- case I2I => ()
- case I2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, LONG)), tree.pos)
- case I2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT)), tree.pos)
- case I2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE)), tree.pos)
-
- case L2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, BYTE)), tree.pos)
- case L2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, SHORT)), tree.pos)
- case L2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, CHAR)), tree.pos)
- case L2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, INT)), tree.pos)
- case L2L => ()
- case L2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT)), tree.pos)
- case L2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE)), tree.pos)
-
- case F2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, BYTE)), tree.pos)
- case F2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, SHORT)), tree.pos)
- case F2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, CHAR)), tree.pos)
- case F2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT)), tree.pos)
- case F2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG)), tree.pos)
- case F2F => ()
- case F2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE)), tree.pos)
-
- case D2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, BYTE)), tree.pos)
- case D2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, SHORT)), tree.pos)
- case D2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, CHAR)), tree.pos)
- case D2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT)), tree.pos)
- case D2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG)), tree.pos)
- case D2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT)), tree.pos)
- case D2D => ()
-
- case _ => abort("Unknown coercion primitive: " + code)
- }
- }
-
- /** The Object => String overload.
- */
- private lazy val String_valueOf: Symbol = getMember(StringModule, nme.valueOf) filter (sym =>
- sym.info.paramTypes match {
- case List(pt) => pt.typeSymbol == ObjectClass
- case _ => false
- }
- )
-
- // I wrote it this way before I realized all the primitive types are
- // boxed at this point, so I'd have to unbox them. Keeping it around in
- // case we want to get more precise.
- //
- // private def valueOfForType(tp: Type): Symbol = {
- // val xs = getMember(StringModule, nme.valueOf) filter (sym =>
- // // We always exclude the Array[Char] overload because java throws an NPE if
- // // you pass it a null. It will instead find the Object one, which doesn't.
- // sym.info.paramTypes match {
- // case List(pt) => pt.typeSymbol != ArrayClass && (tp <:< pt)
- // case _ => false
- // }
- // )
- // xs.alternatives match {
- // case List(sym) => sym
- // case _ => NoSymbol
- // }
- // }
-
- /** Generate string concatenation.
- */
- def genStringConcat(tree: Tree, ctx: Context): Context = {
- liftStringConcat(tree) match {
- // Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
- case List(Literal(Constant("")), arg) =>
- debuglog("Rewriting \"\" + x as String.valueOf(x) for: " + arg)
- val ctx1 = genLoad(arg, ctx, ObjectReference)
- ctx1.bb.emit(CALL_METHOD(String_valueOf, Static(onInstance = false)), arg.pos)
- ctx1
- case concatenations =>
- debuglog("Lifted string concatenations for " + tree + "\n to: " + concatenations)
- var ctx1 = ctx
- ctx1.bb.emit(CALL_PRIMITIVE(StartConcat), tree.pos)
- for (elem <- concatenations) {
- val kind = toTypeKind(elem.tpe)
- ctx1 = genLoad(elem, ctx1, kind)
- ctx1.bb.emit(CALL_PRIMITIVE(StringConcat(kind)), elem.pos)
- }
- ctx1.bb.emit(CALL_PRIMITIVE(EndConcat), tree.pos)
- ctx1
- }
- }
-
- /** Generate the scala ## method.
- */
- def genScalaHash(tree: Tree, ctx: Context): Context = {
- val hashMethod = {
- ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule))
- getMember(ScalaRunTimeModule, nme.hash_)
- }
-
- val ctx1 = genLoad(tree, ctx, ObjectReference)
- ctx1.bb.emit(CALL_METHOD(hashMethod, Static(onInstance = false)))
- ctx1
- }
-
- /**
- * Returns a list of trees that each should be concatenated, from
- * left to right. It turns a chained call like "a".+("b").+("c") into
- * a list of arguments.
- */
- def liftStringConcat(tree: Tree): List[Tree] = tree match {
- case Apply(fun @ Select(larg, method), rarg) =>
- if (isPrimitive(fun.symbol) &&
- scalaPrimitives.getPrimitive(fun.symbol) == scalaPrimitives.CONCAT)
- liftStringConcat(larg) ::: rarg
- else
- List(tree)
- case _ =>
- List(tree)
- }
-
- /**
- * Find the label denoted by `lsym` and enter it in context `ctx`.
- *
- * We only enter one symbol at a time, even though we might traverse the same
- * tree more than once per method. That's because we cannot enter labels that
- * might be duplicated (for instance, inside finally blocks).
- *
- * TODO: restrict the scanning to smaller subtrees than the whole method.
- * It is sufficient to scan the trees of the innermost enclosing block.
- */
- private def resolveForwardLabel(tree: Tree, ctx: Context, lsym: Symbol): Unit = tree foreachPartial {
- case t @ LabelDef(_, params, rhs) if t.symbol == lsym =>
- ctx.labels.getOrElseUpdate(t.symbol, {
- val locals = params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))
- ctx.method addLocals locals
-
- new Label(t.symbol) setParams (params map (_.symbol))
- })
- rhs
- }
-
- /**
- * Generate code for conditional expressions. The two basic blocks
- * represent the continuation in case of success/failure of the
- * test.
- */
- private def genCond(tree: Tree,
- ctx: Context,
- thenCtx: Context,
- elseCtx: Context): Boolean =
- {
- /**
- * Generate the de-sugared comparison mechanism that will underly an '=='
- *
- * @param l left-hand side of the '=='
- * @param r right-hand side of the '=='
- * @param code the comparison operator to use
- * @return true if either branch can continue normally to a follow on block, false otherwise
- */
- def genComparisonOp(l: Tree, r: Tree, code: Int): Boolean = {
- val op: TestOp = code match {
- case scalaPrimitives.LT => LT
- case scalaPrimitives.LE => LE
- case scalaPrimitives.GT => GT
- case scalaPrimitives.GE => GE
- case scalaPrimitives.ID | scalaPrimitives.EQ => EQ
- case scalaPrimitives.NI | scalaPrimitives.NE => NE
-
- case _ => abort("Unknown comparison primitive: " + code)
- }
-
- // special-case reference (in)equality test for null (null eq x, x eq null)
- lazy val nonNullSide = ifOneIsNull(l, r)
- if (isReferenceEqualityOp(code) && nonNullSide != null) {
- val ctx1 = genLoad(nonNullSide, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb.emitOnly(
- CZJUMP(thenCtx.bb, elseCtx.bb, op, ObjectReference)
- )
- branchesReachable
- }
- else {
- val kind = getMaxType(l.tpe :: r.tpe :: Nil)
- var ctx1 = genLoad(l, ctx, kind)
- ctx1 = genLoad(r, ctx1, kind)
- val branchesReachable = !ctx1.bb.ignore
-
- ctx1.bb.emitOnly(
- CJUMP(thenCtx.bb, elseCtx.bb, op, kind) setPos r.pos
- )
- branchesReachable
- }
- }
-
- debuglog("Entering genCond with tree: " + tree)
-
- // the default emission
- def default(): Boolean = {
- val ctx1 = genLoad(tree, ctx, BOOL)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb.closeWith(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) setPos tree.pos)
- branchesReachable
- }
-
- tree match {
- // The comparison symbol is in ScalaPrimitives's "primitives" map
- case Apply(fun, args) if isPrimitive(fun.symbol) =>
- import scalaPrimitives.{ ZNOT, ZAND, ZOR, EQ, getPrimitive }
-
- // lhs and rhs of test
- lazy val Select(lhs, _) = fun
- lazy val rhs = args.head
-
- def genZandOrZor(and: Boolean): Boolean = {
- val ctxInterm = ctx.newBlock()
-
- val lhsBranchesReachable = if (and) genCond(lhs, ctx, ctxInterm, elseCtx)
- else genCond(lhs, ctx, thenCtx, ctxInterm)
- // If lhs is known to throw, we can kill the just created ctxInterm.
- ctxInterm.bb killUnless lhsBranchesReachable
-
- val rhsBranchesReachable = genCond(rhs, ctxInterm, thenCtx, elseCtx)
-
- // Reachable means "it does not always throw", i.e. "it might not throw".
- // In an expression (a && b) or (a || b), the b branch might not be evaluated.
- // Such an expression is therefore known to throw only if both expressions throw. Or,
- // successors are reachable if either of the two is reachable (SI-8625).
- lhsBranchesReachable || rhsBranchesReachable
- }
- def genRefEq(isEq: Boolean) = {
- val f = genEqEqPrimitive(lhs, rhs, ctx) _
- if (isEq) f(thenCtx, elseCtx)
- else f(elseCtx, thenCtx)
- }
-
- getPrimitive(fun.symbol) match {
- case ZNOT => genCond(lhs, ctx, elseCtx, thenCtx)
- case ZAND => genZandOrZor(and = true)
- case ZOR => genZandOrZor(and = false)
- case code =>
- // x == y where LHS is reference type
- if (isUniversalEqualityOp(code) && toTypeKind(lhs.tpe).isReferenceType) {
- if (code == EQ) genRefEq(isEq = true)
- else genRefEq(isEq = false)
- }
- else if (isComparisonOp(code))
- genComparisonOp(lhs, rhs, code)
- else
- default()
- }
-
- case _ => default()
- }
- }
-
- /**
- * Generate the "==" code for object references. It is equivalent of
- * if (l eq null) r eq null else l.equals(r);
- *
- * @param l left-hand side of the '=='
- * @param r right-hand side of the '=='
- * @param ctx current context
- * @param thenCtx target context if the comparison yields true
- * @param elseCtx target context if the comparison yields false
- * @return true if either branch can continue normally to a follow on block, false otherwise
- */
- def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context)(thenCtx: Context, elseCtx: Context): Boolean = {
- def getTempLocal = ctx.method.lookupLocal(nme.EQEQ_LOCAL_VAR) getOrElse {
- ctx.makeLocal(l.pos, AnyRefTpe, nme.EQEQ_LOCAL_VAR.toString)
- }
-
- /* True if the equality comparison is between values that require the use of the rich equality
- * comparator (scala.runtime.Comparator.equals). This is the case when either side of the
- * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character.
- * When it is statically known that both sides are equal and subtypes of Number of Character,
- * not using the rich equality is possible (their own equals method will do ok.)*/
- def mustUseAnyComparator: Boolean = {
- def areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
- !areSameFinals && isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol)
- }
-
- if (mustUseAnyComparator) {
- // when -optimise is on we call the @inline-version of equals, found in ScalaRunTime
- val equalsMethod: Symbol = {
- 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 platform.externalEqualsNumObject
- } else platform.externalEquals
- } else {
- ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule))
- getMember(ScalaRunTimeModule, nme.inlinedEquals)
- }
- }
-
- val ctx1 = genLoad(l, ctx, ObjectReference)
- val ctx2 = genLoad(r, ctx1, ObjectReference)
- val branchesReachable = !ctx2.bb.ignore
- ctx2.bb.emitOnly(
- CALL_METHOD(equalsMethod, if (settings.optimise) Dynamic else Static(onInstance = false)),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- }
- else {
- if (isNull(l)) {
- // null == expr -> expr eq null
- val ctx1 = genLoad(r, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- branchesReachable
- } else if (isNull(r)) {
- // expr == null -> expr eq null
- val ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- branchesReachable
- } else if (isNonNullExpr(l)) {
- // Avoid null check if L is statically non-null.
- //
- // "" == expr -> "".equals(expr)
- // Nil == expr -> Nil.equals(expr)
- //
- // Common enough (through pattern matching) to treat this specially here rather than
- // hoping that -Yconst-opt is enabled. The impossible branches for null checks lead
- // to spurious "branch not covered" warnings in Jacoco code coverage.
- var ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1 = genLoad(r, ctx1, ObjectReference)
- ctx1.bb emitOnly(
- CALL_METHOD(Object_equals, Dynamic),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- } else {
- val eqEqTempLocal = getTempLocal
- var ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- lazy val nonNullCtx = {
- val block = ctx1.newBlock()
- block.bb killUnless branchesReachable
- block
- }
-
- // l == r -> if (l eq null) r eq null else l.equals(r)
- ctx1 = genLoad(r, ctx1, ObjectReference)
- val nullCtx = ctx1.newBlock()
- nullCtx.bb killUnless branchesReachable
-
- ctx1.bb.emitOnly(
- STORE_LOCAL(eqEqTempLocal) setPos l.pos,
- DUP(ObjectReference),
- CZJUMP(nullCtx.bb, nonNullCtx.bb, EQ, ObjectReference)
- )
- nullCtx.bb.emitOnly(
- DROP(ObjectReference) setPos l.pos, // type of AnyRef
- LOAD_LOCAL(eqEqTempLocal),
- CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- )
- nonNullCtx.bb.emitOnly(
- LOAD_LOCAL(eqEqTempLocal) setPos l.pos,
- CALL_METHOD(Object_equals, Dynamic),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- }
- }
- }
-
- /**
- * Add all fields of the given class symbol to the current ICode
- * class.
- */
- private def addClassFields(ctx: Context, cls: Symbol) {
- debugassert(ctx.clazz.symbol eq cls,
- "Classes are not the same: " + ctx.clazz.symbol + ", " + cls)
-
- /* Non-method term members are fields, except for module members. Module
- * members can only happen on .NET (no flatten) for inner traits. There,
- * a module symbol is generated (transformInfo in mixin) which is used
- * as owner for the members of the implementation class (so that the
- * backend emits them as static).
- * No code is needed for this module symbol.
- */
- for (f <- cls.info.decls ; if !f.isMethod && f.isTerm && !f.isModule)
- ctx.clazz addField new IField(f)
- }
-
- /**
- * Add parameters to the current ICode method. It is assumed the methods
- * have been uncurried, so the list of lists contains just one list.
- */
- private def addMethodParams(ctx: Context, vparamss: List[List[ValDef]]) {
- vparamss match {
- case Nil => ()
-
- case vparams :: Nil =>
- for (p <- vparams) {
- val lv = new Local(p.symbol, toTypeKind(p.symbol.info), true)
- ctx.method.addParam(lv)
- ctx.scope.add(lv)
- ctx.bb.varsInScope += lv
- }
- ctx.method.params = ctx.method.params.reverse
-
- case _ =>
- abort("Malformed parameter list: " + vparamss)
- }
- }
-
- /** Does this tree have a try-catch block? */
- def mayCleanStack(tree: Tree): Boolean = tree exists {
- case Try(_, _, _) => true
- case _ => false
- }
-
- /**
- * If the block consists of a single unconditional jump, prune
- * it by replacing the instructions in the predecessor to jump
- * directly to the JUMP target of the block.
- */
- def prune(method: IMethod) = {
- var changed = false
- var n = 0
-
- def prune0(block: BasicBlock): Unit = {
- val optCont = block.lastInstruction match {
- case JUMP(b) if (b != block) => Some(b)
- case _ => None
- }
- if (block.size == 1 && optCont.isDefined) {
- val Some(cont) = optCont
- val pred = block.predecessors
- debuglog("Preds: " + pred + " of " + block + " (" + optCont + ")")
- pred foreach { p =>
- changed = true
- p.lastInstruction match {
- case CJUMP(succ, fail, cond, kind) if (succ == block || fail == block) =>
- debuglog("Pruning empty if branch.")
- p.replaceInstruction(p.lastInstruction,
- if (block == succ)
- if (block == fail)
- CJUMP(cont, cont, cond, kind)
- else
- CJUMP(cont, fail, cond, kind)
- else if (block == fail)
- CJUMP(succ, cont, cond, kind)
- else
- abort("Could not find block in preds: " + method + " " + block + " " + pred + " " + p))
-
- case CZJUMP(succ, fail, cond, kind) if (succ == block || fail == block) =>
- debuglog("Pruning empty ifz branch.")
- p.replaceInstruction(p.lastInstruction,
- if (block == succ)
- if (block == fail)
- CZJUMP(cont, cont, cond, kind)
- else
- CZJUMP(cont, fail, cond, kind)
- else if (block == fail)
- CZJUMP(succ, cont, cond, kind)
- else
- abort("Could not find block in preds"))
-
- case JUMP(b) if (b == block) =>
- debuglog("Pruning empty JMP branch.")
- val replaced = p.replaceInstruction(p.lastInstruction, JUMP(cont))
- debugassert(replaced, "Didn't find p.lastInstruction")
-
- case SWITCH(tags, labels) if (labels contains block) =>
- debuglog("Pruning empty SWITCH branch.")
- p.replaceInstruction(p.lastInstruction,
- SWITCH(tags, labels map (l => if (l == block) cont else l)))
-
- // the last instr of the predecessor `p` is not a jump to the block `block`.
- // this happens when `block` is part of an exception handler covering `b`.
- case _ => ()
- }
- }
- if (changed) {
- debuglog("Removing block: " + block)
- method.code.removeBlock(block)
- for (e <- method.exh) {
- e.covered = e.covered filter (_ != block)
- e.blocks = e.blocks filter (_ != block)
- if (e.startBlock eq block)
- e setStartBlock cont
- }
- }
- }
- }
-
- do {
- changed = false
- n += 1
- method.blocks foreach prune0
- } while (changed)
-
- debuglog("Prune fixpoint reached in " + n + " iterations.")
- }
-
- def getMaxType(ts: List[Type]): TypeKind =
- ts map toTypeKind reduceLeft (_ maxType _)
-
- /** Tree transformer that duplicates code and at the same time creates
- * fresh symbols for existing labels. Since labels may be used before
- * they are defined (forward jumps), all labels found are mapped to fresh
- * symbols. References to the same label (use or definition) will remain
- * consistent after this transformation (both the use and the definition of
- * some label l will be mapped to the same label l').
- *
- * Note: If the tree fragment passed to the duplicator contains unbound
- * label names, the bind to the outer labeldef will be lost! That's because
- * a use of an unbound label l will be transformed to l', and the corresponding
- * label def, being outside the scope of this transformation, will not be updated.
- *
- * All LabelDefs are entered into the context label map, since it makes no sense
- * to delay it any more: they will be used at some point.
- */
- class DuplicateLabels(boundLabels: Set[Symbol]) extends Transformer {
- val labels = perRunCaches.newMap[Symbol, Symbol]()
- var method: Symbol = _
- var ctx: Context = _
-
- def apply(ctx: Context, t: Tree) = {
- this.method = ctx.method.symbol
- this.ctx = ctx
- transform(t)
- }
-
- override def transform(t: Tree): Tree = {
- val sym = t.symbol
- def getLabel(pos: Position, name: Name) =
- labels.getOrElseUpdate(sym,
- method.newLabel(unit.freshTermName(name.toString), sym.pos) setInfo sym.tpe
- )
-
- t match {
- case t @ Apply(_, args) if sym.isLabel && !boundLabels(sym) =>
- val newSym = getLabel(sym.pos, sym.name)
- Apply(global.gen.mkAttributedRef(newSym), transformTrees(args)) setPos t.pos setType t.tpe
-
- case t @ LabelDef(name, params, rhs) =>
- val newSym = getLabel(t.pos, name)
- val tree = treeCopy.LabelDef(t, newSym.name, params, transform(rhs))
- tree.symbol = newSym
-
- val pair = (newSym -> (new Label(newSym) setParams (params map (_.symbol))))
- log("Added " + pair + " to labels.")
- ctx.labels += pair
- ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)))
-
- tree
-
- case _ => super.transform(t)
- }
- }
- }
-
- /////////////////////// Context ////////////////////////////////
-
- sealed abstract class Cleanup(val value: AnyRef) {
- def contains(x: AnyRef) = value == x
- }
- case class MonitorRelease(m: Local) extends Cleanup(m) { }
- case class Finalizer(f: Tree, ctx: Context) extends Cleanup (f) { }
-
- def duplicateFinalizer(boundLabels: Set[Symbol], targetCtx: Context, finalizer: Tree) = {
- (new DuplicateLabels(boundLabels))(targetCtx, finalizer)
- }
-
- def savingCleanups[T](ctx: Context)(body: => T): T = {
- val saved = ctx.cleanups
- try body
- finally ctx.cleanups = saved
- }
-
- /**
- * The Context class keeps information relative to the current state
- * in code generation
- */
- class Context {
- /** The current package. */
- var packg: Name = _
-
- /** The current class. */
- var clazz: IClass = _
-
- /** The current method. */
- var method: IMethod = _
-
- /** The current basic block. */
- var bb: BasicBlock = _
-
- /** Map from label symbols to label objects. */
- var labels = perRunCaches.newMap[Symbol, Label]()
-
- /** Current method definition. */
- var defdef: DefDef = _
-
- /** current exception handlers */
- var handlers: List[ExceptionHandler] = Nil
-
- /** The current monitors or finalizers, to be cleaned up upon `return`. */
- var cleanups: List[Cleanup] = Nil
-
- /** The exception handlers we are currently generating code for */
- var currentExceptionHandlers: List[ExceptionHandler] = Nil
-
- /** The current local variable scope. */
- var scope: Scope = EmptyScope
-
- var handlerCount = 0
-
- override def toString =
- s"package $packg { class $clazz { def $method { bb=$bb } } }"
-
- def loadException(ctx: Context, exh: ExceptionHandler, pos: Position) = {
- debuglog("Emitting LOAD_EXCEPTION for class: " + exh.loadExceptionClass)
- ctx.bb.emit(LOAD_EXCEPTION(exh.loadExceptionClass) setPos pos, pos)
- }
-
- def this(other: Context) = {
- this()
- this.packg = other.packg
- this.clazz = other.clazz
- this.method = other.method
- this.bb = other.bb
- this.labels = other.labels
- this.defdef = other.defdef
- this.handlers = other.handlers
- this.handlerCount = other.handlerCount
- this.cleanups = other.cleanups
- this.currentExceptionHandlers = other.currentExceptionHandlers
- this.scope = other.scope
- }
-
- def setPackage(p: Name): this.type = {
- this.packg = p
- this
- }
-
- def setClass(c: IClass): this.type = {
- this.clazz = c
- this
- }
-
- def setMethod(m: IMethod): this.type = {
- this.method = m
- this
- }
-
- def setBasicBlock(b: BasicBlock): this.type = {
- this.bb = b
- this
- }
-
- def enterSynchronized(monitor: Local): this.type = {
- cleanups = MonitorRelease(monitor) :: cleanups
- this
- }
-
- def exitSynchronized(monitor: Local): this.type = {
- assert(cleanups.head contains monitor,
- "Bad nesting of cleanup operations: " + cleanups + " trying to exit from monitor: " + monitor)
- cleanups = cleanups.tail
- this
- }
-
- def addFinalizer(f: Tree, ctx: Context): this.type = {
- cleanups = Finalizer(f, ctx) :: cleanups
- this
- }
-
- /** Prepare a new context upon entry into a method.
- */
- def enterMethod(m: IMethod, d: DefDef): Context = {
- val ctx1 = new Context(this) setMethod(m)
- ctx1.labels = mutable.HashMap()
- ctx1.method.code = new Code(m)
- ctx1.bb = ctx1.method.startBlock
- ctx1.defdef = d
- ctx1.scope = EmptyScope
- ctx1.enterScope()
- ctx1
- }
-
- /** Return a new context for a new basic block. */
- def newBlock(): Context = {
- val block = method.code.newBlock()
- handlers foreach (_ addCoveredBlock block)
- currentExceptionHandlers foreach (_ addBlock block)
- block.varsInScope.clear()
- block.varsInScope ++= scope.varsInScope
- new Context(this) setBasicBlock block
- }
-
- def enterScope() {
- scope = new Scope(scope)
- }
-
- def exitScope() {
- if (bb.nonEmpty) {
- scope.locals foreach { lv => bb.emit(SCOPE_EXIT(lv)) }
- }
- scope = scope.outer
- }
-
- /** Create a new exception handler and adds it in the list
- * of current exception handlers. All new blocks will be
- * 'covered' by this exception handler (in addition to the
- * previously active handlers).
- */
- private def newExceptionHandler(cls: Symbol, pos: Position): ExceptionHandler = {
- handlerCount += 1
- val exh = new ExceptionHandler(method, newTermNameCached("" + handlerCount), cls, pos)
- method.addHandler(exh)
- handlers = exh :: handlers
- debuglog("added handler: " + exh)
-
- exh
- }
-
- /** Add an active exception handler in this context. It will cover all new basic blocks
- * created from now on. */
- private def addActiveHandler(exh: ExceptionHandler) {
- handlerCount += 1
- handlers = exh :: handlers
- debuglog("added handler: " + exh)
- }
-
- /** Return a new context for generating code for the given
- * exception handler.
- */
- private def enterExceptionHandler(exh: ExceptionHandler): Context = {
- currentExceptionHandlers ::= exh
- val ctx = newBlock()
- exh.setStartBlock(ctx.bb)
- ctx
- }
-
- def endHandler() {
- currentExceptionHandlers = currentExceptionHandlers.tail
- }
-
- /** Clone the current context */
- def dup: Context = new Context(this)
-
- /** Make a fresh local variable. It ensures the 'name' is unique. */
- def makeLocal(pos: Position, tpe: Type, name: String): Local = {
- val sym = method.symbol.newVariable(unit.freshTermName(name), pos, Flags.SYNTHETIC) setInfo tpe
- this.method.addLocal(new Local(sym, toTypeKind(tpe), false))
- }
-
-
- /**
- * Generate exception handlers for the body. Body is evaluated
- * with a context where all the handlers are active. Handlers are
- * evaluated in the 'outer' context.
- *
- * It returns the resulting context, with the same active handlers as
- * before the call. Use it like:
- *
- * ` ctx.Try( ctx => {
- * ctx.bb.emit(...) // protected block
- * }, (ThrowableClass,
- * ctx => {
- * ctx.bb.emit(...); // exception handler
- * }), (AnotherExceptionClass,
- * ctx => {...
- * } ))`
- *
- * The resulting structure will look something like
- *
- * outer:
- * // this 'useless' jump will be removed later,
- * // for now it separates the try body's blocks from previous
- * // code since the try body needs its own exception handlers
- * JUMP body
- *
- * body:
- * [ try body ]
- * JUMP normalExit
- *
- * catch[i]:
- * [ handler[i] body ]
- * JUMP normalExit
- *
- * catchAll:
- * STORE exception
- * [ finally body ]
- * THROW exception
- *
- * normalExit:
- * [ finally body ]
- *
- * each catch[i] will cover body. catchAll will cover both body and each catch[i]
- * Additional finally copies are created on the emission of every RETURN in the try body and exception handlers.
- *
- * This could result in unreachable code which has to be cleaned up later, e.g. if the try and all the exception
- * handlers always end in RETURN then there will be no "normal" flow out of the try/catch/finally.
- * Later reachability analysis will remove unreachable code.
- */
- def Try(body: Context => Context,
- handlers: List[(Symbol, TypeKind, Context => Context)],
- finalizer: Tree,
- tree: Tree) = {
-
- val outerCtx = this.dup // context for generating exception handlers, covered by the catch-all finalizer
- val finalizerCtx = this.dup // context for generating finalizer handler
- val normalExitCtx = outerCtx.newBlock() // context where flow will go on a "normal" (non-return, non-throw) exit from a try or catch handler
- var normalExitReachable = false
- var tmp: Local = null
- val kind = toTypeKind(tree.tpe)
- val guardResult = kind != UNIT && mayCleanStack(finalizer)
- // we need to save bound labels before any code generation is performed on
- // the current context (otherwise, any new labels in the finalizer that need to
- // be duplicated would be incorrectly considered bound -- see #2850).
- val boundLabels: Set[Symbol] = Set.empty ++ labels.keySet
-
- if (guardResult) {
- tmp = this.makeLocal(tree.pos, tree.tpe, "tmp")
- }
-
- def emitFinalizer(ctx: Context): Context = if (!finalizer.isEmpty) {
- val ctx1 = finalizerCtx.dup.newBlock()
- ctx1.bb killIf ctx.bb.ignore
- ctx.bb.closeWith(JUMP(ctx1.bb))
-
- if (guardResult) {
- ctx1.bb.emit(STORE_LOCAL(tmp))
- val ctx2 = genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT)
- ctx2.bb.emit(LOAD_LOCAL(tmp))
- ctx2
- } else
- genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT)
- } else ctx
-
-
- // Generate the catch-all exception handler that deals with uncaught exceptions coming
- // from the try or exception handlers. It catches the exception, runs the finally code, then rethrows
- // the exception
- if (settings.YdisableUnreachablePrevention || !outerCtx.bb.ignore) {
- if (finalizer != EmptyTree) {
- val exh = outerCtx.newExceptionHandler(NoSymbol, finalizer.pos) // finalizer covers exception handlers
- this.addActiveHandler(exh) // .. and body as well
- val exhStartCtx = finalizerCtx.enterExceptionHandler(exh)
- exhStartCtx.bb killIf outerCtx.bb.ignore
- val exception = exhStartCtx.makeLocal(finalizer.pos, ThrowableTpe, "exc")
- loadException(exhStartCtx, exh, finalizer.pos)
- exhStartCtx.bb.emit(STORE_LOCAL(exception))
- val exhEndCtx = genLoad(finalizer, exhStartCtx, UNIT)
- exhEndCtx.bb.emit(LOAD_LOCAL(exception))
- exhEndCtx.bb.closeWith(THROW(ThrowableClass))
- exhEndCtx.bb.enterIgnoreMode()
- finalizerCtx.endHandler()
- }
-
- // Generate each exception handler
- for ((sym, kind, handler) <- handlers) {
- val exh = this.newExceptionHandler(sym, tree.pos)
- val exhStartCtx = outerCtx.enterExceptionHandler(exh)
- exhStartCtx.bb killIf outerCtx.bb.ignore
- exhStartCtx.addFinalizer(finalizer, finalizerCtx)
- loadException(exhStartCtx, exh, tree.pos)
- val exhEndCtx = handler(exhStartCtx)
- normalExitReachable ||= !exhEndCtx.bb.ignore
- exhEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
- outerCtx.endHandler()
- }
- }
-
- val bodyCtx = this.newBlock()
- bodyCtx.bb killIf outerCtx.bb.ignore
- if (finalizer != EmptyTree)
- bodyCtx.addFinalizer(finalizer, finalizerCtx)
-
- val bodyEndCtx = body(bodyCtx)
-
- outerCtx.bb.closeWith(JUMP(bodyCtx.bb))
-
- normalExitReachable ||= !bodyEndCtx.bb.ignore
- normalExitCtx.bb killUnless normalExitReachable
- bodyEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
-
- emitFinalizer(normalExitCtx)
- }
- }
- }
-
- /**
- * Represent a label in the current method code. In order
- * to support forward jumps, labels can be created without
- * having a designated target block. They can later be attached
- * by calling `anchor`.
- */
- class Label(val symbol: Symbol) {
- var anchored = false
- var block: BasicBlock = _
- var params: List[Symbol] = _
-
- private var toPatch: List[Instruction] = Nil
-
- /** Fix this label to the given basic block. */
- def anchor(b: BasicBlock): Label = {
- assert(!anchored, "Cannot anchor an already anchored label!")
- anchored = true
- this.block = b
- this
- }
-
- def setParams(p: List[Symbol]): Label = {
- assert(params eq null, "Cannot set label parameters twice!")
- params = p
- this
- }
-
- /** Add an instruction that refers to this label. */
- def addCallingInstruction(i: Instruction) =
- toPatch = i :: toPatch
-
- /**
- * Patch the code by replacing pseudo call instructions with
- * jumps to the given basic block.
- */
- def patch(code: Code) {
- val map = mapFrom(toPatch)(patch)
- code.blocks foreach (_ subst map)
- }
-
- /**
- * Return the patched instruction. If the given instruction
- * jumps to this label, replace it with the basic block. Otherwise,
- * return the same instruction. Conditional jumps have more than one
- * label, so they are replaced only if all labels are anchored.
- */
- def patch(instr: Instruction): Instruction = {
- assert(anchored, "Cannot patch until this label is anchored: " + this)
-
- instr match {
- case PJUMP(self)
- if (self == this) => JUMP(block)
-
- case PCJUMP(self, failure, cond, kind)
- if (self == this && failure.anchored) =>
- CJUMP(block, failure.block, cond, kind)
-
- case PCJUMP(success, self, cond, kind)
- if (self == this && success.anchored) =>
- CJUMP(success.block, block, cond, kind)
-
- case PCZJUMP(self, failure, cond, kind)
- if (self == this && failure.anchored) =>
- CZJUMP(block, failure.block, cond, kind)
-
- case PCZJUMP(success, self, cond, kind)
- if (self == this && success.anchored) =>
- CZJUMP(success.block, block, cond, kind)
-
- case _ => instr
- }
- }
-
- override def toString() = symbol.toString()
- }
-
- ///////////////// Fake instructions //////////////////////////
-
- /**
- * Pseudo jump: it takes a Label instead of a basic block.
- * It is used temporarily during code generation. It is replaced
- * by a real JUMP instruction when all labels are resolved.
- */
- abstract class PseudoJUMP(label: Label) extends Instruction {
- override def toString = s"PJUMP(${label.symbol})"
- override def consumed = 0
- override def produced = 0
-
- // register with the given label
- if (!label.anchored)
- label.addCallingInstruction(this)
- }
-
- case class PJUMP(whereto: Label) extends PseudoJUMP(whereto)
-
- case class PCJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind)
- extends PseudoJUMP(success) {
- override def toString(): String =
- "PCJUMP (" + kind + ") " + success.symbol.simpleName +
- " : " + failure.symbol.simpleName
-
- if (!failure.anchored)
- failure.addCallingInstruction(this)
- }
-
- case class PCZJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind)
- extends PseudoJUMP(success) {
- override def toString(): String =
- "PCZJUMP (" + kind + ") " + success.symbol.simpleName +
- " : " + failure.symbol.simpleName
-
- if (!failure.anchored)
- failure.addCallingInstruction(this)
- }
-
- /** Local variable scopes. Keep track of line numbers for debugging info. */
- class Scope(val outer: Scope) {
- val locals: ListBuffer[Local] = new ListBuffer
-
- def add(l: Local) = locals += l
-
- /** Return all locals that are in scope. */
- def varsInScope: Buffer[Local] = outer.varsInScope.clone() ++= locals
-
- override def toString() = locals.mkString(outer.toString + "[", ", ", "]")
- }
-
- object EmptyScope extends Scope(null) {
- override def toString() = "[]"
- override def varsInScope: Buffer[Local] = new ListBuffer
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala
deleted file mode 100644
index 0f17b5d694..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala
+++ /dev/null
@@ -1,711 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
-abstract class ICodeCheckers {
- val global: Global
- import global._
-
- /** <p>
- * This class performs a set of checks similar to what the bytecode
- * verifier does. For each basic block, it checks that:
- * </p>
- * <ul>
- * <li>
- * for primitive operations: the type and number of operands match
- * the type of the operation
- * </li>
- * <li>
- * for method calls: the method exists in the type of the receiver
- * and the number and type of arguments match the declared type of
- * the method.
- * </li>
- * <li>
- * for object creation: the constructor can be called.
- * </li>
- * <li>
- * for load/stores: the field/local/param exists and the type
- * of the value matches that of the target.
- * </li>
- * </ul>
- * <p>
- * For a control flow graph it checks that type stacks at entry to
- * each basic block 'agree':
- * </p>
- * <ul>
- * <li>they have the same length</li>
- * <li>there exists a lub for all types at the same position in stacks.</li>
- * </ul>
- *
- * @author Iulian Dragos
- * @version 1.0, 06/09/2005
- *
- * @todo Better checks for `MONITOR_ENTER/EXIT`
- * Better checks for local var initializations
- *
- * @todo Iulian says: I think there's some outdated logic in the checker.
- * The issue with exception handlers being special for least upper
- * bounds pointed out some refactoring in the lattice class. Maybe
- * a worthwhile refactoring would be to make the checker use the
- * DataFlowAnalysis class, and use the lattice trait. In the
- * implementation of LUB, there's a flag telling if one of the
- * successors is 'exceptional'. The inliner is using this mechanism.
- */
- class ICodeChecker {
- import icodes._
- import opcodes._
-
- var clasz: IClass = _
- var method: IMethod = _
- var code: Code = _
-
- val in: mutable.Map[BasicBlock, TypeStack] = perRunCaches.newMap()
- val out: mutable.Map[BasicBlock, TypeStack] = perRunCaches.newMap()
- val emptyStack = new TypeStack() {
- override def toString = "<empty>"
- }
-
- /** The presence of emptyStack means that path has not yet been checked
- * (and may not be empty).
- */
- def notChecked(ts: TypeStack) = ts eq emptyStack
- def initMaps(bs: Seq[BasicBlock]): Unit = {
- in.clear()
- out.clear()
- bs foreach { b =>
- in(b) = emptyStack
- out(b) = emptyStack
- }
- }
-
- /** A wrapper to route log messages to debug output also.
- */
- def logChecker(msg: String) = {
- log(msg)
- checkerDebug(msg)
- }
-
- def checkICodes(): Unit = {
- if (settings.verbose)
- println("[[consistency check at the beginning of phase " + globalPhase.name + "]]")
- classes.values foreach check
- }
-
- private def posStr(p: Position) =
- if (p.isDefined) p.line.toString else "<??>"
-
- private def indent(s: String, prefix: String): String = {
- val lines = s split "\\n"
- lines map (prefix + _) mkString "\n"
- }
-
- /** Only called when m1 < m2, so already known that (m1 ne m2).
- */
- private def isConflict(m1: IMember, m2: IMember, canOverload: Boolean) = (
- (m1.symbol.name == m2.symbol.name) &&
- (!canOverload || (m1.symbol.tpe =:= m2.symbol.tpe))
- )
-
- def check(cls: IClass) {
- logChecker("\n<<-- Checking class " + cls + " -->>")
- clasz = cls
-
- for (f1 <- cls.fields ; f2 <- cls.fields ; if f1 < f2)
- if (isConflict(f1, f2, canOverload = false))
- icodeError("Repetitive field name: " + f1.symbol.fullName)
-
- for (m1 <- cls.methods ; m2 <- cls.methods ; if m1 < m2)
- if (isConflict(m1, m2, canOverload = true))
- icodeError("Repetitive method: " + m1.symbol.fullName)
-
- clasz.methods foreach check
- }
-
- def check(m: IMethod) {
- logChecker("\n<< Checking method " + m.symbol.name + " >>")
- method = m
- if (!m.isAbstractMethod)
- check(m.code)
- }
-
- def check(c: Code) {
- val worklist = new ListBuffer[BasicBlock]
- def append(elems: List[BasicBlock]) =
- worklist ++= (elems filterNot (worklist contains _))
-
- code = c
- worklist += c.startBlock
- initMaps(c.blocks)
-
- while (worklist.nonEmpty) {
- val block = worklist remove 0
- val output = check(block, in(block))
- if (output != out(block) || notChecked(out(block))) {
- if (block.successors.nonEmpty)
- logChecker("** Output change for %s: %s -> %s".format(block, out(block), output))
-
- out(block) = output
- append(block.successors)
- block.successors foreach meet
- }
- }
- }
-
- /**
- * Apply the meet operator of the stack lattice on bl's predecessors.
- * :-). Compute the input to bl by checking that all stacks have the
- * same length, and taking the lub of types at the same positions.
- */
- def meet(bl: BasicBlock) {
- val preds = bl.predecessors
-
- def hasNothingType(s: TypeStack) = s.nonEmpty && (s.head == NothingReference)
-
- /* XXX workaround #1: one stack empty, the other has BoxedUnit.
- * One example where this arises is:
- *
- * def f(b: Boolean): Unit = synchronized { if (b) () }
- */
- def allUnits(s: TypeStack) = s.types forall (_ == BoxedUnitReference)
-
- def ifAthenB[T](f: T => Boolean): PartialFunction[(T, T), T] = {
- case (x1, x2) if f(x1) => x2
- case (x1, x2) if f(x2) => x1
- }
-
- /* XXX workaround #2: different stacks heading into an exception
- * handler which will clear them anyway. Examples where it arises:
- *
- * var bippy: Int = synchronized { if (b) 5 else 10 }
- */
- def isHandlerBlock() = bl.exceptionHandlerStart
-
- def meet2(s1: TypeStack, s2: TypeStack): TypeStack = {
- def workaround(msg: String) = {
- checkerDebug(msg + ": " + method + " at block " + bl)
- checkerDebug(" s1: " + s1)
- checkerDebug(" s2: " + s2)
- new TypeStack()
- }
- def incompatibleString = (
- "Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to block " + bl.label + ":\n" +
- indent(bl.predContents, "// ") +
- indent(bl.succContents, "// ") +
- indent(bl.blockContents, "// ")
- )
-
- val f: ((TypeStack, TypeStack)) => TypeStack = {
- ifAthenB(notChecked) orElse ifAthenB(hasNothingType) orElse {
- case (s1: TypeStack, s2: TypeStack) =>
- if (s1.length != s2.length) {
- if (allUnits(s1) && allUnits(s2))
- workaround("Ignoring mismatched boxed units")
- else if (isHandlerBlock())
- workaround("Ignoring mismatched stacks entering exception handler")
- else
- throw new CheckerException(incompatibleString)
- }
- else {
- val newStack: TypeStack = try {
- new TypeStack((s1.types, s2.types).zipped map lub)
- } catch {
- case t: Exception =>
- checkerDebug(t.toString + ": " + s1.types.toString + " vs " + s2.types.toString)
- new TypeStack(s1.types)
- }
- if (newStack.isEmpty || s1.types == s2.types) () // not interesting to report
- else checkerDebug("Checker created new stack:\n (%s, %s) => %s".format(s1, s2, newStack))
-
- newStack
- }
- }
- }
-
- f((s1, s2))
- }
-
- if (preds.nonEmpty) {
- in(bl) = (preds map out.apply) reduceLeft meet2
- log("Input changed for block: " + bl +" to: " + in(bl))
- }
- }
-
- private var instruction: Instruction = null
- private var basicBlock: BasicBlock = null
- private var stringConcatDepth = 0
- private def stringConcatIndent() = " " * stringConcatDepth
- private def currentInstrString: String = {
- val (indent, str) = this.instruction match {
- case CALL_PRIMITIVE(StartConcat) =>
- val x = stringConcatIndent()
- stringConcatDepth += 1
- (x, "concat(")
- case CALL_PRIMITIVE(EndConcat) =>
- if (stringConcatDepth > 0) {
- stringConcatDepth -= 1
- (stringConcatIndent(), ") // end concat")
- }
- else ("", "")
- case _ =>
- (stringConcatIndent(), this.instruction match {
- case CALL_PRIMITIVE(StringConcat(el)) => "..."
- case null => "null"
- case cm @ CALL_METHOD(_, _) => if (clasz.symbol == cm.hostClass) cm.toShortString else cm.toString
- case x => x.toString
- })
- }
- indent + str
- }
- /** A couple closure creators to reduce noise in the output: when multiple
- * items are pushed or popped, this lets us print something short and sensible
- * for those beyond the first.
- */
- def mkInstrPrinter(f: Int => String): () => String = {
- var counter = -1
- val indent = stringConcatIndent()
- () => {
- counter += 1
- if (counter == 0) currentInstrString
- else indent + f(counter)
- }
- }
- def defaultInstrPrinter: () => String = mkInstrPrinter(_ => "\"\"\"")
-
- /**
- * Check the basic block to be type correct and return the
- * produced type stack.
- */
- def check(b: BasicBlock, initial: TypeStack): TypeStack = {
- this.basicBlock = b
-
- logChecker({
- val prefix = "** Checking " + b.fullString
-
- if (initial.isEmpty) prefix
- else prefix + " with initial stack " + initial.types.mkString("[", ", ", "]")
- })
-
- val stack = new TypeStack(initial)
- def checkStack(len: Int) {
- if (stack.length < len)
- ICodeChecker.this.icodeError("Expected at least " + len + " elements on the stack", stack)
- }
-
- def sizeString(push: Boolean) = {
- val arrow = if (push) "-> " else "<- "
- val sp = " " * stack.length
-
- sp + stack.length + arrow
- }
- def printStackString(isPush: Boolean, value: TypeKind, instrString: String) = {
- val pushString = if (isPush) "+" else "-"
- val posString = posStr(this.instruction.pos)
-
- checkerDebug("%-70s %-4s %s %s".format(sizeString(isPush) + value, posString, pushString, instrString))
- }
- def _popStack: TypeKind = {
- if (stack.isEmpty) {
- icodeError("Popped empty stack in " + b.fullString + ", throwing a Unit")
- return UNIT
- }
- stack.pop
- }
- def popStackN(num: Int, instrFn: () => String = defaultInstrPrinter) = {
- List.range(0, num) map { _ =>
- val res = _popStack
- printStackString(isPush = false, res, instrFn())
- res
- }
- }
- def pushStackN(xs: Seq[TypeKind], instrFn: () => String) = {
- xs foreach { x =>
- stack push x
- printStackString(isPush = true, x, instrFn())
- }
- }
-
- def popStack = { checkStack(1) ; (popStackN(1): @unchecked) match { case List(x) => x } }
- def popStack2 = { checkStack(2) ; (popStackN(2): @unchecked) match { case List(x, y) => (x, y) } }
- def popStack3 = { checkStack(3) ; (popStackN(3): @unchecked) match { case List(x, y, z) => (x, y, z) } }
-
- /* Called by faux instruction LOAD_EXCEPTION to wipe out the stack. */
- def clearStack() = {
- if (stack.nonEmpty)
- logChecker("Wiping out the " + stack.length + " element stack for exception handler: " + stack)
-
- 1 to stack.length foreach (_ => popStack)
- }
-
- def pushStack(xs: TypeKind*): Unit = {
- pushStackN(xs filterNot (_ == UNIT), defaultInstrPrinter)
- }
-
- def typeError(k1: TypeKind, k2: TypeKind) {
- icodeError("\n expected: " + k1 + "\n found: " + k2)
- }
- def isSubtype(k1: TypeKind, k2: TypeKind) = (k1 isAssignabledTo k2) || {
- import platform.isMaybeBoxed
-
- (k1, k2) match {
- case (REFERENCE(_), REFERENCE(_)) if k1.isInterfaceType || k2.isInterfaceType =>
- logChecker("Considering %s <:< %s because at least one is an interface".format(k1, k2))
- true
- case (REFERENCE(cls1), REFERENCE(cls2)) if isMaybeBoxed(cls1) || isMaybeBoxed(cls2) =>
- logChecker("Considering %s <:< %s because at least one might be a boxed primitive".format(cls1, cls2))
- true
- case _ =>
- false
- }
- }
-
- def subtypeTest(k1: TypeKind, k2: TypeKind): Unit =
- if (isSubtype(k1, k2)) ()
- else typeError(k2, k1)
-
- for (instr <- b) {
- this.instruction = instr
-
- def checkLocal(local: Local) {
- if ((method lookupLocal local.sym.name).isEmpty)
- icodeError(s" $local is not defined in method $method")
- }
- def checkField(obj: TypeKind, field: Symbol): Unit = obj match {
- case REFERENCE(sym) =>
- if (sym.info.member(field.name) == NoSymbol)
- icodeError(" " + field + " is not defined in class " + clasz)
- case _ =>
- icodeError(" expected reference type, but " + obj + " found")
- }
-
- /* Checks that tpe is a subtype of one of the allowed types */
- def checkType(tpe: TypeKind, allowed: TypeKind*) = (
- if (allowed exists (k => isSubtype(tpe, k))) ()
- else icodeError(tpe + " is not one of: " + allowed.mkString("{ ", ", ", " }"))
- )
- def checkNumeric(tpe: TypeKind) =
- checkType(tpe, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE)
-
- /* Checks that the 2 topmost elements on stack are of the kind TypeKind. */
- def checkBinop(kind: TypeKind) {
- val (a, b) = popStack2
- checkType(a, kind)
- checkType(b, kind)
- }
-
- /* Check that arguments on the stack match method params. */
- def checkMethodArgs(method: Symbol) {
- val params = method.info.paramTypes
- checkStack(params.length)
- (
- popStackN(params.length, mkInstrPrinter(num => "<arg" + num + ">")),
- params.reverse map toTypeKind).zipped foreach ((x, y) => checkType(x, y)
- )
- }
-
- /* Checks that the object passed as receiver has a method
- * `method` and that it is callable from the current method.
- */
- def checkMethod(receiver: TypeKind, method: Symbol) =
- receiver match {
- case REFERENCE(sym) =>
- checkBool(sym.info.member(method.name) != NoSymbol,
- "Method " + method + " does not exist in " + sym.fullName)
- if (method.isPrivate)
- checkBool(method.owner == clasz.symbol,
- "Cannot call private method of " + method.owner.fullName
- + " from " + clasz.symbol.fullName)
- else if (method.isProtected) {
- val isProtectedOK = (
- (clasz.symbol isSubClass method.owner) ||
- (clasz.symbol.typeOfThis.typeSymbol isSubClass method.owner) // see pos/bug780.scala
- )
-
- checkBool(isProtectedOK,
- "Cannot call protected method of " + method.owner.fullName
- + " from " + clasz.symbol.fullName)
- }
-
- case ARRAY(_) =>
- checkBool(receiver.toType.member(method.name) != NoSymbol,
- "Method " + method + " does not exist in " + receiver)
-
- case t =>
- icodeError("Not a reference type: " + t)
- }
-
- def checkBool(cond: Boolean, msg: String) =
- if (!cond) icodeError(msg)
-
- if (settings.debug) {
- log("PC: " + instr)
- log("stack: " + stack)
- log("================")
- }
- instr match {
- case THIS(clasz) =>
- pushStack(toTypeKind(clasz.tpe))
-
- case CONSTANT(const) =>
- pushStack(toTypeKind(const.tpe))
-
- case LOAD_ARRAY_ITEM(kind) =>
- popStack2 match {
- case (INT, ARRAY(elem)) =>
- subtypeTest(elem, kind)
- pushStack(elem)
- case (a, b) =>
- icodeError(" expected an INT and an array reference, but " +
- a + ", " + b + " found")
- }
-
- case LOAD_LOCAL(local) =>
- checkLocal(local)
- pushStack(local.kind)
-
- case LOAD_FIELD(field, isStatic) =>
- // the symbol's owner should contain its field, but
- // this is already checked by the type checker, no need
- // to redo that here
- if (isStatic) ()
- else checkField(popStack, field)
-
- pushStack(toTypeKind(field.tpe))
-
- case LOAD_MODULE(module) =>
- checkBool((module.isModule || module.isModuleClass),
- "Expected module: " + module + " flags: " + module.flagString)
- pushStack(toTypeKind(module.tpe))
-
- case STORE_THIS(kind) =>
- val actualType = popStack
- if (actualType.isReferenceType) subtypeTest(actualType, kind)
- else icodeError("Expected this reference but found: " + actualType)
-
- case STORE_ARRAY_ITEM(kind) =>
- popStack3 match {
- case (k, INT, ARRAY(elem)) =>
- subtypeTest(k, kind)
- subtypeTest(k, elem)
- case (a, b, c) =>
- icodeError(" expected and array reference, and int and " + kind +
- " but " + a + ", " + b + ", " + c + " found")
- }
-
- case STORE_LOCAL(local) =>
- checkLocal(local)
- val actualType = popStack
- if (local.kind != NullReference)
- subtypeTest(actualType, local.kind)
-
- case STORE_FIELD(field, true) => // static
- val fieldType = toTypeKind(field.tpe)
- val actualType = popStack
- subtypeTest(actualType, fieldType)
-
- case STORE_FIELD(field, false) => // not static
- val (value, obj) = popStack2
- checkField(obj, field)
- val fieldType = toTypeKind(field.tpe)
- if (fieldType == NullReference) ()
- else subtypeTest(value, fieldType)
-
- case CALL_PRIMITIVE(primitive) =>
- checkStack(instr.consumed)
- primitive match {
- case Negation(kind) =>
- checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE)
- checkType(popStack, kind)
- pushStack(kind)
-
- case Test(op, kind, zero) =>
- if (zero) checkType(popStack, kind)
- else checkBinop(kind)
-
- pushStack(BOOL)
-
- case Comparison(op, kind) =>
- checkNumeric(kind)
- checkBinop(kind)
- pushStack(INT)
-
- case Arithmetic(op, kind) =>
- checkNumeric(kind)
- if (op == NOT)
- checkType(popStack, kind)
- else
- checkBinop(kind)
- pushStack(kind)
-
- case Logical(op, kind) =>
- checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG)
- checkBinop(kind)
- pushStack(kind)
-
- case Shift(op, kind) =>
- checkType(kind, BYTE, CHAR, SHORT, INT, LONG)
- val (a, b) = popStack2
- checkType(a, INT)
- checkType(b, kind)
- pushStack(kind)
-
- case Conversion(src, dst) =>
- checkNumeric(src)
- checkNumeric(dst)
- checkType(popStack, src)
- pushStack(dst)
-
- case ArrayLength(kind) =>
- popStack match {
- case ARRAY(elem) => checkType(elem, kind)
- case arr => icodeError(" array reference expected, but " + arr + " found")
- }
- pushStack(INT)
-
- case StartConcat =>
- pushStack(ConcatClass)
-
- case EndConcat =>
- checkType(popStack, ConcatClass)
- pushStack(StringReference)
-
- case StringConcat(el) =>
- checkType(popStack, el)
- checkType(popStack, ConcatClass)
- pushStack(ConcatClass)
- }
-
- case CALL_METHOD(method, style) =>
- // PP to ID: I moved the if (!method.isConstructor) check to cover all
- // the styles to address checker failure. Can you confirm if the change
- // was correct? If I remember right it's a matter of whether some brand
- // of supercall should leave a value on the stack, and I know there is some
- // trickery performed elsewhere regarding this.
- val paramCount = method.info.paramTypes.length match {
- case x if style.hasInstance => x + 1
- case x => x
- }
- if (style == Static(onInstance = true))
- checkBool(method.isPrivate || method.isConstructor, "Static call to non-private method.")
-
- checkStack(paramCount)
- checkMethodArgs(method)
- if (style.hasInstance)
- checkMethod(popStack, method)
- if (!method.isConstructor)
- pushStack(toTypeKind(method.info.resultType))
-
- case NEW(kind) =>
- pushStack(kind)
-
- case CREATE_ARRAY(elem, dims) =>
- checkStack(dims)
- stack.pop(dims) foreach (checkType(_, INT))
- pushStack(ARRAY(elem))
-
- case IS_INSTANCE(tpe) =>
- val ref = popStack
- checkBool(!ref.isValueType, "IS_INSTANCE on primitive type: " + ref)
- checkBool(!tpe.isValueType, "IS_INSTANCE on primitive type: " + tpe)
- pushStack(BOOL)
-
- case CHECK_CAST(tpe) =>
- val ref = popStack
- checkBool(!ref.isValueType, "CHECK_CAST to primitive type: " + ref)
- checkBool(!tpe.isValueType, "CHECK_CAST to primitive type: " + tpe)
- pushStack(tpe)
-
- case SWITCH(tags, labels) =>
- checkType(popStack, INT)
- checkBool(tags.length == labels.length - 1,
- "The number of tags and labels does not coincide.")
- checkBool(labels forall (b => code.blocks contains b),
- "Switch target cannot be found in code.")
-
- case JUMP(whereto) =>
- checkBool(code.blocks contains whereto,
- "Jump to non-existant block " + whereto)
-
- case CJUMP(success, failure, cond, kind) =>
- checkBool(code.blocks contains success,
- "Jump to non-existant block " + success)
- checkBool(code.blocks contains failure,
- "Jump to non-existant block " + failure)
- checkBinop(kind)
-
- case CZJUMP(success, failure, cond, kind) =>
- checkBool(code.blocks contains success,
- "Jump to non-existant block " + success)
- checkBool(code.blocks contains failure,
- "Jump to non-existant block " + failure)
- checkType(popStack, kind)
-
- case RETURN(UNIT) => ()
- case RETURN(kind) =>
- val top = popStack
- if (kind.isValueType) checkType(top, kind)
- else checkBool(!top.isValueType, "" + kind + " is a reference type, but " + top + " is not")
-
- case THROW(clasz) =>
- checkType(popStack, toTypeKind(clasz.tpe))
- pushStack(NothingReference)
-
- case DROP(kind) =>
- checkType(popStack, kind)
-
- case DUP(kind) =>
- val top = popStack
- checkType(top, kind)
- pushStack(top)
- pushStack(top)
-
- case MONITOR_ENTER() =>
- checkBool(popStack.isReferenceType, "MONITOR_ENTER on non-reference type")
-
- case MONITOR_EXIT() =>
- checkBool(popStack.isReferenceType, "MONITOR_EXIT on non-reference type")
-
- case BOX(kind) =>
- checkType(popStack, kind)
- pushStack(REFERENCE(definitions.boxedClass(kind.toType.typeSymbol)))
-
- case UNBOX(kind) =>
- popStack
- pushStack(kind)
-
- case LOAD_EXCEPTION(clasz) =>
- clearStack()
- pushStack(REFERENCE(clasz))
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) =>
- ()
-
- case _ =>
- abort("Unknown instruction: " + instr)
- }
- }
- stack
- }
-
- //////////////// Error reporting /////////////////////////
-
- def icodeError(msg: String) {
- ICodeCheckers.this.global.warning(
- "!! ICode checker fatality in " + method +
- "\n at: " + basicBlock.fullString +
- "\n error message: " + msg
- )
- }
-
- def icodeError(msg: String, stack: TypeStack) {
- icodeError(msg + "\n type stack: " + stack)
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala
deleted file mode 100644
index 10f0c6ee00..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala
+++ /dev/null
@@ -1,129 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import java.io.PrintWriter
-import analysis.{ Liveness, ReachingDefinitions }
-import scala.tools.nsc.symtab.classfile.ICodeReader
-import scala.reflect.io.AbstractFile
-
-/** Glue together ICode parts.
- *
- * @author Iulian Dragos
- */
-abstract class ICodes extends AnyRef
- with Members
- with BasicBlocks
- with Opcodes
- with TypeStacks
- with TypeKinds
- with ExceptionHandlers
- with Primitives
- with Linearizers
- with Printers
- with Repository
-{
- val global: Global
- import global.{ log, definitions, settings, perRunCaches, devWarning }
-
- /** The ICode representation of classes */
- val classes = perRunCaches.newMap[global.Symbol, IClass]()
-
- /** Debugging flag */
- def shouldCheckIcode = settings.check contains global.genicode.phaseName
- def checkerDebug(msg: String) = if (shouldCheckIcode && global.settings.debug) println(msg)
-
- /** The ICode linearizer. */
- val linearizer: Linearizer = settings.Xlinearizer.value match {
- case "rpo" => new ReversePostOrderLinearizer()
- case "dfs" => new DepthFirstLinerizer()
- case "normal" => new NormalLinearizer()
- case "dump" => new DumpLinearizer()
- case x => global.abort("Unknown linearizer: " + x)
- }
-
- def newTextPrinter() =
- new TextPrinter(new PrintWriter(Console.out, true), new DumpLinearizer)
-
- /** Have to be careful because dump calls around, possibly
- * re-entering methods which initiated the dump (like foreach
- * in BasicBlocks) which leads to the icode output olympics.
- */
- private var alreadyDumping = false
-
- /** Print all classes and basic blocks. Used for debugging. */
-
- def dumpClassesAndAbort(msg: String): Nothing = {
- if (alreadyDumping) global.abort(msg)
- else alreadyDumping = true
-
- Console.println(msg)
- val printer = newTextPrinter()
- classes.values foreach printer.printClass
- global.abort(msg)
- }
-
- def dumpMethodAndAbort(m: IMethod, msg: String): Nothing = {
- Console.println("Fatal bug in inlinerwhile traversing " + m + ": " + msg)
- m.dump()
- global.abort("" + m)
- }
- def dumpMethodAndAbort(m: IMethod, b: BasicBlock): Nothing =
- dumpMethodAndAbort(m, "found open block " + b + " " + b.flagsString)
-
- def checkValid(m: IMethod) {
- // always slightly dicey to iterate over mutable structures
- m foreachBlock { b =>
- if (!b.closed) {
- // Something is leaving open/empty blocks around (see SI-4840) so
- // let's not kill the deal unless it's nonempty.
- if (b.isEmpty) {
- devWarning(s"Found open but empty block while inlining $m: removing from block list.")
- m.code removeBlock b
- }
- else dumpMethodAndAbort(m, b)
- }
- }
- }
-
- object liveness extends Liveness {
- val global: ICodes.this.global.type = ICodes.this.global
- }
-
- object reachingDefinitions extends ReachingDefinitions {
- val global: ICodes.this.global.type = ICodes.this.global
- }
-
- lazy val AnyRefReference: TypeKind = REFERENCE(definitions.AnyRefClass)
- lazy val BoxedUnitReference: TypeKind = REFERENCE(definitions.BoxedUnitClass)
- lazy val NothingReference: TypeKind = REFERENCE(definitions.NothingClass)
- lazy val NullReference: TypeKind = REFERENCE(definitions.NullClass)
- lazy val ObjectReference: TypeKind = REFERENCE(definitions.ObjectClass)
- lazy val StringReference: TypeKind = REFERENCE(definitions.StringClass)
-
- object icodeReader extends ICodeReader {
- lazy val global: ICodes.this.global.type = ICodes.this.global
- import global._
- def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol =
- global.loaders.lookupMemberAtTyperPhaseIfPossible(sym, name)
- lazy val symbolTable: global.type = global
- lazy val loaders: global.loaders.type = global.loaders
-
- def classFileLookup: util.ClassFileLookup[AbstractFile] = global.classPath
- }
-
- /** A phase which works on icode. */
- abstract class ICodePhase(prev: Phase) extends global.GlobalPhase(prev) {
- override def erasedTypes = true
- override def apply(unit: global.CompilationUnit): Unit =
- unit.icode foreach apply
-
- def apply(cls: global.icodes.IClass): Unit
- }
-}
-
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala b/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala
deleted file mode 100644
index 54be9d18f1..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala
+++ /dev/null
@@ -1,201 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import mutable.ListBuffer
-
-trait Linearizers {
- self: ICodes =>
-
- import global.debuglog
- import opcodes._
-
- abstract class Linearizer {
- def linearize(c: IMethod): List[BasicBlock]
- def linearizeAt(c: IMethod, start: BasicBlock): List[BasicBlock]
- }
-
- /**
- * A simple linearizer which predicts all branches to
- * take the 'success' branch and tries to schedule those
- * blocks immediately after the test. This is in sync with
- * how 'while' statements are translated (if the test is
- * 'true', the loop continues).
- */
- class NormalLinearizer extends Linearizer with WorklistAlgorithm {
- type Elem = BasicBlock
- val worklist: WList = new mutable.Stack()
- var blocks: List[BasicBlock] = Nil
-
- def linearize(m: IMethod): List[BasicBlock] = {
- val b = m.startBlock
- blocks = Nil
-
- run {
- worklist pushAll (m.exh map (_.startBlock))
- worklist.push(b)
- }
-
- blocks.reverse
- }
-
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
- blocks = Nil
- worklist.clear()
- linearize(start)
- }
-
- /** Linearize another subtree and append it to the existing blocks. */
- def linearize(startBlock: BasicBlock): List[BasicBlock] = {
- //blocks = startBlock :: Nil;
- run( { worklist.push(startBlock); } )
- blocks.reverse
- }
-
- def processElement(b: BasicBlock) =
- if (b.nonEmpty) {
- add(b)
- b.lastInstruction match {
- case JUMP(whereto) =>
- add(whereto)
- case CJUMP(success, failure, _, _) =>
- add(success)
- add(failure)
- case CZJUMP(success, failure, _, _) =>
- add(success)
- add(failure)
- case SWITCH(_, labels) =>
- add(labels)
- case RETURN(_) => ()
- case THROW(clasz) => ()
- }
- }
-
- def dequeue: Elem = worklist.pop()
-
- /**
- * Prepend b to the list, if not already scheduled.
- * TODO: use better test than linear search
- */
- def add(b: BasicBlock) {
- if (blocks.contains(b))
- ()
- else {
- blocks = b :: blocks
- worklist push b
- }
- }
-
- def add(bs: List[BasicBlock]): Unit = bs foreach add
- }
-
- /**
- * Linearize code using a depth first traversal.
- */
- class DepthFirstLinerizer extends Linearizer {
- var blocks: List[BasicBlock] = Nil
-
- def linearize(m: IMethod): List[BasicBlock] = {
- blocks = Nil
-
- dfs(m.startBlock)
- m.exh foreach (b => dfs(b.startBlock))
-
- blocks.reverse
- }
-
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
- blocks = Nil
- dfs(start)
- blocks.reverse
- }
-
- def dfs(b: BasicBlock): Unit =
- if (b.nonEmpty && add(b))
- b.successors foreach dfs
-
- /**
- * Prepend b to the list, if not already scheduled.
- * TODO: use better test than linear search
- * @return Returns true if the block was added.
- */
- def add(b: BasicBlock): Boolean =
- !(blocks contains b) && {
- blocks = b :: blocks
- true
- }
- }
-
- /**
- * Linearize code in reverse post order. In fact, it does
- * a post order traversal, prepending visited nodes to the list.
- * This way, it is constructed already in reverse post order.
- */
- class ReversePostOrderLinearizer extends Linearizer {
- var blocks: List[BasicBlock] = Nil
- val visited = new mutable.HashSet[BasicBlock]
- val added = new mutable.BitSet
-
- def linearize(m: IMethod): List[BasicBlock] = {
- blocks = Nil
- visited.clear()
- added.clear()
-
- m.exh foreach (b => rpo(b.startBlock))
- rpo(m.startBlock)
-
- // if the start block has predecessors, it won't be the first one
- // in the linearization, so we need to enforce it here
- if (m.startBlock.predecessors eq Nil)
- blocks
- else
- m.startBlock :: (blocks.filterNot(_ == m.startBlock))
- }
-
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
- blocks = Nil
- visited.clear()
- added.clear()
-
- rpo(start)
- blocks
- }
-
- def rpo(b: BasicBlock): Unit =
- if (b.nonEmpty && !visited(b)) {
- visited += b
- b.successors foreach rpo
- add(b)
- }
-
- /**
- * Prepend b to the list, if not already scheduled.
- * @return Returns true if the block was added.
- */
- def add(b: BasicBlock) = {
- debuglog("Linearizer adding block " + b.label)
-
- if (!added(b.label)) {
- added += b.label
- blocks = b :: blocks
- }
- }
- }
-
- /** A 'dump' of the blocks in this method, which does not
- * require any well-formedness of the basic blocks (like
- * the last instruction being a jump).
- */
- class DumpLinearizer extends Linearizer {
- def linearize(m: IMethod): List[BasicBlock] = m.blocks
- def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = sys.error("not implemented")
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala
deleted file mode 100644
index 64146585e5..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala
+++ /dev/null
@@ -1,296 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import scala.reflect.internal.util.{ SourceFile, NoSourceFile }
-
-trait ReferenceEquality {
- override def hashCode = System.identityHashCode(this)
- override def equals(that: Any) = this eq that.asInstanceOf[AnyRef]
-}
-
-trait Members {
- self: ICodes =>
-
- import global._
-
- object NoCode extends Code(null, TermName("NoCode")) {
- override def blocksList: List[BasicBlock] = Nil
- }
-
- /**
- * This class represents the intermediate code of a method or
- * other multi-block piece of code, like exception handlers.
- */
- class Code(method: IMethod, name: Name) {
- def this(method: IMethod) = this(method, method.symbol.name)
- /** The set of all blocks */
- val blocks = mutable.ListBuffer[BasicBlock]()
-
- /** The start block of the method */
- var startBlock: BasicBlock = NoBasicBlock
-
- private var currentLabel: Int = 0
- private var _touched = false
-
- def blocksList: List[BasicBlock] = blocks.toList
- def instructions = blocksList flatMap (_.iterator)
- def blockCount = blocks.size
- def instructionCount = (blocks map (_.length)).sum
-
- def touched = _touched
- def touched_=(b: Boolean): Unit = {
- @annotation.tailrec def loop(xs: List[BasicBlock]) {
- xs match {
- case Nil =>
- case x :: xs => x.touched = true ; loop(xs)
- }
- }
- if (b) loop(blocks.toList)
-
- _touched = b
- }
-
- // Constructor code
- startBlock = newBlock()
-
- def removeBlock(b: BasicBlock) {
- if (settings.debug) {
- // only do this sanity check when debug is turned on because it's moderately expensive
- val referers = blocks filter (_.successors contains b)
- assert(referers.isEmpty, s"Trying to removing block $b (with preds ${b.predecessors.mkString}) but it is still refered to from block(s) ${referers.mkString}")
- }
-
- if (b == startBlock) {
- assert(b.successors.length == 1,
- s"Removing start block ${b} with ${b.successors.length} successors (${b.successors.mkString})."
- )
- startBlock = b.successors.head
- }
-
- blocks -= b
- assert(!blocks.contains(b))
- method.exh filter (_ covers b) foreach (_.covered -= b)
- touched = true
- }
-
- /** This methods returns a string representation of the ICode */
- override def toString = "ICode '" + name.decoded + "'"
-
- /* Compute a unique new label */
- def nextLabel: Int = {
- currentLabel += 1
- currentLabel
- }
-
- /* Create a new block and append it to the list
- */
- def newBlock(): BasicBlock = {
- touched = true
- val block = new BasicBlock(nextLabel, method)
- blocks += block
- block
- }
- }
-
- /** Common interface for IClass/IField/IMethod. */
- trait IMember extends Ordered[IMember] {
- def symbol: Symbol
-
- def compare(other: IMember) =
- if (symbol eq other.symbol) 0
- else if (symbol isLess other.symbol) -1
- else 1
-
- override def equals(other: Any): Boolean =
- other match {
- case other: IMember => (this compare other) == 0
- case _ => false
- }
-
- override def hashCode = symbol.##
- }
-
- /** Represent a class in ICode */
- class IClass(val symbol: Symbol) extends IMember {
- var fields: List[IField] = Nil
- var methods: List[IMethod] = Nil
- var cunit: CompilationUnit = _
-
- def addField(f: IField): this.type = {
- fields = f :: fields
- this
- }
-
- def addMethod(m: IMethod): this.type = {
- methods = m :: methods
- this
- }
-
- def setCompilationUnit(unit: CompilationUnit): this.type = {
- this.cunit = unit
- this
- }
-
- override def toString() = symbol.fullName
-
- def lookupMethod(s: Symbol) = methods find (_.symbol == s)
-
- /* returns this methods static ctor if it has one. */
- def lookupStaticCtor: Option[IMethod] = methods find (_.symbol.isStaticConstructor)
- }
-
- /** Represent a field in ICode */
- class IField(val symbol: Symbol) extends IMember { }
-
- object NoIMethod extends IMethod(NoSymbol) { }
-
- /**
- * Represents a method in ICode. Local variables contain
- * both locals and parameters, similar to the way the JVM
- * 'sees' them.
- *
- * Locals and parameters are added in reverse order, as they
- * are kept in cons-lists. The 'builder' is responsible for
- * reversing them and putting them back, when the generation is
- * finished (GenICode does that).
- */
- class IMethod(val symbol: Symbol) extends IMember {
- var code: Code = NoCode
-
- def newBlock() = code.newBlock()
- def startBlock = code.startBlock
- def lastBlock = { assert(blocks.nonEmpty, symbol); blocks.last }
- def blocks = code.blocksList
- def linearizedBlocks(lin: Linearizer = self.linearizer): List[BasicBlock] = lin linearize this
-
- def foreachBlock[U](f: BasicBlock => U): Unit = blocks foreach f
-
- var native = false
-
- /** The list of exception handlers, ordered from innermost to outermost. */
- var exh: List[ExceptionHandler] = Nil
- var sourceFile: SourceFile = NoSourceFile
- var returnType: TypeKind = _
- var recursive: Boolean = false
- var bytecodeHasEHs = false // set by ICodeReader only, used by Inliner to prevent inlining (SI-6188)
- var bytecodeHasInvokeDynamic = false // set by ICodeReader only, used by Inliner to prevent inlining until we have proper invoke dynamic support
-
- /** local variables and method parameters */
- var locals: List[Local] = Nil
-
- /** method parameters */
- var params: List[Local] = Nil
-
- def hasCode = code ne NoCode
- def setCode(code: Code): IMethod = {
- this.code = code
- this
- }
-
- final def updateRecursive(called: Symbol): Unit = {
- recursive ||= (called == symbol)
- }
-
- def addLocal(l: Local): Local = findOrElse(locals)(_ == l) { locals ::= l ; l }
-
- def addParam(p: Local): Unit =
- if (params contains p) ()
- else {
- params ::= p
- locals ::= p
- }
-
- def addLocals(ls: List[Local]) = ls foreach addLocal
-
- def lookupLocal(n: Name): Option[Local] = locals find (_.sym.name == n)
- def lookupLocal(sym: Symbol): Option[Local] = locals find (_.sym == sym)
-
- def addHandler(e: ExceptionHandler) = exh ::= e
-
- /** Is this method deferred ('abstract' in Java sense)?
- */
- def isAbstractMethod = symbol.isDeferred || symbol.owner.isInterface || native
-
- def isStatic: Boolean = symbol.isStaticMember
-
- override def toString() = symbol.fullName
-
- import opcodes._
-
- /** Merge together blocks that have a single successor which has a
- * single predecessor. Exception handlers are taken into account (they
- * might force to break a block of straight line code like that).
- *
- * This method should be most effective after heavy inlining.
- */
- def normalize(): Unit = if (this.hasCode) {
- val nextBlock: mutable.Map[BasicBlock, BasicBlock] = mutable.HashMap.empty
- for (b <- code.blocks.toList
- if b.successors.length == 1;
- succ = b.successors.head
- if succ ne b
- if succ.predecessors.length == 1
- if succ.predecessors.head eq b
- if !(exh.exists { (e: ExceptionHandler) =>
- (e.covers(succ) && !e.covers(b)) || (e.covers(b) && !e.covers(succ)) })) {
- nextBlock(b) = succ
- }
-
- var bb = code.startBlock
- while (!nextBlock.isEmpty) {
- if (nextBlock.isDefinedAt(bb)) {
- bb.open()
- var succ = bb
- do {
- succ = nextBlock(succ)
- val lastInstr = bb.lastInstruction
- /* Ticket SI-5672
- * Besides removing the control-flow instruction at the end of `bb` (usually a JUMP), we have to pop any values it pushes.
- * Examples:
- * `SWITCH` consisting of just the default case, or
- * `CJUMP(targetBlock, targetBlock, _, _)` ie where success and failure targets coincide (this one consumes two stack values).
- */
- val oldTKs = lastInstr.consumedTypes
- assert(lastInstr.consumed == oldTKs.size, "Someone forgot to override consumedTypes() in " + lastInstr)
-
- bb.removeLastInstruction()
- for(tk <- oldTKs.reverse) { bb.emit(DROP(tk), lastInstr.pos) }
- succ.toList foreach { i => bb.emit(i, i.pos) }
- code.removeBlock(succ)
- exh foreach { e => e.covered = e.covered - succ }
-
- nextBlock -= bb
- } while (nextBlock.isDefinedAt(succ))
- bb.close()
- } else
- bb = nextBlock.keysIterator.next()
- }
- checkValid(this)
- }
-
- def dump() {
- Console.println("dumping IMethod(" + symbol + ")")
- newTextPrinter() printMethod this
- }
- }
-
- /** Represent local variables and parameters */
- class Local(val sym: Symbol, val kind: TypeKind, val arg: Boolean) {
- var index: Int = -1
-
- override def equals(other: Any): Boolean = other match {
- case x: Local => sym == x.sym
- case _ => false
- }
- override def hashCode = sym.hashCode
- override def toString(): String = sym.toString
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala
deleted file mode 100644
index 351a8e33d3..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala
+++ /dev/null
@@ -1,767 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.reflect.internal.util.{Position,NoPosition}
-
-/*
- A pattern match
-
- // locals
- case THIS(clasz) =>
- case STORE_THIS(kind) =>
- case LOAD_LOCAL(local) =>
- case STORE_LOCAL(local) =>
- case SCOPE_ENTER(lv) =>
- case SCOPE_EXIT(lv) =>
- // stack
- case LOAD_MODULE(module) =>
- case LOAD_EXCEPTION(clasz) =>
- case DROP(kind) =>
- case DUP(kind) =>
- // constants
- case CONSTANT(const) =>
- // arithlogic
- case CALL_PRIMITIVE(primitive) =>
- // casts
- case IS_INSTANCE(tpe) =>
- case CHECK_CAST(tpe) =>
- // objs
- case NEW(kind) =>
- case MONITOR_ENTER() =>
- case MONITOR_EXIT() =>
- case BOX(boxType) =>
- case UNBOX(tpe) =>
- // flds
- case LOAD_FIELD(field, isStatic) =>
- case STORE_FIELD(field, isStatic) =>
- // mthds
- case CALL_METHOD(method, style) =>
- // arrays
- case LOAD_ARRAY_ITEM(kind) =>
- case STORE_ARRAY_ITEM(kind) =>
- case CREATE_ARRAY(elem, dims) =>
- // jumps
- case SWITCH(tags, labels) =>
- case JUMP(whereto) =>
- case CJUMP(success, failure, cond, kind) =>
- case CZJUMP(success, failure, cond, kind) =>
- // ret
- case RETURN(kind) =>
- case THROW(clasz) =>
-*/
-
-
-/**
- * The ICode intermediate representation. It is a stack-based
- * representation, very close to the JVM and .NET. It uses the
- * erased types of Scala and references Symbols to refer named entities
- * in the source files.
- */
-trait Opcodes { self: ICodes =>
- import global.{Symbol, NoSymbol, Name, Constant}
-
- // categories of ICode instructions
- final val localsCat = 1
- final val stackCat = 2
- final val constCat = 3
- final val arilogCat = 4
- final val castsCat = 5
- final val objsCat = 6
- final val fldsCat = 7
- final val mthdsCat = 8
- final val arraysCat = 9
- final val jumpsCat = 10
- final val retCat = 11
-
- private lazy val ObjectReferenceList = ObjectReference :: Nil
-
- /** This class represents an instruction of the intermediate code.
- * Each case subclass will represent a specific operation.
- */
- abstract class Instruction extends Cloneable {
- // Vlad: I used these for checking the quality of the implementation, and we should regularly run a build with them
- // enabled. But for production these should definitely be disabled, unless we enjoy getting angry emails from Greg :)
- //if (!this.isInstanceOf[opcodes.LOAD_EXCEPTION])
- // assert(consumed == consumedTypes.length)
- //assert(produced == producedTypes.length)
-
- def category: Int = 0 // undefined
-
- /** This abstract method returns the number of used elements on the stack */
- def consumed : Int = 0
-
- /** This abstract method returns the number of produced elements on the stack */
- def produced : Int = 0
-
- /** This instruction consumes these types from the top of the stack, the first
- * element in the list is the deepest element on the stack.
- */
- def consumedTypes: List[TypeKind] = Nil
-
- /** This instruction produces these types on top of the stack. */
- // Vlad: I wonder why we keep producedTypes around -- it looks like an useless thing to have
- def producedTypes: List[TypeKind] = Nil
-
- /** The corresponding position in the source file */
- private var _pos: Position = NoPosition
-
- def pos: Position = _pos
-
- def setPos(p: Position): this.type = {
- _pos = p
- this
- }
-
- /** Clone this instruction. */
- override def clone(): Instruction =
- super.clone.asInstanceOf[Instruction]
- }
-
- object opcodes {
- /** Loads "this" on top of the stack.
- * Stack: ...
- * ->: ...:ref
- */
- case class THIS(clasz: Symbol) extends Instruction {
- /** Returns a string representation of this constant */
- override def toString = "THIS(" + clasz.name + ")"
-
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes =
- // we're not allowed to have REFERENCE(Array), but what about compiling the Array class? Well, we use object for it.
- if (clasz != global.definitions.ArrayClass)
- REFERENCE(clasz) :: Nil
- else
- ObjectReference :: Nil
-
- override def category = localsCat
- }
-
- /** Loads a constant on the stack.
- * Stack: ...
- * ->: ...:constant
- */
- case class CONSTANT(constant: Constant) extends Instruction {
- override def toString = "CONSTANT(" + constant.escapedStringValue + ")"
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes = toTypeKind(constant.tpe) :: Nil
-
- override def category = constCat
- }
-
- /** Loads an element of an array. The array and the index should
- * be on top of the stack.
- * Stack: ...:array[a](Ref):index(Int)
- * ->: ...:element(a)
- */
- case class LOAD_ARRAY_ITEM(kind: TypeKind) extends Instruction {
- override def consumed = 2
- override def produced = 1
-
- override def consumedTypes = ARRAY(kind) :: INT :: Nil
- override def producedTypes = kind :: Nil
-
- override def category = arraysCat
- }
-
- /** Load a local variable on the stack. It can be a method argument.
- * Stack: ...
- * ->: ...:value
- */
- case class LOAD_LOCAL(local: Local) extends Instruction {
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes = local.kind :: Nil
-
- override def category = localsCat
- }
-
- /** Load a field on the stack. The object to which it refers should be
- * on the stack.
- * Stack: ...:ref (assuming isStatic = false)
- * ->: ...:value
- */
- case class LOAD_FIELD(field: Symbol, isStatic: Boolean) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String =
- "LOAD_FIELD " + (if (isStatic) field.fullName else field.toString())
-
- override def consumed = if (isStatic) 0 else 1
- override def produced = 1
-
- override def consumedTypes = if (isStatic) Nil else REFERENCE(field.owner) :: Nil
- override def producedTypes = toTypeKind(field.tpe) :: Nil
-
- // more precise information about how to load this field
- // see #4283
- var hostClass: Symbol = field.owner
- def setHostClass(cls: Symbol): this.type = { hostClass = cls; this }
-
- override def category = fldsCat
- }
-
- case class LOAD_MODULE(module: Symbol) extends Instruction {
- assert(module != NoSymbol, "Invalid module symbol")
- /** Returns a string representation of this instruction */
- override def toString(): String = "LOAD_MODULE " + module
-
- override def consumed = 0
- override def produced = 1
-
- override def producedTypes = REFERENCE(module) :: Nil
-
- override def category = stackCat
- }
-
- /** Store a value into an array at a specified index.
- * Stack: ...:array[a](Ref):index(Int):value(a)
- * ->: ...
- */
- case class STORE_ARRAY_ITEM(kind: TypeKind) extends Instruction {
- override def consumed = 3
- override def produced = 0
-
- override def consumedTypes = ARRAY(kind) :: INT :: kind :: Nil
-
- override def category = arraysCat
- }
-
- /** Store a value into a local variable. It can be an argument.
- * Stack: ...:value
- * ->: ...
- */
- case class STORE_LOCAL(local: Local) extends Instruction {
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = local.kind :: Nil
-
- override def category = localsCat
- }
-
- /** Store a value into a field.
- * Stack: ...:ref:value (assuming isStatic=false)
- * ->: ...
- */
- case class STORE_FIELD(field: Symbol, isStatic: Boolean) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String =
- "STORE_FIELD "+field + (if (isStatic) " (static)" else " (dynamic)")
-
- override def consumed = if(isStatic) 1 else 2
-
- override def produced = 0
-
- override def consumedTypes =
- if (isStatic)
- toTypeKind(field.tpe) :: Nil
- else
- REFERENCE(field.owner) :: toTypeKind(field.tpe) :: Nil
-
- override def category = fldsCat
- }
-
- /** Store a value into the 'this' pointer.
- * Stack: ...:ref
- * ->: ...
- */
- case class STORE_THIS(kind: TypeKind) extends Instruction {
- override def consumed = 1
- override def produced = 0
- override def consumedTypes = kind :: Nil
- override def category = localsCat
- }
-
- /** Call a primitive function.
- * Stack: ...:arg1:arg2:...:argn
- * ->: ...:result
- */
- case class CALL_PRIMITIVE(primitive: Primitive) extends Instruction {
- override def consumed = primitive match {
- case Negation(_) => 1
- case Test(_,_, true) => 1
- case Test(_,_, false) => 2
- case Comparison(_,_) => 2
- case Arithmetic(NOT,_) => 1
- case Arithmetic(_,_) => 2
- case Logical(_,_) => 2
- case Shift(_,_) => 2
- case Conversion(_,_) => 1
- case ArrayLength(_) => 1
- case StringConcat(_) => 2
- case StartConcat => 0
- case EndConcat => 1
- }
- override def produced = 1
-
- override def consumedTypes = primitive match {
- case Negation(kind) => kind :: Nil
- case Test(_, kind, true) => kind :: Nil
- case Test(_, kind, false) => kind :: kind :: Nil
- case Comparison(_, kind) => kind :: kind :: Nil
- case Arithmetic(NOT, kind) => kind :: Nil
- case Arithmetic(_, kind) => kind :: kind :: Nil
- case Logical(_, kind) => kind :: kind :: Nil
- case Shift(_, kind) => kind :: INT :: Nil
- case Conversion(from, _) => from :: Nil
- case ArrayLength(kind) => ARRAY(kind) :: Nil
- case StringConcat(kind) => ConcatClass :: kind :: Nil
- case StartConcat => Nil
- case EndConcat => ConcatClass :: Nil
- }
-
- override def producedTypes = primitive match {
- case Negation(kind) => kind :: Nil
- case Test(_, _, true) => BOOL :: Nil
- case Test(_, _, false) => BOOL :: Nil
- case Comparison(_, _) => INT :: Nil
- case Arithmetic(_, kind) => kind :: Nil
- case Logical(_, kind) => kind :: Nil
- case Shift(_, kind) => kind :: Nil
- case Conversion(_, to) => to :: Nil
- case ArrayLength(_) => INT :: Nil
- case StringConcat(_) => ConcatClass :: Nil
- case StartConcat => ConcatClass :: Nil
- case EndConcat => REFERENCE(global.definitions.StringClass) :: Nil
- }
-
- override def category = arilogCat
- }
-
- /** This class represents a CALL_METHOD instruction
- * STYLE: dynamic / static(StaticInstance)
- * Stack: ...:ref:arg1:arg2:...:argn
- * ->: ...:result
- *
- * STYLE: static(StaticClass)
- * Stack: ...:arg1:arg2:...:argn
- * ->: ...:result
- *
- */
- case class CALL_METHOD(method: Symbol, style: InvokeStyle) extends Instruction with ReferenceEquality {
- def toShortString =
- "CALL_METHOD " + method.name +" ("+style+")"
-
- /** Returns a string representation of this instruction */
- override def toString(): String =
- "CALL_METHOD " + method.fullName +" ("+style+")"
-
- var hostClass: Symbol = method.owner
- def setHostClass(cls: Symbol): this.type = { hostClass = cls; this }
-
- /** This is specifically for preserving the target native Array type long
- * enough that clone() can generate the right call.
- */
- var targetTypeKind: TypeKind = UNIT // the default should never be used, so UNIT should fail fast.
- def setTargetTypeKind(tk: TypeKind) = targetTypeKind = tk
-
- private def params = method.info.paramTypes
- private def consumesInstance = style match {
- case Static(false) => 0
- case _ => 1
- }
-
- override def consumed = params.length + consumesInstance
- override def consumedTypes = {
- val args = params map toTypeKind
- if (consumesInstance > 0) ObjectReference :: args
- else args
- }
-
- private val producedList = toTypeKind(method.info.resultType) match {
- case UNIT => Nil
- case _ if method.isConstructor => Nil
- case kind => kind :: Nil
- }
- override def produced = producedList.size
- override def producedTypes = producedList
-
- /** object identity is equality for CALL_METHODs. Needed for
- * being able to store such instructions into maps, when more
- * than one CALL_METHOD to the same method might exist.
- */
-
- override def category = mthdsCat
- }
-
- /**
- * A place holder entry that allows us to parse class files with invoke dynamic
- * instructions. Because the compiler doesn't yet really understand the
- * behavior of invokeDynamic, this op acts as a poison pill. Any attempt to analyze
- * this instruction will cause a failure. The only optimization that
- * should ever look at non-Scala generated icode is the inliner, and it
- * has been modified to not examine any method with invokeDynamic
- * instructions. So if this poison pill ever causes problems then
- * there's been a serious misunderstanding
- */
- // TODO do the real thing
- case class INVOKE_DYNAMIC(poolEntry: Int) extends Instruction {
- private def error = sys.error("INVOKE_DYNAMIC is not fully implemented and should not be analyzed")
- override def consumed = error
- override def produced = error
- override def producedTypes = error
- override def category = error
- }
-
- case class BOX(boxType: TypeKind) extends Instruction {
- assert(boxType.isValueType && (boxType ne UNIT)) // documentation
- override def toString(): String = "BOX " + boxType
- override def consumed = 1
- override def consumedTypes = boxType :: Nil
- override def produced = 1
- override def producedTypes = BOXED(boxType) :: Nil
- override def category = objsCat
- }
-
- case class UNBOX(boxType: TypeKind) extends Instruction {
- assert(boxType.isValueType && !boxType.isInstanceOf[BOXED] && (boxType ne UNIT)) // documentation
- override def toString(): String = "UNBOX " + boxType
- override def consumed = 1
- override def consumedTypes = ObjectReferenceList
- override def produced = 1
- override def producedTypes = boxType :: Nil
- override def category = objsCat
- }
-
- /** Create a new instance of a class through the specified constructor
- * Stack: ...:arg1:arg2:...:argn
- * ->: ...:ref
- */
- case class NEW(kind: REFERENCE) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String = "NEW "+ kind
-
- override def consumed = 0
-
- override def produced = 1
-
- override def producedTypes = kind :: Nil
-
- /** The corresponding constructor call. */
- var init: CALL_METHOD = _
-
- override def category = objsCat
- }
-
-
- /** This class represents a CREATE_ARRAY instruction
- * Stack: ...:size_1:size_2:..:size_n
- * ->: ...:arrayref
- */
- case class CREATE_ARRAY(elem: TypeKind, dims: Int) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="CREATE_ARRAY "+elem + " x " + dims
-
- override def consumed = dims
-
- override def consumedTypes = List.fill(dims)(INT)
- override def produced = 1
-
- override def producedTypes = ARRAY(elem) :: Nil
-
- override def category = arraysCat
- }
-
- /** This class represents a IS_INSTANCE instruction
- * Stack: ...:ref
- * ->: ...:result(boolean)
- */
- case class IS_INSTANCE(typ: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="IS_INSTANCE "+typ
-
- override def consumed = 1
- override def produced = 1
- override def consumedTypes = ObjectReferenceList
- override def producedTypes = BOOL :: Nil
-
- override def category = castsCat
- }
-
- /** This class represents a CHECK_CAST instruction
- * Stack: ...:ref(oldtype)
- * ->: ...:ref(typ <=: oldtype)
- */
- case class CHECK_CAST(typ: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="CHECK_CAST "+typ
-
- override def consumed = 1
- override def produced = 1
- override def consumedTypes = ObjectReferenceList
- override def producedTypes = typ :: Nil
-
- override def category = castsCat
- }
-
- /** This class represents a SWITCH instruction
- * Stack: ...:index(int)
- * ->: ...:
- *
- * The tags array contains one entry per label, each entry consisting of
- * an array of ints, any of which will trigger the jump to the corresponding label.
- * labels should contain an extra label, which is the 'default' jump.
- */
- case class SWITCH(tags: List[List[Int]], labels: List[BasicBlock]) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="SWITCH ..."
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = INT :: Nil
-
- def flatTagsCount: Int = { var acc = 0; var rest = tags; while(rest.nonEmpty) { acc += rest.head.length; rest = rest.tail }; acc } // a one-liner
-
- override def category = jumpsCat
- }
-
- /** This class represents a JUMP instruction
- * Stack: ...
- * ->: ...
- */
- case class JUMP(whereto: BasicBlock) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="JUMP "+whereto.label
-
- override def consumed = 0
- override def produced = 0
-
- override def category = jumpsCat
- }
-
- /** This class represents a CJUMP instruction
- * It compares the two values on the stack with the 'cond' test operator
- * Stack: ...:value1:value2
- * ->: ...
- */
- case class CJUMP(successBlock: BasicBlock,
- failureBlock: BasicBlock,
- cond: TestOp,
- kind: TypeKind) extends Instruction
- {
-
- /** Returns a string representation of this instruction */
- override def toString(): String = (
- "CJUMP (" + kind + ")" +
- cond + " ? "+successBlock.label+" : "+failureBlock.label
- )
-
- override def consumed = 2
- override def produced = 0
-
- override def consumedTypes = kind :: kind :: Nil
-
- override def category = jumpsCat
- }
-
- /** This class represents a CZJUMP instruction
- * It compares the one value on the stack and zero with the 'cond' test operator
- * Stack: ...:value:
- * ->: ...
- */
- case class CZJUMP(successBlock: BasicBlock,
- failureBlock: BasicBlock,
- cond: TestOp,
- kind: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String = (
- "CZJUMP (" + kind + ")" +
- cond + " ? "+successBlock.label+" : "+failureBlock.label
- )
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = kind :: Nil
- override def category = jumpsCat
- }
-
-
- /** This class represents a RETURN instruction
- * Stack: ...
- * ->: ...
- */
- case class RETURN(kind: TypeKind) extends Instruction {
- override def consumed = if (kind == UNIT) 0 else 1
- override def produced = 0
-
- override def consumedTypes = if (kind == UNIT) Nil else kind :: Nil
-
- override def category = retCat
- }
-
- /** This class represents a THROW instruction
- * Stack: ...:Throwable(Ref)
- * ->: ...:
- */
- case class THROW(clasz: Symbol) extends Instruction {
- /** PP to ID: We discussed parameterizing LOAD_EXCEPTION but
- * not THROW, which came about organically. It seems like the
- * right thing, but can you confirm?
- */
- override def toString = "THROW(" + clasz.name + ")"
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = toTypeKind(clasz.tpe) :: Nil
-
- override def category = retCat
- }
-
- /** This class represents a DROP instruction
- * Stack: ...:something
- * ->: ...
- */
- case class DROP (typ: TypeKind) extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="DROP "+typ
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = typ :: Nil
-
- override def category = stackCat
- }
-
- /** This class represents a DUP instruction
- * Stack: ...:something
- * ->: ...:something:something
- */
- case class DUP (typ: TypeKind) extends Instruction {
- override def consumed = 1
- override def produced = 2
- override def consumedTypes = typ :: Nil
- override def producedTypes = typ :: typ :: Nil
- override def category = stackCat
- }
-
- /** This class represents a MONITOR_ENTER instruction
- * Stack: ...:object(ref)
- * ->: ...:
- */
- case class MONITOR_ENTER() extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="MONITOR_ENTER"
-
- override def consumed = 1
- override def produced = 0
-
- override def consumedTypes = ObjectReference :: Nil
-
- override def category = objsCat
- }
-
- /** This class represents a MONITOR_EXIT instruction
- * Stack: ...:object(ref)
- * ->: ...:
- */
- case class MONITOR_EXIT() extends Instruction {
- /** Returns a string representation of this instruction */
- override def toString(): String ="MONITOR_EXIT"
-
- override def consumed = 1
-
- override def produced = 0
-
- override def consumedTypes = ObjectReference :: Nil
-
- override def category = objsCat
- }
-
- /** A local variable becomes visible at this point in code.
- * Used only for generating precise local variable tables as
- * debugging information.
- */
- case class SCOPE_ENTER(lv: Local) extends Instruction {
- override def toString(): String = "SCOPE_ENTER " + lv
- override def consumed = 0
- override def produced = 0
- override def category = localsCat
- }
-
- /** A local variable leaves its scope at this point in code.
- * Used only for generating precise local variable tables as
- * debugging information.
- */
- case class SCOPE_EXIT(lv: Local) extends Instruction {
- override def toString(): String = "SCOPE_EXIT " + lv
- override def consumed = 0
- override def produced = 0
- override def category = localsCat
- }
-
- /** Fake instruction. It designates the VM who pushes an exception
- * on top of the /empty/ stack at the beginning of each exception handler.
- * Note: Unlike other instructions, it consumes all elements on the stack!
- * then pushes one exception instance.
- */
- case class LOAD_EXCEPTION(clasz: Symbol) extends Instruction {
- override def consumed = sys.error("LOAD_EXCEPTION does clean the whole stack, no idea how many things it consumes!")
- override def produced = 1
- override def producedTypes = REFERENCE(clasz) :: Nil
- override def category = stackCat
- }
-
- /** This class represents a method invocation style. */
- sealed abstract class InvokeStyle {
- /** Is this a dynamic method call? */
- def isDynamic: Boolean = false
-
- /** Is this a static method call? */
- def isStatic: Boolean = false
-
- def isSuper: Boolean = false
-
- /** Is this an instance method call? */
- def hasInstance: Boolean = true
-
- /** Returns a string representation of this style. */
- override def toString(): String
- }
-
- /** Virtual calls.
- * On JVM, translated to either `invokeinterface` or `invokevirtual`.
- */
- case object Dynamic extends InvokeStyle {
- override def isDynamic = true
- override def toString(): String = "dynamic"
- }
-
- /**
- * Special invoke:
- * Static(true) is used for calls to private members, ie `invokespecial` on JVM.
- * Static(false) is used for calls to class-level instance-less static methods, ie `invokestatic` on JVM.
- */
- case class Static(onInstance: Boolean) extends InvokeStyle {
- override def isStatic = true
- override def hasInstance = onInstance
- override def toString(): String = {
- if(onInstance) "static-instance"
- else "static-class"
- }
- }
-
- /** Call through super[mix].
- * On JVM, translated to `invokespecial`.
- */
- case class SuperCall(mix: Name) extends InvokeStyle {
- override def isSuper = true
- override def toString(): String = { "super(" + mix + ")" }
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala
deleted file mode 100644
index dd930ba52f..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala
+++ /dev/null
@@ -1,247 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend
-package icode
-
-import java.io.PrintWriter
-
-trait Primitives { self: ICodes =>
-
- /** This class represents a primitive operation. */
- class Primitive {
- }
-
-
- // type : (type) => type
- // range: type <- { BOOL, Ix, Ux, Rx }
- // jvm : {i, l, f, d}neg
- case class Negation(kind: TypeKind) extends Primitive
-
- // type : zero ? (type) => BOOL : (type,type) => BOOL
- // range: type <- { BOOL, Ix, Ux, Rx, REF }
- // jvm : if{eq, ne, lt, ge, le, gt}, if{null, nonnull}
- // if_icmp{eq, ne, lt, ge, le, gt}, if_acmp{eq,ne}
- case class Test(op: TestOp, kind: TypeKind, zero: Boolean) extends Primitive
-
- // type : (type,type) => I4
- // range: type <- { Ix, Ux, Rx }
- // jvm : lcmp, {f, d}cmp{l, g}
- case class Comparison(op: ComparisonOp, kind: TypeKind) extends Primitive
-
- // type : (type,type) => type
- // range: type <- { Ix, Ux, Rx }
- // jvm : {i, l, f, d}{add, sub, mul, div, rem}
- case class Arithmetic(op: ArithmeticOp, kind: TypeKind) extends Primitive
-
- // type : (type,type) => type
- // range: type <- { BOOL, Ix, Ux }
- // jvm : {i, l}{and, or, xor}
- case class Logical(op: LogicalOp, kind: TypeKind) extends Primitive
-
- // type : (type,I4) => type
- // range: type <- { Ix, Ux }
- // jvm : {i, l}{shl, ushl, shr}
- case class Shift(op: ShiftOp, kind: TypeKind) extends Primitive
-
- // type : (src) => dst
- // range: src,dst <- { Ix, Ux, Rx }
- // jvm : i2{l, f, d}, l2{i, f, d}, f2{i, l, d}, d2{i, l, f}, i2{b, c, s}
- case class Conversion(src: TypeKind, dst: TypeKind) extends Primitive
-
- // type : (Array[REF]) => I4
- // range: type <- { BOOL, Ix, Ux, Rx, REF }
- // jvm : arraylength
- case class ArrayLength(kind: TypeKind) extends Primitive
-
- // type : (buf,el) => buf
- // range: lf,rg <- { BOOL, Ix, Ux, Rx, REF, STR }
- // jvm : It should call the appropriate 'append' method on StringBuffer
- case class StringConcat(el: TypeKind) extends Primitive
-
- /** Signals the beginning of a series of concatenations.
- * On the JVM platform, it should create a new StringBuffer
- */
- case object StartConcat extends Primitive
-
- /**
- * type: (buf) => STR
- * jvm : It should turn the StringBuffer into a String.
- */
- case object EndConcat extends Primitive
-
- /** Pretty printer for primitives */
- class PrimitivePrinter(out: PrintWriter) {
- def print(s: String): PrimitivePrinter = {
- out.print(s)
- this
- }
- }
-
- /** This class represents a comparison operation. */
- class ComparisonOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case CMPL => "CMPL"
- case CMP => "CMP"
- case CMPG => "CMPG"
- case _ => throw new RuntimeException("ComparisonOp unknown case")
- }
- }
-
- /** A comparison operation with -1 default for NaNs */
- case object CMPL extends ComparisonOp
-
- /** A comparison operation with no default for NaNs */
- case object CMP extends ComparisonOp
-
- /** A comparison operation with +1 default for NaNs */
- case object CMPG extends ComparisonOp
-
-
- /** This class represents a test operation. */
- sealed abstract class TestOp {
-
- /** Returns the negation of this operation. */
- def negate(): TestOp
-
- /** Returns a string representation of this operation. */
- override def toString(): String
-
- /** used only from GenASM */
- def opcodeIF(): Int
-
- /** used only from GenASM */
- def opcodeIFICMP(): Int
-
- }
-
- /** An equality test */
- case object EQ extends TestOp {
- def negate() = NE
- override def toString() = "EQ"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFEQ
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPEQ
- }
-
- /** A non-equality test */
- case object NE extends TestOp {
- def negate() = EQ
- override def toString() = "NE"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFNE
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPNE
- }
-
- /** A less-than test */
- case object LT extends TestOp {
- def negate() = GE
- override def toString() = "LT"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFLT
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPLT
- }
-
- /** A greater-than-or-equal test */
- case object GE extends TestOp {
- def negate() = LT
- override def toString() = "GE"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFGE
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPGE
- }
-
- /** A less-than-or-equal test */
- case object LE extends TestOp {
- def negate() = GT
- override def toString() = "LE"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFLE
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPLE
- }
-
- /** A greater-than test */
- case object GT extends TestOp {
- def negate() = LE
- override def toString() = "GT"
- override def opcodeIF() = scala.tools.asm.Opcodes.IFGT
- override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPGT
- }
-
- /** This class represents an arithmetic operation. */
- class ArithmeticOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case ADD => "ADD"
- case SUB => "SUB"
- case MUL => "MUL"
- case DIV => "DIV"
- case REM => "REM"
- case NOT => "NOT"
- case _ => throw new RuntimeException("ArithmeticOp unknown case")
- }
- }
-
- /** An arithmetic addition operation */
- case object ADD extends ArithmeticOp
-
- /** An arithmetic subtraction operation */
- case object SUB extends ArithmeticOp
-
- /** An arithmetic multiplication operation */
- case object MUL extends ArithmeticOp
-
- /** An arithmetic division operation */
- case object DIV extends ArithmeticOp
-
- /** An arithmetic remainder operation */
- case object REM extends ArithmeticOp
-
- /** Bitwise negation. */
- case object NOT extends ArithmeticOp
-
- /** This class represents a shift operation. */
- class ShiftOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case LSL => "LSL"
- case ASR => "ASR"
- case LSR => "LSR"
- case _ => throw new RuntimeException("ShiftOp unknown case")
- }
- }
-
- /** A logical shift to the left */
- case object LSL extends ShiftOp
-
- /** An arithmetic shift to the right */
- case object ASR extends ShiftOp
-
- /** A logical shift to the right */
- case object LSR extends ShiftOp
-
- /** This class represents a logical operation. */
- class LogicalOp {
-
- /** Returns a string representation of this operation. */
- override def toString(): String = this match {
- case AND => "AND"
- case OR => "OR"
- case XOR => "XOR"
- case _ => throw new RuntimeException("LogicalOp unknown case")
- }
- }
-
- /** A bitwise AND operation */
- case object AND extends LogicalOp
-
- /** A bitwise OR operation */
- case object OR extends LogicalOp
-
- /** A bitwise XOR operation */
- case object XOR extends LogicalOp
-}
-
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Printers.scala b/src/compiler/scala/tools/nsc/backend/icode/Printers.scala
deleted file mode 100644
index 1fe33f78e7..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Printers.scala
+++ /dev/null
@@ -1,126 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-import java.io.PrintWriter
-
-trait Printers { self: ICodes =>
- import global._
-
- class TextPrinter(writer: PrintWriter, lin: Linearizer) {
- private var margin = 0
- private var out = writer
-
- final val TAB = 2
-
- def setWriter(w: PrintWriter) { out = w }
-
- def indent() { margin += TAB }
- def undent() { margin -= TAB }
-
- def print(s: String) { out.print(s) }
- def print(o: Any) { print(o.toString()) }
-
- def println(s: String) {
- print(s)
- println()
- }
-
- def println() {
- out.println()
- var i = 0
- while (i < margin) {
- print(" ")
- i += 1
- }
- }
-
- def printList[A](l: List[A], sep: String): Unit = l match {
- case Nil =>
- case x :: Nil => print(x)
- case x :: xs => print(x); print(sep); printList(xs, sep)
- }
-
- def printList[A](pr: A => Unit)(l: List[A], sep: String): Unit = l match {
- case Nil =>
- case x :: Nil => pr(x)
- case x :: xs => pr(x); print(sep); printList(pr)(xs, sep)
- }
-
- def printClass(cls: IClass) {
- print(cls.symbol.toString()); print(" extends ")
- printList(cls.symbol.info.parents, ", ")
- indent(); println(" {")
- println("// fields:")
- cls.fields.foreach(printField); println()
- println("// methods")
- cls.methods.foreach(printMethod)
- undent(); println()
- println("}")
- }
-
- def printField(f: IField) {
- print(f.symbol.keyString); print(" ")
- print(f.symbol.nameString); print(": ")
- println(f.symbol.info.toString())
- }
-
- def printMethod(m: IMethod) {
- print("def "); print(m.symbol.name)
- print("("); printList(printParam)(m.params, ", "); print(")")
- print(": "); print(m.symbol.info.resultType)
-
- if (!m.isAbstractMethod) {
- println(" {")
- println("locals: " + m.locals.mkString("", ", ", ""))
- println("startBlock: " + m.startBlock)
- println("blocks: " + m.code.blocks.mkString("[", ",", "]"))
- println()
- lin.linearize(m) foreach printBlock
- println("}")
-
- indent(); println("Exception handlers: ")
- m.exh foreach printExceptionHandler
-
- undent(); println()
- } else
- println()
- }
-
- def printParam(p: Local) {
- print(p.sym.name); print(": "); print(p.sym.info)
- print(" ("); print(p.kind); print(")")
- }
-
- def printExceptionHandler(e: ExceptionHandler) {
- indent()
- println("catch (" + e.cls.simpleName + ") in " + e.covered.toSeq.sortBy(_.label) + " starting at: " + e.startBlock)
- println("consisting of blocks: " + e.blocks)
- undent()
- println("with finalizer: " + e.finalizer)
- // linearizer.linearize(e.startBlock) foreach printBlock;
- }
-
- def printBlock(bb: BasicBlock) {
- print(bb.label)
- if (bb.loopHeader) print("[loop header]")
- print(": ")
- if (settings.debug) print("pred: " + bb.predecessors + " succs: " + bb.successors + " flags: " + bb.flagsString)
- indent(); println()
- bb.toList foreach printInstruction
- undent(); println()
- }
-
- def printInstruction(i: Instruction) {
-// if (settings.Xdce.value)
-// print(if (i.useful) " " else " * ");
- if (i.pos.isDefined) print(i.pos.line.toString + "\t") else print("?\t")
- println(i.toString())
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Repository.scala b/src/compiler/scala/tools/nsc/backend/icode/Repository.scala
deleted file mode 100644
index 10d57df4a3..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/Repository.scala
+++ /dev/null
@@ -1,47 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend
-package icode
-
-import scala.collection._
-
-/**
- * @author Iulian Dragos
- */
-trait Repository {
- val global: Global
- import global._
- import icodes._
-
- val loaded: mutable.Map[Symbol, IClass] = perRunCaches.newMap()
-
- /** Is the given class available as icode? */
- def available(sym: Symbol) = classes.contains(sym) || loaded.contains(sym)
-
- /** The icode of the given class, if available */
- def icode(sym: Symbol): Option[IClass] = (classes get sym) orElse (loaded get sym)
-
- /** Load bytecode for given symbol. */
- def load(sym: Symbol): Boolean = {
- try {
- val (c1, c2) = icodeReader.readClass(sym)
-
- assert(c1.symbol == sym || c2.symbol == sym, "c1.symbol = %s, c2.symbol = %s, sym = %s".format(c1.symbol, c2.symbol, sym))
- loaded += (c1.symbol -> c1)
- loaded += (c2.symbol -> c2)
-
- true
- } catch {
- case e: Throwable => // possible exceptions are MissingRequirementError, IOException and TypeError -> no better common supertype
- log("Failed to load %s. [%s]".format(sym.fullName, e.getMessage))
- if (settings.debug) { e.printStackTrace }
-
- false
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala
deleted file mode 100644
index a6d0d3b9fa..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala
+++ /dev/null
@@ -1,438 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-/* A type case
-
- case UNIT =>
- case BOOL =>
- case BYTE =>
- case SHORT =>
- case CHAR =>
- case INT =>
- case LONG =>
- case FLOAT =>
- case DOUBLE =>
- case REFERENCE(cls) =>
- case ARRAY(elem) =>
-
-*/
-
-trait TypeKinds { self: ICodes =>
- import global._
- import definitions.{ ArrayClass, AnyRefClass, ObjectClass, NullClass, NothingClass, arrayType }
-
- /** A map from scala primitive Types to ICode TypeKinds */
- lazy val primitiveTypeMap: Map[Symbol, TypeKind] = {
- import definitions._
- Map(
- UnitClass -> UNIT,
- BooleanClass -> BOOL,
- CharClass -> CHAR,
- ByteClass -> BYTE,
- ShortClass -> SHORT,
- IntClass -> INT,
- LongClass -> LONG,
- FloatClass -> FLOAT,
- DoubleClass -> DOUBLE
- )
- }
- /** Reverse map for toType */
- private lazy val reversePrimitiveMap: Map[TypeKind, Symbol] =
- (primitiveTypeMap map (_.swap)).toMap
-
- /** This class represents a type kind. Type kinds
- * represent the types that the VM know (or the ICode
- * view of what VMs know).
- */
- sealed abstract class TypeKind {
- def maxType(other: TypeKind): TypeKind
-
- def toType: Type = reversePrimitiveMap get this map (_.tpe) getOrElse {
- this match {
- case REFERENCE(cls) => cls.tpe_*
- case ARRAY(elem) => arrayType(elem.toType)
- case _ => abort("Unknown type kind.")
- }
- }
-
- def isReferenceType = false
- def isArrayType = false
- def isValueType = false
- def isBoxedType = false
- final def isRefOrArrayType = isReferenceType || isArrayType
- final def isNothingType = this == NothingReference
- final def isNullType = this == NullReference
- final def isInterfaceType = this match {
- case REFERENCE(cls) if cls.isInterface || cls.isTrait => true
- case _ => false
- }
-
- /** On the JVM,
- * BOOL, BYTE, CHAR, SHORT, and INT
- * are like Ints for the purposes of calculating the lub.
- */
- def isIntSizedType: Boolean = false
-
- /** On the JVM, similar to isIntSizedType except that BOOL isn't integral while LONG is. */
- def isIntegralType: Boolean = false
-
- /** On the JVM, FLOAT and DOUBLE. */
- def isRealType: Boolean = false
-
- final def isNumericType: Boolean = isIntegralType | isRealType
-
- /** Simple subtyping check */
- def <:<(other: TypeKind): Boolean
-
- /**
- * this is directly assignable to other if no coercion or
- * casting is needed to convert this to other. It's a distinct
- * relationship from <:< because on the JVM, BOOL, BYTE, CHAR,
- * SHORT need no coercion to INT even though JVM arrays
- * are covariant, ARRAY[SHORT] is not a subtype of ARRAY[INT]
- */
- final def isAssignabledTo(other: TypeKind): Boolean = other match {
- case INT => this.isIntSizedType
- case _ => this <:< other
- }
-
- /** Is this type a category 2 type in JVM terms? (ie, is it LONG or DOUBLE?) */
- def isWideType: Boolean = false
-
- /** The number of dimensions for array types. */
- def dimensions: Int = 0
-
- protected def uncomparable(thisKind: String, other: TypeKind): Nothing =
- abort("Uncomparable type kinds: " + thisKind + " with " + other)
-
- protected def uncomparable(other: TypeKind): Nothing =
- uncomparable(this.toString, other)
- }
-
- sealed abstract class ValueTypeKind extends TypeKind {
- override def isValueType = true
- override def toString = {
- this.getClass.getName stripSuffix "$" dropWhile (_ != '$') drop 1
- }
- def <:<(other: TypeKind): Boolean = this eq other
- }
-
- /**
- * The least upper bound of two typekinds. They have to be either
- * REFERENCE or ARRAY kinds.
- *
- * The lub is based on the lub of scala types.
- */
- def lub(a: TypeKind, b: TypeKind): TypeKind = {
- /* The compiler's lub calculation does not order classes before traits.
- * This is apparently not wrong but it is inconvenient, and causes the
- * icode checker to choke when things don't match up. My attempts to
- * alter the calculation at the compiler level were failures, so in the
- * interests of a working icode checker I'm making the adjustment here.
- *
- * Example where we'd like a different answer:
- *
- * abstract class Tom
- * case object Bob extends Tom
- * case object Harry extends Tom
- * List(Bob, Harry) // compiler calculates "Product with Tom" rather than "Tom with Product"
- *
- * Here we make the adjustment by rewinding to a pre-erasure state and
- * sifting through the parents for a class type.
- */
- def lub0(tk1: TypeKind, tk2: TypeKind): Type = enteringUncurry {
- val tp = global.lub(List(tk1.toType, tk2.toType))
- val (front, rest) = tp.parents span (_.typeSymbol.isTrait)
-
- if (front.isEmpty || rest.isEmpty || rest.head.typeSymbol == ObjectClass) tp
- else rest.head
- }
-
- def isIntLub = (
- (a == INT && b.isIntSizedType) ||
- (b == INT && a.isIntSizedType)
- )
-
- if (a == b) a
- else if (a.isNothingType) b
- else if (b.isNothingType) a
- else if (a.isBoxedType || b.isBoxedType) AnyRefReference // we should do better
- else if (isIntLub) INT
- else if (a.isRefOrArrayType && b.isRefOrArrayType) {
- if (a.isNullType) b
- else if (b.isNullType) a
- else toTypeKind(lub0(a, b))
- }
- else throw new CheckerException("Incompatible types: " + a + " with " + b)
- }
-
- /** The unit value */
- case object UNIT extends ValueTypeKind {
- def maxType(other: TypeKind) = other match {
- case UNIT | REFERENCE(NothingClass) => UNIT
- case _ => uncomparable(other)
- }
- }
-
- /** A boolean value */
- case object BOOL extends ValueTypeKind {
- override def isIntSizedType = true
- def maxType(other: TypeKind) = other match {
- case BOOL | REFERENCE(NothingClass) => BOOL
- case _ => uncomparable(other)
- }
- }
-
- /** Note that the max of Char/Byte and Char/Short is Int, because
- * neither strictly encloses the other due to unsignedness.
- * See ticket #2087 for a consequence.
- */
-
- /** A 1-byte signed integer */
- case object BYTE extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- def maxType(other: TypeKind) = {
- if (other == BYTE || other.isNothingType) BYTE
- else if (other == CHAR) INT
- else if (other.isNumericType) other
- else uncomparable(other)
- }
- }
-
- /** A 2-byte signed integer */
- case object SHORT extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- override def maxType(other: TypeKind) = other match {
- case BYTE | SHORT | REFERENCE(NothingClass) => SHORT
- case CHAR => INT
- case INT | LONG | FLOAT | DOUBLE => other
- case _ => uncomparable(other)
- }
- }
-
- /** A 2-byte UNSIGNED integer */
- case object CHAR extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- override def maxType(other: TypeKind) = other match {
- case CHAR | REFERENCE(NothingClass) => CHAR
- case BYTE | SHORT => INT
- case INT | LONG | FLOAT | DOUBLE => other
- case _ => uncomparable(other)
- }
- }
-
- /** A 4-byte signed integer */
- case object INT extends ValueTypeKind {
- override def isIntSizedType = true
- override def isIntegralType = true
- override def maxType(other: TypeKind) = other match {
- case BYTE | SHORT | CHAR | INT | REFERENCE(NothingClass) => INT
- case LONG | FLOAT | DOUBLE => other
- case _ => uncomparable(other)
- }
- }
-
- /** An 8-byte signed integer */
- case object LONG extends ValueTypeKind {
- override def isIntegralType = true
- override def isWideType = true
- override def maxType(other: TypeKind): TypeKind =
- if (other.isIntegralType || other.isNothingType) LONG
- else if (other.isRealType) DOUBLE
- else uncomparable(other)
- }
-
- /** A 4-byte floating point number */
- case object FLOAT extends ValueTypeKind {
- override def isRealType = true
- override def maxType(other: TypeKind): TypeKind =
- if (other == DOUBLE) DOUBLE
- else if (other.isNumericType || other.isNothingType) FLOAT
- else uncomparable(other)
- }
-
- /** An 8-byte floating point number */
- case object DOUBLE extends ValueTypeKind {
- override def isRealType = true
- override def isWideType = true
- override def maxType(other: TypeKind): TypeKind =
- if (other.isNumericType || other.isNothingType) DOUBLE
- else uncomparable(other)
- }
-
- /** A class type. */
- final case class REFERENCE(cls: Symbol) extends TypeKind {
- override def toString = "REF(" + cls + ")"
- assert(cls ne null,
- "REFERENCE to null class symbol.")
- assert(cls != ArrayClass,
- "REFERENCE to Array is not allowed, should be ARRAY[..] instead")
- assert(cls != NoSymbol,
- "REFERENCE to NoSymbol not allowed!")
-
- /**
- * Approximate `lub`. The common type of two references is
- * always AnyRef. For 'real' least upper bound wrt to subclassing
- * use method 'lub'.
- */
- override def maxType(other: TypeKind) = other match {
- case REFERENCE(_) | ARRAY(_) => AnyRefReference
- case _ => uncomparable("REFERENCE", other)
- }
-
- /** Checks subtyping relationship. */
- def <:<(other: TypeKind) = isNothingType || (other match {
- case REFERENCE(cls2) => cls.tpe <:< cls2.tpe
- case ARRAY(_) => cls == NullClass
- case _ => false
- })
- override def isReferenceType = true
- }
-
- def ArrayN(elem: TypeKind, dims: Int): ARRAY = {
- assert(dims > 0)
- if (dims == 1) ARRAY(elem)
- else ARRAY(ArrayN(elem, dims - 1))
- }
-
- final case class ARRAY(elem: TypeKind) extends TypeKind {
- override def toString = "ARRAY[" + elem + "]"
- override def isArrayType = true
- override def dimensions = 1 + elem.dimensions
-
- /** The ultimate element type of this array. */
- def elementKind: TypeKind = elem match {
- case a @ ARRAY(_) => a.elementKind
- case k => k
- }
-
- /**
- * Approximate `lub`. The common type of two references is
- * always AnyRef. For 'real' least upper bound wrt to subclassing
- * use method 'lub'.
- */
- override def maxType(other: TypeKind) = other match {
- case ARRAY(elem2) if elem == elem2 => ARRAY(elem)
- case ARRAY(_) | REFERENCE(_) => AnyRefReference
- case _ => uncomparable("ARRAY", other)
- }
-
- /** Array subtyping is covariant, as in Java. Necessary for checking
- * code that interacts with Java. */
- def <:<(other: TypeKind) = other match {
- case ARRAY(elem2) => elem <:< elem2
- case REFERENCE(AnyRefClass | ObjectClass) => true // TODO: platform dependent!
- case _ => false
- }
- }
-
- /** A boxed value. */
- case class BOXED(kind: TypeKind) extends TypeKind {
- override def isBoxedType = true
-
- override def maxType(other: TypeKind) = other match {
- case BOXED(`kind`) => this
- case REFERENCE(_) | ARRAY(_) | BOXED(_) => AnyRefReference
- case _ => uncomparable("BOXED", other)
- }
-
- /** Checks subtyping relationship. */
- def <:<(other: TypeKind) = other match {
- case BOXED(`kind`) => true
- case REFERENCE(AnyRefClass | ObjectClass) => true // TODO: platform dependent!
- case _ => false
- }
- }
-
- /**
- * Dummy TypeKind to represent the ConcatClass in a platform-independent
- * way. For JVM it would have been a REFERENCE to 'StringBuffer'.
- */
- case object ConcatClass extends TypeKind {
- override def toString = "ConcatClass"
- def <:<(other: TypeKind): Boolean = this eq other
-
- /**
- * Approximate `lub`. The common type of two references is
- * always AnyRef. For 'real' least upper bound wrt to subclassing
- * use method 'lub'.
- */
- override def maxType(other: TypeKind) = other match {
- case REFERENCE(_) => AnyRefReference
- case _ => uncomparable(other)
- }
- }
-
- ////////////////// Conversions //////////////////////////////
-
- /** Return the TypeKind of the given type
- *
- * Call to dealiasWiden fixes #3003 (follow type aliases). Otherwise,
- * arrayOrClassType below would return ObjectReference.
- */
- def toTypeKind(t: Type): TypeKind = t.dealiasWiden match {
- case ThisType(ArrayClass) => ObjectReference
- case ThisType(sym) => REFERENCE(sym)
- case SingleType(_, sym) => primitiveOrRefType(sym)
- case ConstantType(_) => toTypeKind(t.underlying)
- case TypeRef(_, sym, args) => primitiveOrClassType(sym, args)
- case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!")
- case ClassInfoType(_, _, sym) => primitiveOrRefType(sym)
-
- // !!! Iulian says types which make no sense after erasure should not reach here,
- // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know
- // if the first two cases exist because they do or as a defensive measure, but
- // at the time I added it, RefinedTypes were indeed reaching here.
- case ExistentialType(_, t) => toTypeKind(t)
- case AnnotatedType(_, t) => toTypeKind(t)
- case RefinedType(parents, _) => parents map toTypeKind reduceLeft lub
- // For sure WildcardTypes shouldn't reach here either, but when
- // debugging such situations this may come in handy.
- // case WildcardType => REFERENCE(ObjectClass)
- case norm => abort(
- "Unknown type: %s, %s [%s, %s] TypeRef? %s".format(
- t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef]
- )
- )
- }
-
- /** Return the type kind of a class, possibly an array type.
- */
- private def arrayOrClassType(sym: Symbol, targs: List[Type]) = sym match {
- case ArrayClass => ARRAY(toTypeKind(targs.head))
- case _ if sym.isClass => newReference(sym)
- case _ =>
- assert(sym.isType, sym) // it must be compiling Array[a]
- ObjectReference
- }
- /** Interfaces have to be handled delicately to avoid introducing
- * spurious errors, but if we treat them all as AnyRef we lose too
- * much information.
- */
- private def newReference(sym: Symbol): TypeKind = {
- // Can't call .toInterface (at this phase) or we trip an assertion.
- // See PackratParser#grow for a method which fails with an apparent mismatch
- // between "object PackratParsers$class" and "trait PackratParsers"
- if (sym.isImplClass) {
- // pos/spec-List.scala is the sole failure if we don't check for NoSymbol
- val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name))
- if (traitSym != NoSymbol)
- return REFERENCE(traitSym)
- }
- REFERENCE(sym)
- }
-
- private def primitiveOrRefType(sym: Symbol) =
- primitiveTypeMap.getOrElse(sym, newReference(sym))
- private def primitiveOrClassType(sym: Symbol, targs: List[Type]) =
- primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, targs))
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala
deleted file mode 100644
index 57d51dad49..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala
+++ /dev/null
@@ -1,82 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend
-package icode
-
-/** This trait ...
- *
- * @author Iulian Dragos
- * @version 1.0
- */
-trait TypeStacks {
- self: ICodes =>
-
- /* This class simulates the type of the operand
- * stack of the ICode.
- */
- type Rep = List[TypeKind]
-
- class TypeStack(var types: Rep) {
- if (types.nonEmpty)
- checkerDebug("Created " + this)
-
- def this() = this(Nil)
- def this(that: TypeStack) = this(that.types)
-
- def length: Int = types.length
- def isEmpty = length == 0
- def nonEmpty = length != 0
-
- /** Push a type on the type stack. UNITs are ignored. */
- def push(t: TypeKind) = {
- if (t != UNIT)
- types = t :: types
- }
-
- def head: TypeKind = types.head
-
- /** Removes the value on top of the stack, and returns it. It assumes
- * the stack contains at least one element.
- */
- def pop: TypeKind = {
- val t = types.head
- types = types.tail
- t
- }
-
- /** Return the topmost two values on the stack. It assumes the stack
- * is large enough. Topmost element first.
- */
- def pop2: (TypeKind, TypeKind) = (pop, pop)
-
- /** Return the topmost three values on the stack. It assumes the stack
- * is large enough. Topmost element first.
- */
- def pop3: (TypeKind, TypeKind, TypeKind) = (pop, pop, pop)
-
- /** Drop the first n elements of the stack. */
- def pop(n: Int): List[TypeKind] = {
- val prefix = types.take(n)
- types = types.drop(n)
- prefix
- }
-
- def apply(n: Int): TypeKind = types(n)
-
- /* This method returns a String representation of the stack */
- override def toString() =
- if (types.isEmpty) "[]"
- else types.mkString("[", " ", "]")
-
- override def hashCode() = types.hashCode()
- override def equals(other: Any): Boolean = other match {
- case x: TypeStack => x.types == types
- case _ => false
- }
- }
-
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala
deleted file mode 100644
index 9d48d7a0d3..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/CopyPropagation.scala
+++ /dev/null
@@ -1,553 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend.icode.analysis
-
-import scala.collection.{ mutable, immutable }
-
-/** A modified copy-propagation like analysis. It
- * is augmented with a record-like value which is used
- * to represent closures.
- *
- * @author Iulian Dragos
- */
-abstract class CopyPropagation {
- val global: Global
- import global._
- import icodes._
-
- /** Locations can be local variables, this, and fields. */
- abstract sealed class Location
- case class LocalVar(l: Local) extends Location
- case class Field(r: Record, sym: Symbol) extends Location
- case object This extends Location
-
- /** Values that can be on the stack. */
- sealed abstract class Value { }
- case class Record(cls: Symbol, bindings: mutable.Map[Symbol, Value]) extends Value { }
- /** The value of some location in memory. */
- case class Deref(l: Location) extends Value
-
- /** The boxed value of some location. */
- case class Boxed(l: Location) extends Value
-
- /** The constant value c. */
- case class Const(c: Constant) extends Value
-
- /** Unknown. */
- case object Unknown extends Value
-
- /** The bottom record. */
- object AllRecords extends Record(NoSymbol, mutable.HashMap[Symbol, Value]())
-
- /** The lattice for this analysis. */
- object copyLattice extends SemiLattice {
- type Bindings = mutable.Map[Location, Value]
-
- def emptyBinding = mutable.HashMap[Location, Value]()
-
- class State(val bindings: Bindings, var stack: List[Value]) {
-
- override def hashCode = bindings.hashCode + stack.hashCode
- /* comparison with bottom is reference equality! */
- override def equals(that: Any): Boolean = that match {
- case x: State =>
- if ((this eq bottom) || (this eq top) || (x eq bottom) || (x eq top)) this eq x
- else bindings == x.bindings && stack == x.stack
- case _ =>
- false
- }
-
- /* Return an alias for the given local. It returns the last
- * local in the chain of aliased locals. Cycles are not allowed
- * to exist (by construction).
- */
- def getAlias(l: Local): Local = {
- var target = l
- var stop = false
-
- while (bindings.isDefinedAt(LocalVar(target)) && !stop) {
- bindings(LocalVar(target)) match {
- case Deref(LocalVar(t)) => target = t
- case _ => stop = true
- }
- }
- target
- }
-
- /* Return the value bound to the given local. */
- def getBinding(l: Local): Value = {
- def loop(lv: Local): Option[Value] = (bindings get LocalVar(lv)) match {
- case Some(Deref(LocalVar(t))) => loop(t)
- case x => x
- }
- loop(l) getOrElse Deref(LocalVar(l))
- }
-
- /** Return a local which contains the same value as this field, if any.
- * If the field holds a reference to a local, the returned value is the
- * binding of that local.
- */
- def getFieldValue(r: Record, f: Symbol): Option[Value] = r.bindings get f map {
- case Deref(LocalVar(l)) => getBinding(l)
- case target @ Deref(Field(r1, f1)) => getFieldValue(r1, f1) getOrElse target
- case target => target
- }
-
- /** The same as getFieldValue, but never returns Record/Field values. Use
- * this when you want to find a replacement for a field value (either a local,
- * or a constant/this value).
- */
- def getFieldNonRecordValue(r: Record, f: Symbol): Option[Value] = {
- assert(r.bindings contains f, "Record " + r + " does not contain a field " + f)
-
- r.bindings(f) match {
- case Deref(LocalVar(l)) =>
- val alias = getAlias(l)
- val derefAlias = Deref(LocalVar(alias))
-
- Some(getBinding(alias) match {
- case Record(_, _) => derefAlias
- case Deref(Field(r1, f1)) => getFieldNonRecordValue(r1, f1) getOrElse derefAlias
- case Boxed(_) => derefAlias
- case v => v
- })
- case Deref(Field(r1, f1)) => getFieldNonRecordValue(r1, f1)
- case target @ Deref(This) => Some(target)
- case target @ Const(k) => Some(target)
- case _ => None
- }
- }
-
- override def toString(): String =
- "\nBindings: " + bindings + "\nStack: " + stack
-
- def dup: State = {
- val b: Bindings = mutable.HashMap()
- b ++= bindings
- new State(b, stack)
- }
- }
-
- type Elem = State
-
- val top = new State(emptyBinding, Nil)
- val bottom = new State(emptyBinding, Nil)
-
- val exceptionHandlerStack = Unknown :: Nil
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem = {
- if (a eq bottom) b
- else if (b eq bottom) a
- else if (a == b) a
- else {
- //assert(!(a.stack eq exceptionHandlerStack) && !(b.stack eq exceptionHandlerStack))
- val resStack =
- if (exceptional) exceptionHandlerStack
- else {
-// if (a.stack.length != b.stack.length)
-// throw new LubException(a, b, "Invalid stacks in states: ");
- (a.stack, b.stack).zipped map { (v1, v2) =>
- if (v1 == v2) v1 else Unknown
- }
- }
-
-/* if (a.stack.length != b.stack.length)
- throw new LubException(a, b, "Invalid stacks in states: ");
- val resStack = List.map2(a.stack, b.stack) { (v1, v2) =>
- if (v1 == v2) v1 else Unknown
- }
- */
- val resBindings = mutable.HashMap[Location, Value]()
-
- for ((k, v) <- a.bindings if b.bindings.isDefinedAt(k) && v == b.bindings(k))
- resBindings += (k -> v)
- new State(resBindings, resStack)
- }
- }
- }
-
- final class CopyAnalysis extends DataFlowAnalysis[copyLattice.type] {
- type P = BasicBlock
- val lattice = copyLattice
-
- var method: IMethod = _
-
- def init(m: IMethod) {
- this.method = m
-
- init {
- worklist += m.startBlock
- worklist ++= (m.exh map (_.startBlock))
- m foreachBlock { b =>
- in(b) = lattice.bottom
- out(b) = lattice.bottom
- assert(out.contains(b), out)
- debuglog("CopyAnalysis added point: " + b)
- }
- m.exh foreach { e =>
- in(e.startBlock) = new copyLattice.State(copyLattice.emptyBinding, copyLattice.exceptionHandlerStack)
- }
-
- // first block is special: it's not bottom, but a precisely defined state with no bindings
- in(m.startBlock) = new lattice.State(lattice.emptyBinding, Nil)
- }
- }
-
- override def run() {
- forwardAnalysis(blockTransfer)
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(in(b) != lattice.bottom,
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited?"))
- }
- }
-
- def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem =
- b.iterator.foldLeft(in)(interpret)
-
- import opcodes._
-
- private def retain[A, B](map: mutable.Map[A, B])(p: (A, B) => Boolean) = {
- for ((k, v) <- map ; if !p(k, v)) map -= k
- map
- }
-
- /** Abstract interpretation for one instruction. */
- def interpret(in: copyLattice.Elem, i: Instruction): copyLattice.Elem = {
- var out = in.dup
- debuglog("- " + i + "\nin: " + in + "\n")
-
- i match {
- case THIS(_) =>
- out.stack = Deref(This) :: out.stack
-
- case CONSTANT(k) =>
- if (k.tag != UnitTag)
- out.stack = Const(k) :: out.stack
-
- case LOAD_ARRAY_ITEM(_) =>
- out.stack = (Unknown :: out.stack.drop(2))
-
- case LOAD_LOCAL(local) =>
- out.stack = Deref(LocalVar(local)) :: out.stack
-
- case LOAD_FIELD(field, isStatic) =>
- if (isStatic)
- out.stack = Unknown :: out.stack; /* ignore static fields */
- else {
- val v1 = in.stack match {
- case (r @ Record(cls, bindings)) :: xs =>
- Deref(Field(r, field))
-
- case Deref(LocalVar(l)) :: _ =>
- in.getBinding(l) match {
- case r @ Record(cls, bindings) => Deref(Field(r, field))
- case _ => Unknown
- }
-
- case Deref(Field(r, f)) :: _ =>
- val fld = in.getFieldValue(r, f)
- fld match {
- case Some(r @ Record(cls, bindings)) if bindings.isDefinedAt(f) =>
- in.getFieldValue(r, f).getOrElse(Unknown)
- case _ => Unknown
- }
-
- case _ => Unknown
- }
- out.stack = v1 :: out.stack.drop(1)
- }
-
- case LOAD_MODULE(module) =>
- out.stack = Unknown :: out.stack
-
- case STORE_ARRAY_ITEM(kind) =>
- out.stack = out.stack.drop(3)
-
- case STORE_LOCAL(local) =>
- cleanReferencesTo(out, LocalVar(local))
- in.stack match {
- case Unknown :: xs => ()
- case v :: vs =>
- v match {
- case Deref(LocalVar(other)) =>
- if (other != local)
- out.bindings += (LocalVar(local) -> v)
- case _ =>
- out.bindings += (LocalVar(local) -> v)
- }
- case Nil =>
- sys.error("Incorrect icode in " + method + ". Expecting something on the stack.")
- }
- out.stack = out.stack drop 1
-
- case STORE_THIS(_) =>
- cleanReferencesTo(out, This)
- out.stack = out.stack drop 1
-
- case STORE_FIELD(field, isStatic) =>
- if (isStatic)
- out.stack = out.stack.drop(1)
- else {
- out.stack = out.stack.drop(2)
- cleanReferencesTo(out, Field(AllRecords, field))
- in.stack match {
- case v :: Record(_, bindings) :: vs =>
- bindings += (field -> v)
- case _ => ()
- }
- }
-
- case CALL_PRIMITIVE(primitive) =>
- // TODO: model primitives
- out.stack = Unknown :: out.stack.drop(i.consumed)
-
- case CALL_METHOD(method, style) => style match {
- case Dynamic =>
- out = simulateCall(in, method, static = false)
-
- case Static(onInstance) =>
- if (onInstance) {
- val obj = out.stack.drop(method.info.paramTypes.length).head
-// if (method.isPrimaryConstructor) {
- if (method.isPrimaryConstructor) {
- obj match {
- case Record(_, bindings) =>
- for (v <- out.stack.take(method.info.paramTypes.length + 1)
- if v ne obj) {
- bindings ++= getBindingsForPrimaryCtor(in, method)
- }
- case _ => ()
- }
- // put the Record back on the stack and remove the 'returned' value
- out.stack = out.stack.drop(1 + method.info.paramTypes.length)
- } else
- out = simulateCall(in, method, static = false)
- } else
- out = simulateCall(in, method, static = true)
-
- case SuperCall(_) =>
- out = simulateCall(in, method, static = false)
- }
-
- case BOX(tpe) =>
- val top = out.stack.head match {
- case Deref(loc) => Boxed(loc)
- case _ => Unknown
- }
- out.stack = top :: out.stack.tail
-
- case UNBOX(tpe) =>
- val top = out.stack.head
- top match {
- case Boxed(loc) => Deref(loc) :: out.stack.tail
- case _ => out.stack = Unknown :: out.stack.drop(1)
- }
-
- case NEW(kind) =>
- val v1 = kind match {
- case REFERENCE(cls) => Record(cls, mutable.HashMap[Symbol, Value]())
- case _ => Unknown
- }
- out.stack = v1 :: out.stack
-
- case CREATE_ARRAY(elem, dims) =>
- out.stack = Unknown :: out.stack.drop(dims)
-
- case IS_INSTANCE(tpe) =>
- out.stack = Unknown :: out.stack.drop(1)
-
- case CHECK_CAST(tpe) =>
- out.stack = Unknown :: out.stack.drop(1)
-
- case SWITCH(tags, labels) =>
- out.stack = out.stack.drop(1)
-
- case JUMP(whereto) =>
- ()
-
- case CJUMP(success, failure, cond, kind) =>
- out.stack = out.stack.drop(2)
-
- case CZJUMP(success, failure, cond, kind) =>
- out.stack = out.stack.drop(1)
-
- case RETURN(kind) =>
- if (kind != UNIT)
- out.stack = out.stack.drop(1)
-
- case THROW(_) =>
- out.stack = out.stack.drop(1)
-
- case DROP(kind) =>
- out.stack = out.stack.drop(1)
-
- case DUP(kind) =>
- out.stack = out.stack.head :: out.stack
-
- case MONITOR_ENTER() =>
- out.stack = out.stack.drop(1)
-
- case MONITOR_EXIT() =>
- out.stack = out.stack.drop(1)
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) =>
- ()
-
- case LOAD_EXCEPTION(_) =>
- out.stack = Unknown :: Nil
-
- case _ =>
- dumpClassesAndAbort("Unknown instruction: " + i)
- }
- out
- } /* def interpret */
-
- /** Remove all references to this local variable from both stack
- * and bindings. It is called when a new assignment destroys
- * previous copy-relations.
- */
- final def cleanReferencesTo(s: copyLattice.State, target: Location) {
- def cleanRecord(r: Record): Record = {
- retain(r.bindings) { (loc, value) =>
- (value match {
- case Deref(loc1) if (loc1 == target) => false
- case Boxed(loc1) if (loc1 == target) => false
- case _ => true
- }) && (target match {
- case Field(AllRecords, sym1) => !(loc == sym1)
- case _ => true
- })
- }
- r
- }
-
- s.stack = s.stack map { v => v match {
- case Record(_, bindings) =>
- cleanRecord(v.asInstanceOf[Record])
- case Boxed(loc1) if (loc1 == target) => Unknown
- case _ => v
- }}
-
- retain(s.bindings) { (loc, value) =>
- (value match {
- case Deref(loc1) if (loc1 == target) => false
- case Boxed(loc1) if (loc1 == target) => false
- case rec @ Record(_, _) =>
- cleanRecord(rec)
- true
- case _ => true
- }) &&
- (loc match {
- case l: Location if (l == target) => false
- case _ => true
- })
- }
- }
-
- /** Update the state `s` after the call to `method`.
- * The stack elements are dropped and replaced by the result of the call.
- * If the method is impure, all bindings to record fields are cleared.
- */
- final def simulateCall(state: copyLattice.State, method: Symbol, static: Boolean): copyLattice.State = {
- val out = new copyLattice.State(state.bindings, state.stack)
- out.stack = out.stack.drop(method.info.paramTypes.length + (if (static) 0 else 1))
- if (method.info.resultType != definitions.UnitTpe && !method.isConstructor)
- out.stack = Unknown :: out.stack
- if (!isPureMethod(method))
- invalidateRecords(out)
- out
- }
-
- /** Drop everything known about mutable record fields.
- *
- * A simple escape analysis would help here. Some of the records we
- * track never leak to other methods, therefore they can not be changed.
- * We should not drop their bindings in this case. A closure object
- * would be such an example. Some complications:
- *
- * - outer pointers. An closure escapes as an outer pointer to another
- * nested closure.
- */
- final def invalidateRecords(state: copyLattice.State) {
- def shouldRetain(sym: Symbol): Boolean = {
- if (sym.isMutable)
- log("dropping binding for " + sym.fullName)
- !sym.isMutable
- }
- state.stack = state.stack map { v => v match {
- case Record(cls, bindings) =>
- retain(bindings) { (sym, _) => shouldRetain(sym) }
- Record(cls, bindings)
- case _ => v
- }}
-
- retain(state.bindings) { (loc, value) =>
- value match {
- case Deref(Field(rec, sym)) => shouldRetain(sym)
- case Boxed(Field(rec, sym)) => shouldRetain(sym)
- case _ => true
- }
- }
- }
-
- /** Return bindings from an object fields to the values on the stack. This
- * method has to find the correct mapping from fields to the order in which
- * they are passed on the stack. It works for primary constructors.
- */
- private def getBindingsForPrimaryCtor(in: copyLattice.State, ctor: Symbol): mutable.Map[Symbol, Value] = {
- val paramAccessors = ctor.owner.constrParamAccessors
- var values = in.stack.take(1 + ctor.info.paramTypes.length).reverse.drop(1)
- val bindings = mutable.HashMap[Symbol, Value]()
-
- debuglog("getBindings for: " + ctor + " acc: " + paramAccessors)
-
- var paramTypes = ctor.tpe.paramTypes
- val diff = paramTypes.length - paramAccessors.length
- diff match {
- case 0 => ()
- case 1 if ctor.tpe.paramTypes.head == ctor.owner.rawowner.tpe =>
- // it's an unused outer
- debuglog("considering unused outer at position 0 in " + ctor.tpe.paramTypes)
- paramTypes = paramTypes.tail
- values = values.tail
- case _ =>
- debuglog("giving up on " + ctor + "(diff: " + diff + ")")
- return bindings
- }
-
- // this relies on having the same order in paramAccessors and
- // the arguments on the stack. It should be the same!
- for ((p, i) <- paramAccessors.zipWithIndex) {
-// assert(p.tpe == paramTypes(i), "In: " + ctor.fullName
-// + " having acc: " + (paramAccessors map (_.tpe))+ " vs. params" + paramTypes
-// + "\n\t failed at pos " + i + " with " + p.tpe + " == " + paramTypes(i))
- if (p.tpe == paramTypes(i))
- bindings += (p -> values.head)
- values = values.tail
- }
-
- debuglog("\t" + bindings)
- bindings
- }
-
- /** Is symbol `m` a pure method?
- */
- final def isPureMethod(m: Symbol): Boolean =
- m.isGetter // abstract getters are still pure, as we 'know'
-
- final override def toString() = (
- if (method eq null) List("<null>")
- else method.blocks map { b =>
- "\nIN(%s):\t Bindings: %s".format(b.label, in(b).bindings) +
- "\nIN(%s):\t Stack: %s".format(b.label, in(b).stack)
- }
- ).mkString
-
- } /* class CopyAnalysis */
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala
deleted file mode 100644
index a378998f8f..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/DataFlowAnalysis.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend.icode.analysis
-
-import scala.collection.{ mutable, immutable }
-
-/** A generic framework for data flow analysis.
- */
-trait DataFlowAnalysis[L <: SemiLattice] {
- /** A type for program points. */
- type P <: ProgramPoint[P]
- val lattice: L
-
- val worklist: mutable.Set[P] = new mutable.LinkedHashSet
- val in: mutable.Map[P, lattice.Elem] = new mutable.HashMap
- val out: mutable.Map[P, lattice.Elem] = new mutable.HashMap
- val visited: mutable.HashSet[P] = new mutable.HashSet
-
- /** collect statistics? */
- var stat = true
-
- /** the number of times we iterated before reaching a fixpoint. */
- var iterations = 0
-
- /* Implement this function to initialize the worklist. */
- def init(f: => Unit): Unit = {
- iterations = 0
- in.clear(); out.clear(); worklist.clear(); visited.clear()
- f
- }
-
- def run(): Unit
-
- /** Implements forward dataflow analysis: the transfer function is
- * applied when inputs to a Program point change, to obtain the new
- * output value.
- *
- * @param f the transfer function.
- */
- def forwardAnalysis(f: (P, lattice.Elem) => lattice.Elem): Unit = try {
- while (!worklist.isEmpty) {
- if (stat) iterations += 1
- //Console.println("worklist in: " + worklist);
- val point = worklist.iterator.next(); worklist -= point; visited += point
- //Console.println("taking out point: " + point + " worklist out: " + worklist);
- val output = f(point, in(point))
-
- if ((lattice.bottom == out(point)) || output != out(point)) {
- // Console.println("Output changed at " + point
- // + " from: " + out(point) + " to: " + output
- // + " for input: " + in(point) + " and they are different: " + (output != out(point)))
- out(point) = output
- val succs = point.successors
- succs foreach { p =>
- val updated = lattice.lub(in(p) :: (p.predecessors map out.apply), p.exceptionHandlerStart)
- if(updated != in(p)) {
- in(p) = updated
- if (!worklist(p)) { worklist += p; }
- }
- }
- }
- }
- } catch {
- case e: NoSuchElementException =>
- Console.println("in: " + in.mkString("", "\n", ""))
- Console.println("out: " + out.mkString("", "\n", ""))
- e.printStackTrace
- sys.error("Could not find element " + e.getMessage)
- }
-
- def backwardAnalysis(f: (P, lattice.Elem) => lattice.Elem): Unit =
- while (worklist.nonEmpty) {
- if (stat) iterations += 1
- val point = worklist.head
- worklist -= point
-
- out(point) = lattice.lub(point.successors map in.apply, exceptional = false) // TODO check for exception handlers
- val input = f(point, out(point))
-
- if ((lattice.bottom == in(point)) || input != in(point)) {
- in(point) = input
- worklist ++= point.predecessors
- }
- }
-
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala
deleted file mode 100644
index 939641c3eb..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/Liveness.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode
-package analysis
-
-import scala.collection.{ mutable, immutable }
-import immutable.ListSet
-
-/**
- * Compute liveness information for local variables.
- *
- * @author Iulian Dragos
- */
-abstract class Liveness {
- val global: Global
- import global._
- import icodes._
-
- /** The lattice for this analysis. */
- object livenessLattice extends SemiLattice {
- type Elem = Set[Local]
-
- object top extends ListSet[Local] with ReferenceEquality
- object bottom extends ListSet[Local] with ReferenceEquality
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem = a ++ b
- }
-
- final class LivenessAnalysis extends DataFlowAnalysis[livenessLattice.type] {
- type P = BasicBlock
- val lattice = livenessLattice
- var method: IMethod = _
- val gen: mutable.Map[BasicBlock, Set[Local]] = perRunCaches.newMap()
- val kill: mutable.Map[BasicBlock, Set[Local]] = perRunCaches.newMap()
-
- def init(m: IMethod) {
- this.method = m
- gen.clear()
- kill.clear()
-
- m foreachBlock { b =>
- val (g, k) = genAndKill(b)
- gen += (b -> g)
- kill += (b -> k)
- }
-
- init {
- m foreachBlock { b =>
- worklist += b
- in(b) = lattice.bottom
- out(b) = lattice.bottom
- }
- }
- }
-
- import opcodes._
-
- /** Return the gen and kill sets for this block. */
- def genAndKill(b: BasicBlock): (Set[Local], Set[Local]) = {
- var genSet = new ListSet[Local]
- var killSet = new ListSet[Local]
- for (i <- b) i match {
- case LOAD_LOCAL(local) if (!killSet(local)) => genSet = genSet + local
- case STORE_LOCAL(local) if (!genSet(local)) => killSet = killSet + local
- case _ => ()
- }
- (genSet, killSet)
- }
-
- override def run() {
- backwardAnalysis(blockTransfer)
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(lattice.bottom != in(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited?"))
- }
- }
-
- def blockTransfer(b: BasicBlock, out: lattice.Elem): lattice.Elem =
- gen(b) ++ (out -- kill(b))
-
- /** Abstract interpretation for one instruction. Very important:
- * liveness is a backward DFA, so this method should be used to compute
- * liveness *before* the given instruction `i`.
- */
- def interpret(out: lattice.Elem, i: Instruction): lattice.Elem = {
- debuglog("- " + i + "\nout: " + out + "\n")
- i match {
- case LOAD_LOCAL(l) => out + l
- case STORE_LOCAL(l) => out - l
- case _ => out
- }
- }
- override def toString() =
- (method.blocks map (b => "\nlive-in(%s)=%s\nlive-out(%s)=%s".format(b, in(b), b, out(b)))).mkString
- } /* Liveness analysis */
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala
deleted file mode 100644
index e91bf7a044..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/LubException.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode.analysis
-
-class LubException(a: Any, b: Any, msg: String) extends Exception {
- override def toString() = "Lub error: " + msg + a + b
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala
deleted file mode 100644
index 4e4026f526..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/ProgramPoint.scala
+++ /dev/null
@@ -1,18 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode.analysis
-
-/** Program points are locations in the program where we want to
- * assert certain properties through data flow analysis, e.g.
- * basic blocks.
- */
-trait ProgramPoint[a <: ProgramPoint[a]] {
- def predecessors: List[a]
- def successors: List[a]
- def exceptionHandlerStart: Boolean
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala
deleted file mode 100644
index fecd48ed27..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/ReachingDefinitions.scala
+++ /dev/null
@@ -1,250 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala.tools.nsc
-package backend.icode
-package analysis
-
-import scala.collection.{ mutable, immutable }
-import immutable.ListSet
-
-/** Compute reaching definitions. We are only interested in reaching
- * definitions for local variables, since values on the stack
- * behave as-if in SSA form: the closest instruction which produces a value
- * on the stack is a reaching definition.
- */
-abstract class ReachingDefinitions {
- val global: Global
- import global._
- import icodes._
-
- /** The lattice for reaching definitions. Elements are
- * a triple (local variable, basic block, index of instruction of that basic block)
- */
- object rdefLattice extends SemiLattice {
- type Definition = (Local, BasicBlock, Int)
- type Elem = IState[ListSet[Definition], Stack]
- type StackPos = ListSet[(BasicBlock, Int)]
- type Stack = List[StackPos]
-
- private def referenceEqualSet(name: String) = new ListSet[Definition] with ReferenceEquality {
- override def toString = "<" + name + ">"
- }
-
- val top: Elem = IState(referenceEqualSet("top"), Nil)
- val bottom: Elem = IState(referenceEqualSet("bottom"), Nil)
-
- /** The least upper bound is set inclusion for locals, and pairwise set inclusion for stacks. */
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem = {
- if (bottom == a) b
- else if (bottom == b) a
- else IState(a.vars ++ b.vars,
- if (a.stack.isEmpty) b.stack
- else if (b.stack.isEmpty) a.stack
- else {
- // !!! These stacks are with some frequency not of the same size.
- // I can't reverse engineer the logic well enough to say whether this
- // indicates a problem. Even if it doesn't indicate a problem,
- // it'd be nice not to call zip with mismatched sequences because
- // it makes it harder to spot the real problems.
- val result = (a.stack, b.stack).zipped map (_ ++ _)
- if (settings.debug && (a.stack.length != b.stack.length))
- devWarning(s"Mismatched stacks in ReachingDefinitions#lub2: ${a.stack}, ${b.stack}, returning $result")
- result
- }
- )
- }
- }
-
- class ReachingDefinitionsAnalysis extends DataFlowAnalysis[rdefLattice.type] {
- type P = BasicBlock
- val lattice = rdefLattice
- import lattice.{ Definition, Stack, Elem, StackPos }
- var method: IMethod = _
-
- val gen = mutable.Map[BasicBlock, ListSet[Definition]]()
- val kill = mutable.Map[BasicBlock, ListSet[Local]]()
- val drops = mutable.Map[BasicBlock, Int]()
- val outStack = mutable.Map[BasicBlock, Stack]()
-
- def init(m: IMethod) {
- this.method = m
-
- gen.clear()
- kill.clear()
- drops.clear()
- outStack.clear()
-
- m foreachBlock { b =>
- val (g, k) = genAndKill(b)
- val (d, st) = dropsAndGen(b)
-
- gen += (b -> g)
- kill += (b -> k)
- drops += (b -> d)
- outStack += (b -> st)
- }
-
- init {
- m foreachBlock { b =>
- worklist += b
- in(b) = lattice.bottom
- out(b) = lattice.bottom
- }
- m.exh foreach { e =>
- in(e.startBlock) = lattice.IState(new ListSet[Definition], List(new StackPos))
- }
- }
- }
-
- import opcodes._
-
- def genAndKill(b: BasicBlock): (ListSet[Definition], ListSet[Local]) = {
- var genSet = ListSet[Definition]()
- var killSet = ListSet[Local]()
- for ((STORE_LOCAL(local), idx) <- b.toList.zipWithIndex) {
- killSet = killSet + local
- genSet = updateReachingDefinition(b, idx, genSet)
- }
- (genSet, killSet)
- }
-
- private def dropsAndGen(b: BasicBlock): (Int, Stack) = {
- var depth, drops = 0
- var stackOut: Stack = Nil
-
- for ((instr, idx) <- b.toList.zipWithIndex) {
- instr match {
- case LOAD_EXCEPTION(_) => ()
- case _ if instr.consumed > depth =>
- drops += (instr.consumed - depth)
- depth = 0
- stackOut = Nil
- case _ =>
- stackOut = stackOut.drop(instr.consumed)
- depth -= instr.consumed
- }
- var prod = instr.produced
- depth += prod
- while (prod > 0) {
- stackOut ::= ListSet((b, idx))
- prod -= 1
- }
- }
-// Console.println("drops(" + b + ") = " + drops)
-// Console.println("stackout(" + b + ") = " + stackOut)
- (drops, stackOut)
- }
-
- override def run() {
- forwardAnalysis(blockTransfer)
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(lattice.bottom != in(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited? " + in(b)
- + ": bot: " + lattice.bottom
- + "\nin(b) == bottom: " + (in(b) == lattice.bottom)
- + "\nbottom == in(b): " + (lattice.bottom == in(b))))
- }
- }
-
- import opcodes._
- import lattice.IState
- def updateReachingDefinition(b: BasicBlock, idx: Int, rd: ListSet[Definition]): ListSet[Definition] = {
- val STORE_LOCAL(local) = b(idx)
- val tmp = local
- (rd filter { case (l, _, _) => l != tmp }) + ((tmp, b, idx))
- }
-
- private def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem = {
- var locals: ListSet[Definition] = (in.vars filter { case (l, _, _) => !kill(b)(l) }) ++ gen(b)
- if (locals eq lattice.bottom.vars) locals = new ListSet[Definition]
- IState(locals, outStack(b) ::: in.stack.drop(drops(b)))
- }
-
- /** Return the reaching definitions corresponding to the point after idx. */
- def interpret(b: BasicBlock, idx: Int, in: lattice.Elem): Elem = {
- var locals = in.vars
- var stack = in.stack
- val instr = b(idx)
-
- instr match {
- case STORE_LOCAL(l1) =>
- locals = updateReachingDefinition(b, idx, locals)
- stack = stack.drop(instr.consumed)
- case LOAD_EXCEPTION(_) =>
- stack = Nil
- case _ =>
- stack = stack.drop(instr.consumed)
- }
-
- var prod = instr.produced
- while (prod > 0) {
- stack ::= ListSet((b, idx))
- prod -= 1
- }
-
- IState(locals, stack)
- }
-
- /** Return the instructions that produced the 'm' elements on the stack, below given 'depth'.
- * for instance, findefs(bb, idx, 1, 1) returns the instructions that might have produced the
- * value found below the topmost element of the stack.
- */
- def findDefs(bb: BasicBlock, idx: Int, m: Int, depth: Int): List[(BasicBlock, Int)] = if (idx > 0) {
- assert(bb.closed, bb)
-
- val instrs = bb.getArray
- var res: List[(BasicBlock, Int)] = Nil
- var i = idx
- var n = m
- var d = depth
- // "I look for who produced the 'n' elements below the 'd' topmost slots of the stack"
- while (n > 0 && i > 0) {
- i -= 1
- val prod = instrs(i).produced
- if (prod > d) {
- res = (bb, i) :: res
- n = n - (prod - d)
- instrs(i) match {
- case LOAD_EXCEPTION(_) => ()
- case _ => d = instrs(i).consumed
- }
- } else {
- d -= prod
- d += instrs(i).consumed
- }
- }
-
- if (n > 0) {
- val stack = this.in(bb).stack
- assert(stack.length >= n, "entry stack is too small, expected: " + n + " found: " + stack)
- stack.drop(d).take(n) foreach { defs =>
- res = defs.toList ::: res
- }
- }
- res
- } else {
- val stack = this.in(bb).stack
- assert(stack.length >= m, "entry stack is too small, expected: " + m + " found: " + stack)
- stack.drop(depth).take(m) flatMap (_.toList)
- }
-
- /** Return the definitions that produced the topmost 'm' elements on the stack,
- * and that reach the instruction at index 'idx' in basic block 'bb'.
- */
- def findDefs(bb: BasicBlock, idx: Int, m: Int): List[(BasicBlock, Int)] =
- findDefs(bb, idx, m, 0)
-
- override def toString: String = {
- if (method eq null) "<null>"
- else method.code.blocks map { b =>
- " entry(%s) = %s\n".format(b, in(b)) +
- " exit(%s) = %s\n".format(b, out(b))
- } mkString ("ReachingDefinitions {\n", "\n", "\n}")
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala
deleted file mode 100644
index f718c705c2..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/SemiLattice.scala
+++ /dev/null
@@ -1,49 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend.icode
-package analysis
-
-/** A complete lattice.
- */
-trait SemiLattice {
- type Elem <: AnyRef
-
- /** Hold together local variable and stack state. The
- * equals method uses reference equality for top and bottom,
- * and structural equality for other values.
- */
- final case class IState[V, S](vars: V, stack: S) {
- override def hashCode = vars.hashCode + stack.hashCode
- override def equals(other: Any): Boolean = other match {
- case x: IState[_, _] =>
- if ((this eq bottom) || (this eq top) || (x eq bottom) || (x eq top)) this eq x
- else stack == x.stack && vars == x.vars
- case _ =>
- false
- }
- private def tstring(x: Any): String = x match {
- case xs: TraversableOnce[_] => xs map tstring mkString " "
- case _ => "" + x
- }
- override def toString = "IState(" + tstring(vars) + ", " + tstring(stack) + ")"
- }
-
- /** Return the least upper bound of a and b. */
- def lub2(exceptional: Boolean)(a: Elem, b: Elem): Elem
-
- /** Return the top element. */
- def top: Elem
-
- /** Return the bottom element. */
- def bottom: Elem
-
- /** Compute the least upper bound of a list of elements. */
- def lub(xs: List[Elem], exceptional: Boolean): Elem =
- if (xs.isEmpty) bottom
- else try xs reduceLeft lub2(exceptional)
- catch { case e: LubException => Console.println("Lub on blocks: " + xs) ; throw e }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
deleted file mode 100644
index 64c9901a3e..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
+++ /dev/null
@@ -1,725 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend.icode.analysis
-
-import scala.collection.{mutable, immutable}
-import java.util.concurrent.TimeUnit
-
-/** A data-flow analysis on types, that works on `ICode`.
- *
- * @author Iulian Dragos
- */
-abstract class TypeFlowAnalysis {
- val global: Global
- import global._
- import definitions.{ ObjectClass, NothingClass, AnyRefClass, StringClass, ThrowableClass }
-
- /** The lattice of ICode types.
- */
- object typeLattice extends SemiLattice {
- type Elem = icodes.TypeKind
-
- val top = icodes.REFERENCE(ObjectClass)
- val bottom = icodes.REFERENCE(NothingClass)
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem) =
- if (a eq bottom) b
- else if (b eq bottom) a
- else icodes.lub(a, b)
- }
-
- /** The lattice of type stacks. It is a straight forward extension of
- * the type lattice (lub is pairwise lub of the list elements).
- */
- object typeStackLattice extends SemiLattice {
- import icodes._
- type Elem = TypeStack
-
- val top = new TypeStack
- val bottom = new TypeStack
- val exceptionHandlerStack = new TypeStack(List(REFERENCE(AnyRefClass)))
-
- def lub2(exceptional: Boolean)(s1: TypeStack, s2: TypeStack) = {
- if (s1 eq bottom) s2
- else if (s2 eq bottom) s1
- else if ((s1 eq exceptionHandlerStack) || (s2 eq exceptionHandlerStack)) sys.error("merging with exhan stack")
- else {
-// if (s1.length != s2.length)
-// throw new CheckerException("Incompatible stacks: " + s1 + " and " + s2);
- new TypeStack((s1.types, s2.types).zipped map icodes.lub)
- }
- }
- }
-
- /** A map which returns the bottom type for unfound elements */
- class VarBinding extends mutable.HashMap[icodes.Local, icodes.TypeKind] {
- override def default(l: icodes.Local) = typeLattice.bottom
-
- def this(o: VarBinding) = {
- this()
- this ++= o
- }
- }
-
- /** The type flow lattice contains a binding from local variable
- * names to types and a type stack.
- */
- object typeFlowLattice extends SemiLattice {
- type Elem = IState[VarBinding, icodes.TypeStack]
-
- val top = new Elem(new VarBinding, typeStackLattice.top)
- val bottom = new Elem(new VarBinding, typeStackLattice.bottom)
-
- def lub2(exceptional: Boolean)(a: Elem, b: Elem) = {
- val IState(env1, _) = a
- val IState(env2, _) = b
-
- val resultingLocals = new VarBinding
- env1 foreach { case (k, v) =>
- resultingLocals += ((k, typeLattice.lub2(exceptional)(v, env2(k))))
- }
- env2 collect { case (k, v) if resultingLocals(k) eq typeLattice.bottom =>
- resultingLocals += ((k, typeLattice.lub2(exceptional)(v, env1(k))))
- }
- val stack =
- if (exceptional) typeStackLattice.exceptionHandlerStack
- else typeStackLattice.lub2(exceptional)(a.stack, b.stack)
-
- IState(resultingLocals, stack)
- }
- }
-
- val timer = new Timer
-
- class MethodTFA extends DataFlowAnalysis[typeFlowLattice.type] {
- import icodes._
- import icodes.opcodes._
-
- type P = BasicBlock
- val lattice = typeFlowLattice
-
- val STRING = icodes.REFERENCE(StringClass)
- var method: IMethod = _
-
- /** Initialize the in/out maps for the analysis of the given method. */
- def init(m: icodes.IMethod) {
- this.method = m
- //typeFlowLattice.lubs = 0
- init {
- worklist += m.startBlock
- worklist ++= (m.exh map (_.startBlock))
- m foreachBlock { b =>
- in(b) = typeFlowLattice.bottom
- out(b) = typeFlowLattice.bottom
- }
-
- // start block has var bindings for each of its parameters
- val entryBindings = new VarBinding ++= (m.params map (p => ((p, p.kind))))
- in(m.startBlock) = lattice.IState(entryBindings, typeStackLattice.bottom)
-
- m.exh foreach { e =>
- in(e.startBlock) = lattice.IState(in(e.startBlock).vars, typeStackLattice.exceptionHandlerStack)
- }
- }
- }
-
- def this(m: icodes.IMethod) {
- this()
- init(m)
- }
-
- def run() = {
- timer.start()
- // icodes.lubs0 = 0
- forwardAnalysis(blockTransfer)
- timer.stop
- if (settings.debug) {
- linearizer.linearize(method).foreach(b => if (b != method.startBlock)
- assert(visited.contains(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited? .." + visited))
- }
- // log("" + method.symbol.fullName + " [" + method.code.blocks.size + " blocks] "
- // + "\n\t" + iterations + " iterations: " + t + " ms."
- // + "\n\tlubs: " + typeFlowLattice.lubs + " out of which " + icodes.lubs0 + " typer lubs")
- }
-
- def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem = {
- var result = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
- var instrs = b.toList
- while(!instrs.isEmpty) {
- val i = instrs.head
- result = mutatingInterpret(result, i)
- instrs = instrs.tail
- }
- result
- }
-
- /** Abstract interpretation for one instruction. */
- def interpret(in: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = {
- val out = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
- mutatingInterpret(out, i)
- }
-
- def mutatingInterpret(out: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = {
- val bindings = out.vars
- val stack = out.stack
-
- if (settings.debug) {
- // Console.println("[before] Stack: " + stack);
- // Console.println(i);
- }
- i match {
-
- case THIS(clasz) => stack push toTypeKind(clasz.tpe)
- case CONSTANT(const) => stack push toTypeKind(const.tpe)
-
- case LOAD_ARRAY_ITEM(kind) =>
- stack.pop2 match {
- case (idxKind, ARRAY(elem)) =>
- assert(idxKind == INT || idxKind == CHAR || idxKind == SHORT || idxKind == BYTE)
- stack.push(elem)
- case (_, _) =>
- stack.push(kind)
- }
-
- case LOAD_LOCAL(local) =>
- val t = bindings(local)
- stack push (if (t == typeLattice.bottom) local.kind else t)
-
- case LOAD_FIELD(field, isStatic) =>
- if (!isStatic) { stack.pop }
- stack push toTypeKind(field.tpe)
-
- case LOAD_MODULE(module) => stack push toTypeKind(module.tpe)
- case STORE_ARRAY_ITEM(kind) => stack.pop3
- case STORE_LOCAL(local) => val t = stack.pop; bindings += (local -> t)
- case STORE_THIS(_) => stack.pop
-
- case STORE_FIELD(field, isStatic) => if (isStatic) stack.pop else stack.pop2
-
- case CALL_PRIMITIVE(primitive) =>
- primitive match {
- case Negation(kind) => stack.pop; stack.push(kind)
-
- case Test(_, kind, zero) =>
- stack.pop
- if (!zero) { stack.pop }
- stack push BOOL
-
- case Comparison(_, _) => stack.pop2; stack push INT
-
- case Arithmetic(op, kind) =>
- stack.pop
- if (op != NOT) { stack.pop }
- val k = kind match {
- case BYTE | SHORT | CHAR => INT
- case _ => kind
- }
- stack push k
-
- case Logical(op, kind) => stack.pop2; stack push kind
- case Shift(op, kind) => stack.pop2; stack push kind
- case Conversion(src, dst) => stack.pop; stack push dst
- case ArrayLength(kind) => stack.pop; stack push INT
- case StartConcat => stack.push(ConcatClass)
- case EndConcat => stack.pop; stack.push(STRING)
- case StringConcat(el) => stack.pop2; stack push ConcatClass
- }
-
- case cm @ CALL_METHOD(_, _) =>
- stack pop cm.consumed
- cm.producedTypes foreach (stack push _)
-
- case BOX(kind) => stack.pop; stack.push(BOXED(kind))
- case UNBOX(kind) => stack.pop; stack.push(kind)
-
- case NEW(kind) => stack.push(kind)
-
- case CREATE_ARRAY(elem, dims) => stack.pop(dims); stack.push(ARRAY(elem))
-
- case IS_INSTANCE(tpe) => stack.pop; stack.push(BOOL)
- case CHECK_CAST(tpe) => stack.pop; stack.push(tpe)
-
- case _: SWITCH => stack.pop
- case _: JUMP => ()
- case _: CJUMP => stack.pop2
- case _: CZJUMP => stack.pop
-
- case RETURN(kind) => if (kind != UNIT) { stack.pop }
- case THROW(_) => stack.pop
-
- case DROP(kind) => stack.pop
- case DUP(kind) => stack.push(stack.head)
-
- case MONITOR_ENTER() | MONITOR_EXIT() => stack.pop
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) => ()
-
- case LOAD_EXCEPTION(clasz) =>
- stack.pop(stack.length)
- stack.push(toTypeKind(clasz.tpe))
-
- case _ =>
- dumpClassesAndAbort("Unknown instruction: " + i)
- }
- out
- } // interpret
-
- abstract class InferredType {
- /** Return the type kind pointed by this inferred type. */
- def getKind(in: lattice.Elem): icodes.TypeKind = this match {
- case Const(k) =>
- k
- case TypeOfVar(l: icodes.Local) =>
- if (in.vars.isDefinedAt(l)) in.vars(l) else l.kind
- case TypeOfStackPos(n: Int) =>
- assert(in.stack.length >= n)
- in.stack(n)
- }
- }
- /** A type that does not depend on input to the transfer function. */
- case class Const(t: icodes.TypeKind) extends InferredType
- /** The type of a given local variable. */
- case class TypeOfVar(l: icodes.Local) extends InferredType
- /** The type found at a stack position. */
- case class TypeOfStackPos(n: Int) extends InferredType
-
- abstract class Gen
- case class Bind(l: icodes.Local, t: InferredType) extends Gen
- case class Push(t: InferredType) extends Gen
-
- /** A flow transfer function of a basic block. */
- class TransferFunction(consumed: Int, gens: List[Gen]) extends (lattice.Elem => lattice.Elem) {
- def apply(in: lattice.Elem): lattice.Elem = {
- val out = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
- val stack = out.stack
-
- out.stack.pop(consumed)
- for (g <- gens) g match {
- case Bind(l, t) =>
- out.vars += (l -> t.getKind(in))
- case Push(t) =>
- stack.push(t.getKind(in))
- }
- out
- }
- }
- }
-
- case class CallsiteInfo(bb: icodes.BasicBlock, receiver: Symbol, stackLength: Int, concreteMethod: Symbol)
-
- /**
-
- A full type-flow analysis on a method computes in- and out-flows for each basic block (that's what MethodTFA does).
-
- For the purposes of Inliner, doing so guarantees that an abstract typestack-slot is available by the time an inlining candidate (a CALL_METHOD instruction) is visited.
- This subclass (MTFAGrowable) of MethodTFA also aims at performing such analysis on CALL_METHOD instructions, with some differences:
-
- (a) early screening is performed while the type-flow is being computed (in an override of `blockTransfer`) by testing a subset of the conditions that Inliner checks later.
- The reasoning here is: if the early check fails at some iteration, there's no chance a follow-up iteration (with a yet more lub-ed typestack-slot) will succeed.
- Failure is sufficient to remove that particular CALL_METHOD from the typeflow's `remainingCALLs`.
- A forward note: in case inlining occurs at some basic block B, all blocks reachable from B get their CALL_METHOD instructions considered again as candidates
- (because of the more precise types that -- perhaps -- can be computed).
-
- (b) in case the early check does not fail, no conclusive decision can be made, thus the CALL_METHOD stays `isOnwatchlist`.
-
- In other words, `remainingCALLs` tracks those callsites that still remain as candidates for inlining, so that Inliner can focus on those.
- `remainingCALLs` also caches info about the typestack just before the callsite, so as to spare computing them again at inlining time.
-
- Besides caching, a further optimization involves skipping those basic blocks whose in-flow and out-flow isn't needed anyway (as explained next).
- A basic block lacking a callsite in `remainingCALLs`, when visited by the standard algorithm, won't cause any inlining.
- But as we know from the way type-flows are computed, computing the in- and out-flow for a basic block relies in general on those of other basic blocks.
- In detail, we want to focus on that sub-graph of the CFG such that control flow may reach a remaining candidate callsite.
- Those basic blocks not in that subgraph can be skipped altogether. That's why:
- - `forwardAnalysis()` in `MTFAGrowable` now checks for inclusion of a basic block in `relevantBBs`
- - same check is performed before adding a block to the worklist, and as part of choosing successors.
- The bookkeeping supporting on-the-fly pruning of irrelevant blocks requires overriding most methods of the dataflow-analysis.
-
- The rest of the story takes place in Inliner, which does not visit all of the method's basic blocks but only on those represented in `remainingCALLs`.
-
- @author Miguel Garcia, http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
-
- */
- class MTFAGrowable extends MethodTFA {
-
- import icodes._
-
- val remainingCALLs = mutable.Map.empty[opcodes.CALL_METHOD, CallsiteInfo]
-
- val preCandidates = mutable.Set.empty[BasicBlock]
-
- var callerLin: Traversable[BasicBlock] = null
-
- override def run {
-
- timer.start()
- forwardAnalysis(blockTransfer)
- timer.stop
-
- /* Now that `forwardAnalysis(blockTransfer)` has finished, all inlining candidates can be found in `remainingCALLs`,
- whose keys are callsites and whose values are pieces of information about the typestack just before the callsite in question.
- In order to keep `analyzeMethod()` simple, we collect in `preCandidates` those basic blocks containing at least one candidate. */
- preCandidates.clear()
- for(rc <- remainingCALLs) {
- preCandidates += rc._2.bb
- }
-
- if (settings.debug) {
- for(b <- callerLin; if (b != method.startBlock) && preCandidates(b)) {
- assert(visited.contains(b),
- "Block " + b + " in " + this.method + " has input equal to bottom -- not visited? .." + visited)
- }
- }
-
- }
-
- var shrinkedWatchlist = false
-
- /*
- This is the method where information cached elsewhere is put to use. References are given those other places that populate those caches.
-
- The goal is avoiding computing type-flows for blocks we don't need (ie blocks not tracked in `relevantBBs`). The method used to add to `relevantBBs` is `putOnRadar`.
-
- Moreover, it's often the case that the last CALL_METHOD of interest ("of interest" equates to "being tracked in `isOnWatchlist`) isn't the last instruction on the block.
- There are cases where the typeflows computed past this `lastInstruction` are needed, and cases when they aren't.
- The reasoning behind this decision is described in `populatePerimeter()`. All `blockTransfer()` needs to do (in order to know at which instruction it can stop)
- is querying `isOnPerimeter`.
-
- Upon visiting a CALL_METHOD that's an inlining candidate, the relevant pieces of information about the pre-instruction typestack are collected for future use.
- That is, unless the candidacy test fails. The reasoning here is: if such early check fails at some iteration, there's no chance a follow-up iteration
- (with a yet more lub-ed typestack-slot) will succeed. In case of failure we can safely remove the CALL_METHOD from both `isOnWatchlist` and `remainingCALLs`.
-
- */
- override def blockTransfer(b: BasicBlock, in: lattice.Elem): lattice.Elem = {
- var result = lattice.IState(new VarBinding(in.vars), new TypeStack(in.stack))
-
- val stopAt = if(isOnPerimeter(b)) lastInstruction(b) else null
- var isPastLast = false
-
- var instrs = b.toList
- while(!isPastLast && !instrs.isEmpty) {
- val i = instrs.head
-
- if(isOnWatchlist(i)) {
- val cm = i.asInstanceOf[opcodes.CALL_METHOD]
- val msym = cm.method
- val paramsLength = msym.info.paramTypes.size
- val receiver = result.stack.types.drop(paramsLength).head match {
- case REFERENCE(s) => s
- case _ => NoSymbol // e.g. the scrutinee is BOX(s) or ARRAY
- }
- val concreteMethod = inliner.lookupImplFor(msym, receiver)
- val isCandidate = {
- ( inliner.isClosureClass(receiver) || concreteMethod.isEffectivelyFinalOrNotOverridden || receiver.isEffectivelyFinalOrNotOverridden ) &&
- !blackballed(concreteMethod)
- }
- if(isCandidate) {
- remainingCALLs(cm) = CallsiteInfo(b, receiver, result.stack.length, concreteMethod)
- } else {
- remainingCALLs.remove(cm)
- isOnWatchlist.remove(cm)
- shrinkedWatchlist = true
- }
- }
-
- isPastLast = (i eq stopAt)
-
- if(!isPastLast) {
- result = mutatingInterpret(result, i)
- instrs = instrs.tail
- }
- }
-
- result
- } // end of method blockTransfer
-
- val isOnWatchlist = mutable.Set.empty[Instruction]
-
- val warnIfInlineFails = mutable.Set.empty[opcodes.CALL_METHOD] // cache for a given IMethod (ie cleared on Inliner.analyzeMethod).
-
- /* Each time CallerCalleeInfo.isSafeToInline determines a concrete callee is unsafe to inline in the current caller,
- the fact is recorded in this TFA instance for the purpose of avoiding devoting processing to that callsite next time.
- The condition of "being unsafe to inline in the current caller" sticks across inlinings and TFA re-inits
- because it depends on the instructions of the callee, which stay unchanged during the course of `analyzeInc(caller)`
- (with the caveat of the side-effecting `makePublic` in `helperIsSafeToInline`).*/
- val knownUnsafe = mutable.Set.empty[Symbol]
- val knownSafe = mutable.Set.empty[Symbol]
- val knownNever = mutable.Set.empty[Symbol] // `knownNever` needs be cleared only at the very end of the inlining phase (unlike `knownUnsafe` and `knownSafe`)
- final def blackballed(msym: Symbol): Boolean = { knownUnsafe(msym) || knownNever(msym) }
-
- val relevantBBs = mutable.Set.empty[BasicBlock]
-
- /*
- * Rationale to prevent some methods from ever being inlined:
- *
- * (1) inlining getters and setters results in exposing a private field,
- * which may itself prevent inlining of the caller (at best) or
- * lead to situations like SI-5442 ("IllegalAccessError when mixing optimized and unoptimized bytecode")
- *
- * (2) only invocations having a receiver object are considered (ie no static-methods are ever inlined).
- * This is taken care of by checking `isDynamic` (ie virtual method dispatch) and `Static(true)` (ie calls to private members)
- */
- private def isPreCandidate(cm: opcodes.CALL_METHOD): Boolean = {
- val msym = cm.method
- val style = cm.style
-
- !blackballed(msym) &&
- !msym.isConstructor &&
- (!msym.isAccessor || inliner.isClosureClass(msym.owner)) &&
- (style.isDynamic || (style.hasInstance && style.isStatic))
- }
-
- override def init(m: icodes.IMethod) {
- super.init(m)
- remainingCALLs.clear()
- knownUnsafe.clear()
- knownSafe.clear()
- // initially populate the watchlist with all callsites standing a chance of being inlined
- isOnWatchlist.clear()
- relevantBBs.clear()
- warnIfInlineFails.clear()
- /* TODO Do we want to perform inlining in non-finally exception handlers?
- * Seems counterproductive (the larger the method the less likely it will be JITed.
- * It's not that putting on radar only `linearizer linearizeAt (m, m.startBlock)` makes for much shorter inlining times (a minor speedup nonetheless)
- * but the effect on method size could be explored. */
- putOnRadar(m.linearizedBlocks(linearizer))
- populatePerimeter()
- // usually but not always true (counterexample in SI-6015) `(relevantBBs.isEmpty || relevantBBs.contains(m.startBlock))`
- }
-
- def conclusives(b: BasicBlock): List[opcodes.CALL_METHOD] = {
- knownBeforehand(b) filter { cm => inliner.isMonadicMethod(cm.method) || inliner.hasInline(cm.method) }
- }
-
- def knownBeforehand(b: BasicBlock): List[opcodes.CALL_METHOD] = {
- b.toList collect { case c : opcodes.CALL_METHOD => c } filter { cm => isPreCandidate(cm) && isReceiverKnown(cm) }
- }
-
- private def isReceiverKnown(cm: opcodes.CALL_METHOD): Boolean = {
- cm.method.isEffectivelyFinalOrNotOverridden && cm.method.owner.isEffectivelyFinalOrNotOverridden
- }
-
- private def putOnRadar(blocks: Traversable[BasicBlock]) {
- for(bb <- blocks) {
- val calls = bb.toList collect { case cm : opcodes.CALL_METHOD => cm }
- for(c <- calls; if(inliner.hasInline(c.method))) {
- warnIfInlineFails += c
- }
- val preCands = calls filter isPreCandidate
- isOnWatchlist ++= preCands
- }
- relevantBBs ++= blocks
- }
-
- /* those BBs in the argument are also included in the result */
- private def transitivePreds(starters: Traversable[BasicBlock]): Set[BasicBlock] = {
- val result = mutable.Set.empty[BasicBlock]
- var toVisit: List[BasicBlock] = starters.toList.distinct
- while(toVisit.nonEmpty) {
- val h = toVisit.head
- toVisit = toVisit.tail
- result += h
- for(p <- h.predecessors; if !result(p) && !toVisit.contains(p)) { toVisit = p :: toVisit }
- }
- result.toSet
- }
-
- /* A basic block B is "on the perimeter" of the current control-flow subgraph if none of its successors belongs to that subgraph.
- * In that case, for the purposes of inlining, we're interested in the typestack right before the last inline candidate in B, not in those afterwards.
- * In particular we can do without computing the outflow at B. */
- private def populatePerimeter() {
- isOnPerimeter.clear()
- var done = true
- do {
- val (frontier, toPrune) = (relevantBBs filter hasNoRelevantSuccs) partition isWatching
- isOnPerimeter ++= frontier
- relevantBBs --= toPrune
- done = toPrune.isEmpty
- } while(!done)
-
- lastInstruction.clear()
- for (b <- isOnPerimeter; lastIns = b.toList.reverse find isOnWatchlist) {
- lastInstruction += (b -> lastIns.get.asInstanceOf[opcodes.CALL_METHOD])
- }
-
- // assertion: "no relevant block can have a predecessor that is on perimeter"
- assert((for (b <- relevantBBs; if transitivePreds(b.predecessors) exists isOnPerimeter) yield b).isEmpty)
- }
-
- private val isOnPerimeter = mutable.Set.empty[BasicBlock]
- private val lastInstruction = mutable.Map.empty[BasicBlock, opcodes.CALL_METHOD]
-
- def hasNoRelevantSuccs(x: BasicBlock): Boolean = { !(x.successors exists relevantBBs) }
-
- def isWatching(x: BasicBlock): Boolean = (x.toList exists isOnWatchlist)
-
-
-
-
- /**
-
- This method is invoked after one or more inlinings have been performed in basic blocks whose in-flow is non-bottom (this makes a difference later).
- What we know about those inlinings is given by:
-
- - `staleOut`: These are the blocks where a callsite was inlined.
- For each callsite, all instructions in that block before the callsite were left in the block, and the rest moved to an `afterBlock`.
- The out-flow of these basic blocks is thus in general stale, that's why we'll add them to the TFA worklist.
-
- - `inlined` : These blocks were spliced into the method's CFG as part of inlining. Being new blocks, they haven't been visited yet by the typeflow analysis.
-
- - `staleIn` : These blocks are what `doInline()` calls `afterBlock`s, ie the new home for instructions that previously appeared
- after a callsite in a `staleOut` block.
-
- Based on the above information, we have to bring up-to-date the caches that `forwardAnalysis` and `blockTransfer` use to skip blocks and instructions.
- Those caches are `relevantBBs` and `isOnPerimeter` (for blocks) and `isOnWatchlist` and `lastInstruction` (for CALL_METHODs).
- Please notice that all `inlined` and `staleIn` blocks are reachable from `staleOut` blocks.
-
- The update takes place in two steps:
-
- (1) `staleOut foreach { so => putOnRadar(linearizer linearizeAt (m, so)) }`
- This results in initial populations for `relevantBBs` and `isOnWatchlist`.
- Because of the way `isPreCandidate` reuses previous decision-outcomes that are still valid,
- this already prunes some candidates standing no chance of being inlined.
-
- (2) `populatePerimeter()`
- Based on the CFG-subgraph determined in (1) as reflected in `relevantBBs`,
- this method detects some blocks whose typeflows aren't needed past a certain CALL_METHOD
- (not needed because none of its successors is relevant for the purposes of inlining, see `hasNoRelevantSuccs`).
- The blocks thus chosen are said to be "on the perimeter" of the CFG-subgraph.
- For each of them, its `lastInstruction` (after which no more typeflows are needed) is found.
-
- */
- def reinit(m: icodes.IMethod, staleOut: List[BasicBlock], inlined: scala.collection.Set[BasicBlock], staleIn: scala.collection.Set[BasicBlock]) {
- if (this.method == null || this.method.symbol != m.symbol) {
- init(m)
- return
- } else if(staleOut.isEmpty && inlined.isEmpty && staleIn.isEmpty) {
- // this promotes invoking reinit if in doubt, no performance degradation will ensue!
- return
- }
-
- worklist.clear() // calling reinit(f: => Unit) would also clear visited, thus forgetting about blocks visited before reinit.
-
- // asserts conveying an idea what CFG shapes arrive here:
- // staleIn foreach (p => assert( !in.isDefinedAt(p), p))
- // staleIn foreach (p => assert(!out.isDefinedAt(p), p))
- // inlined foreach (p => assert( !in.isDefinedAt(p), p))
- // inlined foreach (p => assert(!out.isDefinedAt(p), p))
- // inlined foreach (p => assert(!p.successors.isEmpty || p.lastInstruction.isInstanceOf[icodes.opcodes.THROW], p))
- // staleOut foreach (p => assert( in.isDefinedAt(p), p))
-
- // remainingCALLs.clear()
- isOnWatchlist.clear()
- relevantBBs.clear()
-
- // never rewrite in(m.startBlock)
- staleOut foreach { b =>
- enqueue(b)
- out(b) = typeFlowLattice.bottom
- }
- // nothing else is added to the worklist, bb's reachable via succs will be tfa'ed
- blankOut(inlined)
- blankOut(staleIn)
- // no need to add startBlocks from m.exh
-
- staleOut foreach { so => putOnRadar(linearizer linearizeAt (m, so)) }
- populatePerimeter()
-
- } // end of method reinit
-
- /* this is not a general purpose method to add to the worklist,
- * because the assert is expected to hold only when called from MTFAGrowable.reinit() */
- private def enqueue(b: BasicBlock) {
- assert(in(b) ne typeFlowLattice.bottom)
- if(!worklist.contains(b)) { worklist += b }
- }
-
- private def blankOut(blocks: scala.collection.Set[BasicBlock]) {
- blocks foreach { b =>
- in(b) = typeFlowLattice.bottom
- out(b) = typeFlowLattice.bottom
- }
- }
-
- /*
- This is basically the plain-old forward-analysis part of a dataflow algorithm,
- adapted to skip non-relevant blocks (as determined by `reinit()` via `populatePerimeter()`).
-
- The adaptations are:
-
- - only relevant blocks dequeued from the worklist move on to have the transfer function applied
-
- - `visited` now means the transfer function was applied to the block,
- but please notice that this does not imply anymore its out-flow to be different from bottom,
- because a block on the perimeter will have per-instruction typeflows computed only up to its `lastInstruction`.
- In case you need to know whether a visted block `v` has been "fully visited", evaluate `out(v) ne typeflowLattice.bottom`
-
- - given that the transfer function may remove callsite-candidates from the watchlist (thus, they are not candidates anymore)
- there's an opportunity to detect whether a previously relevant block has been left without candidates.
- That's what `shrinkedWatchlist` detects. Provided the block was on the perimeter, we know we can skip it from now now,
- and we can also constrain the CFG-subgraph by finding a new perimeter (thus the invocation to `populatePerimeter()`).
- */
- override def forwardAnalysis(f: (P, lattice.Elem) => lattice.Elem): Unit = {
- while (!worklist.isEmpty && relevantBBs.nonEmpty) {
- if (stat) iterations += 1
- val point = worklist.iterator.next(); worklist -= point
- if(relevantBBs(point)) {
- shrinkedWatchlist = false
- val output = f(point, in(point))
- visited += point
- if(isOnPerimeter(point)) {
- if(shrinkedWatchlist && !isWatching(point)) {
- relevantBBs -= point
- populatePerimeter()
- }
- } else {
- val propagate = ((lattice.bottom == out(point)) || output != out(point))
- if (propagate) {
- out(point) = output
- val succs = point.successors filter relevantBBs
- succs foreach { p =>
- assert((p.predecessors filter isOnPerimeter).isEmpty)
- val existing = in(p)
- // TODO move the following assertion to typeFlowLattice.lub2 for wider applicability (ie MethodTFA in addition to MTFAGrowable).
- assert(existing == lattice.bottom ||
- p.exceptionHandlerStart ||
- (output.stack.length == existing.stack.length),
- "Trying to merge non-bottom type-stacks with different stack heights. For a possible cause see SI-6157.")
- val updated = lattice.lub(List(output, existing), p.exceptionHandlerStart)
- if(updated != in(p)) {
- in(p) = updated
- enqueue(p)
- }
- }
- }
- }
- }
- }
- }
-
- }
-
- class Timer {
- var millis = 0L
-
- private var lastStart = 0L
-
- def start() {
- lastStart = System.nanoTime()
- }
-
- /** Stop the timer and return the number of milliseconds since the last
- * call to start. The 'millis' field is increased by the elapsed time.
- */
- def stop: Long = {
- val elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStart)
- millis += elapsed
- elapsed
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
index cd7e0b83e8..402dc66a7f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
@@ -5,11 +5,15 @@
package scala.tools.nsc.backend.jvm
-import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode}
-import java.io.{StringWriter, PrintWriter}
-import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier}
-import scala.tools.asm.{ClassWriter, Attribute, ClassReader}
-import scala.collection.convert.decorateAsScala._
+import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, FieldNode, InsnList, MethodNode}
+import java.io.{PrintWriter, StringWriter}
+import java.util
+
+import scala.tools.asm.util.{CheckClassAdapter, Textifier, TraceClassVisitor, TraceMethodVisitor}
+import scala.tools.asm.{Attribute, ClassReader, ClassWriter}
+import scala.collection.JavaConverters._
+import scala.concurrent.duration.Duration
+import scala.concurrent.{Await, Future}
import scala.tools.nsc.backend.jvm.analysis.InitialProducer
import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype
@@ -29,7 +33,7 @@ object AsmUtils {
final val traceClassPattern = ""
/**
- * Print the bytedcode of classes as they are serialized by the ASM library. The serialization
+ * Print the bytecode of classes as they are serialized by the ASM library. The serialization
* performed by `asm.ClassWriter` can change the code generated by GenBCode. For example, it
* introduces stack map frames, it computes the maximal stack sizes, and it replaces dead
* code by NOPs (see also https://github.com/scala/scala/pull/3726#issuecomment-42861780).
@@ -55,6 +59,48 @@ object AsmUtils {
node
}
+ def readClass(filename: String): ClassNode = readClass(classBytes(filename))
+
+ def classBytes(file: String): Array[Byte] = {
+ val f = new java.io.RandomAccessFile(file, "r")
+ val bytes = new Array[Byte](f.length.toInt)
+ f.read(bytes)
+ bytes
+ }
+
+ def classFromBytes(bytes: Array[Byte]): ClassNode = {
+ val node = new ClassNode()
+ new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
+
+ node
+ }
+
+// def main(args: Array[String]): Unit = println(textify(sortedClassRead(classBytes(args.head))))
+
+ def sortClassMembers(node: ClassNode): node.type = {
+ node.fields.sort(_.name compareTo _.name)
+ node.methods.sort(_.name compareTo _.name)
+ node
+ }
+
+ // drop ScalaSig annotation and class attributes
+ def zapScalaClassAttrs(node: ClassNode): node.type = {
+ if (node.visibleAnnotations != null)
+ node.visibleAnnotations = node.visibleAnnotations.asScala.filterNot(a => a == null || a.desc.contains("Lscala/reflect/ScalaSignature")).asJava
+
+ node.attrs = null
+ node
+ }
+
+ def main(args: Array[String]): Unit = args.par.foreach { classFileName =>
+ val node = zapScalaClassAttrs(sortClassMembers(classFromBytes(classBytes(classFileName))))
+
+ val pw = new PrintWriter(classFileName + ".asm")
+ val trace = new TraceClassVisitor(pw)
+ node.accept(trace)
+ pw.close()
+ }
+
/**
* Returns a human-readable representation of the cnode ClassNode.
*/
@@ -115,12 +161,12 @@ object AsmUtils {
* Run ASM's CheckClassAdapter over a class. Returns None if no problem is found, otherwise
* Some(msg) with the verifier's error message.
*/
- def checkClass(classNode: ClassNode): Option[String] = {
+ def checkClass(classNode: ClassNode, dumpNonErroneous: Boolean = false): Option[String] = {
val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
classNode.accept(cw)
val sw = new StringWriter()
val pw = new PrintWriter(sw)
- CheckClassAdapter.verify(new ClassReader(cw.toByteArray), false, pw)
+ CheckClassAdapter.verify(new ClassReader(cw.toByteArray), dumpNonErroneous, pw)
val res = sw.toString
if (res.isEmpty) None else Some(res)
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
deleted file mode 100644
index 93f5159f89..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ /dev/null
@@ -1,465 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2014 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc
-package backend.jvm
-
-import scala.tools.nsc.Global
-import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo}
-import BackendReporting.ClassSymbolInfoFailureSI9111
-import scala.tools.asm
-
-/**
- * This trait contains code shared between GenBCode and GenASM that depends on types defined in
- * the compiler cake (Global).
- */
-final class BCodeAsmCommon[G <: Global](val global: G) {
- import global._
- import definitions._
-
- val ExcludedForwarderFlags = {
- import scala.tools.nsc.symtab.Flags._
- // Should include DEFERRED but this breaks findMember.
- SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
- }
-
- /**
- * True for classes generated by the Scala compiler that are considered top-level in terms of
- * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes.
- */
- def considerAsTopLevelImplementationArtifact(classSym: Symbol) = {
- classSym.isImplClass || classSym.isSpecialized
- }
-
- /**
- * Cache the value of delambdafy == "inline" for each run. We need to query this value many
- * times, so caching makes sense.
- */
- object delambdafyInline {
- private var runId = -1
- private var value = false
-
- def apply(): Boolean = {
- if (runId != global.currentRunId) {
- runId = global.currentRunId
- value = settings.Ydelambdafy.value == "inline"
- }
- value
- }
- }
-
- /**
- * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
- * member class. This method is used to decide if we should emit an EnclosingMethod attribute.
- * It is also used to decide whether the "owner" field in the InnerClass attribute should be
- * null.
- */
- def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
- assert(classSym.isClass, s"not a class: $classSym")
- val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
- if (r && settings.Ybackend.value == "GenBCode") {
- // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally
- // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode
- // we prevent this, see `nonAnon` in LambdaLift.
- // phase travel necessary: after flatten, the name includes the name of outer classes.
- // if some outer name contains $lambda, a non-lambda class is considered lambda.
- assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name)
- }
- r
- }
-
- /**
- * The next enclosing definition in the source structure. Includes anonymous function classes
- * under delambdafy:inline, even though they are only generated during UnCurry.
- */
- def nextEnclosing(sym: Symbol): Symbol = {
- val origOwner = sym.originalOwner
- // phase travel necessary: after flatten, the name includes the name of outer classes.
- // if some outer name contains $anon, a non-anon class is considered anon.
- if (delambdafyInline() && sym.rawowner.isAnonymousFunction) {
- // SI-9105: special handling for anonymous functions under delambdafy:inline.
- //
- // class C { def t = () => { def f { class Z } } }
- //
- // class C { def t = byNameMethod { def f { class Z } } }
- //
- // In both examples, the method f lambda-lifted into the anonfun class.
- //
- // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun.
- // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ...
- //
- // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!)
- // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!).
- //
- // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if`
- // above makes sure we don't jump over the anonymous function in the by-name argument case.
- //
- // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f
- // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym`
- // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and
- // we need to return it.
- // If the rawowners are different, the symbol was not in between. In the first example, the
- // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing
- // of `f` is its rawowner, the anonFunClassSym.
- //
- // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C,
- // not into the anonymous function class. The originalOwner chain is Z - f - C.
- if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner
- else sym.rawowner
- } else {
- origOwner
- }
- }
-
- def nextEnclosingClass(sym: Symbol): Symbol = {
- if (sym.isClass) sym
- else nextEnclosingClass(nextEnclosing(sym))
- }
-
- def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={
- nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass
- }
-
- /**
- * Returns the enclosing method for non-member classes. In the following example
- *
- * class A {
- * def f = {
- * class B {
- * class C
- * }
- * }
- * }
- *
- * the method returns Some(f) for B, but None for C, because C is a member class. For non-member
- * classes that are not enclosed by a method, it returns None:
- *
- * class A {
- * { class B }
- * }
- *
- * In this case, for B, we return None.
- *
- * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
- * This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
- */
- private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
- assert(classSym.isClass, classSym)
-
- def doesNotExist(method: Symbol) = {
- // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes.
- // (2) Value classes. Member methods of value classes exist in the generated box class. However,
- // nested methods lifted into a value class are moved to the companion object and don't exist
- // in the value class itself. We can identify such nested methods: the initial enclosing class
- // is a value class, but the current owner is some other class (the module class).
- method.owner.isTrait && method.isImplOnly || { // (1)
- val enclCls = nextEnclosingClass(method)
- exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2)
- }
- }
-
- def enclosingMethod(sym: Symbol): Option[Symbol] = {
- if (sym.isClass || sym == NoSymbol) None
- else if (sym.isMethod) {
- if (doesNotExist(sym)) None else Some(sym)
- }
- else enclosingMethod(nextEnclosing(sym))
- }
- enclosingMethod(nextEnclosing(classSym))
- }
-
- /**
- * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
- * property, this method looks at the originalOwner chain. See doc in BTypes.
- */
- private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
- assert(classSym.isClass, classSym)
- val r = nextEnclosingClass(nextEnclosing(classSym))
- // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release
- if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r")
- r
- }
-
- final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
-
- /**
- * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
- * an anonymous or local class). See doc in BTypes.
- *
- * The class is parametrized by two functions to obtain a bytecode class descriptor for a class
- * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
- * on the implementation of GenASM / GenBCode, so they need to be passed in.
- */
- def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = {
- // trait impl classes are always top-level, see comment in BTypes
- if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
- val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
- val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match {
- case some @ Some(m) =>
- if (m.owner != enclosingClass) {
- // This should never happen. In case it does, it prevents emitting an invalid
- // EnclosingMethod attribute: if the attribute specifies an enclosing method,
- // it needs to exist in the specified enclosing class.
- devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
- None
- } else some
- case none => none
- }
- Some(EnclosingMethodEntry(
- classDesc(enclosingClass),
- methodOpt.map(_.javaSimpleName.toString).orNull,
- methodOpt.map(methodDesc).orNull))
- } else {
- None
- }
- }
-
- /**
- * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
- *
- * The problem is that we are interested in a source-level property. Various phases changed the
- * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
- * Therefore, `sym.isStatic` is not what we want. For example, in
- * object T { def f { object U } }
- * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
- */
- def isOriginallyStaticOwner(sym: Symbol): Boolean = {
- sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
- }
-
- /**
- * Reconstruct the classfile flags from a Java defined class symbol.
- *
- * The implementation of this method is slightly different that `javaFlags` in BTypesFromSymbols.
- * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags
- * that are used in the generated classfiles. For example, all classes emitted by the Scala
- * compiler have ACC_PUBLIC.
- *
- * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have
- * to correspond exactly to the flags in the classfile. For example, if the class is package
- * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the
- * ClassBType. For example, the inliner needs the correct flags for access checks.
- *
- * Class flags are listed here:
- * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1
- */
- def javaClassfileFlags(classSym: Symbol): Int = {
- assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
- import asm.Opcodes._
- def enumFlags = ACC_ENUM | {
- // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
- // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
- // Java enums for exhaustiveness checking.
- val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
- if (hasAbstractMethod) ACC_ABSTRACT else 0
- }
- GenBCode.mkFlags(
- // SI-9393: the classfile / java source parser make java annotation symbols look like classes.
- // here we recover the actual classfile flags.
- if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0,
- if (classSym.isPublic) ACC_PUBLIC else 0,
- if (classSym.isFinal) ACC_FINAL else 0,
- // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
- if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER,
- // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags)
- if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (classSym.isArtifact) ACC_SYNTHETIC else 0,
- if (classSym.hasJavaEnumFlag) enumFlags else 0
- )
- }
-
- /**
- * The member classes of a class symbol. Note that the result of this method depends on the
- * current phase, for example, after lambdalift, all local classes become member of the enclosing
- * class.
- *
- * Impl classes are always considered top-level, see comment in BTypes.
- */
- def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
- case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
- sym
- case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin
- val r = exitingPickler(sym.moduleClass)
- assert(r != NoSymbol, sym.fullLocationString)
- r
- })(collection.breakOut)
-
- lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule
- lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
- lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
- lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
-
- /** Whether an annotation should be emitted as a Java annotation
- * .initialize: if 'annot' is read from pickle, atp might be uninitialized
- */
- def shouldEmitAnnotation(annot: AnnotationInfo) = {
- annot.symbol.initialize.isJavaDefined &&
- annot.matches(ClassfileAnnotationClass) &&
- retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
- annot.args.isEmpty
- }
-
- def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
- annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
- case Some(retentionAnnot) =>
- retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
- case _ =>
- // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
- // annotation is emitted with visibility `RUNTIME`
- true
- }
- }
-
- private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
- annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
- assoc.collectFirst {
- case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
- }).getOrElse(AnnotationRetentionPolicyClassValue)
-
- def implementedInterfaces(classSym: Symbol): List[Symbol] = {
- // Additional interface parents based on annotations and other cues
- def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match {
- case RemoteAttr => Some(RemoteInterfaceClass.tpe)
- case _ => None
- }
-
- // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes.
- def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag
-
- val classParents = {
- val parents = classSym.info.parents
- // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the
- // parents of a java annotations. undo this for the backend (where we need classfile-level information).
- if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass)
- else parents
- }
-
- val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation)
-
- // We keep the superClass when computing minimizeParents to eliminate more interfaces.
- // Example: T can be eliminated from D
- // trait T
- // class C extends T
- // class D extends C with T
- val interfaces = erasure.minimizeParents(allParents) match {
- case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) =>
- ifs
- case ifs =>
- // minimizeParents removes the superclass if it's redundant, for example:
- // trait A
- // class C extends Object with A // minimizeParents removes Object
- ifs
- }
- interfaces.map(_.typeSymbol)
- }
-
- /**
- * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
- * cannot change the typer context of the completer at this point and make it silent: the context
- * captured when creating the completer in the namer. However, we can temporarily replace
- * global.reporter (it's a var) to store errors.
- */
- def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = {
- if (sym.hasCompleteInfo) false
- else {
- val originalReporter = global.reporter
- val storeReporter = new reporters.StoreReporter()
- global.reporter = storeReporter
- try {
- sym.info
- } finally {
- global.reporter = originalReporter
- }
- sym.isErroneous
- }
- }
-
- /**
- * Build the [[InlineInfo]] for a class symbol.
- */
- def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
- val traitSelfType = if (classSym.isTrait && !classSym.isImplClass) {
- // The mixin phase uses typeOfThis for the self parameter in implementation class methods.
- val selfSym = classSym.typeOfThis.typeSymbol
- if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None
- } else {
- None
- }
-
- val isEffectivelyFinal = classSym.isEffectivelyFinal
-
- var warning = Option.empty[ClassSymbolInfoFailureSI9111]
-
- // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
- // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
- val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
- case methodSym =>
- if (completeSilentlyAndCheckErroneous(methodSym)) {
- // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
- if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
- warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
- None
- } else {
- val name = methodSym.javaSimpleName.toString // same as in genDefDef
- val signature = name + methodSymToDescriptor(methodSym)
-
- // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE):
- // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin.
- // This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final
- // but non-overridden methods of sealed traits from being inlined.
- // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the
- // lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late
- // flag is ignored. The members are therefore not isEffectivelyFinal (their owner
- // is not a module). Since we know that all impl class members are static, we can
- // just take the shortcut.
- val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden)
-
- // Identify trait interface methods that have a static implementation in the implementation
- // class. Invocations of these methods can be re-wrired directly to the static implementation
- // if they are final or the receiver is known.
- //
- // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters
- // and super accessors. When AddInterfaces creates the impl class, these methods are
- // initially added to it.
- //
- // The mixin phase later on filters out most of these members from the impl class (see
- // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the
- // impl class after mixin. So the filter in mixin is not exactly what we need here (we
- // want to identify concrete trait methods, not any accessors). So we check some symbol
- // properties manually.
- val traitMethodWithStaticImplementation = {
- import symtab.Flags._
- classSym.isTrait && !classSym.isImplClass &&
- erasure.needsImplMethod(methodSym) &&
- !methodSym.isModule &&
- !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR))
- }
-
- val info = MethodInlineInfo(
- effectivelyFinal = effectivelyFinal,
- traitMethodWithStaticImplementation = traitMethodWithStaticImplementation,
- annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
- annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)
- )
- Some((signature, info))
- }
- }).toMap
-
- InlineInfo(traitSelfType, isEffectivelyFinal, methodInlineInfos, warning)
- }
-}
-
-object BCodeAsmCommon {
- /**
- * Valid flags for InnerClass attribute entry.
- * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
- */
- val INNER_CLASSES_FLAGS = {
- asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
- asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE |
- asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
- asm.Opcodes.ACC_ENUM
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 416628d5ba..37dea477c6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -11,10 +11,13 @@ package jvm
import scala.annotation.switch
import scala.reflect.internal.Flags
-
import scala.tools.asm
import GenBCode._
import BackendReporting._
+import scala.collection.mutable
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{MethodInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp}
/*
*
@@ -26,24 +29,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
import global._
import definitions._
import bTypes._
- import bCodeICodeCommon._
import coreBTypes._
/*
* Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions.
*/
abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) {
- import icodes.TestOp
- import icodes.opcodes.InvokeStyle
-
- /* If the selector type has a member with the right name,
- * it is the host class; otherwise the symbol's owner.
- */
- def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match {
- case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner
- case _ => selector.typeSymbol
- }
-
/* ---------------- helper utils for generating methods and code ---------------- */
def emit(opc: Int) { mnode.visitInsn(opc) }
@@ -71,12 +62,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genStat(tree: Tree) {
lineNumber(tree)
tree match {
- case Assign(lhs @ Select(_, _), rhs) =>
+ case Assign(lhs @ Select(qual, _), rhs) =>
val isStatic = lhs.symbol.isStaticMember
if (!isStatic) { genLoadQualifier(lhs) }
genLoad(rhs, symInfoTK(lhs.symbol))
lineNumber(tree)
- fieldStore(lhs.symbol)
+ // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283
+ val receiverClass = qual.tpe.typeSymbol
+ fieldStore(lhs.symbol, receiverClass)
case Assign(lhs, rhs) =>
val s = lhs.symbol
@@ -94,12 +87,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val thrownKind = tpeTK(expr)
// `throw null` is valid although scala.Null (as defined in src/library-aux) isn't a subtype of Throwable.
// Similarly for scala.Nothing (again, as defined in src/library-aux).
- assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get)
+ assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(jlThrowableRef).get)
genLoad(expr, thrownKind)
lineNumber(expr)
emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level.
- RT_NOTHING // always returns the same, the invoker should know :)
+ srNothingRef // always returns the same, the invoker should know :)
}
/* Generate code for primitive arithmetic operations. */
@@ -119,21 +112,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
code match {
case POS => () // nothing
case NEG => bc.neg(resKind)
- case NOT => bc.genPrimitiveArithmetic(icodes.NOT, resKind)
+ case NOT => bc.genPrimitiveNot(resKind)
case _ => abort(s"Unknown unary operation: ${fun.symbol.fullName} code: $code")
}
// binary operation
case rarg :: Nil =>
- resKind = tpeTK(larg).maxType(tpeTK(rarg))
- if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) {
+ val isShiftOp = scalaPrimitives.isShiftOp(code)
+ resKind = tpeTK(larg).maxType(if (isShiftOp) INT else tpeTK(rarg))
+
+ if (isShiftOp || scalaPrimitives.isBitwiseOp(code)) {
assert(resKind.isIntegralType || (resKind == BOOL),
s"$resKind incompatible with arithmetic modulo operation.")
}
genLoad(larg, resKind)
- genLoad(rarg, // check .NET size of shift arguments!
- if (scalaPrimitives.isShiftOp(code)) INT else resKind)
+ genLoad(rarg, if (isShiftOp) INT else resKind)
(code: @switch) match {
case ADD => bc add resKind
@@ -171,21 +165,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
genLoad(args.head, INT)
generatedType = k.asArrayBType.componentType
bc.aload(elementType)
- }
- else if (scalaPrimitives.isArraySet(code)) {
- args match {
- case a1 :: a2 :: Nil =>
- genLoad(a1, INT)
- genLoad(a2)
- // the following line should really be here, but because of bugs in erasure
- // we pretend we generate whatever type is expected from us.
- //generatedType = UNIT
- bc.astore(elementType)
- case _ =>
- abort(s"Too many arguments for array set operation: $tree")
- }
- }
- else {
+ } else if (scalaPrimitives.isArraySet(code)) {
+ val List(a1, a2) = args
+ genLoad(a1, INT)
+ genLoad(a2, elementType)
+ generatedType = UNIT
+ bc.astore(elementType)
+ } else {
generatedType = INT
emit(asm.Opcodes.ARRAYLENGTH)
}
@@ -203,14 +189,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val hasElse = !elsep.isEmpty
val postIf = if (hasElse) new asm.Label else failure
- genCond(condp, success, failure)
+ genCond(condp, success, failure, targetIfNoJump = success)
+ markProgramPoint(success)
val thenKind = tpeTK(thenp)
val elseKind = if (!hasElse) UNIT else tpeTK(elsep)
def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT)
val resKind = if (hasUnitBranch) UNIT else tpeTK(tree)
- markProgramPoint(success)
genLoad(thenp, resKind)
if (hasElse) { bc goTo postIf }
markProgramPoint(failure)
@@ -235,14 +221,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
else if (isArrayOp(code)) genArrayOp(tree, code, expectedType)
else if (isLogicalOp(code) || isComparisonOp(code)) {
val success, failure, after = new asm.Label
- genCond(tree, success, failure)
+ genCond(tree, success, failure, targetIfNoJump = success)
// success block
- markProgramPoint(success)
- bc boolconst true
- bc goTo after
+ markProgramPoint(success)
+ bc boolconst true
+ bc goTo after
// failure block
- markProgramPoint(failure)
- bc boolconst false
+ markProgramPoint(failure)
+ bc boolconst false
// after
markProgramPoint(after)
@@ -311,6 +297,15 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case app : Apply =>
generatedType = genApply(app, expectedType)
+ case app @ ApplyDynamic(qual, Literal(Constant(bootstrapMethodRef: Symbol)) :: staticAndDynamicArgs) =>
+ val numStaticArgs = bootstrapMethodRef.paramss.head.size - 3 /*JVM provided args*/
+ val (staticArgs, dynamicArgs) = staticAndDynamicArgs.splitAt(numStaticArgs)
+ val bootstrapDescriptor = staticHandleFromSymbol(bootstrapMethodRef)
+ val bootstrapArgs = staticArgs.map({case t @ Literal(c: Constant) => bootstrapMethodArg(c, t.pos)})
+ val descriptor = methodBTypeFromMethodType(qual.symbol.info, false)
+ genLoadArguments(dynamicArgs, qual.symbol.info.params.map(param => typeToBType(param.info)))
+ mnode.visitInvokeDynamicInsn(qual.symbol.name.encoded, descriptor.descriptor, bootstrapDescriptor, bootstrapArgs : _*)
+
case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.")
case This(qual) =>
@@ -323,7 +318,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
else {
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
generatedType =
- if (tree.symbol == ArrayClass) ObjectReference
+ if (tree.symbol == ArrayClass) ObjectRef
else classBTypeFromSymbol(claszSymbol)
}
@@ -331,26 +326,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
assert(tree.symbol.isModule, s"Selection of non-module from empty package: $tree sym: ${tree.symbol} at: ${tree.pos}")
genLoadModule(tree)
- case Select(qualifier, selector) =>
+ case Select(qualifier, _) =>
val sym = tree.symbol
generatedType = symInfoTK(sym)
- val hostClass = findHostClass(qualifier.tpe, sym)
- debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass")
val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier
-
def genLoadQualUnlessElidable() { if (!qualSafeToElide) { genLoadQualifier(tree) } }
-
+ // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283
+ def receiverClass = qualifier.tpe.typeSymbol
if (sym.isModule) {
genLoadQualUnlessElidable()
genLoadModule(tree)
- }
- else if (sym.isStaticMember) {
+ } else if (sym.isStaticMember) {
genLoadQualUnlessElidable()
- fieldLoad(sym, hostClass)
- }
- else {
+ fieldLoad(sym, receiverClass)
+ } else {
genLoadQualifier(tree)
- fieldLoad(sym, hostClass)
+ fieldLoad(sym, receiverClass)
}
case Ident(name) =>
@@ -366,7 +357,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (value.tag != UnitTag) (value.tag, expectedType) match {
case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG
case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE
- case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL
+ case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = srNullRef
case _ => genConstant(value); generatedType = tpeTK(tree)
}
@@ -403,24 +394,18 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
/*
* must-single-thread
*/
- def fieldLoad( field: Symbol, hostClass: Symbol = null) {
- fieldOp(field, isLoad = true, hostClass)
- }
+ def fieldLoad(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = true, hostClass)
+
/*
* must-single-thread
*/
- def fieldStore(field: Symbol, hostClass: Symbol = null) {
- fieldOp(field, isLoad = false, hostClass)
- }
+ def fieldStore(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = false, hostClass)
/*
* must-single-thread
*/
- private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol) {
- // LOAD_FIELD.hostClass , CALL_METHOD.hostClass , and #4283
- val owner =
- if (hostClass == null) internalName(field.owner)
- else internalName(hostClass)
+ private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol): Unit = {
+ val owner = internalName(if (hostClass == null) field.owner else hostClass)
val fieldJName = field.javaSimpleName.toString
val fieldDescr = symInfoTK(field).descriptor
val isStatic = field.isStaticMember
@@ -428,7 +413,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD }
else { if (isStatic) asm.Opcodes.PUTSTATIC else asm.Opcodes.PUTFIELD }
mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
}
// ---------------- emitting constant values ----------------
@@ -461,19 +445,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case NullTag => emit(asm.Opcodes.ACONST_NULL)
case ClazzTag =>
- val toPush: BType = {
- toTypeKind(const.typeValue) match {
- case kind: PrimitiveBType => boxedClassOfPrimitive(kind)
- case kind => kind
- }
- }
- mnode.visitLdcInsn(toPush.toASMType)
+ val tp = typeToBType(const.typeValue)
+ // classOf[Int] is transformed to Integer.TYPE by CleanUp
+ assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp")
+ mnode.visitLdcInsn(tp.toASMType)
case EnumTag =>
val sym = const.symbolValue
val ownerName = internalName(sym.owner)
val fieldName = sym.javaSimpleName.toString
- val fieldDesc = toTypeKind(sym.tpe.underlying).descriptor
+ val fieldDesc = typeToBType(sym.tpe.underlying).descriptor
mnode.visitFieldInsn(
asm.Opcodes.GETSTATIC,
ownerName,
@@ -507,16 +488,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
bc emitRETURN returnType
case nextCleanup :: rest =>
if (saveReturnValue) {
- if (insideCleanupBlock) {
- reporter.warning(r.pos, "Return statement found in finally-clause, discarding its return-value in favor of that of a more deeply nested return.")
- bc drop returnType
- } else {
- // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
- if (earlyReturnVar == null) {
- earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar")
- }
- locals.store(earlyReturnVar)
+ // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
+ if (earlyReturnVar == null) {
+ earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar")
}
+ locals.store(earlyReturnVar)
}
bc goTo nextCleanup
shouldEmitCleanup = true
@@ -527,6 +503,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
private def genApply(app: Apply, expectedType: BType): BType = {
var generatedType = expectedType
lineNumber(app)
+
app match {
case Apply(TypeApply(fun, targs), _) =>
@@ -551,8 +528,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
else if (l.isPrimitive) {
bc drop l
if (cast) {
- mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.internalName)
- bc dup ObjectReference
+ mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName)
+ bc dup ObjectRef
emit(asm.Opcodes.ATHROW)
} else {
bc boolconst false
@@ -574,19 +551,33 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
generatedType = genTypeApply()
- // 'super' call: Note: since constructors are supposed to
- // return an instance of what they construct, we have to take
- // special care. On JVM they are 'void', and Scala forbids (syntactically)
- // to call super constructors explicitly and/or use their 'returned' value.
- // therefore, we can ignore this fact, and generate code that leaves nothing
- // on the stack (contrary to what the type in the AST says).
- case Apply(fun @ Select(Super(_, mix), _), args) =>
- val invokeStyle = icodes.opcodes.SuperCall(mix)
- // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix);
+ case Apply(fun @ Select(Super(_, _), _), args) =>
+ def initModule() {
+ // we initialize the MODULE$ field immediately after the super ctor
+ if (!isModuleInitialized &&
+ jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
+ fun.symbol.javaSimpleName.toString == INSTANCE_CONSTRUCTOR_NAME &&
+ isStaticModuleClass(claszSymbol)) {
+ isModuleInitialized = true
+ mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
+ mnode.visitFieldInsn(
+ asm.Opcodes.PUTSTATIC,
+ thisBType.internalName,
+ strMODULE_INSTANCE_FIELD,
+ thisBType.descriptor
+ )
+ }
+ }
+ // 'super' call: Note: since constructors are supposed to
+ // return an instance of what they construct, we have to take
+ // special care. On JVM they are 'void', and Scala forbids (syntactically)
+ // to call super constructors explicitly and/or use their 'returned' value.
+ // therefore, we can ignore this fact, and generate code that leaves nothing
+ // on the stack (contrary to what the type in the AST says).
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
genLoadArguments(args, paramTKs(app))
- genCallMethod(fun.symbol, invokeStyle, app.pos)
- generatedType = asmMethodType(fun.symbol).returnType
+ generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.pos)
+ initModule()
// 'new' constructor call: Note: since constructors are
// thought to return an instance of what they construct,
@@ -617,8 +608,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
argsSize match {
case 1 => bc newarray elemKind
- case _ =>
- val descr = ('[' * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor
+ case _ => // this is currently dead code in Scalac, unlike in Dotty
+ val descr = ("[" * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor
mnode.visitMultiANewArrayInsn(descr, argsSize)
}
@@ -627,7 +618,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName)
bc dup generatedType
genLoadArguments(args, paramTKs(app))
- genCallMethod(ctor, icodes.opcodes.Static(onInstance = true), app.pos)
+ genCallMethod(ctor, InvokeStyle.Special, app.pos)
case _ =>
abort(s"Cannot instantiate $tpt of kind: $generatedType")
@@ -635,85 +626,97 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] =>
val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get
genLoadArguments(args, paramTKs(app))
- genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface)
- generatedType = asmMethodType(fun.symbol).returnType
+ genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface, attachment.sam, attachment.isSerializable, attachment.addScalaSerializableMarker)
+ generatedType = methodBTypeFromSymbol(fun.symbol).returnType
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
- val nativeKind = tpeTK(expr)
+ case Apply(fun, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
+ val nativeKind = typeToBType(fun.symbol.firstParam.info)
genLoad(expr, nativeKind)
- val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
- generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType)
+ val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
+ generatedType = boxResultType(fun.symbol)
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
+ case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
genLoad(expr)
- val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
+ val boxType = unboxResultType(fun.symbol)
generatedType = boxType
- val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType)
- bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos)
+ val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
case app @ Apply(fun, args) =>
val sym = fun.symbol
- if (sym.isLabel) { // jump to a label
+ if (sym.isLabel) { // jump to a label
genLoadLabelArguments(args, labelDef(sym), app.pos)
bc goTo programPoint(sym)
} else if (isPrimitive(sym)) { // primitive method call
generatedType = genPrimitiveOp(app, expectedType)
- } else { // normal method call
-
- def genNormalMethodCall() {
-
- val invokeStyle =
- if (sym.isStaticMember) icodes.opcodes.Static(onInstance = false)
- else if (sym.isPrivate || sym.isClassConstructor) icodes.opcodes.Static(onInstance = true)
- else icodes.opcodes.Dynamic;
-
- if (invokeStyle.hasInstance) {
- genLoadQualifier(fun)
+ } else { // normal method call
+ def isTraitSuperAccessorBodyCall = app.hasAttachment[UseInvokeSpecial.type]
+ val invokeStyle =
+ if (sym.isStaticMember)
+ InvokeStyle.Static
+ else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special
+ else if (isTraitSuperAccessorBodyCall)
+ InvokeStyle.Special
+ else InvokeStyle.Virtual
+
+ if (invokeStyle.hasInstance) genLoadQualifier(fun)
+ genLoadArguments(args, paramTKs(app))
+
+ val Select(qual, _) = fun // fun is a Select, also checked in genLoadQualifier
+ if (sym == definitions.Array_clone) {
+ // Special-case Array.clone, introduced in 36ef60e. The goal is to generate this call
+ // as "[I.clone" instead of "java/lang/Object.clone". This is consistent with javac.
+ // Arrays have a public method `clone` (jls 10.7).
+ //
+ // The JVMS is not explicit about this, but that receiver type can be an array type
+ // descriptor (instead of a class internal name):
+ // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object
+ //
+ // Note that using `Object.clone()` would work as well, but only because the JVM
+ // relaxes protected access specifically if the receiver is an array:
+ // http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/interpreter/linkResolver.cpp#l439
+ // Example: `class C { override def clone(): Object = "hi" }`
+ // Emitting `def f(c: C) = c.clone()` as `Object.clone()` gives a VerifyError.
+ val target: String = tpeTK(qual).asRefBType.classOrArrayType
+ val methodBType = methodBTypeFromSymbol(sym)
+ bc.invokevirtual(target, sym.javaSimpleName.toString, methodBType.descriptor, app.pos)
+ generatedType = methodBType.returnType
+ } else {
+ val receiverClass = if (!invokeStyle.isVirtual) null else {
+ // receiverClass is used in the bytecode to as the method receiver. using sym.owner
+ // may lead to IllegalAccessErrors, see 9954eaf / aladdin bug 455.
+ val qualSym = qual.tpe.typeSymbol
+ if (qualSym == ArrayClass) {
+ // For invocations like `Array(1).hashCode` or `.wait()`, use Object as receiver
+ // in the bytecode. Using the array descriptor (like we do for clone above) seems
+ // to work as well, but it seems safer not to change this. Javac also uses Object.
+ // Note that array apply/update/length are handled by isPrimitive (above).
+ assert(sym.owner == ObjectClass, s"unexpected array call: ${show(app)}")
+ ObjectClass
+ } else qualSym
}
- genLoadArguments(args, paramTKs(app))
-
- // In "a couple cases", squirrel away a extra information (hostClass, targetTypeKind). TODO Document what "in a couple cases" refers to.
- var hostClass: Symbol = null
- var targetTypeKind: BType = null
- fun match {
- case Select(qual, _) =>
- val qualSym = findHostClass(qual.tpe, sym)
- if (qualSym == ArrayClass) {
- targetTypeKind = tpeTK(qual)
- log(s"Stored target type kind for ${sym.fullName} as $targetTypeKind")
- }
- else {
- hostClass = qualSym
- if (qual.tpe.typeSymbol != qualSym) {
- log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}")
- }
- }
-
- case _ =>
- }
- if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) {
- // An invokevirtual points to a CONSTANT_Methodref_info which in turn points to a
- // CONSTANT_Class_info of the receiver type.
- // The JVMS is not explicit about this, but that receiver type may be an array type
- // descriptor (instead of a class internal name):
- // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object
- val target: String = targetTypeKind.asRefBType.classOrArrayType
- bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos)
- }
- else {
- genCallMethod(sym, invokeStyle, app.pos, hostClass)
+ generatedType = genCallMethod(sym, invokeStyle, app.pos, receiverClass)
+
+ // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer
+ // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment
+ // is on the Select node (not on the Apply node added by UnCurry).
+ def recordInlineAnnotated(t: Tree): Unit = {
+ if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match {
+ case m: MethodInsnNode =>
+ if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m
+ else inlineAnnotatedCallsites += m
+ case _ =>
+ } else t match {
+ case Apply(fun, _) => recordInlineAnnotated(fun)
+ case _ =>
+ }
}
-
- } // end of genNormalMethodCall()
-
- genNormalMethodCall()
-
- generatedType = asmMethodType(sym).returnType
+ recordInlineAnnotated(app)
+ }
}
-
}
generatedType
@@ -767,7 +770,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
for (caze @ CaseDef(pat, guard, body) <- tree.cases) {
assert(guard == EmptyTree, guard)
val switchBlockPoint = new asm.Label
- switchBlocks ::= (switchBlockPoint, body)
+ switchBlocks ::= ((switchBlockPoint, body))
pat match {
case Literal(value) =>
flatKeys ::= value.intValue
@@ -843,8 +846,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.
@@ -866,10 +868,24 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* emitted instruction was an ATHROW. As explained above, it is OK to emit a second ATHROW,
* the verifiers will be happy.
*/
- emit(asm.Opcodes.ATHROW)
+ if (lastInsn.getOpcode != asm.Opcodes.ATHROW)
+ emit(asm.Opcodes.ATHROW)
} else if (from.isNullType) {
- bc drop from
- emit(asm.Opcodes.ACONST_NULL)
+ /* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL.
+ * This is required to pass the verifier: in Scala's type system, Null conforms to any
+ * reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which
+ * is not a subtype of all reference types. Example:
+ *
+ * def nl: Null = null // in bytecode, nl has return type scala.runtime.Null$
+ * val a: String = nl // OK for Scala but not for the JVM, scala.runtime.Null$ does not conform to String
+ *
+ * In order to fix the above problem, the value returned by nl is dropped and ACONST_NULL is
+ * inserted instead - after all, an expression of type scala.runtime.Null$ can only be null.
+ */
+ if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) {
+ bc drop from
+ emit(asm.Opcodes.ACONST_NULL)
+ }
}
else (from, to) match {
case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG)
@@ -902,7 +918,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
(args zip params) filterNot isTrivial
}
- // first push *all* arguments. This makes sure muliple uses of the same labelDef-var will all denote the (previous) value.
+ // first push *all* arguments. This makes sure multiple uses of the same labelDef-var will all denote the (previous) value.
aps foreach { case (arg, param) => genLoad(arg, locals(param).tk) } // `locals` is known to contain `param` because `genDefDef()` visited `labelDefsAtOrUnder`
// second assign one by one to the LabelDef's variables.
@@ -922,7 +938,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")
}
@@ -942,7 +958,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
asm.Opcodes.GETSTATIC,
mbt.internalName /* + "$" */ ,
strMODULE_INSTANCE_FIELD,
- mbt.descriptor // for nostalgics: toTypeKind(module.tpe).descriptor
+ mbt.descriptor // for nostalgics: typeToBType(module.tpe).descriptor
)
}
}
@@ -978,92 +994,113 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genStringConcat(tree: Tree): BType = {
lineNumber(tree)
liftStringConcat(tree) match {
-
// Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
case List(Literal(Constant("")), arg) =>
- genLoad(arg, ObjectReference)
- genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos)
+ genLoad(arg, ObjectRef)
+ genCallMethod(String_valueOf, InvokeStyle.Static, arg.pos)
case concatenations =>
bc.genStartConcat(tree.pos)
for (elem <- concatenations) {
- val kind = tpeTK(elem)
- genLoad(elem, kind)
- bc.genStringConcat(kind, elem.pos)
+ val loadedElem = elem match {
+ case Apply(boxOp, value :: Nil) if currentRun.runDefinitions.isBox(boxOp.symbol) =>
+ // Eliminate boxing of primitive values. Boxing is introduced by erasure because
+ // there's only a single synthetic `+` method "added" to the string class.
+ value
+
+ case _ => elem
+ }
+ val elemType = tpeTK(loadedElem)
+ genLoad(loadedElem, elemType)
+ bc.genConcat(elemType, loadedElem.pos)
}
bc.genEndConcat(tree.pos)
-
}
-
- StringReference
+ StringRef
}
- def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) {
-
- val siteSymbol = claszSymbol
- val hostSymbol = if (hostClass0 == null) method.owner else hostClass0
+ /**
+ * Generate a method invocation. If `specificReceiver != null`, it is used as receiver in the
+ * invocation instruction, otherwise `method.owner`. A specific receiver class is needed to
+ * prevent an IllegalAccessError, (aladdin bug 455).
+ */
+ def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, specificReceiver: Symbol = null): BType = {
val methodOwner = method.owner
- // info calls so that types are up to date; erasure may add lateINTERFACE to traits
- hostSymbol.info ; methodOwner.info
-
- def needsInterfaceCall(sym: Symbol) = (
- sym.isInterface
- || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
- )
-
- // whether to reference the type of the receiver or
- // the type of the method owner
- val useMethodOwner = (
- style != icodes.opcodes.Dynamic
- || hostSymbol.isBottomClass
- || methodOwner == definitions.ObjectClass
- )
- val receiver = if (useMethodOwner) methodOwner else hostSymbol
- val jowner = internalName(receiver)
- val jname = method.javaSimpleName.toString
- val bmType = asmMethodType(method)
- val mdescr = bmType.descriptor
-
- def initModule() {
- // we initialize the MODULE$ field immediately after the super ctor
- if (!isModuleInitialized &&
- jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
- jname == INSTANCE_CONSTRUCTOR_NAME &&
- isStaticModuleClass(siteSymbol)) {
- isModuleInitialized = true
- mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
- mnode.visitFieldInsn(
- asm.Opcodes.PUTSTATIC,
- thisName,
- strMODULE_INSTANCE_FIELD,
- "L" + thisName + ";"
- )
+ // the class used in the invocation's method descriptor in the classfile
+ val receiverClass = {
+ if (specificReceiver != null)
+ assert(style.isVirtual || specificReceiver == methodOwner, s"specificReceiver can only be specified for virtual calls. $method - $specificReceiver")
+
+ val useSpecificReceiver = specificReceiver != null && !specificReceiver.isBottomClass
+ val receiver = if (useSpecificReceiver) specificReceiver else methodOwner
+
+ // workaround for a JVM bug: https://bugs.openjdk.java.net/browse/JDK-8154587
+ // when an interface method overrides a member of Object (note that all interfaces implicitly
+ // have superclass Object), the receiver needs to be the interface declaring the override (and
+ // not a sub-interface that inherits it). example:
+ // trait T { override def clone(): Object = "" }
+ // trait U extends T
+ // class C extends U
+ // class D { def f(u: U) = u.clone() }
+ // The invocation `u.clone()` needs `T` as a receiver:
+ // - using Object is illegal, as Object.clone is protected
+ // - using U results in a `NoSuchMethodError: U.clone. This is the JVM bug.
+ // Note that a mixin forwarder is generated, so the correct method is executed in the end:
+ // class C { override def clone(): Object = super[T].clone() }
+ val isTraitMethodOverridingObjectMember = {
+ receiver != methodOwner && // fast path - the boolean is used to pick either of these two, if they are the same it does not matter
+ style.isVirtual &&
+ receiver.isTraitOrInterface &&
+ ObjectTpe.decl(method.name).exists && // fast path - compute overrideChain on the next line only if necessary
+ method.overrideChain.last.owner == ObjectClass
}
+ if (isTraitMethodOverridingObjectMember) methodOwner else receiver
}
- if (style.isStatic) {
- if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) }
- else { bc.invokestatic (jowner, jname, mdescr, pos) }
- }
- else if (style.isDynamic) {
- if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) }
- else { bc.invokevirtual (jowner, jname, mdescr, pos) }
+ receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits
+ val receiverBType = classBTypeFromSymbol(receiverClass)
+ val receiverName = receiverBType.internalName
+
+ def needsInterfaceCall(sym: Symbol) = {
+ sym.isTraitOrInterface ||
+ sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
}
- else {
- assert(style.isSuper, s"An unknown InvokeStyle: $style")
- bc.invokespecial(jowner, jname, mdescr, pos)
- initModule()
+
+ val jname = method.javaSimpleName.toString
+ val bmType = methodBTypeFromSymbol(method)
+ val mdescr = bmType.descriptor
+
+ val isInterface = receiverBType.isInterface.get
+ import InvokeStyle._
+ if (style == Super) {
+ assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner")
+ if (receiverClass.isTrait && !receiverClass.isJavaDefined) {
+ val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor
+ val staticName = traitSuperAccessorName(method).toString
+ bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
+ } else {
+ if (receiverClass.isTraitOrInterface) {
+ // An earlier check in Mixin reports an error in this case, so it doesn't reach the backend
+ assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.")
+ }
+ bc.invokespecial(receiverName, jname, mdescr, isInterface, pos)
+ }
+ } else {
+ val opc = style match {
+ case Static => Opcodes.INVOKESTATIC
+ case Special => Opcodes.INVOKESPECIAL
+ case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL
+ }
+ bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos)
}
+ bmType.returnType
} // end of genCallMethod()
/* Generate the scala ## method. */
def genScalaHash(tree: Tree, applyPos: Position): BType = {
- genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ?
- genLoad(tree, ObjectReference)
- genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos)
-
- INT
+ genLoad(tree, ObjectRef)
+ genCallMethod(hashMethodSym, InvokeStyle.Static, applyPos)
}
/*
@@ -1082,86 +1119,98 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
/* Emit code to compare the two top-most stack values using the 'op' operator. */
- private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) {
- if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- bc.emitIF_ICMP(op, success)
- } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
- bc.emitIF_ACMP(op, success)
- } else {
- (tk: @unchecked) match {
- case LONG => emit(asm.Opcodes.LCMP)
- case FLOAT =>
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG)
- else emit(asm.Opcodes.FCMPL)
- case DOUBLE =>
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG)
- else emit(asm.Opcodes.DCMPL)
+ private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) {
+ if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated)
+ else {
+ if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ bc.emitIF_ICMP(op, success)
+ } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
+ bc.emitIF_ACMP(op, success)
+ } else {
+ def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE
+ (tk: @unchecked) match {
+ case LONG => emit(asm.Opcodes.LCMP)
+ case FLOAT => emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL)
+ case DOUBLE => emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL)
+ }
+ bc.emitIF(op, success)
}
- bc.emitIF(op, success)
+ if (targetIfNoJump != failure) bc goTo failure
}
- bc goTo failure
}
/* Emits code to compare (and consume) stack-top and zero using the 'op' operator */
- private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) {
- if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- bc.emitIF(op, success)
- } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
- // @unchecked because references aren't compared with GT, GE, LT, LE.
- (op : @unchecked) match {
- case icodes.EQ => bc emitIFNULL success
- case icodes.NE => bc emitIFNONNULL success
- }
- } else {
- (tk: @unchecked) match {
- case LONG =>
- emit(asm.Opcodes.LCONST_0)
- emit(asm.Opcodes.LCMP)
- case FLOAT =>
- emit(asm.Opcodes.FCONST_0)
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG)
- else emit(asm.Opcodes.FCMPL)
- case DOUBLE =>
- emit(asm.Opcodes.DCONST_0)
- if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG)
- else emit(asm.Opcodes.DCMPL)
+ private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label, negated: Boolean = false) {
+ if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump, negated = !negated)
+ else {
+ if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ bc.emitIF(op, success)
+ } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_)
+ op match { // references are only compared with EQ and NE
+ case TestOp.EQ => bc emitIFNULL success
+ case TestOp.NE => bc emitIFNONNULL success
+ }
+ } else {
+ def useCmpG = if (negated) op == TestOp.GT || op == TestOp.GE else op == TestOp.LT || op == TestOp.LE
+ (tk: @unchecked) match {
+ case LONG =>
+ emit(asm.Opcodes.LCONST_0)
+ emit(asm.Opcodes.LCMP)
+ case FLOAT =>
+ emit(asm.Opcodes.FCONST_0)
+ emit(if (useCmpG) asm.Opcodes.FCMPG else asm.Opcodes.FCMPL)
+ case DOUBLE =>
+ emit(asm.Opcodes.DCONST_0)
+ emit(if (useCmpG) asm.Opcodes.DCMPG else asm.Opcodes.DCMPL)
+ }
+ bc.emitIF(op, success)
}
- bc.emitIF(op, success)
+ if (targetIfNoJump != failure) bc goTo failure
}
- bc goTo failure
}
- val testOpForPrimitive: Array[TestOp] = Array(
- icodes.EQ, icodes.NE, icodes.EQ, icodes.NE, icodes.LT, icodes.LE, icodes.GE, icodes.GT
- )
+ def testOpForPrimitive(primitiveCode: Int) = (primitiveCode: @switch) match {
+ case scalaPrimitives.ID => TestOp.EQ
+ case scalaPrimitives.NI => TestOp.NE
+ case scalaPrimitives.EQ => TestOp.EQ
+ case scalaPrimitives.NE => TestOp.NE
+ case scalaPrimitives.LT => TestOp.LT
+ case scalaPrimitives.LE => TestOp.LE
+ case scalaPrimitives.GT => TestOp.GT
+ case scalaPrimitives.GE => TestOp.GE
+ }
+
+ /** Some useful equality helpers. */
+ def isNull(t: Tree) = PartialFunction.cond(t) { case Literal(Constant(null)) => true }
+ def isLiteral(t: Tree) = PartialFunction.cond(t) { case Literal(_) => true }
+ def isNonNullExpr(t: Tree) = isLiteral(t) || ((t.symbol ne null) && t.symbol.isModule)
+ /** If l or r is constant null, returns the other ; otherwise null */
+ def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null
/*
* Generate code for conditional expressions.
* The jump targets success/failure of the test are `then-target` and `else-target` resp.
*/
- private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) {
+ private def genCond(tree: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label) {
def genComparisonOp(l: Tree, r: Tree, code: Int) {
- val op: TestOp = testOpForPrimitive(code - scalaPrimitives.ID)
- // special-case reference (in)equality test for null (null eq x, x eq null)
- var nonNullSide: Tree = null
- if (scalaPrimitives.isReferenceEqualityOp(code) &&
- { nonNullSide = ifOneIsNull(l, r); nonNullSide != null }
- ) {
- genLoad(nonNullSide, ObjectReference)
- genCZJUMP(success, failure, op, ObjectReference)
- }
- else {
+ val op = testOpForPrimitive(code)
+ val nonNullSide = if (scalaPrimitives.isReferenceEqualityOp(code)) ifOneIsNull(l, r) else null
+ if (nonNullSide != null) {
+ // special-case reference (in)equality test for null (null eq x, x eq null)
+ genLoad(nonNullSide, ObjectRef)
+ genCZJUMP(success, failure, op, ObjectRef, targetIfNoJump)
+ } else {
val tk = tpeTK(l).maxType(tpeTK(r))
genLoad(l, tk)
genLoad(r, tk)
- genCJUMP(success, failure, op, tk)
+ genCJUMP(success, failure, op, tk, targetIfNoJump)
}
}
- def default() = {
+ def loadAndTestBoolean() = {
genLoad(tree, BOOL)
- genCZJUMP(success, failure, icodes.NE, BOOL)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
}
lineNumber(tree)
@@ -1172,37 +1221,35 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
// lhs and rhs of test
lazy val Select(lhs, _) = fun
- val rhs = if (args.isEmpty) EmptyTree else args.head; // args.isEmpty only for ZNOT
+ val rhs = if (args.isEmpty) EmptyTree else args.head // args.isEmpty only for ZNOT
- def genZandOrZor(and: Boolean) { // TODO WRONG
+ def genZandOrZor(and: Boolean) {
// reaching "keepGoing" indicates the rhs should be evaluated too (ie not short-circuited).
val keepGoing = new asm.Label
- if (and) genCond(lhs, keepGoing, failure)
- else genCond(lhs, success, keepGoing)
+ if (and) genCond(lhs, keepGoing, failure, targetIfNoJump = keepGoing)
+ else genCond(lhs, success, keepGoing, targetIfNoJump = keepGoing)
markProgramPoint(keepGoing)
- genCond(rhs, success, failure)
+ genCond(rhs, success, failure, targetIfNoJump)
}
getPrimitive(fun.symbol) match {
- case ZNOT => genCond(lhs, failure, success)
+ case ZNOT => genCond(lhs, failure, success, targetIfNoJump)
case ZAND => genZandOrZor(and = true)
case ZOR => genZandOrZor(and = false)
case code =>
- // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null)
if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) {
- // `lhs` has reference type
- if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos)
- else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos)
- }
- else if (scalaPrimitives.isComparisonOp(code))
+ // rewrite `==` to null tests and `equals`. not needed for arrays (`equals` is reference equality).
+ if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, targetIfNoJump, tree.pos)
+ else genEqEqPrimitive(lhs, rhs, failure, success, targetIfNoJump, tree.pos)
+ } else if (scalaPrimitives.isComparisonOp(code)) {
genComparisonOp(lhs, rhs, code)
- else
- default
+ } else
+ loadAndTestBoolean()
}
- case _ => default
+ case _ => loadAndTestBoolean()
}
} // end of genCond()
@@ -1214,69 +1261,75 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
* @param l left-hand-side of the '=='
* @param r right-hand-side of the '=='
*/
- def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) {
+ def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label, pos: Position) {
/* True if the equality comparison is between values that require the use of the rich equality
- * comparator (scala.runtime.Comparator.equals). This is the case when either side of the
+ * comparator (scala.runtime.BoxesRunTime.equals). This is the case when either side of the
* comparison might have a run-time type subtype of java.lang.Number or java.lang.Character.
- * When it is statically known that both sides are equal and subtypes of Number of Character,
- * not using the rich equality is possible (their own equals method will do ok.)
+ *
+ * When it is statically known that both sides are equal and subtypes of Number or Character,
+ * not using the rich equality is possible (their own equals method will do ok), except for
+ * java.lang.Float and java.lang.Double: their `equals` have different behavior around `NaN`
+ * and `-0.0`, see Javadoc (scala-dev#329).
*/
val mustUseAnyComparator: Boolean = {
- val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
-
- !areSameFinals && platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol)
+ platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol) && {
+ val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) && {
+ val sym = l.tpe.typeSymbol
+ sym != BoxedFloatClass && sym != BoxedDoubleClass
+ }
+ !areSameFinals
+ }
}
if (mustUseAnyComparator) {
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
}
- genLoad(l, ObjectReference)
- genLoad(r, ObjectReference)
- genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos)
- genCZJUMP(success, failure, icodes.NE, BOOL)
- }
- else {
+ genLoad(l, ObjectRef)
+ genLoad(r, ObjectRef)
+ genCallMethod(equalsMethod, InvokeStyle.Static, pos)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
+ } else {
if (isNull(l)) {
// null == expr -> expr eq null
- genLoad(r, ObjectReference)
- genCZJUMP(success, failure, icodes.EQ, ObjectReference)
+ genLoad(r, ObjectRef)
+ genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump)
} else if (isNull(r)) {
// expr == null -> expr eq null
- genLoad(l, ObjectReference)
- genCZJUMP(success, failure, icodes.EQ, ObjectReference)
+ genLoad(l, ObjectRef)
+ genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump)
} else if (isNonNullExpr(l)) {
// SI-7852 Avoid null check if L is statically non-null.
- genLoad(l, ObjectReference)
- genLoad(r, ObjectReference)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
- genCZJUMP(success, failure, icodes.NE, BOOL)
+ genLoad(l, ObjectRef)
+ genLoad(r, ObjectRef)
+ genCallMethod(Object_equals, InvokeStyle.Virtual, pos)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
} else {
// l == r -> if (l eq null) r eq null else l.equals(r)
- val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.toString)
+ val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.toString)
val lNull = new asm.Label
val lNonNull = new asm.Label
- genLoad(l, ObjectReference)
- genLoad(r, ObjectReference)
+ genLoad(l, ObjectRef)
+ genLoad(r, ObjectRef)
locals.store(eqEqTempLocal)
- bc dup ObjectReference
- genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectReference)
+ bc dup ObjectRef
+ genCZJUMP(lNull, lNonNull, TestOp.EQ, ObjectRef, targetIfNoJump = lNull)
markProgramPoint(lNull)
- bc drop ObjectReference
+ bc drop ObjectRef
locals.load(eqEqTempLocal)
- genCZJUMP(success, failure, icodes.EQ, ObjectReference)
+ genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump = lNonNull)
markProgramPoint(lNonNull)
locals.load(eqEqTempLocal)
- genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos)
- genCZJUMP(success, failure, icodes.NE, BOOL)
+ genCallMethod(Object_equals, InvokeStyle.Virtual, pos)
+ genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump)
}
}
}
@@ -1285,44 +1338,37 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
def genSynchronized(tree: Apply, expectedType: BType): BType
def genLoadTry(tree: Try): BType
- def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) {
+ def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol, isSerializable: Boolean, addScalaSerializableMarker: Boolean) {
val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC)
def asmType(sym: Symbol) = classBTypeFromSymbol(sym).toASMType
+ val isInterface = lambdaTarget.owner.isTrait
val implMethodHandle =
- new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL,
+ new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else if (isInterface) asm.Opcodes.H_INVOKEINTERFACE else asm.Opcodes.H_INVOKEVIRTUAL,
classBTypeFromSymbol(lambdaTarget.owner).internalName,
lambdaTarget.name.toString,
- asmMethodType(lambdaTarget).descriptor)
+ methodBTypeFromSymbol(lambdaTarget).descriptor,
+ /* itf = */ isInterface)
val receiver = if (isStaticMethod) Nil else lambdaTarget.owner :: Nil
val (capturedParams, lambdaParams) = lambdaTarget.paramss.head.splitAt(lambdaTarget.paramss.head.length - arity)
- // Requires https://github.com/scala/scala-java8-compat on the runtime classpath
- val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => toTypeKind(sym.info).toASMType): _*)
-
- val constrainedType = new MethodBType(lambdaParams.map(p => toTypeKind(p.tpe)), toTypeKind(lambdaTarget.tpe.resultType)).toASMType
- val sam = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply))
- val samName = sam.name.toString
- val samMethodType = asmMethodType(sam).toASMType
-
- val flags = 3 // TODO 2.12.x Replace with LambdaMetafactory.FLAG_SERIALIZABLE | LambdaMetafactory.FLAG_MARKERS
-
- val ScalaSerializable = classBTypeFromSymbol(definitions.SerializableClass).toASMType
- bc.jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryBootstrapHandle,
- /* samMethodType = */ samMethodType,
- /* implMethod = */ implMethodHandle,
- /* instantiatedMethodType = */ constrainedType,
- /* flags = */ flags.asInstanceOf[AnyRef],
- /* markerInterfaceCount = */ 1.asInstanceOf[AnyRef],
- /* markerInterfaces[0] = */ ScalaSerializable,
- /* bridgeCount = */ 0.asInstanceOf[AnyRef]
- )
- indyLambdaHosts += this.claszSymbol
+ val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => typeToBType(sym.info).toASMType): _*)
+ val constrainedType = new MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType
+ val samMethodType = methodBTypeFromSymbol(sam).toASMType
+ val markers = if (addScalaSerializableMarker) classBTypeFromSymbol(definitions.SerializableClass).toASMType :: Nil else Nil
+ visitInvokeDynamicInsnLMF(bc.jmethod, sam.name.toString, invokedType, samMethodType, implMethodHandle, constrainedType, isSerializable, markers)
+ if (isSerializable)
+ addIndyLambdaImplMethod(cnode.name, implMethodHandle :: Nil)
}
}
- lazy val lambdaMetaFactoryBootstrapHandle =
- new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
- definitions.LambdaMetaFactory.fullName('/'), sn.AltMetafactory.toString,
- "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;")
+ private def visitInvokeDynamicInsnLMF(jmethod: MethodNode, samName: String, invokedType: String, samMethodType: asm.Type,
+ implMethodHandle: asm.Handle, instantiatedMethodType: asm.Type,
+ serializable: Boolean, markerInterfaces: Seq[asm.Type]) = {
+ import java.lang.invoke.LambdaMetafactory.{FLAG_MARKERS, FLAG_SERIALIZABLE}
+ def flagIf(b: Boolean, flag: Int): Int = if (b) flag else 0
+ val flags = FLAG_MARKERS | flagIf(serializable, FLAG_SERIALIZABLE)
+ val bsmArgs = Seq(samMethodType, implMethodHandle, instantiatedMethodType, Int.box(flags), Int.box(markerInterfaces.length)) ++ markerInterfaces
+ jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryAltMetafactoryHandle, bsmArgs: _*)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 1b97681743..a74c70a684 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -8,10 +8,10 @@ package tools.nsc
package backend.jvm
import scala.tools.asm
-import scala.collection.mutable
import scala.tools.nsc.io.AbstractFile
import GenBCode._
import BackendReporting._
+import scala.reflect.internal.Flags
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -22,8 +22,223 @@ import BackendReporting._
*/
abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
import global._
+ import definitions._
import bTypes._
import coreBTypes._
+ import BTypes.{InternalName, InlineInfo, MethodInlineInfo}
+
+ /**
+ * True for classes generated by the Scala compiler that are considered top-level in terms of
+ * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes.
+ */
+ def considerAsTopLevelImplementationArtifact(classSym: Symbol) = classSym.isSpecialized
+
+ /**
+ * Cache the value of delambdafy == "inline" for each run. We need to query this value many
+ * times, so caching makes sense.
+ */
+ object delambdafyInline {
+ private var runId = -1
+ private var value = false
+
+ def apply(): Boolean = {
+ if (runId != global.currentRunId) {
+ runId = global.currentRunId
+ value = settings.Ydelambdafy.value == "inline"
+ }
+ value
+ }
+ }
+
+ def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]
+
+ final def traitSuperAccessorName(sym: Symbol): Name = {
+ val name = sym.javaSimpleName
+ if (sym.isMixinConstructor) name
+ else name.append(nme.NAME_JOIN_STRING)
+ }
+
+ /**
+ * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
+ * member class. This method is used to decide if we should emit an EnclosingMethod attribute.
+ * It is also used to decide whether the "owner" field in the InnerClass attribute should be
+ * null.
+ */
+ def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
+ assert(classSym.isClass, s"not a class: $classSym")
+ val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
+ if (r) {
+ // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true.
+ // we prevent this, see `nonAnon` in LambdaLift.
+ // phase travel necessary: after flatten, the name includes the name of outer classes.
+ // if some outer name contains $lambda, a non-lambda class is considered lambda.
+ assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name)
+ }
+ r
+ }
+
+ /**
+ * The next enclosing definition in the source structure. Includes anonymous function classes
+ * under delambdafy:inline, even though they are only generated during UnCurry.
+ */
+ def nextEnclosing(sym: Symbol): Symbol = {
+ val origOwner = sym.originalOwner
+ // phase travel necessary: after flatten, the name includes the name of outer classes.
+ // if some outer name contains $anon, a non-anon class is considered anon.
+ if (delambdafyInline() && exitingPickler(sym.rawowner.isAnonymousFunction)) {
+ // SI-9105: special handling for anonymous functions under delambdafy:inline.
+ //
+ // class C { def t = () => { def f { class Z } } }
+ //
+ // class C { def t = byNameMethod { def f { class Z } } }
+ //
+ // In both examples, the method f lambda-lifted into the anonfun class.
+ //
+ // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun.
+ // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ...
+ //
+ // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!)
+ // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!).
+ //
+ // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if`
+ // above makes sure we don't jump over the anonymous function in the by-name argument case.
+ //
+ // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f
+ // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym`
+ // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and
+ // we need to return it.
+ // If the rawowners are different, the symbol was not in between. In the first example, the
+ // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing
+ // of `f` is its rawowner, the anonFunClassSym.
+ //
+ // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C,
+ // not into the anonymous function class. The originalOwner chain is Z - f - C.
+ if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner
+ else sym.rawowner
+ } else {
+ origOwner
+ }
+ }
+
+ def nextEnclosingClass(sym: Symbol): Symbol =
+ if (sym.isClass) sym
+ else nextEnclosingClass(nextEnclosing(sym))
+
+ def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) =
+ nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass
+
+ /**
+ * Returns the enclosing method for non-member classes. In the following example
+ *
+ * class A {
+ * def f = {
+ * class B {
+ * class C
+ * }
+ * }
+ * }
+ *
+ * the method returns Some(f) for B, but None for C, because C is a member class. For non-member
+ * classes that are not enclosed by a method, it returns None:
+ *
+ * class A {
+ * { class B }
+ * }
+ *
+ * In this case, for B, we return None.
+ *
+ * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
+ * This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
+ */
+ private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
+ assert(classSym.isClass, classSym)
+
+ def doesNotExist(method: Symbol) = {
+ // Value classes. Member methods of value classes exist in the generated box class. However,
+ // nested methods lifted into a value class are moved to the companion object and don't exist
+ // in the value class itself. We can identify such nested methods: the initial enclosing class
+ // is a value class, but the current owner is some other class (the module class).
+ val enclCls = nextEnclosingClass(method)
+ exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls
+ }
+
+ def enclosingMethod(sym: Symbol): Option[Symbol] = {
+ if (sym.isClass || sym == NoSymbol) None
+ else if (sym.isMethod && !sym.isGetter) {
+ if (doesNotExist(sym)) None else Some(sym)
+ }
+ else enclosingMethod(nextEnclosing(sym))
+ }
+ enclosingMethod(nextEnclosing(classSym))
+ }
+
+ /**
+ * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
+ * property, this method looks at the originalOwner chain. See doc in BTypes.
+ */
+ private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
+ assert(classSym.isClass, classSym)
+ val r = nextEnclosingClass(nextEnclosing(classSym))
+ r
+ }
+
+ final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
+
+ /**
+ * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
+ * an anonymous or local class). See doc in BTypes.
+ *
+ * The class is parameterized by two functions to obtain a bytecode class descriptor for a class
+ * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
+ * on the implementation of GenASM / GenBCode, so they need to be passed in.
+ */
+ def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = {
+ // specialized classes are always top-level, see comment in BTypes
+ if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
+ val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
+ val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym)
+ for (m <- methodOpt) assert(m.owner == enclosingClass, s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
+ Some(EnclosingMethodEntry(
+ classDesc(enclosingClass),
+ methodOpt.map(_.javaSimpleName.toString).orNull,
+ methodOpt.map(methodDesc).orNull))
+ } else {
+ None
+ }
+ }
+
+ /**
+ * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
+ *
+ * The problem is that we are interested in a source-level property. Various phases changed the
+ * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
+ * Therefore, `sym.isStatic` is not what we want. For example, in
+ * object T { def f { object U } }
+ * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
+ */
+ def isOriginallyStaticOwner(sym: Symbol): Boolean =
+ sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
+
+ /**
+ * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
+ * cannot change the typer context of the completer at this point and make it silent: the context
+ * captured when creating the completer in the namer. However, we can temporarily replace
+ * global.reporter (it's a var) to store errors.
+ */
+ def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean =
+ if (sym.hasCompleteInfo) false
+ else {
+ val originalReporter = global.reporter
+ val storeReporter = new reporters.StoreReporter()
+ global.reporter = storeReporter
+ try {
+ sym.info
+ } finally {
+ global.reporter = originalReporter
+ }
+ sym.isErroneous
+ }
+
/*
* must-single-thread
@@ -117,7 +332,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
else if (companion.isTrait)
failNoForwarder("companion is a trait")
- // Now either succeeed, or issue some additional warnings for things which look like
+ // Now either succeed, or issue some additional warnings for things which look like
// attempts to be java main methods.
else (possibles exists definitions.isJavaMainMethod) || {
possibles exists { m =>
@@ -191,20 +406,18 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
/*
- * Populates the InnerClasses JVM attribute with `refedInnerClasses`.
- * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted)
- * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass`
- * but otherwise not mentioned in `jclass`.
+ * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner
+ * classes in BTypes.scala.
*
- * `refedInnerClasses` may contain duplicates,
- * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency).
+ * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
+ * each inner class it lists (those are looked up and included).
*
- * This method serializes in the InnerClasses JVM attribute in an appropriate order,
- * not necessarily that given by `refedInnerClasses`.
+ * This method serializes in the InnerClasses JVM attribute in an appropriate order, not
+ * necessarily that given by `refedInnerClasses`.
*
* can-multi-thread
*/
- final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
+ final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) {
val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
@@ -310,85 +523,114 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
final val emitLines = debugLevel >= 2
final val emitVars = debugLevel >= 3
- /*
- * Contains class-symbols that:
- * (a) are known to denote inner classes
- * (b) are mentioned somewhere in the class being generated.
- *
- * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated".
- */
- final val innerClassBufferASM = mutable.Set.empty[ClassBType]
-
/**
- * The class internal name for a given class symbol. If the symbol describes a nested class, the
- * ClassBType is added to the innerClassBufferASM.
+ * The class internal name for a given class symbol.
*/
- final def internalName(sym: Symbol): String = {
- // For each java class, the scala compiler creates a class and a module (thus a module class).
- // If the `sym` is a java module class, we use the java class instead. This ensures that we
- // register the class (instead of the module class) in innerClassBufferASM.
- // The two symbols have the same name, so the resulting internalName is the same.
- // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
- val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
- getClassBTypeAndRegisterInnerClass(classSym).internalName
- }
+ final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
+ } // end of trait BCInnerClassGen
+
+ trait BCAnnotGen extends BCInnerClassGen {
+ private lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule
+ private lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
+ private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
+ private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
/**
- * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the
- * innerClassBufferASM.
+ * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the
+ * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding
+ * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure).
*
- * TODO: clean up the way we track referenced inner classes.
- * doing it during code generation is not correct when the optimizer changes the code.
+ * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend.
+ * Java annotations on the other hand are additionally emitted to the classfile in Java's format.
+ *
+ * This means that [[Type]] instances within an AnnotationInfo reach the backend non-erased. Examples:
+ * - @(javax.annotation.Resource @annotation.meta.getter) val x = 0
+ * Here, annotationInfo.atp is an AnnotatedType.
+ * - @SomeAnnotation[T] val x = 0
+ * In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot
+ * actually happen because Java annotations cannot be generic.
+ * - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0
+ * The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the
+ * non-erased existential type.
*/
- final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = {
- val r = classBTypeFromSymbol(sym)
- if (r.isNestedClass.get) innerClassBufferASM += r
- r
+ def erasedType(tp: Type): Type = enteringErasure {
+ // make sure we don't erase value class references to the type that the value class boxes
+ // this is basically the same logic as in erasure's preTransform, case Literal(classTag).
+ tp.dealiasWiden match {
+ case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr)
+ case tpe => erasure.erasure(tpe.typeSymbol)(tpe)
+ }
}
- /**
- * The BType for a type reference. If the result is a ClassBType for a nested class, it is added
- * to the innerClassBufferASM.
- * TODO: clean up the way we track referenced inner classes.
- */
- final def toTypeKind(t: Type): BType = typeToBType(t) match {
- case c: ClassBType if c.isNestedClass.get =>
- innerClassBufferASM += c
- c
- case r => r
+ def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor
+
+ /** Whether an annotation should be emitted as a Java annotation
+ * .initialize: if 'annot' is read from pickle, atp might be uninitialized
+ */
+ private def shouldEmitAnnotation(annot: AnnotationInfo) = {
+ annot.symbol.initialize.isJavaDefined &&
+ annot.matches(ClassfileAnnotationClass) &&
+ retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
+ annot.args.isEmpty
}
- /**
- * Class components that are nested classes are added to the innerClassBufferASM.
- * TODO: clean up the way we track referenced inner classes.
- */
- final def asmMethodType(msym: Symbol): MethodBType = {
- val r = methodBTypeFromSymbol(msym)
- (r.returnType :: r.argumentTypes) foreach {
- case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c
+ private def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
+ annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
+ case Some(retentionAnnot) =>
+ retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
case _ =>
+ // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
+ // annotation is emitted with visibility `RUNTIME`
+ true
}
- r
}
- /**
- * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to
- * the innerClassBufferASM.
- */
- final def descriptor(t: Type): String = { toTypeKind(t).descriptor }
-
- /**
- * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added
- * to the innerClassBufferASM.
- */
- final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor }
-
- } // end of trait BCInnerClassGen
-
- trait BCAnnotGen extends BCInnerClassGen {
+ private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
+ annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
+ assoc.collectFirst {
+ case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
+ }).getOrElse(AnnotationRetentionPolicyClassValue)
+
+ def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
+ val ca = new Array[Char](bytes.length)
+ var idx = 0
+ while(idx < bytes.length) {
+ val b: Byte = bytes(idx)
+ assert((b & ~0x7f) == 0)
+ ca(idx) = b.asInstanceOf[Char]
+ idx += 1
+ }
+ ca
+ }
- import genASM.{ubytesToCharArray, arrEncode}
- import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible}
+ final def arrEncode(sb: ScalaSigBytes): Array[String] = {
+ var strs: List[String] = Nil
+ val bSeven: Array[Byte] = sb.sevenBitsMayBeZero
+ // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure)
+ var prevOffset = 0
+ var offset = 0
+ var encLength = 0
+ while(offset < bSeven.length) {
+ val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1)
+ val newEncLength = encLength.toLong + deltaEncLength
+ if(newEncLength >= 65535) {
+ val ba = bSeven.slice(prevOffset, offset)
+ strs ::= new java.lang.String(ubytesToCharArray(ba))
+ encLength = 0
+ prevOffset = offset
+ } else {
+ encLength += deltaEncLength
+ offset += 1
+ }
+ }
+ if(prevOffset < offset) {
+ assert(offset == bSeven.length)
+ val ba = bSeven.slice(prevOffset, offset)
+ strs ::= new java.lang.String(ubytesToCharArray(ba))
+ }
+ assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict?
+ strs.reverse.toArray
+ }
/*
* can-multi-thread
@@ -420,9 +662,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
case StringTag =>
assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
- case ClazzTag => av.visit(name, toTypeKind(const.typeValue).toASMType)
+ case ClazzTag =>
+ av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType)
case EnumTag =>
- val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
+ val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class.
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
av.visitEnum(name, edesc, evalue)
}
@@ -435,7 +678,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
av.visit(name, strEncode(sb))
} else {
val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
- for(arg <- genASM.arrEncode(sb)) { arrAnnotV.visit(name, arg) }
+ for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) }
arrAnnotV.visitEnd()
} // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape.
@@ -447,7 +690,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
case NestedAnnotArg(annInfo) =>
val AnnotationInfo(typ, args, assocs) = annInfo
assert(args.isEmpty, args)
- val desc = descriptor(typ) // the class descriptor of the nested annotation class
+ val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class
val nestedVisitor = av.visitAnnotation(name, desc)
emitAssocs(nestedVisitor, assocs)
}
@@ -472,7 +715,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
+ val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
@@ -484,7 +727,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
+ val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
@@ -496,7 +739,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
+ val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(av, assocs)
}
}
@@ -511,16 +754,40 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
annot <- annots) {
val AnnotationInfo(typ, args, assocs) = annot
assert(args.isEmpty, args)
- val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
+ val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot))
emitAssocs(pannVisitor, assocs)
}
}
+ /*
+ * must-single-thread
+ */
+ def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = {
+ for (param <- params) {
+ var access = asm.Opcodes.ACC_FINAL
+ if (param.isArtifact)
+ access |= asm.Opcodes.ACC_SYNTHETIC
+ jmethod.visitParameter(param.name.decoded, access)
+ }
+ }
} // end of trait BCAnnotGen
trait BCJGenSigGen {
- def getCurrentCUnit(): CompilationUnit
+ // @M don't generate java generics sigs for (members of) implementation
+ // classes, as they are monomorphic (TODO: ok?)
+ private def needsGenericSignature(sym: Symbol) = !(
+ // PP: This condition used to include sym.hasExpandedName, but this leads
+ // to the total loss of generic information if a private member is
+ // accessed from a closure: both the field and the accessor were generated
+ // without it. This is particularly bad because the availability of
+ // generic information could disappear as a consequence of a seemingly
+ // unrelated change.
+ settings.Ynogenericsig
+ || sym.isArtifact
+ || sym.isLiftedMethod
+ || sym.isBridge
+ )
/* @return
* - `null` if no Java signature is to be added (`null` is what ASM expects in these cases).
@@ -528,7 +795,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
*
* must-single-thread
*/
- def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit())
+ def getGenericSignature(sym: Symbol, owner: Symbol): String = {
+ val memberTpe = enteringErasure(owner.thisType.memberInfo(sym))
+ getGenericSignature(sym, owner, memberTpe)
+ }
+
+ def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = {
+ if (!needsGenericSignature(sym)) { return null }
+
+ val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe)
+ if (jsOpt.isEmpty) { return null }
+
+ val sig = jsOpt.get
+ log(sig) // This seems useful enough in the general case.
+
+ def wrap(op: => Unit) = {
+ try { op; true }
+ catch { case _: Throwable => false }
+ }
+
+ if (settings.Xverify) {
+ // Run the signature parser to catch bogus signatures.
+ val isValidSignature = wrap {
+ // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser)
+ import scala.tools.asm.util.CheckClassAdapter
+ if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar
+ else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig }
+ else { CheckClassAdapter checkClassSignature sig }
+ }
+
+ if(!isValidSignature) {
+ reporter.warning(sym.pos,
+ sm"""|compiler bug: created invalid generic signature for $sym in ${sym.owner.skipPackageObject.fullName}
+ |signature: $sig
+ |if this is reproducible, please report bug at https://issues.scala-lang.org/
+ """.trim)
+ return null
+ }
+ }
+
+ if ((settings.check containsName phaseName)) {
+ val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe))
+ val bytecodeTpe = owner.thisType.memberInfo(sym)
+ if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) {
+ reporter.warning(sym.pos,
+ sm"""|compiler bug: created generic signature for $sym in ${sym.owner.skipPackageObject.fullName} that does not conform to its erasure
+ |signature: $sig
+ |original type: $memberTpe
+ |normalized type: $normalizedTpe
+ |erasure type: $bytecodeTpe
+ |if this is reproducible, please report bug at http://issues.scala-lang.org/
+ """.trim)
+ return null
+ }
+ }
+
+ sig
+ }
} // end of trait BCJGenSigGen
@@ -541,11 +864,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* must-single-thread
*/
def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
- val needsAnnotation = (
- ( isRemoteClass ||
- isRemote(meth) && isJMethodPublic
- ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass)
- )
+ def hasThrowsRemoteException = meth.annotations.exists {
+ case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass
+ case _ => false
+ }
+ val needsAnnotation = {
+ (isRemoteClass ||
+ isRemote(meth) && isJMethodPublic
+ ) && !hasThrowsRemoteException
+ }
if (needsAnnotation) {
val c = Constant(definitions.RemoteExceptionClass.tpe)
val arg = Literal(c) setType c.tpe
@@ -557,10 +884,23 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
*
* must-single-thread
*/
- private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) {
- val moduleName = internalName(module)
- val methodInfo = module.thisType.memberInfo(m)
- val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind
+ private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, moduleClass: Symbol, m: Symbol): Unit = {
+ def staticForwarderGenericSignature: String = {
+ // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to.
+ // By rights, it should use the signature as-seen-from the module class, and add suitable
+ // primitive and value-class boxing/unboxing.
+ // But for now, just like we did in mixin, we just avoid writing a wrong generic signature
+ // (one that doesn't erase to the actual signature). See run/t3452b for a test case.
+ val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(m))
+ val erasedMemberType = erasure.erasure(m)(memberTpe)
+ if (erasedMemberType =:= m.info)
+ getGenericSignature(m, moduleClass, memberTpe)
+ else null
+ }
+
+ val moduleName = internalName(moduleClass)
+ val methodInfo = moduleClass.thisType.memberInfo(m)
+ val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType
// val paramNames = 0 until paramJavaTypes.length map ("x_" + _)
/* Forwarders must not be marked final,
@@ -574,12 +914,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
)
// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
- val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit())
+ val jgensig = staticForwarderGenericSignature
addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass)
val thrownExceptions: List[String] = getExceptions(throws)
- val jReturnType = toTypeKind(methodInfo.resultType)
+ val jReturnType = typeToBType(methodInfo.resultType)
val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor
val mirrorMethodName = m.javaSimpleName.toString
val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
@@ -595,7 +935,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
mirrorMethod.visitCode()
- mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
+ mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(moduleClass).descriptor)
var index = 0
for(jparamType <- paramJavaTypes) {
@@ -604,7 +944,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
index += jparamType.size
}
- mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).descriptor, false)
+ mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false)
mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN))
mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
@@ -629,9 +969,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
debuglog(s"Potentially conflicting names for forwarders: $conflictingNames")
- for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) {
+ for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) {
if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor)
- debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'")
+ debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}")
else if (conflictingNames(m.name))
log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}")
else if (m.hasAccessBoundary)
@@ -654,8 +994,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* must-single-thread
*/
def getExceptions(excs: List[AnnotationInfo]): List[String] = {
- for (ThrownException(exc) <- excs.distinct)
- yield internalName(exc)
+ for (ThrownException(tp) <- excs.distinct)
+ yield {
+ val erased = erasedType(tp)
+ internalName(erased.typeSymbol)
+ }
}
} // end of trait BCForwardersGen
@@ -682,60 +1025,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
new java.lang.Long(id)
).visitEnd()
}
-
- /**
- * Add:
- * private static java.util.Map $deserializeLambdaCache$ = null
- * private static Object $deserializeLambda$(SerializedLambda l) {
- * var cache = $deserializeLambdaCache$
- * if (cache eq null) {
- * cache = new java.util.HashMap()
- * $deserializeLambdaCache$ = cache
- * }
- * return scala.compat.java8.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l);
- * }
- */
- def addLambdaDeserialize(clazz: Symbol, jclass: asm.ClassVisitor): Unit = {
- val cw = jclass
- import scala.tools.asm.Opcodes._
-
- // Need to force creation of BTypes for these as `getCommonSuperClass` is called on
- // automatically computing the max stack size (`visitMaxs`) during method writing.
- javaUtilHashMapReference
- javaUtilMapReference
-
- cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC)
-
- {
- val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null)
- fv.visitEnd()
- }
-
- {
- val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null)
- mv.visitCode()
- // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol.
- mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;")
- mv.visitVarInsn(ASTORE, 1)
- mv.visitVarInsn(ALOAD, 1)
- val l0 = new asm.Label()
- mv.visitJumpInsn(IFNONNULL, l0)
- mv.visitTypeInsn(NEW, "java/util/HashMap")
- mv.visitInsn(DUP)
- mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false)
- mv.visitVarInsn(ASTORE, 1)
- 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.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.visitInsn(ARETURN)
- mv.visitEnd()
- }
- }
} // end of trait BCClassGen
/* functionality for building plain and mirror classes */
@@ -748,9 +1037,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
/* builder of mirror classes */
class JMirrorBuilder extends JCommonBuilder {
- private var cunit: CompilationUnit = _
- def getCurrentCUnit(): CompilationUnit = cunit;
-
/* Generate a mirror class for a top-level module. A mirror class is a class
* containing only static methods that forward to the corresponding method
* on the MODULE instance of the given Scala object. It will only be
@@ -762,8 +1048,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
assert(moduleClass.isModuleClass)
assert(moduleClass.companionClass == NoSymbol, moduleClass)
- innerClassBufferASM.clear()
- this.cunit = cunit
val bType = mirrorClassClassBType(moduleClass)
val mirrorClass = new asm.tree.ClassNode
@@ -772,7 +1056,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
bType.info.get.flags,
bType.internalName,
null /* no java-generic-signature */,
- ObjectReference.internalName,
+ ObjectRef.internalName,
EMPTY_STRING_ARRAY
)
@@ -785,9 +1069,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass)
- innerClassBufferASM ++= bType.info.get.nestedClasses
- addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)
-
mirrorClass.visitEnd()
("" + moduleClass.name) // this side-effect is necessary, really.
@@ -811,18 +1092,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString }
- innerClassBufferASM.clear()
-
- val flags = javaFlags(cls)
+ val beanInfoType = beanInfoClassClassBType(cls)
- val beanInfoName = (internalName(cls) + "BeanInfo")
val beanInfoClass = new asm.tree.ClassNode
beanInfoClass.visit(
classfileVersion,
- flags,
- beanInfoName,
+ beanInfoType.info.get.flags,
+ beanInfoType.internalName,
null, // no java-generic-signature
- "scala/beans/ScalaBeanInfo",
+ sbScalaBeanInfoRef.internalName,
EMPTY_STRING_ARRAY
)
@@ -859,7 +1137,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
EMPTY_STRING_ARRAY // no throwable exceptions
)
- val stringArrayJType: BType = ArrayBType(StringReference)
+ val stringArrayJType: BType = ArrayBType(StringRef)
val conJType: BType = MethodBType(
classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil,
UNIT
@@ -872,7 +1150,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitLdcInsn(new java.lang.Integer(fi))
if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) }
else { constructor.visitLdcInsn(f) }
- constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE))
+ constructor.visitInsn(StringRef.typedOpcode(asm.Opcodes.IASTORE))
fi += 1
}
}
@@ -885,12 +1163,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
// push the string array of field information
constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName)
+ constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
push(fieldList)
// push the string array of method information
constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName)
+ constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName)
push(methodList)
// invoke the superclass constructor, which will do the
@@ -901,9 +1179,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
constructor.visitEnd()
- innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses
- addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList)
-
beanInfoClass.visitEnd()
beanInfoClass
@@ -932,8 +1207,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
* must-single-thread
*/
def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) {
- // this tracks the inner class in innerClassBufferASM, if needed.
- val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass)
+ val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass)
val tdesc_creator = androidCreatorType.descriptor
cnode.visitField(
@@ -975,3 +1249,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
} // end of trait JAndroidBuilder
}
+
+object BCodeHelpers {
+ val ExcludedForwarderFlags = {
+ import scala.tools.nsc.symtab.Flags._
+ // Should include DEFERRED but this breaks findMember.
+ SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
+ }
+
+ /**
+ * Valid flags for InnerClass attribute entry.
+ * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
+ */
+ val INNER_CLASSES_FLAGS = {
+ asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
+ asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE |
+ asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
+ asm.Opcodes.ACC_ENUM
+ }
+
+ class TestOp(val op: Int) extends AnyVal {
+ import TestOp._
+ def negate = this match {
+ case EQ => NE
+ case NE => EQ
+ case LT => GE
+ case GE => LT
+ case GT => LE
+ case LE => GT
+ }
+ def opcodeIF = asm.Opcodes.IFEQ + op
+ def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op
+ }
+
+ object TestOp {
+ // the order here / op numbers are important to get the correct result when calling opcodeIF
+ val EQ = new TestOp(0)
+ val NE = new TestOp(1)
+ val LT = new TestOp(2)
+ val GE = new TestOp(3)
+ val GT = new TestOp(4)
+ val LE = new TestOp(5)
+ }
+
+ class InvokeStyle(val style: Int) extends AnyVal {
+ import InvokeStyle._
+ def isVirtual: Boolean = this == Virtual
+ def isStatic : Boolean = this == Static
+ def isSpecial: Boolean = this == Special
+ def isSuper : Boolean = this == Super
+
+ def hasInstance = this != Static
+ }
+
+ object InvokeStyle {
+ val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface
+ val Static = new InvokeStyle(1) // InvokeStatic
+ val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors)
+ val Super = new InvokeStyle(3) // InvokeSpecial (super calls)
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala
deleted file mode 100644
index 50d20921d5..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2014 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala.tools.nsc.backend.jvm
-
-import scala.tools.nsc.Global
-import PartialFunction._
-
-/**
- * This trait contains code shared between GenBCode and GenICode that depends on types defined in
- * the compiler cake (Global).
- */
-final class BCodeICodeCommon[G <: Global](val global: G) {
- import global._
-
- /** Some useful equality helpers. */
- def isNull(t: Tree) = cond(t) { case Literal(Constant(null)) => true }
- def isLiteral(t: Tree) = cond(t) { case Literal(_) => true }
- def isNonNullExpr(t: Tree) = isLiteral(t) || ((t.symbol ne null) && t.symbol.isModule)
-
- /** If l or r is constant null, returns the other ; otherwise null */
- def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index eb0da7caef..e3d45a9b3e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -12,6 +12,7 @@ import scala.annotation.switch
import scala.collection.mutable
import GenBCode._
import scala.tools.asm.tree.MethodInsnNode
+import scala.tools.nsc.backend.jvm.BCodeHelpers.TestOp
/*
* A high-level facade to the ASM API for bytecode generation.
@@ -28,9 +29,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
}
@@ -42,7 +40,7 @@ abstract class BCodeIdiomatic extends SubComponent {
if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
)
- val StringBuilderClassName = "scala/collection/mutable/StringBuilder"
+ lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName
val EMPTY_STRING_ARRAY = Array.empty[String]
val EMPTY_INT_ARRAY = Array.empty[Int]
@@ -109,41 +107,20 @@ abstract class BCodeIdiomatic extends SubComponent {
def jmethod: asm.tree.MethodNode
import asm.Opcodes;
- import icodes.opcodes.{ Static, Dynamic, SuperCall }
final def emit(opc: Int) { jmethod.visitInsn(opc) }
- /*
- * can-multi-thread
- */
- final def genPrimitiveArithmetic(op: icodes.ArithmeticOp, kind: BType) {
-
- import icodes.{ ADD, SUB, MUL, DIV, REM, NOT }
-
- op match {
-
- case ADD => add(kind)
- case SUB => sub(kind)
- case MUL => mul(kind)
- case DIV => div(kind)
- case REM => rem(kind)
-
- case NOT =>
- if (kind.isIntSizedType) {
- emit(Opcodes.ICONST_M1)
- emit(Opcodes.IXOR)
- } else if (kind == LONG) {
- jmethod.visitLdcInsn(new java.lang.Long(-1))
- jmethod.visitInsn(Opcodes.LXOR)
- } else {
- abort(s"Impossible to negate an $kind")
- }
-
- case _ =>
- abort(s"Unknown arithmetic primitive $op")
+ final def genPrimitiveNot(bType: BType): Unit = {
+ if (bType.isIntSizedType) {
+ emit(Opcodes.ICONST_M1)
+ emit(Opcodes.IXOR)
+ } else if (bType == LONG) {
+ jmethod.visitLdcInsn(new java.lang.Long(-1))
+ jmethod.visitInsn(Opcodes.LXOR)
+ } else {
+ abort(s"Impossible to negate a $bType")
}
-
- } // end of method genPrimitiveArithmetic()
+ }
/*
* can-multi-thread
@@ -207,12 +184,13 @@ abstract class BCodeIdiomatic extends SubComponent {
* can-multi-thread
*/
final def genStartConcat(pos: Position): Unit = {
- jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
+ jmethod.visitTypeInsn(Opcodes.NEW, JavaStringBuilderClassName)
jmethod.visitInsn(Opcodes.DUP)
invokespecial(
- StringBuilderClassName,
+ JavaStringBuilderClassName,
INSTANCE_CONSTRUCTOR_NAME,
"()V",
+ itf = false,
pos
)
}
@@ -220,22 +198,27 @@ abstract class BCodeIdiomatic extends SubComponent {
/*
* can-multi-thread
*/
- final def genStringConcat(el: BType, pos: Position): Unit = {
-
- val jtype =
- if (el.isArray || el.isClass) ObjectReference
- else el
-
- val bt = MethodBType(List(jtype), StringBuilderReference)
-
- invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos)
+ def genConcat(elemType: BType, pos: Position): Unit = {
+ val paramType = elemType match {
+ case ct: ClassBType if ct.isSubtypeOf(StringRef).get => StringRef
+ case ct: ClassBType if ct.isSubtypeOf(jlStringBufferRef).get => jlStringBufferRef
+ case ct: ClassBType if ct.isSubtypeOf(jlCharSequenceRef).get => jlCharSequenceRef
+ // Don't match for `ArrayBType(CHAR)`, even though StringBuilder has such an overload:
+ // `"a" + Array('b')` should NOT be "ab", but "a[C@...".
+ case _: RefBType => ObjectRef
+ // jlStringBuilder does not have overloads for byte and short, but we can just use the int version
+ case BYTE | SHORT => INT
+ case pt: PrimitiveBType => pt
+ }
+ val bt = MethodBType(List(paramType), jlStringBuilderRef)
+ invokevirtual(JavaStringBuilderClassName, "append", bt.descriptor, pos)
}
/*
* can-multi-thread
*/
final def genEndConcat(pos: Position): Unit = {
- invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
+ invokevirtual(JavaStringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
}
/*
@@ -391,41 +374,38 @@ abstract class BCodeIdiomatic extends SubComponent {
final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
// can-multi-thread
- final def invokespecial(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
+ final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
+ final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
+ final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos)
}
// can-multi-thread
- final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
+ final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos)
}
- private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
+ def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
val node = new MethodInsnNode(opcode, owner, name, desc, itf)
jmethod.instructions.add(node)
- if (settings.YoptInlinerEnabled) callsitePositions(node) = pos
- }
- final def invokedynamic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
+ if (settings.optInlinerEnabled) callsitePositions(node) = pos
}
// can-multi-thread
final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
// can-multi-thread
- final def emitIF(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) }
+ final def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) }
// can-multi-thread
- final def emitIF_ICMP(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) }
+ final def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) }
// can-multi-thread
- final def emitIF_ACMP(cond: icodes.TestOp, label: asm.Label) {
- assert((cond == icodes.EQ) || (cond == icodes.NE), cond)
- val opc = (if (cond == icodes.EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
+ final def emitIF_ACMP(cond: TestOp, label: asm.Label) {
+ assert((cond == TestOp.EQ) || (cond == TestOp.NE), cond)
+ val opc = (if (cond == TestOp.EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
jmethod.visitJumpInsn(opc, label)
}
// can-multi-thread
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index a9b6a312e9..03df1c76fa 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -8,13 +8,12 @@ package scala.tools.nsc
package backend
package jvm
-import scala.collection.{ mutable, immutable }
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository
+import scala.collection.{immutable, mutable}
import scala.tools.nsc.symtab._
-
import scala.tools.asm
import GenBCode._
import BackendReporting._
+import scala.tools.nsc.backend.jvm.BCodeHelpers.InvokeStyle
/*
*
@@ -26,7 +25,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
import global._
import bTypes._
import coreBTypes._
- import bCodeAsmCommon._
/*
* There's a dedicated PlainClassBuilder for each CompilationUnit,
@@ -61,48 +59,39 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
// current class
var cnode: asm.tree.ClassNode = null
- var thisName: String = null // the internal name of the class being emitted
+ var thisBType: ClassBType = null
var claszSymbol: Symbol = null
var isCZParcelable = false
var isCZStaticModule = false
var isCZRemote = false
- protected val indyLambdaHosts = collection.mutable.Set[Symbol]()
-
/* ---------------- idiomatic way to ask questions to typer ---------------- */
def paramTKs(app: Apply): List[BType] = {
val Apply(fun, _) = app
val funSym = fun.symbol
- (funSym.info.paramTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM)
+ funSym.info.paramTypes map typeToBType
}
- def symInfoTK(sym: Symbol): BType = {
- toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM)
- }
+ def symInfoTK(sym: Symbol): BType = typeToBType(sym.info)
- def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) }
+ def tpeTK(tree: Tree): BType = typeToBType(tree.tpe)
def log(msg: => AnyRef) {
global synchronized { global.log(msg) }
}
- override def getCurrentCUnit(): CompilationUnit = { cunit }
-
/* ---------------- helper utils for generating classes and fields ---------------- */
def genPlainClass(cd: ClassDef) {
assert(cnode == null, "GenBCode detected nested methods.")
- innerClassBufferASM.clear()
claszSymbol = cd.symbol
isCZParcelable = isAndroidParcelableClass(claszSymbol)
isCZStaticModule = isStaticModuleClass(claszSymbol)
isCZRemote = isRemote(claszSymbol)
- thisName = internalName(claszSymbol)
-
- val classBType = classBTypeFromSymbol(claszSymbol)
+ thisBType = classBTypeFromSymbol(claszSymbol)
cnode = new asm.tree.ClassNode()
@@ -121,30 +110,13 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
addClassFields()
- innerClassBufferASM ++= classBType.info.get.nestedClasses
gen(cd.impl)
-
- val shouldAddLambdaDeserialize = (
- settings.target.value == "jvm-1.8"
- && settings.Ydelambdafy.value == "method"
- && indyLambdaHosts.contains(claszSymbol))
-
- if (shouldAddLambdaDeserialize)
- addLambdaDeserialize(claszSymbol, cnode)
-
- addInnerClassesASM(cnode, innerClassBufferASM.toList)
-
- cnode.visitAttribute(classBType.inlineInfoAttribute.get)
+ cnode.visitAttribute(thisBType.inlineInfoAttribute.get)
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()
@@ -154,31 +126,27 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
private def initJClass(jclass: asm.ClassVisitor) {
val bType = classBTypeFromSymbol(claszSymbol)
- val superClass = bType.info.get.superClass.getOrElse(ObjectReference).internalName
- val interfaceNames = bType.info.get.interfaces map {
- case classBType =>
- if (classBType.isNestedClass.get) { innerClassBufferASM += classBType }
- classBType.internalName
- }
+ val superClass = bType.info.get.superClass.getOrElse(ObjectRef).internalName
+ val interfaceNames = bType.info.get.interfaces.map(_.internalName)
val flags = javaFlags(claszSymbol)
val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner)
cnode.visit(classfileVersion, flags,
- thisName, thisSignature,
+ thisBType.internalName, thisSignature,
superClass, interfaceNames.toArray)
if (emitSource) {
cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */)
}
- enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match {
+ enclosingMethodAttribute(claszSymbol, internalName, methodBTypeFromSymbol(_).descriptor) match {
case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) =>
cnode.visitOuterClass(className, methodName, methodDescriptor)
case _ => ()
}
- val ssa = getAnnotPickle(thisName, claszSymbol)
+ val ssa = getAnnotPickle(thisBType.internalName, claszSymbol)
cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
emitAnnotations(cnode, claszSymbol.annotations ++ ssa)
@@ -188,18 +156,17 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
} else {
- val skipStaticForwarders = (claszSymbol.isInterface || settings.noForwarders)
- if (!skipStaticForwarders) {
+ if (!settings.noForwarders) {
val lmoc = claszSymbol.companionModule
// add static forwarders if there are no name conflicts; see bugs #363 and #1735
if (lmoc != NoSymbol) {
// it must be a top level class (name contains no $s)
val isCandidateForForwarders = {
- exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass }
+ exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isNestedClass }
}
if (isCandidateForForwarders) {
log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'")
- addForwarders(isRemote(claszSymbol), cnode, thisName, lmoc.moduleClass)
+ addForwarders(isRemote(claszSymbol), cnode, thisBType.internalName, lmoc.moduleClass)
}
}
}
@@ -214,10 +181,17 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
* can-multi-thread
*/
private def addModuleInstanceField() {
+ // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
+ // SD-194 This can't be FINAL on JVM 1.9+ because we assign it from within the
+ // instance constructor, not from <clinit> directly. Assignment from <clinit>,
+ // after the constructor has completely finished, seems like the principled
+ // thing to do, but it would change behaviour when "benign" cyclic references
+ // between modules exist.
+ val mods = GenBCode.PublicStatic
val fv =
- cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
+ cnode.visitField(mods,
strMODULE_INSTANCE_FIELD,
- "L" + thisName + ";",
+ thisBType.descriptor,
null, // no java-generic-signature
null // no initial value
)
@@ -241,11 +215,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
/* "legacy static initialization" */
if (isCZStaticModule) {
- clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
+ clinit.visitTypeInsn(asm.Opcodes.NEW, thisBType.internalName)
clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
- thisName, INSTANCE_CONSTRUCTOR_NAME, "()V", false)
+ thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", false)
}
- if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) }
+ if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) }
clinit.visitInsn(asm.Opcodes.RETURN)
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
@@ -253,13 +227,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
}
def addClassFields() {
- /* Non-method term members are fields, except for module members. Module
- * members can only happen on .NET (no flatten) for inner traits. There,
- * a module symbol is generated (transformInfo in mixin) which is used
- * as owner for the members of the implementation class (so that the
- * backend emits them as static).
- * No code is needed for this module symbol.
- */
for (f <- fieldSymbols(claszSymbol)) {
val javagensig = getGenericSignature(f, claszSymbol)
val flags = javaFieldFlags(f)
@@ -288,7 +255,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
// used by genLoadTry() and genSynchronized()
var earlyReturnVar: Symbol = null
var shouldEmitCleanup = false
- var insideCleanupBlock = false
// line numbers
var lastEmittedLineNr = -1
@@ -458,9 +424,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
var varsInScope: List[Tuple2[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope)
// helpers around program-points.
- def lastInsn: asm.tree.AbstractInsnNode = {
- mnode.instructions.getLast
- }
+ def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast
def currProgramPoint(): asm.Label = {
lastInsn match {
case labnode: asm.tree.LabelNode => labnode.getLabel
@@ -522,7 +486,27 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
- case dd : DefDef => genDefDef(dd)
+ case dd : DefDef =>
+ val sym = dd.symbol
+ if (needsStaticImplMethod(sym)) {
+ if (sym.isMixinConstructor) {
+ val statified = global.gen.mkStatic(dd, sym.name, _.cloneSymbol)
+ genDefDef(statified)
+ } else {
+ val forwarderDefDef = {
+ val dd1 = global.gen.mkStatic(deriveDefDef(dd)(_ => EmptyTree), traitSuperAccessorName(sym), _.cloneSymbol.withoutAnnotations)
+ dd1.symbol.setFlag(Flags.ARTIFACT).resetFlag(Flags.OVERRIDE)
+ val selfParam :: realParams = dd1.vparamss.head.map(_.symbol)
+ deriveDefDef(dd1)(_ =>
+ atPos(dd1.pos)(
+ Apply(Select(global.gen.mkAttributedIdent(selfParam).setType(sym.owner.typeConstructor), dd.symbol),
+ realParams.map(global.gen.mkAttributedIdent)).updateAttachment(UseInvokeSpecial))
+ )
+ }
+ genDefDef(forwarderDefDef)
+ genDefDef(dd)
+ }
+ } else genDefDef(dd)
case Template(_, _, body) => body foreach gen
@@ -533,7 +517,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
/*
* must-single-thread
*/
- def initJMethod(flags: Int, paramAnnotations: List[List[AnnotationInfo]]) {
+ def initJMethod(flags: Int, params: List[Symbol]) {
val jgensig = getGenericSignature(methSymbol, claszSymbol)
addRemoteExceptionAnnot(isCZRemote, hasPublicBitSet(flags), methSymbol)
@@ -544,7 +528,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME
else jMethodName
- val mdesc = asmMethodType(methSymbol).descriptor
+ val mdesc = methodBTypeFromSymbol(methSymbol).descriptor
mnode = cnode.visitMethod(
flags,
bytecodeName,
@@ -553,10 +537,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
mkArray(thrownExceptions)
).asInstanceOf[asm.tree.MethodNode]
- // TODO param names: (m.params map (p => javaName(p.sym)))
-
+ emitParamNames(mnode, params)
emitAnnotations(mnode, others)
- emitParamAnnotations(mnode, paramAnnotations)
+ emitParamAnnotations(mnode, params.map(_.annotations))
} // end of method initJMethod
@@ -568,7 +551,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
methSymbol = dd.symbol
jMethodName = methSymbol.javaSimpleName.toString
- returnType = asmMethodType(dd.symbol).returnType
+ returnType = methodBTypeFromSymbol(dd.symbol).returnType
isMethSymStaticCtor = methSymbol.isStaticConstructor
resetMethodBookkeeping(dd)
@@ -587,16 +570,15 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
}
val isNative = methSymbol.hasAnnotation(definitions.NativeAttr)
- val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface)
+ val isAbstractMethod = rhs == EmptyTree
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
)
- // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
- initJMethod(flags, params.map(p => p.symbol.annotations))
+ initJMethod(flags, params.map(_.symbol))
/* Add method-local vars for LabelDef-params.
*
@@ -621,13 +603,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
genLoad(rhs, returnType)
rhs match {
- case Block(_, Return(_)) => ()
- case Return(_) => ()
+ case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => ()
case EmptyTree =>
globalError("Concrete method has no definition: " + dd + (
if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"
- else "")
- )
+ else ""))
case _ =>
bc emitRETURN returnType
}
@@ -638,7 +618,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (!hasStaticBitSet) {
mnode.visitLocalVariable(
"this",
- "L" + thisName + ";",
+ thisBType.descriptor,
null,
veryFirstProgramPoint,
onePastLastProgramPoint,
@@ -697,7 +677,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val callee = methSymbol.enclClass.primaryConstructor
val jname = callee.javaSimpleName.toString
val jowner = internalName(callee.owner)
- val jtype = asmMethodType(callee).descriptor
+ val jtype = methodBTypeFromSymbol(callee).descriptor
insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype, false)
}
@@ -706,7 +686,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
// android creator code
if (isCZParcelable) {
// add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator
- val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor
+ val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor
cnode.visitField(
asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL,
"CREATOR",
@@ -718,10 +698,10 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName)
val jowner = internalName(callee.owner)
val jname = callee.javaSimpleName.toString
- val jtype = asmMethodType(callee).descriptor
+ val jtype = methodBTypeFromSymbol(callee).descriptor
insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false)
- // PUTSTATIC `thisName`.CREATOR;
- insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr)
+ // PUTSTATIC `thisBType.internalName`.CREATOR;
+ insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisBType.internalName, "CREATOR", andrFieldDescr)
}
// insert a few instructions for initialization before each return instruction
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala
index b94208c1a5..add2c5ffe6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala
@@ -30,17 +30,17 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
def genSynchronized(tree: Apply, expectedType: BType): BType = {
val Apply(fun, args) = tree
- val monitor = locals.makeLocal(ObjectReference, "monitor")
+ val monitor = locals.makeLocal(ObjectRef, "monitor")
val monCleanup = new asm.Label
// if the synchronized block returns a result, store it in a local variable.
// Just leaving it on the stack is not valid in MSIL (stack is cleaned when leaving try-blocks).
val hasResult = (expectedType != UNIT)
- val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null;
+ val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null
/* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */
genLoadQualifier(fun)
- bc dup ObjectReference
+ bc dup ObjectRef
locals.store(monitor)
emit(asm.Opcodes.MONITORENTER)
@@ -73,9 +73,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
/* ------ (4) exception-handler version of monitor-exit code.
* Reached upon abrupt termination of (2).
* Protected by whatever protects the whole synchronized expression.
+ * null => "any" exception in bytecode, like we emit for finally.
+ * Important not to use j/l/Throwable which dooms the method to a life of interpretation! (SD-233)
* ------
*/
- protect(startProtected, endProtected, currProgramPoint(), ThrowableReference)
+ protect(startProtected, endProtected, currProgramPoint(), null)
locals.load(monitor)
emit(asm.Opcodes.MONITOREXIT)
emit(asm.Opcodes.ATHROW)
@@ -184,7 +186,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
for (CaseDef(pat, _, caseBody) <- catches) yield {
pat match {
case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody)
- case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody)
+ case Ident(nme.WILDCARD) => NamelessEH(jlThrowableRef, caseBody)
case Bind(_, _) => BoundEH (pat.symbol, caseBody)
}
}
@@ -213,7 +215,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
* please notice `tmp` has type tree.tpe, while `earlyReturnVar` has the method return type.
* Because those two types can be different, dedicated vars are needed.
*/
- val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null;
+ val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null
/*
* upon early return from the try-body or one of its EHs (but not the EH-version of the finally-clause)
@@ -236,6 +238,34 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
val endTryBody = currProgramPoint()
bc goTo postHandlers
+ /**
+ * A return within a `try` or `catch` block where a `finally` is present ("early return")
+ * emits a store of the result to a local, jump to a "cleanup" version of the `finally` block,
+ * and sets `shouldEmitCleanup = true` (see [[PlainBodyBuilder.genReturn]]).
+ *
+ * If the try-catch is nested, outer `finally` blocks need to be emitted in a cleanup version
+ * as well, so the `shouldEmitCleanup` variable remains `true` until the outermost `finally`.
+ * Nested cleanup `finally` blocks jump to the next enclosing one. For the outermost, we emit
+ * a read of the local variable, a return, and we set `shouldEmitCleanup = false` (see
+ * [[pendingCleanups]]).
+ *
+ * Now, assume we have
+ *
+ * try { return 1 } finally {
+ * try { println() } finally { println() }
+ * }
+ *
+ * Here, the outer `finally` needs a cleanup version, but the inner one does not. The method
+ * here makes sure that `shouldEmitCleanup` is only propagated outwards, not inwards to
+ * nested `finally` blocks.
+ */
+ def withFreshCleanupScope(body: => Unit) = {
+ val savedShouldEmitCleanup = shouldEmitCleanup
+ shouldEmitCleanup = false
+ body
+ shouldEmitCleanup = savedShouldEmitCleanup || shouldEmitCleanup
+ }
+
/* ------ (2) One EH for each case-clause (this does not include the EH-version of the finally-clause)
* An EH in (2) is reached upon abrupt termination of (1).
* An EH in (2) is protected by:
@@ -244,8 +274,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
* ------
*/
- for (ch <- caseHandlers) {
-
+ for (ch <- caseHandlers) withFreshCleanupScope {
// (2.a) emit case clause proper
val startHandler = currProgramPoint()
var endHandler: asm.Label = null
@@ -275,9 +304,13 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
protect(startTryBody, endTryBody, startHandler, excType)
// (2.c) emit jump to the program point where the finally-clause-for-normal-exit starts, or in effect `after` if no finally-clause was given.
bc goTo postHandlers
-
}
+ // Need to save the state of `shouldEmitCleanup` at this point: while emitting the first
+ // version of the `finally` block below, the variable may become true. But this does not mean
+ // that we need a cleanup version for the current block, only for the enclosing ones.
+ val currentFinallyBlockNeedsCleanup = shouldEmitCleanup
+
/* ------ (3.A) The exception-handler-version of the finally-clause.
* Reached upon abrupt termination of (1) or one of the EHs in (2).
* Protected only by whatever protects the whole try-catch-finally expression.
@@ -286,11 +319,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
// a note on terminology: this is not "postHandlers", despite appearances.
// "postHandlers" as in the source-code view. And from that perspective, both (3.A) and (3.B) are invisible implementation artifacts.
- if (hasFinally) {
+ if (hasFinally) withFreshCleanupScope {
nopIfNeeded(startTryBody)
val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception.
protect(startTryBody, finalHandler, finalHandler, null)
- val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc"))
+ val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(jlThrowableRef, "exc"))
bc.store(eIdx, eTK)
emitFinalizer(finalizer, null, isDuplicate = true)
bc.load(eIdx, eTK)
@@ -314,14 +347,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder {
// this is not "postHandlers" either.
// `shouldEmitCleanup` can be set, and at the same time this try expression may lack a finally-clause.
// In other words, all combinations of (hasFinally, shouldEmitCleanup) are valid.
- if (hasFinally && shouldEmitCleanup) {
- val savedInsideCleanup = insideCleanupBlock
- insideCleanupBlock = true
+ if (hasFinally && currentFinallyBlockNeedsCleanup) {
markProgramPoint(finCleanup)
// regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
emitFinalizer(finalizer, null, isDuplicate = true)
pendingCleanups()
- insideCleanupBlock = savedInsideCleanup
}
/* ------ (4) finally-clause-for-normal-nonEarlyReturn-exit
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 0c26e01322..3e3229d2c3 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -7,15 +7,18 @@ package scala.tools.nsc
package backend.jvm
import scala.annotation.switch
+import scala.collection.{concurrent, mutable}
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.BackendUtils
import scala.tools.nsc.backend.jvm.opt._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
+import scala.collection.mutable.ListBuffer
import scala.tools.nsc.settings.ScalaSettings
/**
@@ -29,6 +32,8 @@ import scala.tools.nsc.settings.ScalaSettings
abstract class BTypes {
import BTypes.InternalName
+ val backendUtils: BackendUtils[this.type]
+
// Some core BTypes are required here, in class BType, where no Global instance is available.
// The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual
// implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol.
@@ -38,12 +43,14 @@ 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]
@@ -56,7 +63,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.
@@ -68,19 +74,27 @@ abstract class BTypes {
* Concurrent because stack map frames are computed when in the class writer, which might run
* on multiple classes concurrently.
*/
- val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
+ val classBTypeFromInternalName: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
/**
* Store the position of every MethodInsnNode during code generation. This allows each callsite
* in the call graph to remember its source position, which is required for inliner warnings.
*/
- val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
+ val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
+
+ /**
+ * Stores callsite instructions of invocations annotated `f(): @inline/noinline`.
+ * Instructions are added during code generation (BCodeBodyBuilder). The maps are then queried
+ * when building the CallGraph, every Callsite object has an annotated(No)Inline field.
+ */
+ val inlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty)
+ val noInlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty)
/**
* Contains the internal names of all classes that are defined in Java source files of the current
* compilation run (mixed compilation). Used for more detailed error reporting.
*/
- val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty)
+ val javaDefinedClasses: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty)
/**
* Cache, contains methods whose unreachable instructions are eliminated.
@@ -92,12 +106,47 @@ abstract class BTypes {
* This cache allows running dead code elimination whenever an analyzer is used. If the method
* is already optimized, DCE can return early.
*/
- val unreachableCodeEliminated: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty)
+ val unreachableCodeEliminated: mutable.Set[MethodNode] = recordPerRunCache(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: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty)
+
+ /**
+ * Classes with indyLambda closure instantiations where the SAM type is serializable (e.g. Scala's
+ * FunctionN) need a `$deserializeLambda$` method. This map contains classes for which such a
+ * method has been generated. It is used during ordinary code generation, as well as during
+ * inlining: when inlining an indyLambda instruction into a class, we need to make sure the class
+ * has the method.
+ */
+ val indyLambdaImplMethods: mutable.AnyRefMap[InternalName, mutable.LinkedHashSet[asm.Handle]] = recordPerRunCache(mutable.AnyRefMap())
+ def addIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Seq[asm.Handle] = {
+ if (handle.isEmpty) Nil else {
+ val set = indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet())
+ val added = handle.filterNot(set)
+ set ++= handle
+ added
+ }
+ }
+ def removeIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Unit = {
+ if (handle.nonEmpty)
+ indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) --= handle
+ }
+
+ def getIndyLambdaImplMethods(hostClass: InternalName): Iterable[asm.Handle] = {
+ indyLambdaImplMethods.getOrNull(hostClass) match {
+ case null => Nil
+ case xs => xs
+ }
+ }
/**
* Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType
* is constructed by parsing the corresponding classfile.
- *
+ *
* Some JVM operations use either a full descriptor or only an internal name. Example:
* ANEWARRAY java/lang/String // a new array of strings (internal name for the String class)
* ANEWARRAY [Ljava/lang/String; // a new array of array of string (full descriptor for the String class)
@@ -128,7 +177,7 @@ abstract class BTypes {
val res = ClassBType(internalName)
byteCodeRepository.classNode(internalName) match {
case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res
- case Right(c) => setClassInfoFromParsedClassfile(c, res)
+ case Right(c) => setClassInfoFromClassNode(c, res)
}
})
}
@@ -138,21 +187,19 @@ abstract class BTypes {
*/
def classBTypeFromClassNode(classNode: ClassNode): ClassBType = {
classBTypeFromInternalName.getOrElse(classNode.name, {
- setClassInfoFromParsedClassfile(classNode, ClassBType(classNode.name))
+ setClassInfoFromClassNode(classNode, ClassBType(classNode.name))
})
}
- private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = {
+ private def setClassInfoFromClassNode(classNode: ClassNode, classBType: ClassBType): ClassBType = {
val superClass = classNode.superName match {
case null =>
- assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}")
+ assert(classNode.name == ObjectRef.internalName, s"class with missing super type: ${classNode.name}")
None
case superName =>
Some(classBTypeFromParsedClassfile(superName))
}
- val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
-
val flags = classNode.access
/**
@@ -197,6 +244,8 @@ abstract class BTypes {
val inlineInfo = inlineInfoFromClassfile(classNode)
+ val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
+
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
}
@@ -226,22 +275,21 @@ abstract class BTypes {
val methodInfos = classNode.methods.asScala.map(methodNode => {
val info = MethodInlineInfo(
effectivelyFinal = BytecodeUtils.isFinalMethod(methodNode),
- traitMethodWithStaticImplementation = false,
annotatedInline = false,
annotatedNoInline = false)
(methodNode.name + methodNode.desc, info)
}).toMap
InlineInfo(
- traitImplClassSelfType = None,
isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode),
+ sam = inlinerHeuristics.javaSam(classNode.name),
methodInfos = methodInfos,
warning)
}
// The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT
- // being compiled. For those classes, the info is only needed if the inliner is enabled, othewise
+ // being compiled. For those classes, the info is only needed if the inliner is enabled, otherwise
// we can save the memory.
- if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo
+ if (!compilerSettings.optInlinerEnabled) BTypes.EmptyInlineInfo
else fromClassfileAttribute getOrElse fromClassfileWithoutAttribute
}
@@ -291,8 +339,8 @@ abstract class BTypes {
final def isNonVoidPrimitiveType = isPrimitive && this != UNIT
- final def isNullType = this == RT_NULL
- final def isNothingType = this == RT_NOTHING
+ final def isNullType = this == srNullRef
+ final def isNothingType = this == srNothingRef
final def isBoxed = this.isClass && boxedClasses(this.asClassBType)
@@ -315,7 +363,7 @@ abstract class BTypes {
this match {
case ArrayBType(component) =>
- if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true
+ if (other == ObjectRef || other == jlCloneableRef || other == jiSerializableRef) true
else other match {
case ArrayBType(otherComponent) => component.conformsTo(otherComponent).orThrow
case _ => false
@@ -324,7 +372,7 @@ abstract class BTypes {
case classType: ClassBType =>
if (isBoxed) {
if (other.isBoxed) this == other
- else if (other == ObjectReference) true
+ else if (other == ObjectRef) true
else other match {
case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number
case _ => false
@@ -367,7 +415,7 @@ abstract class BTypes {
assert(other.isRef, s"Cannot compute maxType: $this, $other")
// Approximate `lub`. The common type of two references is always ObjectReference.
- ObjectReference
+ ObjectRef
case _: MethodBType =>
assertionError(s"unexpected method type when computing maxType: $this")
@@ -554,6 +602,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
@@ -603,7 +653,7 @@ abstract class BTypes {
* Fields in the InnerClass entries:
* - inner class: the (nested) class C we are talking about
* - outer class: the class of which C is a member. Has to be null for non-members, i.e. for
- * local and anonymous classes. NOTE: this co-incides with the presence of an
+ * local and anonymous classes. NOTE: this coincides with the presence of an
* EnclosingMethod attribute (see below)
* - inner name: A string with the simple name of the inner class. Null for anonymous classes.
* - flags: access property flags, details in JVMS, table in 4.7.6. Static flag: see
@@ -652,7 +702,7 @@ abstract class BTypes {
* local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the
* "class" field (see below) must be always defined, while the "method" field may be null.
*
- * NOTE: When an EnclosingMethod attribute is requried (local and anonymous classes), the "outer"
+ * NOTE: When an EnclosingMethod attribute is required (local and anonymous classes), the "outer"
* field in the InnerClass table must be null.
*
* Fields:
@@ -760,26 +810,17 @@ abstract class BTypes {
* }
*
*
- * Traits Members
- * --------------
- *
- * Some trait methods don't exist in the generated interface, but only in the implementation class
- * (private methods in traits for example). Since EnclosingMethod expresses a source-level property,
- * but the source-level enclosing method doesn't exist in the classfile, we the enclosing method
- * is null (the enclosing class is still emitted).
- * See BCodeAsmCommon.considerAsTopLevelImplementationArtifact
- *
+ * Specialized Classes, Delambdafy:method closure classes
+ * ------------------------------------------------------
*
- * Implementation Classes, Specialized Classes, Delambdafy:method closure classes
- * ------------------------------------------------------------------------------
- *
- * Trait implementation classes and specialized classes are always considered top-level. Again,
- * the InnerClass / EnclosingMethod attributes describe a source-level properties. The impl
- * classes are compilation artifacts.
+ * Specialized classes are always considered top-level, as the InnerClass / EnclosingMethod
+ * attributes describe a source-level properties.
*
* The same is true for delambdafy:method closure classes. These classes are generated at
* top-level in the delambdafy phase, no special support is required in the backend.
*
+ * See also BCodeHelpers.considerAsTopLevelImplementationArtifact.
+ *
*
* Mirror Classes
* --------------
@@ -837,7 +878,7 @@ abstract class BTypes {
// best-effort verification. also we don't report an error if the info is a Left.
def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c)
- def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName
+ def isJLO(t: ClassBType) = t.internalName == ObjectRef.internalName
assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this")
@@ -900,7 +941,7 @@ abstract class BTypes {
// the static flag in the InnerClass table has a special meaning, see InnerClass comment
i.flags & ~Opcodes.ACC_STATIC,
if (isStaticNestedClass) Opcodes.ACC_STATIC else 0
- ) & BCodeAsmCommon.INNER_CLASSES_FLAGS
+ ) & BCodeHelpers.INNER_CLASSES_FLAGS
)
})
@@ -917,7 +958,7 @@ abstract class BTypes {
def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try {
if (this == other) return Right(true)
if (isInterface.orThrow) {
- if (other == ObjectReference) return Right(true) // interfaces conform to Object
+ if (other == ObjectRef) return Right(true) // interfaces conform to Object
if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false.
// else: this and other are both interfaces. continue to (*)
} else {
@@ -950,13 +991,13 @@ abstract class BTypes {
// exercised by test/files/run/t4761.scala
if (other.isSubtypeOf(this).orThrow) this
else if (this.isSubtypeOf(other).orThrow) other
- else ObjectReference
+ else ObjectRef
case (true, false) =>
- if (other.isSubtypeOf(this).orThrow) this else ObjectReference
+ if (other.isSubtypeOf(this).orThrow) this else ObjectRef
case (false, true) =>
- if (this.isSubtypeOf(other).orThrow) other else ObjectReference
+ if (this.isSubtypeOf(other).orThrow) other else ObjectRef
case _ =>
// TODO @lry I don't really understand the reasoning here.
@@ -1081,7 +1122,7 @@ abstract class BTypes {
*/
/**
- * Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo.
+ * Just a named pair, used in CoreBTypes.srBoxesRuntimeBoxToMethods/srBoxesRuntimeUnboxToMethods.
*/
final case class MethodNameAndType(name: String, methodType: MethodBType)
@@ -1103,24 +1144,14 @@ object BTypes {
/**
* Metadata about a ClassBType, used by the inliner.
*
- * More information may be added in the future to enable more elaborate inlinine heuristics.
- *
- * @param traitImplClassSelfType `Some(tp)` if this InlineInfo describes a trait, and the `self`
- * parameter type of the methods in the implementation class is not
- * the trait itself. Example:
- * trait T { self: U => def f = 1 }
- * Generates something like:
- * class T$class { static def f(self: U) = 1 }
- *
- * In order to inline a trat method call, the INVOKEINTERFACE is
- * rewritten to an INVOKESTATIC of the impl class, so we need the
- * self type (U) to get the right signature.
- *
- * `None` if the self type is the interface type, or if this
- * InlineInfo does not describe a trait.
+ * More information may be added in the future to enable more elaborate inline 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 isEffectivelyFinal True if the class cannot have subclasses: final classes, module
- * classes, trait impl classes.
+ * 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
@@ -1130,29 +1161,28 @@ object BTypes {
* InlineInfo, for example if some classfile could not be found on
* the classpath. This warning can be reported later by the inliner.
*/
- final case class InlineInfo(traitImplClassSelfType: Option[InternalName],
- isEffectivelyFinal: Boolean,
+ final case class InlineInfo(isEffectivelyFinal: Boolean,
+ sam: Option[String],
methodInfos: Map[String, MethodInlineInfo],
warning: Option[ClassInlineInfoWarning])
- val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None)
+ val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None)
/**
* Metadata about a method, used by the inliner.
*
* @param effectivelyFinal True if the method cannot be overridden (in Scala)
- * @param traitMethodWithStaticImplementation True if the method is an interface method method of
- * a trait method and has a static counterpart in the
- * implementation class.
* @param annotatedInline True if the method is annotated `@inline`
* @param annotatedNoInline True if the method is annotated `@noinline`
*/
final case class MethodInlineInfo(effectivelyFinal: Boolean,
- traitMethodWithStaticImplementation: Boolean,
annotatedInline: Boolean,
annotatedNoInline: Boolean)
// no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR
val ScalaAttributeName = "Scala"
val ScalaSigAttributeName = "ScalaSig"
-} \ No newline at end of file
+
+ // when inlining, local variable names of the callee are prefixed with the name of the callee method
+ val InlinedLocalVariablePrefixMaxLenght = 128
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 45d9cc3ff3..f7ee36c1ba 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,10 +7,12 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
+import scala.tools.nsc.backend.jvm.analysis.BackendUtils
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
+import scala.reflect.internal.Flags.{DEFERRED, SYNTHESIZE_IMPL_IN_SUBCLASS}
/**
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -27,21 +29,22 @@ import scala.tools.nsc.settings.ScalaSettings
class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
import global._
import definitions._
+ import genBCode._
- val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global)
- val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global)
- import bCodeAsmCommon._
+ val backendUtils: BackendUtils[this.type] = new BackendUtils(this)
// Why the proxy, see documentation of class [[CoreBTypes]].
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.optimizerClassPath(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)
@@ -94,21 +97,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files
* for the Nothing / Null. If used for example as a parameter type, we use the runtime classes
* in the classfile method signature.
- *
- * Note that the referenced class symbol may be an implementation class. For example when
- * compiling a mixed-in method that forwards to the static method in the implementation class,
- * the class descriptor of the receiver (the implementation class) is obtained by creating the
- * ClassBType.
*/
- final def classBTypeFromSymbol(classSym: Symbol): ClassBType = {
+ final def classBTypeFromSymbol(sym: Symbol): ClassBType = {
+ // For each java class, the scala compiler creates a class and a module (thus a module class).
+ // If the `sym` is a java module class, we use the java class instead. This ensures that the
+ // ClassBType is created from the main class (instead of the module class).
+ // The two symbols have the same name, so the resulting internalName is the same.
+ // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
+ val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
+
assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol")
assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym")
assertClassNotArrayNotPrimitive(classSym)
- assert(!primitiveTypeMap.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym")
- if (classSym == NothingClass) RT_NOTHING
- else if (classSym == NullClass) RT_NULL
+ assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym")
+
+ if (classSym == NothingClass) srNothingRef
+ else if (classSym == NullClass) srNullRef
else {
- val internalName = classSym.javaBinaryName.toString
+ val internalName = classSym.javaBinaryNameString
classBTypeFromInternalName.getOrElse(internalName, {
// The new ClassBType is added to the map in its constructor, before we set its info. This
// allows initializing cyclic dependencies, see the comment on variable ClassBType._info.
@@ -128,17 +134,36 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
*/
final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = {
assert(methodSymbol.isMethod, s"not a method-symbol: $methodSymbol")
+ methodBTypeFromMethodType(methodSymbol.info, methodSymbol.isClassConstructor || methodSymbol.isConstructor)
+ }
+
+ /**
+ * Builds a [[MethodBType]] for a method type.
+ */
+ final def methodBTypeFromMethodType(tpe: Type, isConstructor: Boolean): MethodBType = {
val resultType: BType =
- if (methodSymbol.isClassConstructor || methodSymbol.isConstructor) UNIT
- else typeToBType(methodSymbol.tpe.resultType)
- MethodBType(methodSymbol.tpe.paramTypes map typeToBType, resultType)
+ if (isConstructor) UNIT
+ else typeToBType(tpe.resultType)
+ MethodBType(tpe.paramTypes map typeToBType, resultType)
+ }
+
+ def bootstrapMethodArg(t: Constant, pos: Position): AnyRef = t match {
+ case Constant(mt: Type) => methodBTypeFromMethodType(transformedType(mt), isConstructor = false).toASMType
+ case c @ Constant(sym: Symbol) => staticHandleFromSymbol(sym)
+ case c @ Constant(value: String) => value
+ case c @ Constant(value) if c.isNonUnitAnyVal => c.value.asInstanceOf[AnyRef]
+ case _ => reporter.error(pos, "Unable to convert static argument of ApplyDynamic into a classfile constant: " + t); null
+ }
+
+ def staticHandleFromSymbol(sym: Symbol): asm.Handle = {
+ val owner = if (sym.owner.isModuleClass) sym.owner.linkedClassOfClass else sym.owner
+ val descriptor = methodBTypeFromMethodType(sym.info, isConstructor = false).descriptor
+ val ownerBType = classBTypeFromSymbol(owner)
+ new asm.Handle(asm.Opcodes.H_INVOKESTATIC, ownerBType.internalName, sym.name.encoded, descriptor, /* itf = */ ownerBType.isInterface.get)
}
/**
* This method returns the BType for a type reference, for example a parameter type.
- *
- * If `t` references a class, typeToBType ensures that the class is not an implementation class.
- * See also comment on classBTypeFromSymbol, which is invoked for implementation classes.
*/
final def typeToBType(t: Type): BType = {
import definitions.ArrayClass
@@ -149,17 +174,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
*/
def primitiveOrClassToBType(sym: Symbol): BType = {
assertClassNotArray(sym)
- assert(!sym.isImplClass, sym)
- primitiveTypeMap.getOrElse(sym, classBTypeFromSymbol(sym))
+ primitiveTypeToBType.getOrElse(sym, classBTypeFromSymbol(sym))
}
/**
* When compiling Array.scala, the type parameter T is not erased and shows up in method
- * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference.
+ * signatures, e.g. `def apply(i: Int): T`. A TypeRef for T is replaced by ObjectRef.
*/
def nonClassTypeRefToBType(sym: Symbol): ClassBType = {
assert(sym.isType && isCompilingArray, sym)
- ObjectReference
+ ObjectRef
}
t.dealiasWiden match {
@@ -168,39 +192,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String
case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info)
- /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
- * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
- * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
- */
- case a @ AnnotatedType(_, t) =>
- debuglog(s"typeKind of annotated type $a")
- typeToBType(t)
-
- /* ExistentialType should (probably) be eliminated by erasure. We know they get here for
- * classOf constants:
- * class C[T]
- * class T { final val k = classOf[C[_]] }
- */
- case e @ ExistentialType(_, t) =>
- debuglog(s"typeKind of existential type $e")
- typeToBType(t)
-
/* The cases below should probably never occur. They are kept for now to avoid introducing
* new compiler crashes, but we added a warning. The compiler / library bootstrap and the
* test suite don't produce any warning.
*/
case tp =>
- currentUnit.warning(tp.typeSymbol.pos,
+ warning(tp.typeSymbol.pos,
s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " +
"If possible, please file a bug on issues.scala-lang.org.")
tp match {
- case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
- case ThisType(sym) => classBTypeFromSymbol(sym)
- case SingleType(_, sym) => primitiveOrClassToBType(sym)
- case ConstantType(_) => typeToBType(t.underlying)
- case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get)
+ case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
+ case ThisType(sym) => classBTypeFromSymbol(sym)
+ case SingleType(_, sym) => primitiveOrClassToBType(sym)
+ case ConstantType(_) => typeToBType(t.underlying)
+ case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get)
+ case AnnotatedType(_, t) => typeToBType(t)
+ case ExistentialType(_, t) => typeToBType(t)
}
}
}
@@ -212,15 +221,109 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = {
assertClassNotArray(sym)
- assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym)
+ assert(!primitiveTypeToBType.contains(sym) || isCompilingPrimitive, sym)
}
+ def implementedInterfaces(classSym: Symbol): List[Symbol] = {
+ // Additional interface parents based on annotations and other cues
+ def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match {
+ case RemoteAttr => Some(RemoteInterfaceClass.tpe)
+ case _ => None
+ }
+
+ // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes.
+ def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag
+
+ val classParents = {
+ val parents = classSym.info.parents
+ // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the
+ // parents of a java annotations. undo this for the backend (where we need classfile-level information).
+ if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass)
+ else parents
+ }
+
+ val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation)
+
+ val minimizedParents = if (classSym.isJavaDefined) allParents else erasure.minimizeParents(allParents)
+ // We keep the superClass when computing minimizeParents to eliminate more interfaces.
+ // Example: T can be eliminated from D
+ // trait T
+ // class C extends T
+ // class D extends C with T
+ val interfaces = minimizedParents match {
+ case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) =>
+ ifs
+ case ifs =>
+ // minimizeParents removes the superclass if it's redundant, for example:
+ // trait A
+ // class C extends Object with A // minimizeParents removes Object
+ ifs
+ }
+ interfaces.map(_.typeSymbol)
+ }
+
+ /**
+ * The member classes of a class symbol. Note that the result of this method depends on the
+ * current phase, for example, after lambdalift, all local classes become member of the enclosing
+ * class.
+ *
+ * Specialized classes are always considered top-level, see comment in BTypes.
+ */
+ private def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
+ case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
+ sym
+ case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) =>
+ val r = exitingPickler(sym.moduleClass)
+ assert(r != NoSymbol, sym.fullLocationString)
+ r
+ })(collection.breakOut)
+
private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = {
- // Check for isImplClass: trait implementation classes have NoSymbol as superClass
+ /**
+ * Reconstruct the classfile flags from a Java defined class symbol.
+ *
+ * The implementation of this method is slightly different from `javaFlags` in BTypesFromSymbols.
+ * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags
+ * that are used in the generated classfiles. For example, all classes emitted by the Scala
+ * compiler have ACC_PUBLIC.
+ *
+ * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have
+ * to correspond exactly to the flags in the classfile. For example, if the class is package
+ * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the
+ * ClassBType. For example, the inliner needs the correct flags for access checks.
+ *
+ * Class flags are listed here:
+ * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1
+ */
+ def javaClassfileFlags(classSym: Symbol): Int = {
+ assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
+ import asm.Opcodes._
+ def enumFlags = ACC_ENUM | {
+ // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
+ // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
+ // Java enums for exhaustiveness checking.
+ val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
+ if (hasAbstractMethod) ACC_ABSTRACT else 0
+ }
+ GenBCode.mkFlags(
+ // SI-9393: the classfile / java source parser make java annotation symbols look like classes.
+ // here we recover the actual classfile flags.
+ if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0,
+ if (classSym.isPublic) ACC_PUBLIC else 0,
+ if (classSym.isFinal) ACC_FINAL else 0,
+ // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
+ if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER,
+ // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags)
+ if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0,
+ if (classSym.isArtifact) ACC_SYNTHETIC else 0,
+ if (classSym.hasJavaEnumFlag) enumFlags else 0
+ )
+ }
+
// Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add
// scala.annotation.Annotation as superclass to java annotations. In reality, java
// annotation classfiles have superclass Object (like any interface classfile).
- val superClassSym = if (classSym.isImplClass || classSym.hasJavaAnnotationFlag) ObjectClass else {
+ val superClassSym = if (classSym.hasJavaAnnotationFlag) ObjectClass else {
val sc = classSym.superClass
// SI-9393: Java annotation classes don't have the ABSTRACT/INTERFACE flag, so they appear
// (wrongly) as superclasses. Fix this for BTypes: the java annotation will appear as interface
@@ -235,7 +338,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
superClassSym == ObjectClass
else
// A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes.
- ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeMap.contains(classSym)),
+ ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeToBType.contains(classSym)),
s"Bad superClass for $classSym: $superClassSym"
)
val superClass = if (superClassSym == NoSymbol) None
@@ -251,13 +354,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
/* The InnerClass table of a class C must contain all nested classes of C, even if they are only
* declared but not otherwise referenced in C (from the bytecode or a method / field signature).
* We collect them here.
- *
- * Nested classes that are also referenced in C will be added to the innerClassBufferASM during
- * code generation, but those duplicates will be eliminated when emitting the InnerClass
- * attribute.
- *
- * Why do we need to collect classes into innerClassBufferASM at all? To collect references to
- * nested classes, but NOT nested in C, that are used within C.
*/
val nestedClassSymbols = {
val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases
@@ -286,8 +382,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
val companionModuleMembers = if (considerAsTopLevelImplementationArtifact(classSym)) Nil else {
- // If this is a top-level non-impl (*) class, the member classes of the companion object are
- // added as members of the class. For example:
+ // If this is a top-level class, the member classes of the companion object are added as
+ // members of the class. For example:
// class C { }
// object C {
// class D
@@ -298,11 +394,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// (done by buildNestedInfo). See comment in BTypes.
// For consistency, the InnerClass entry for D needs to be present in C - to Java it looks
// like D is a member of C, not C$.
- //
- // (*) We exclude impl classes: if the classfile for the impl class exists on the classpath,
- // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching
- // members of that weird impl-class-module-class-symbol. that search probably cannot return
- // any classes, but it's better to exclude it.
val javaCompatMembers = {
if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass))
// phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member
@@ -360,7 +451,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym")
val isTopLevel = innerClassSym.rawowner.isPackageClass
- // impl classes are considered top-level, see comment in BTypes
+ // specialized classes are considered top-level, see comment in BTypes
if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None
else if (innerClassSym.rawowner.isTerm) {
// This case should never be reached: the lambdalift phase mutates the rawowner field of all
@@ -428,13 +519,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* classfile attribute.
*/
private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
- def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
+ def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym)
// phase travel required, see implementation of `compiles`. for nested classes, it checks if the
// enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level,
// so `compiles` would return `false`.
if (exitingPickler(currentRun.compiles(classSym))) buildFromSymbol // InlineInfo required for classes being compiled, we have to create the classfile attribute
- else if (!compilerSettings.YoptInlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled.
+ else if (!compilerSettings.optInlinerEnabled) BTypes.EmptyInlineInfo // For other classes, we need the InlineInfo only inf the inliner is enabled.
else {
// For classes not being compiled, the InlineInfo is read from the classfile attribute. This
// fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class
@@ -444,30 +535,123 @@ 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)))
+ }
+ }
+ }
+
+ /**
+ * Build the [[InlineInfo]] for a class symbol.
+ */
+ def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = {
+ val isEffectivelyFinal = classSym.isEffectivelyFinal
+
+ val sam = {
+ if (classSym.isEffectivelyFinal) None
+ else {
+ // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
+ // empty parameter list in uncurry and would therefore be picked as SAM.
+ // Similarly, the fields phases adds abstract trait setters, which should not be considered
+ // abstract for SAMs (they do disqualify the SAM from LMF treatment,
+ // but an anonymous subclasss can be spun up by scalac after making just the single abstract method concrete)
+ val samSym = exitingPickler(definitions.samOf(classSym.tpe))
+ if (samSym == NoSymbol) None
+ else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor)
}
}
+
+ var warning = Option.empty[ClassSymbolInfoFailureSI9111]
+
+ def keepMember(sym: Symbol) = sym.isMethod && !scalaPrimitives.isPrimitive(sym)
+ val classMethods = classSym.info.decls.iterator.filter(keepMember)
+ val methods = if (!classSym.isJavaDefined) classMethods else {
+ val staticMethods = classSym.companionModule.info.decls.iterator.filter(m => !m.isConstructor && keepMember(m))
+ staticMethods ++ classMethods
+ }
+
+ // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
+ // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
+ val methodInlineInfos = methods.flatMap({
+ case methodSym =>
+ if (completeSilentlyAndCheckErroneous(methodSym)) {
+ // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
+ if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
+ warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
+ Nil
+ } else {
+ val name = methodSym.javaSimpleName.toString // same as in genDefDef
+ val signature = name + methodBTypeFromSymbol(methodSym).descriptor
+
+ // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
+ // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
+ // so they are not marked final in the InlineInfo attribute.
+ //
+ // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
+ // work, the abstract accessor for O will be marked effectivelyFinal.
+ val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !(methodSym hasFlag DEFERRED | SYNTHESIZE_IMPL_IN_SUBCLASS)
+
+ val info = MethodInlineInfo(
+ effectivelyFinal = effectivelyFinal,
+ annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
+ annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass))
+
+ if (needsStaticImplMethod(methodSym)) {
+ val staticName = traitSuperAccessorName(methodSym).toString
+ val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF)
+ val staticMethodType = methodSym.info match {
+ case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res)
+ }
+ val staticMethodSignature = staticName + methodBTypeFromMethodType(staticMethodType, isConstructor = false)
+ val staticMethodInfo = MethodInlineInfo(
+ effectivelyFinal = true,
+ annotatedInline = info.annotatedInline,
+ annotatedNoInline = info.annotatedNoInline)
+ if (methodSym.isMixinConstructor)
+ List((staticMethodSignature, staticMethodInfo))
+ else
+ List((signature, info), (staticMethodSignature, staticMethodInfo))
+ } else
+ List((signature, info))
+ }
+ }).toMap
+
+ InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
}
/**
- * For top-level objects without a companion class, the compilere generates a mirror class with
+ * For top-level objects without a companion class, the compiler generates a mirror class with
* static forwarders (Java compat). There's no symbol for the mirror class, but we still need a
* ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes).
*/
def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = {
assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym")
- val internalName = moduleClassSym.javaBinaryName.dropModule.toString
+ val internalName = moduleClassSym.javaBinaryNameString.stripSuffix(nme.MODULE_SUFFIX_STRING)
classBTypeFromInternalName.getOrElse(internalName, {
val c = ClassBType(internalName)
// class info consistent with BCodeHelpers.genMirrorClass
val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol
c.info = Right(ClassInfo(
- superClass = Some(ObjectReference),
+ superClass = Some(ObjectRef),
interfaces = Nil,
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
nestedClasses = nested,
nestedInfo = None,
- InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class
+ inlineInfo = EmptyInlineInfo.copy(isEffectivelyFinal = true))) // no method inline infos needed, scala never invokes methods on the mirror class
+ c
+ })
+ }
+
+ def beanInfoClassClassBType(mainClass: Symbol): ClassBType = {
+ val internalName = mainClass.javaBinaryNameString + "BeanInfo"
+ classBTypeFromInternalName.getOrElse(internalName, {
+ val c = ClassBType(internalName)
+ c.info = Right(ClassInfo(
+ superClass = Some(sbScalaBeanInfoRef),
+ interfaces = Nil,
+ flags = javaFlags(mainClass),
+ nestedClasses = Nil,
+ nestedInfo = None,
+ inlineInfo = EmptyInlineInfo))
c
})
}
@@ -478,26 +662,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
*/
final def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler {
// phase travel to pickler required for isNestedClass (looks at owner)
- val r = sym.isModuleClass && !sym.isNestedClass
- // The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag
- // is late, it should not be visible here inside the time travel. We check this.
- if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym")
- r
+ sym.isModuleClass && !sym.isNestedClass
}
/**
* True for module classes of modules that are top-level or owned only by objects. Module classes
- * for such objects will get a MODULE$ flag and a corresponding static initializer.
+ * for such objects will get a MODULE$ field and a corresponding static initializer.
*/
final def isStaticModuleClass(sym: Symbol): Boolean = {
- /* (1) Phase travel to to pickler is required to exclude implementation classes; they have the
- * lateMODULEs after mixin, so isModuleClass would be true.
- * (2) isStaticModuleClass is a source-level property. See comment on isOriginallyStaticOwner.
- */
- exitingPickler { // (1)
- sym.isModuleClass &&
- isOriginallyStaticOwner(sym.originalOwner) // (2)
- }
+ sym.isModuleClass &&
+ isOriginallyStaticOwner(sym.originalOwner) // isStaticModuleClass is a source-level property, see comment on isOriginallyStaticOwner
}
// legacy, to be removed when the @remote annotation gets removed
@@ -550,34 +724,28 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
// scala compiler. The word final is heavily overloaded unfortunately;
// for us it means "not overridable". At present you can't override
// vars regardless; this may change.
- //
- // The logic does not check .isFinal (which checks flags for the FINAL flag,
- // and includes symbols marked lateFINAL) instead inspecting rawflags so
- // we can exclude lateFINAL. Such symbols are eligible for inlining, but to
- // avoid breaking proxy software which depends on subclassing, we do not
- // emit ACC_FINAL.
val finalFlag = (
- (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym))
- && !sym.enclClass.isInterface
+ (sym.isFinal || isTopLevelModuleClass(sym))
+ && !sym.enclClass.isTrait
&& !sym.isClassConstructor
- && !sym.isMutable // lazy vals and vars both
+ && (!sym.isMutable || nme.isTraitSetterName(sym.name)) // lazy vals and vars and their setters cannot be final, but trait setters are
)
// Primitives are "abstract final" to prohibit instantiation
// without having to provide any implementations, but that is an
// illegal combination of modifiers at the bytecode level so
- // suppress final if abstract if present.
+ // suppress final if abstract is present.
import asm.Opcodes._
GenBCode.mkFlags(
if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
- if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (sym.isInterface) ACC_INTERFACE else 0,
+ if ((sym.isDeferred && !sym.hasFlag(symtab.Flags.JAVA_DEFAULTMETHOD))|| sym.hasAbstractFlag) ACC_ABSTRACT else 0,
+ if (sym.isTraitOrInterface) ACC_INTERFACE else 0,
if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
if (sym.isStaticMember) ACC_STATIC else 0,
if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
if (sym.isArtifact) ACC_SYNTHETIC else 0,
- if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
+ if (sym.isClass && !sym.isTraitOrInterface) ACC_SUPER else 0,
if (sym.hasJavaEnumFlag) ACC_ENUM else 0,
if (sym.isVarargsMethod) ACC_VARARGS else 0,
if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED 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..e6ae073a2a 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
@@ -26,9 +26,7 @@ final class BackendReportingImpl(val global: Global) extends BackendReporting {
/**
* Utilities for error reporting.
*
- * Defines some tools to make error reporting with Either easier. Would be subsumed by a right-biased
- * Either in the standard library (or scalaz \/) (Validation is different, it accumulates multiple
- * errors).
+ * Defines some utility methods to make error reporting with Either easier.
*/
object BackendReporting {
def methodSignature(classInternalName: InternalName, name: String, desc: String) = {
@@ -42,19 +40,12 @@ object BackendReporting {
def assertionError(message: String): Nothing = throw new AssertionError(message)
implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal {
- def map[U](f: B => U) = v.right.map(f)
- def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f)
- def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match {
+ def withFilter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match {
case Left(_) => v
case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty
}
- def foreach[U](f: B => U) = v.right.foreach(f)
- def getOrElse[BB >: B](alt: => BB): BB = v.right.getOrElse(alt)
-
- /**
- * Get the value, fail with an assertion if this is an error.
- */
+ /** Get the value, fail with an assertion if this is an error. */
def get: B = {
assert(v.isRight, v.left.get)
v.right.get
@@ -86,8 +77,8 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): Boolean
}
- // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here
- // in scope allows for-comprehensions that desugar into filter calls (for example when using a
+ // Method withFilter in RightBiasedEither requires an implicit empty value. Taking the value here
+ // in scope allows for-comprehensions that desugar into withFilter calls (for example when using a
// tuple de-constructor).
implicit object emptyOptimizerWarning extends OptimizerWarning {
def emitWarning(settings: ScalaSettings): Boolean = false
@@ -101,11 +92,14 @@ object BackendReporting {
else ""
}
- case MethodNotFound(name, descriptor, ownerInternalName, missingClasses) =>
- val (javaDef, others) = missingClasses.partition(_.definedInJavaSource)
- s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." +
- (if (others.isEmpty) "" else others.map(_.internalName).mkString("\nNote that the following parent classes could not be found on the classpath: ", ", ", "")) +
- (if (javaDef.isEmpty) "" else javaDef.map(_.internalName).mkString("\nNote that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: ", ",", ""))
+ case MethodNotFound(name, descriptor, ownerInternalName, missingClass) =>
+ val missingClassWarning = missingClass match {
+ case None => ""
+ case Some(c) =>
+ if (c.definedInJavaSource) s"\nNote that class ${c.internalName} is defined in a Java source (mixed compilation), no bytecode is available."
+ else s"\nNote that class ${c.internalName} could not be found on the classpath."
+ }
+ s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." + missingClassWarning
case FieldNotFound(name, descriptor, ownerInternalName, missingClass) =>
s"The field node $name$descriptor could not be found because the classfile $ownerInternalName cannot be found on the classpath." +
@@ -114,20 +108,20 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): Boolean = this match {
case ClassNotFound(_, javaDefined) =>
- if (javaDefined) settings.YoptWarningNoInlineMixed
- else settings.YoptWarningNoInlineMissingBytecode
+ if (javaDefined) settings.optWarningNoInlineMixed
+ else settings.optWarningNoInlineMissingBytecode
case m @ MethodNotFound(_, _, _, missing) =>
if (m.isArrayMethod) false
- else settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+ else settings.optWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
case FieldNotFound(_, _, _, missing) =>
- settings.YoptWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
+ settings.optWarningNoInlineMissingBytecode || missing.exists(_.emitWarning(settings))
}
}
case class ClassNotFound(internalName: InternalName, definedInJavaSource: Boolean) extends MissingBytecodeWarning
- case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClasses: List[ClassNotFound]) extends MissingBytecodeWarning {
+ case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning {
def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '['
}
case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning
@@ -143,7 +137,7 @@ object BackendReporting {
def emitWarning(settings: ScalaSettings): Boolean = this match {
case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings)
- case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.YoptWarningNoInlineMissingBytecode
+ case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.optWarningNoInlineMissingBytecode
}
}
@@ -170,85 +164,89 @@ object BackendReporting {
case MethodInlineInfoError(_, _, _, cause) =>
s"Error while computing the inline information for method $warningMessageSignature:\n" + cause
-
- case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) =>
- cause.toString
}
def emitWarning(settings: ScalaSettings): Boolean = this match {
case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings)
case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings)
- case MethodInlineInfoMissing(_, _, _, None) => settings.YoptWarningNoInlineMissingBytecode
+ case MethodInlineInfoMissing(_, _, _, None) => settings.optWarningNoInlineMissingBytecode
case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings)
-
- case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings)
}
}
case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning
case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning
case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning
- case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning
sealed trait CannotInlineWarning extends OptimizerWarning {
def calleeDeclarationClass: InternalName
def name: String
def descriptor: String
- def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor)
+ /** Either the callee or the callsite is annotated @inline */
+ def annotatedInline: Boolean
- override def toString = this match {
- case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) =>
- s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" +
- s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass."
-
- case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) =>
- s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause
-
- case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
- s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the
- |arguments expected by the callee $calleeMethodSig. These values would be discarded
- |when entering an exception handler declared in the inlined method.""".stripMargin
-
- case SynchronizedMethod(_, _, _) =>
- s"Method $calleeMethodSig cannot be inlined because it is synchronized."
+ def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor)
- case StrictfpMismatch(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
- s"""The callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
- |does not have the same strictfp mode as the callee $calleeMethodSig.
+ override def toString = {
+ val annotWarn = if (annotatedInline) " is annotated @inline but" else ""
+ val warning = s"$calleeMethodSig$annotWarn could not be inlined:\n"
+ val reason = this match {
+ case CalleeNotFinal(_, _, _, _) =>
+ s"The method is not final and may be overridden."
+ case IllegalAccessInstruction(_, _, _, _, callsiteClass, instruction) =>
+ s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" +
+ s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass."
+
+ case IllegalAccessCheckFailed(_, _, _, _, callsiteClass, instruction, cause) =>
+ s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause
+
+ case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the
+ |arguments expected by the callee $calleeMethodSig. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ case SynchronizedMethod(_, _, _, _) =>
+ s"Method $calleeMethodSig cannot be inlined because it is synchronized."
+
+ case StrictfpMismatch(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
+ |does not have the same strictfp mode as the callee $calleeMethodSig.
""".stripMargin
- case ResultingMethodTooLarge(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
- s"""The size of the callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
- |would exceed the JVM method size limit after inlining $calleeMethodSig.
+ case ResultingMethodTooLarge(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) =>
+ s"""The size of the callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)}
+ |would exceed the JVM method size limit after inlining $calleeMethodSig.
""".stripMargin
+ }
+ warning + reason
}
- def emitWarning(settings: ScalaSettings): Boolean = this match {
- case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge =>
- settings.YoptWarningEmitAtInlineFailed
-
- case IllegalAccessCheckFailed(_, _, _, _, _, cause) =>
- cause.emitWarning(settings)
+ def emitWarning(settings: ScalaSettings): Boolean = {
+ settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) ||
+ annotatedInline && settings.optWarningEmitAtInlineFailed
}
}
- case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class CalleeNotFinal(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean) extends CannotInlineWarning
+ case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning
- case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning
- case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
- case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning
- case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean) extends CannotInlineWarning
+ case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
- case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String,
+ case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean,
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.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed)
}
/**
@@ -260,7 +258,7 @@ object BackendReporting {
override def emitWarning(settings: ScalaSettings): Boolean = this match {
case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings)
- case RewriteClosureIllegalAccess(_, _) => settings.YoptWarningEmitAtInlineFailed
+ case RewriteClosureIllegalAccess(_, _) => settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed)
}
override def toString: String = this match {
@@ -285,17 +283,17 @@ object BackendReporting {
s"Failed to get the type of a method of class symbol $classFullName due to SI-9111."
case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) =>
- s"Failed to build the inline information: $missingClass."
+ s"Failed to build the inline information: $missingClass"
case UnknownScalaInlineInfoVersion(internalName, version) =>
s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler."
}
def emitWarning(settings: ScalaSettings): Boolean = this match {
- case NoInlineInfoAttribute(_) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr
+ case NoInlineInfoAttribute(_) => settings.optWarningNoInlineMissingScalaInlineInfoAttr
case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings)
- case ClassSymbolInfoFailureSI9111(_) => settings.YoptWarningNoInlineMissingBytecode
- case UnknownScalaInlineInfoVersion(_, _) => settings.YoptWarningNoInlineMissingScalaInlineInfoAttr
+ case ClassSymbolInfoFailureSI9111(_) => settings.optWarningNoInlineMissingBytecode
+ case UnknownScalaInlineInfoVersion(_, _) => settings.optWarningNoInlineMissingScalaInlineInfoAttr
}
}
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/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
index 1d29fdee10..2cf5cfcb8d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
@@ -6,7 +6,7 @@
package scala.tools.nsc
package backend.jvm
-import java.io.{ DataOutputStream, FileOutputStream, IOException, OutputStream, File => JFile }
+import java.io.{ DataOutputStream, FileOutputStream, IOException, File => JFile }
import scala.tools.nsc.io._
import java.util.jar.Attributes.Name
import scala.language.postfixOps
@@ -78,7 +78,7 @@ trait BytecodeWriters {
}
/*
- * The ASM textual representation for bytecode overcomes disadvantages of javap ouput in three areas:
+ * The ASM textual representation for bytecode overcomes disadvantages of javap output in three areas:
* (a) pickle dingbats undecipherable to the naked eye;
* (b) two constant pools, while having identical contents, are displayed differently due to physical layout.
* (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap,
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
index 00ca096e59..acb950929f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala
@@ -1,7 +1,8 @@
package scala.tools.nsc
package backend.jvm
-import scala.annotation.switch
+import scala.tools.asm
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
/**
* Core BTypes and some other definitions. The initialization of these definitions requires access
@@ -9,7 +10,7 @@ import scala.annotation.switch
*
* The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To
* make sure the definitions are consistent with the symbols in the current run, the
- * `intializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each
+ * `initializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each
* compiler run.
*
* The class BTypesFromSymbols does not directly reference CoreBTypes, but CoreBTypesProxy. The
@@ -29,14 +30,14 @@ import scala.annotation.switch
class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
import bTypes._
import global._
- import rootMirror.{requiredClass, getClassIfDefined}
+ import rootMirror.{requiredClass, getRequiredClass, getClassIfDefined}
import definitions._
/**
* Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above
* the first use of `classBTypeFromSymbol` because that method looks at the map.
*/
- lazy val primitiveTypeMap: Map[Symbol, PrimitiveBType] = Map(
+ lazy val primitiveTypeToBType: Map[Symbol, PrimitiveBType] = Map(
UnitClass -> UNIT,
BooleanClass -> BOOL,
CharClass -> CHAR,
@@ -45,34 +46,22 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
IntClass -> INT,
LongClass -> LONG,
FloatClass -> FLOAT,
- DoubleClass -> DOUBLE
- )
-
- lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void])
- lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass)
- lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass)
- lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass)
- lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass)
- lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass)
- lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass)
- lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass)
- lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass)
+ DoubleClass -> DOUBLE)
/**
* Map from primitive types to their boxed class type. Useful when pushing class literals onto the
* operand stack (ldc instruction taking a class literal), see genConstant.
*/
lazy val boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = Map(
- UNIT -> BOXED_UNIT,
- BOOL -> BOXED_BOOLEAN,
- BYTE -> BOXED_BYTE,
- SHORT -> BOXED_SHORT,
- CHAR -> BOXED_CHAR,
- INT -> BOXED_INT,
- LONG -> BOXED_LONG,
- FLOAT -> BOXED_FLOAT,
- DOUBLE -> BOXED_DOUBLE
- )
+ UNIT -> classBTypeFromSymbol(requiredClass[java.lang.Void]),
+ BOOL -> classBTypeFromSymbol(BoxedBooleanClass),
+ BYTE -> classBTypeFromSymbol(BoxedByteClass),
+ SHORT -> classBTypeFromSymbol(BoxedShortClass),
+ CHAR -> classBTypeFromSymbol(BoxedCharacterClass),
+ INT -> classBTypeFromSymbol(BoxedIntClass),
+ LONG -> classBTypeFromSymbol(BoxedLongClass),
+ FLOAT -> classBTypeFromSymbol(BoxedFloatClass),
+ DOUBLE -> classBTypeFromSymbol(BoxedDoubleClass))
lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet
@@ -82,7 +71,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
*/
lazy val boxResultType: Map[Symbol, ClassBType] = {
for ((valueClassSym, boxMethodSym) <- currentRun.runDefinitions.boxMethod)
- yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym))
+ yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeToBType(valueClassSym))
}
/**
@@ -90,96 +79,148 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
* For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */
lazy val unboxResultType: Map[Symbol, PrimitiveBType] = {
for ((valueClassSym, unboxMethodSym) <- currentRun.runDefinitions.unboxMethod)
- yield unboxMethodSym -> primitiveTypeMap(valueClassSym)
+ yield unboxMethodSym -> primitiveTypeToBType(valueClassSym)
}
/*
* RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in
- * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs.
+ * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass
+ * (scala.Null) in Scala ASTs.
*
* Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal
* names of NothingClass and NullClass can't be emitted as-is.
*/
- lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$])
- lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$])
-
- lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass)
- lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference)
-
- lazy val StringReference : ClassBType = classBTypeFromSymbol(StringClass)
- lazy val StringBuilderReference : ClassBType = classBTypeFromSymbol(StringBuilderClass)
- lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(ThrowableClass)
- lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable
- lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException
- lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable
- lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable
- lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException
- lazy val javaUtilMapReference : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map
- lazy val javaUtilHashMapReference : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap
-
- lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef])
- lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef])
- lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef])
- lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef])
- lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef])
- lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef])
- lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef])
-
- lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_)
+ lazy val srNothingRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$])
+ lazy val srNullRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$])
+
+ lazy val ObjectRef : ClassBType = classBTypeFromSymbol(ObjectClass)
+ lazy val StringRef : ClassBType = classBTypeFromSymbol(StringClass)
+ lazy val PredefRef : ClassBType = classBTypeFromSymbol(PredefModule.moduleClass)
+ lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(JavaStringBuilderClass)
+ lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(JavaStringBufferClass)
+ lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(JavaCharSequenceClass)
+ lazy val jlThrowableRef : ClassBType = classBTypeFromSymbol(ThrowableClass)
+ lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable
+ lazy val jiSerializableRef : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable
+ lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException
+ lazy val juMapRef : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map
+ lazy val juHashMapRef : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap
+ lazy val sbScalaBeanInfoRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.beans.ScalaBeanInfo])
+ lazy val jliSerializedLambdaRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda])
+ lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandle])
+ lazy val jliMethodHandlesRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles])
+ lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(exitingPickler(getRequiredClass("java.lang.invoke.MethodHandles.Lookup"))) // didn't find a reliable non-stringly-typed way that works for inner classes in the backend
+ lazy val jliMethodTypeRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType])
+ lazy val jliCallSiteRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite])
+ lazy val jliLambdaMetafactoryRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory])
+ lazy val srBoxesRunTimeRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])
+ lazy val srSymbolLiteral : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.SymbolLiteral])
+ lazy val srStructuralCallSite : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.StructuralCallSite])
+ lazy val srLambdaDeserialize : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize])
+ lazy val srBoxedUnitRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxedUnit])
+
+ private def methodNameAndType(cls: Symbol, name: Name, static: Boolean = false, filterOverload: Symbol => Boolean = _ => true): MethodNameAndType = {
+ val holder = if (static) cls.companionModule.moduleClass else cls
+ val method = holder.info.member(name).suchThat(filterOverload)
+ assert(!method.isOverloaded, method)
+ MethodNameAndType(name.toString, methodBTypeFromSymbol(method))
+ }
- // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540
- lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable")
- lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator")
+ private def srBoxesRuntimeMethods(getName: (String, String) => String): Map[BType, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val bType = primitiveTypeToBType(primitive)
+ val name = newTermName(getName(primitive.name.toString, boxedClass(primitive).name.toString))
+ (bType, methodNameAndType(BoxesRunTimeClass, name))
+ })(collection.breakOut)
+ }
- lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo]
+ // Z -> MethodNameAndType(boxToBoolean,(Z)Ljava/lang/Boolean;)
+ lazy val srBoxesRuntimeBoxToMethods: Map[BType, MethodNameAndType] = srBoxesRuntimeMethods((primitive, boxed) => "boxTo" + boxed)
- /* The Object => String overload. */
- lazy val String_valueOf: Symbol = {
- getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match {
- case List(pt) => pt.typeSymbol == ObjectClass
- case _ => false
- })
+ // Z -> MethodNameAndType(unboxToBoolean,(Ljava/lang/Object;)Z)
+ lazy val srBoxesRuntimeUnboxToMethods: Map[BType, MethodNameAndType] = srBoxesRuntimeMethods((primitive, boxed) => "unboxTo" + primitive)
+
+ def singleParamOfClass(cls: Symbol) = (s: Symbol) => s.paramss match {
+ case List(List(param)) => param.info.typeSymbol == cls
+ case _ => false
}
- // scala.FunctionX and scala.runtim.AbstractFunctionX
- lazy val FunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut)
- lazy val AbstractFunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut)
- lazy val AbstractFunctionArityMap : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap
+ // java/lang/Boolean -> MethodNameAndType(valueOf,(Z)Ljava/lang/Boolean;)
+ lazy val javaBoxMethods: Map[InternalName, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ val method = methodNameAndType(boxed, newTermName("valueOf"), static = true, filterOverload = singleParamOfClass(primitive))
+ (classBTypeFromSymbol(boxed).internalName, method)
+ })(collection.breakOut)
+ }
- lazy val PartialFunctionReference : ClassBType = classBTypeFromSymbol(PartialFunctionClass)
- lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass)
+ // java/lang/Boolean -> MethodNameAndType(booleanValue,()Z)
+ lazy val javaUnboxMethods: Map[InternalName, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ val name = primitive.name.toString.toLowerCase + "Value"
+ (classBTypeFromSymbol(boxed).internalName, methodNameAndType(boxed, newTermName(name)))
+ })(collection.breakOut)
+ }
- lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])
+ private def predefBoxingMethods(getName: (String, String) => String): Map[String, MethodBType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ val name = getName(primitive.name.toString, boxed.name.toString)
+ (name, methodNameAndType(PredefModule.moduleClass, newTermName(name)).methodType)
+ })(collection.breakOut)
+ }
- /**
- * Methods in scala.runtime.BoxesRuntime
- */
- lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map(
- BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), BOXED_BOOLEAN)),
- BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), BOXED_BYTE)),
- CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), BOXED_CHAR)),
- SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), BOXED_SHORT)),
- INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), BOXED_INT)),
- LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), BOXED_LONG)),
- FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), BOXED_FLOAT)),
- DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), BOXED_DOUBLE))
- )
-
- lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map(
- BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)),
- BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)),
- CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)),
- SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)),
- INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)),
- LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)),
- FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)),
- DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE))
- )
+ // boolean2Boolean -> (Z)Ljava/lang/Boolean;
+ lazy val predefAutoBoxMethods: Map[String, MethodBType] = predefBoxingMethods((primitive, boxed) => primitive.toLowerCase + "2" + boxed)
+
+ // Boolean2boolean -> (Ljava/lang/Boolean;)Z
+ lazy val predefAutoUnboxMethods: Map[String, MethodBType] = predefBoxingMethods((primitive, boxed) => boxed + "2" + primitive.toLowerCase)
+
+ private def staticRefMethods(name: Name): Map[InternalName, MethodNameAndType] = {
+ allRefClasses.map(refClass =>
+ (classBTypeFromSymbol(refClass).internalName, methodNameAndType(refClass, name, static = true)))(collection.breakOut)
+ }
+
+ // scala/runtime/BooleanRef -> MethodNameAndType(create,(Z)Lscala/runtime/BooleanRef;)
+ lazy val srRefCreateMethods: Map[InternalName, MethodNameAndType] = staticRefMethods(nme.create)
+
+ // scala/runtime/BooleanRef -> MethodNameAndType(zero,()Lscala/runtime/BooleanRef;)
+ lazy val srRefZeroMethods: Map[InternalName, MethodNameAndType] = staticRefMethods(nme.zero)
+
+ // java/lang/Boolean -> MethodNameAndType(<init>,(Z)V)
+ lazy val primitiveBoxConstructors: Map[InternalName, MethodNameAndType] = {
+ ScalaValueClassesNoUnit.map(primitive => {
+ val boxed = boxedClass(primitive)
+ (classBTypeFromSymbol(boxed).internalName, methodNameAndType(boxed, nme.CONSTRUCTOR, filterOverload = singleParamOfClass(primitive)))
+ })(collection.breakOut)
+ }
+
+ private def nonOverloadedConstructors(classes: Iterable[Symbol]): Map[InternalName, MethodNameAndType] = {
+ classes.map(cls => (classBTypeFromSymbol(cls).internalName, methodNameAndType(cls, nme.CONSTRUCTOR)))(collection.breakOut)
+ }
+
+ // scala/runtime/BooleanRef -> MethodNameAndType(<init>,(Z)V)
+ lazy val srRefConstructors: Map[InternalName, MethodNameAndType] = nonOverloadedConstructors(allRefClasses)
+
+ private def specializedSubclasses(cls: Symbol): List[Symbol] = {
+ exitingSpecialize(cls.info) // the `transformInfo` method of specialization adds specialized subclasses to the `specializedClass` map
+ specializeTypes.specializedClass.collect({
+ case ((`cls`, _), specCls) => specCls
+ }).toList
+ }
+
+ // scala/Tuple3 -> MethodNameAndType(<init>,(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V)
+ // scala/Tuple2$mcZC$sp -> MethodNameAndType(<init>,(ZC)V)
+ lazy val tupleClassConstructors: Map[InternalName, MethodNameAndType] = {
+ val tupleClassSymbols = TupleClass.seq ++ specializedSubclasses(TupleClass(1)) ++ specializedSubclasses(TupleClass(2))
+ nonOverloadedConstructors(tupleClassSymbols)
+ }
lazy val typeOfArrayOp: Map[Int, BType] = {
import scalaPrimitives._
Map(
- (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++
+ (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++
(List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++
(List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++
(List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++
@@ -187,9 +228,67 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
(List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++
(List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++
(List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++
- (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _*
+ (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) : _*
)
}
+
+ lazy val hashMethodSym: Symbol = getMember(RuntimeStaticsModule, nme.anyHash)
+
+ // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540
+ lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable")
+ lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator")
+
+ lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo]
+
+ /* The Object => String overload. */
+ lazy val String_valueOf: Symbol = {
+ getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match {
+ case List(pt) => pt.typeSymbol == ObjectClass
+ case _ => false
+ })
+ }
+
+ lazy val lambdaMetaFactoryMetafactoryHandle =
+ new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
+ coreBTypes.jliLambdaMetafactoryRef.internalName, sn.Metafactory.toString,
+ MethodBType(
+ List(
+ coreBTypes.jliMethodHandlesLookupRef,
+ coreBTypes.StringRef,
+ coreBTypes.jliMethodTypeRef,
+ coreBTypes.jliMethodTypeRef,
+ coreBTypes.jliMethodHandleRef,
+ coreBTypes.jliMethodTypeRef),
+ coreBTypes.jliCallSiteRef
+ ).descriptor,
+ /* itf = */ coreBTypes.jliLambdaMetafactoryRef.isInterface.get)
+
+ lazy val lambdaMetaFactoryAltMetafactoryHandle =
+ new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
+ coreBTypes.jliLambdaMetafactoryRef.internalName, sn.AltMetafactory.toString,
+ MethodBType(
+ List(
+ coreBTypes.jliMethodHandlesLookupRef,
+ coreBTypes.StringRef,
+ coreBTypes.jliMethodTypeRef,
+ ArrayBType(ObjectRef)),
+ coreBTypes.jliCallSiteRef
+ ).descriptor,
+ /* itf = */ coreBTypes.jliLambdaMetafactoryRef.isInterface.get)
+
+ lazy val lambdaDeserializeBootstrapHandle =
+ new scala.tools.asm.Handle(scala.tools.asm.Opcodes.H_INVOKESTATIC,
+ coreBTypes.srLambdaDeserialize.internalName, sn.Bootstrap.toString,
+ MethodBType(
+ List(
+ coreBTypes.jliMethodHandlesLookupRef,
+ coreBTypes.StringRef,
+ coreBTypes.jliMethodTypeRef,
+ ArrayBType(jliMethodHandleRef)
+ ),
+ coreBTypes.jliCallSiteRef
+ ).descriptor,
+ /* itf = */ coreBTypes.srLambdaDeserialize.isInterface.get)
}
/**
@@ -205,13 +304,46 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] {
import bTypes._
def boxedClasses: Set[ClassBType]
-
- def RT_NOTHING : ClassBType
- def RT_NULL : ClassBType
-
- def ObjectReference : ClassBType
- def jlCloneableReference : ClassBType
- def jioSerializableReference : ClassBType
+ def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType]
+
+ def srNothingRef : ClassBType
+ def srNullRef : ClassBType
+
+ def ObjectRef : ClassBType
+ def StringRef : ClassBType
+ def PredefRef : ClassBType
+ def jlCloneableRef : ClassBType
+ def jiSerializableRef : ClassBType
+ def juHashMapRef : ClassBType
+ def juMapRef : ClassBType
+ def jliCallSiteRef : ClassBType
+ def jliLambdaMetafactoryRef : ClassBType
+ def jliMethodTypeRef : ClassBType
+ def jliSerializedLambdaRef : ClassBType
+ def jliMethodHandleRef : ClassBType
+ def jliMethodHandlesLookupRef : ClassBType
+ def srBoxesRunTimeRef : ClassBType
+ def srBoxedUnitRef : ClassBType
+
+ def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType]
+ def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType]
+
+ def javaBoxMethods : Map[InternalName, MethodNameAndType]
+ def javaUnboxMethods : Map[InternalName, MethodNameAndType]
+
+ def predefAutoBoxMethods : Map[String, MethodBType]
+ def predefAutoUnboxMethods : Map[String, MethodBType]
+
+ def srRefCreateMethods : Map[InternalName, MethodNameAndType]
+ def srRefZeroMethods : Map[InternalName, MethodNameAndType]
+
+ def primitiveBoxConstructors : Map[InternalName, MethodNameAndType]
+ def srRefConstructors : Map[InternalName, MethodNameAndType]
+ def tupleClassConstructors : Map[InternalName, MethodNameAndType]
+
+ def lambdaMetaFactoryMetafactoryHandle : asm.Handle
+ def lambdaMetaFactoryAltMetafactoryHandle : asm.Handle
+ def lambdaDeserializeBootstrapHandle : asm.Handle
}
/**
@@ -226,50 +358,63 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes:
_coreBTypes = coreBTypes.asInstanceOf[CoreBTypes[bTypes.type]]
}
- def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap
-
- def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT
- def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN
- def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE
- def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT
- def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR
- def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT
- def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG
- def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT
- def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE
+ def primitiveTypeToBType: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeToBType
def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses
-
def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive
def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType
-
def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType
- def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING
- def RT_NULL : ClassBType = _coreBTypes.RT_NULL
-
- def ObjectReference : ClassBType = _coreBTypes.ObjectReference
- def objArrayReference : ArrayBType = _coreBTypes.objArrayReference
-
- def StringReference : ClassBType = _coreBTypes.StringReference
- def StringBuilderReference : ClassBType = _coreBTypes.StringBuilderReference
- def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference
- def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference
- def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference
- def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference
- def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference
- def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference
- def javaUtilMapReference : ClassBType = _coreBTypes.javaUtilMapReference
- def javaUtilHashMapReference : ClassBType = _coreBTypes.javaUtilHashMapReference
-
- def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef
- def srByteRef : ClassBType = _coreBTypes.srByteRef
- def srCharRef : ClassBType = _coreBTypes.srCharRef
- def srIntRef : ClassBType = _coreBTypes.srIntRef
- def srLongRef : ClassBType = _coreBTypes.srLongRef
- def srFloatRef : ClassBType = _coreBTypes.srFloatRef
- def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef
+ def srNothingRef : ClassBType = _coreBTypes.srNothingRef
+ def srNullRef : ClassBType = _coreBTypes.srNullRef
+
+ def ObjectRef : ClassBType = _coreBTypes.ObjectRef
+ def StringRef : ClassBType = _coreBTypes.StringRef
+ def PredefRef : ClassBType = _coreBTypes.PredefRef
+ def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef
+ def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef
+ def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef
+ def jlThrowableRef : ClassBType = _coreBTypes.jlThrowableRef
+ def jlCloneableRef : ClassBType = _coreBTypes.jlCloneableRef
+ def jiSerializableRef : ClassBType = _coreBTypes.jiSerializableRef
+ def jlClassCastExceptionRef : ClassBType = _coreBTypes.jlClassCastExceptionRef
+ def juMapRef : ClassBType = _coreBTypes.juMapRef
+ def juHashMapRef : ClassBType = _coreBTypes.juHashMapRef
+ def sbScalaBeanInfoRef : ClassBType = _coreBTypes.sbScalaBeanInfoRef
+ def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef
+ def jliMethodHandleRef : ClassBType = _coreBTypes.jliMethodHandleRef
+ def jliMethodHandlesRef : ClassBType = _coreBTypes.jliMethodHandlesRef
+ def jliMethodHandlesLookupRef : ClassBType = _coreBTypes.jliMethodHandlesLookupRef
+ def jliMethodTypeRef : ClassBType = _coreBTypes.jliMethodTypeRef
+ def jliCallSiteRef : ClassBType = _coreBTypes.jliCallSiteRef
+ def jliLambdaMetafactoryRef : ClassBType = _coreBTypes.jliLambdaMetafactoryRef
+ def srBoxesRunTimeRef : ClassBType = _coreBTypes.srBoxesRunTimeRef
+ def srBoxedUnitRef : ClassBType = _coreBTypes.srBoxedUnitRef
+
+ def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType] = _coreBTypes.srBoxesRuntimeBoxToMethods
+ def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType] = _coreBTypes.srBoxesRuntimeUnboxToMethods
+
+ def javaBoxMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.javaBoxMethods
+ def javaUnboxMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.javaUnboxMethods
+
+ def predefAutoBoxMethods : Map[String, MethodBType] = _coreBTypes.predefAutoBoxMethods
+ def predefAutoUnboxMethods : Map[String, MethodBType] = _coreBTypes.predefAutoUnboxMethods
+
+ def srRefCreateMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefCreateMethods
+ def srRefZeroMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefZeroMethods
+
+ def primitiveBoxConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.primitiveBoxConstructors
+ def srRefConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefConstructors
+ def tupleClassConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.tupleClassConstructors
+
+ def srSymbolLiteral : ClassBType = _coreBTypes.srSymbolLiteral
+ def srStructuralCallSite : ClassBType = _coreBTypes.srStructuralCallSite
+ def srLambdaDeserialize : ClassBType = _coreBTypes.srLambdaDeserialize
+
+ def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp
+
+ // Some symbols. These references should probably be moved to Definitions.
def hashMethodSym: Symbol = _coreBTypes.hashMethodSym
@@ -280,17 +425,7 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes:
def String_valueOf: Symbol = _coreBTypes.String_valueOf
- def FunctionReference : Vector[ClassBType] = _coreBTypes.FunctionReference
- def AbstractFunctionReference : Vector[ClassBType] = _coreBTypes.AbstractFunctionReference
- def AbstractFunctionArityMap : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap
-
- def PartialFunctionReference : ClassBType = _coreBTypes.PartialFunctionReference
- def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference
-
- def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime
-
- def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo
- def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo
-
- def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp
+ def lambdaMetaFactoryMetafactoryHandle : asm.Handle = _coreBTypes.lambdaMetaFactoryMetafactoryHandle
+ def lambdaMetaFactoryAltMetafactoryHandle : asm.Handle = _coreBTypes.lambdaMetaFactoryAltMetafactoryHandle
+ def lambdaDeserializeBootstrapHandle : asm.Handle = _coreBTypes.lambdaDeserializeBootstrapHandle
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
deleted file mode 100644
index 4768417c67..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ /dev/null
@@ -1,3350 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-package scala
-package tools.nsc
-package backend.jvm
-
-import scala.collection.{ mutable, immutable }
-import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }
-import scala.tools.nsc.backend.jvm.opt.InlineInfoAttribute
-import scala.tools.nsc.symtab._
-import scala.tools.asm
-import asm.Label
-import scala.annotation.tailrec
-
-/**
- * @author Iulian Dragos (version 1.0, FJBG-based implementation)
- * @author Miguel Garcia (version 2.0, ASM-based implementation)
- *
- * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf
- */
-abstract class GenASM extends SubComponent with BytecodeWriters { self =>
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions._
-
- val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global)
- import bCodeAsmCommon._
-
- // Strangely I can't find this in the asm code
- // 255, but reserving 1 for "this"
- final val MaximumJvmParameters = 254
-
- val phaseName = "jvm"
-
- /** Create a new phase */
- override def newPhase(p: Phase): Phase = new AsmPhase(p)
-
- /** From the reference documentation of the Android SDK:
- * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`.
- * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`,
- * which is an object implementing the `Parcelable.Creator` interface.
- */
- private val androidFieldName = newTermName("CREATOR")
-
- private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable")
- private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator")
-
- /** JVM code generation phase
- */
- class AsmPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
- override def erasedTypes = true
- def apply(cls: IClass) = sys.error("no implementation")
-
- // An AsmPhase starts and ends within a Run, thus the caches in question will get populated and cleared within a Run, too), SI-7422
- javaNameCache.clear()
- javaNameCache ++= List(
- NothingClass -> binarynme.RuntimeNothing,
- RuntimeNothingClass -> binarynme.RuntimeNothing,
- NullClass -> binarynme.RuntimeNull,
- RuntimeNullClass -> binarynme.RuntimeNull
- )
-
- // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
- reverseJavaName.clear()
- reverseJavaName ++= List(
- binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type.
- binarynme.RuntimeNull.toString() -> RuntimeNullClass
- )
-
- // Lazy val; can't have eager vals in Phase constructors which may
- // cause cycles before Global has finished initialization.
- lazy val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo")
-
- private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = {
- settings.outputDirs.getSingleOutput match {
- case Some(f) if f hasExtension "jar" =>
- // If no main class was specified, see if there's only one
- // entry point among the classes going into the jar.
- if (settings.mainClass.isDefault) {
- entryPoints map (_.symbol fullName '.') match {
- case Nil =>
- log("No Main-Class designated or discovered.")
- case name :: Nil =>
- log("Unique entry point: setting Main-Class to " + name)
- settings.mainClass.value = name
- case names =>
- log("No Main-Class due to multiple entry points:\n " + names.mkString("\n "))
- }
- }
- else log("Main-Class was specified: " + settings.mainClass.value)
-
- new DirectToJarfileWriter(f.file)
-
- case _ => factoryNonJarBytecodeWriter()
- }
- }
-
- private def isJavaEntryPoint(icls: IClass) = {
- val sym = icls.symbol
- def fail(msg: String, pos: Position = sym.pos) = {
- reporter.warning(sym.pos,
- sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" +
- " Reason: " + msg
- // TODO: make this next claim true, if possible
- // by generating valid main methods as static in module classes
- // not sure what the jvm allows here
- // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead."
- )
- false
- }
- def failNoForwarder(msg: String) = {
- fail(msg + ", which means no static forwarder can be generated.\n")
- }
- val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil
- val hasApproximate = possibles exists { m =>
- m.info match {
- case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass
- case _ => false
- }
- }
- // At this point it's a module with a main-looking method, so either succeed or warn that it isn't.
- hasApproximate && {
- // Before erasure so we can identify generic mains.
- enteringErasure {
- val companion = sym.linkedClassOfClass
-
- if (hasJavaMainMethod(companion))
- failNoForwarder("companion contains its own main method")
- else if (companion.tpe.member(nme.main) != NoSymbol)
- // this is only because forwarders aren't smart enough yet
- failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
- else if (companion.isTrait)
- failNoForwarder("companion is a trait")
- // Now either succeeed, or issue some additional warnings for things which look like
- // attempts to be java main methods.
- else (possibles exists isJavaMainMethod) || {
- possibles exists { m =>
- m.info match {
- case PolyType(_, _) =>
- fail("main methods cannot be generic.")
- case MethodType(params, res) =>
- if (res.typeSymbol :: params exists (_.isAbstractType))
- fail("main methods cannot refer to type parameters or abstract types.", m.pos)
- else
- isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
- case tp =>
- fail("don't know what this is: " + tp, m.pos)
- }
- }
- }
- }
- }
- }
-
- override def run() {
-
- if (settings.debug)
- inform("[running phase " + name + " on icode]")
-
- if (settings.Xdce) {
- val classes = icodes.classes.keys.toList // copy to avoid mutating the map while iterating
- for (sym <- classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) {
- log(s"Optimizer eliminated ${sym.fullNameString}")
- deadCode.elidedClosures += sym
- icodes.classes -= sym
- }
- }
-
- // For predictably ordered error messages.
- var sortedClasses = classes.values.toList sortBy (_.symbol.fullName)
-
- // Warn when classes will overwrite one another on case-insensitive systems.
- for ((_, v1 :: v2 :: _) <- sortedClasses groupBy (_.symbol.javaClassName.toString.toLowerCase)) {
- reporter.warning(v1.symbol.pos,
- s"Class ${v1.symbol.javaClassName} differs only in case from ${v2.symbol.javaClassName}. " +
- "Such classes will overwrite one another on case-insensitive filesystems.")
- }
-
- debuglog(s"Created new bytecode generator for ${classes.size} classes.")
- val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
- val needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter]
- val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile)
- val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile)
- val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile)
-
- def emitFor(c: IClass) {
- if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) {
- if (c.symbol.companionClass == NoSymbol)
- mirrorCodeGen genMirrorClass (c.symbol, c.cunit)
- else
- log(s"No mirror class for module with linked class: ${c.symbol.fullName}")
- }
- plainCodeGen genClass c
- if (c.symbol hasAnnotation BeanInfoAttr) beanInfoCodeGen genBeanInfoClass c
- }
-
- while (!sortedClasses.isEmpty) {
- val c = sortedClasses.head
- try emitFor(c)
- catch {
- case e: FileConflictException =>
- reporter.error(c.symbol.pos, s"error writing ${c.symbol}: ${e.getMessage}")
- }
- sortedClasses = sortedClasses.tail
- classes -= c.symbol // GC opportunity
- }
-
- bytecodeWriter.close()
-
- /* don't javaNameCache.clear() because that causes the following tests to fail:
- * test/files/run/macro-repl-dontexpand.scala
- * test/files/jvm/interpreter.scala
- * TODO but why? what use could javaNameCache possibly see once GenASM is over?
- */
-
- /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification:
- *
- * (1) call the asm.util.CheckAdapter.verify() overload:
- * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw)
- *
- * (2) passing a custom ClassLoader to verify inter-dependent classes.
- *
- * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool).
- */
-
- } // end of AsmPhase.run()
-
- } // end of class AsmPhase
-
- var pickledBytes = 0 // statistics
-
- val javaNameCache = perRunCaches.newAnyRefMap[Symbol, Name]()
-
- // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
- val reverseJavaName = perRunCaches.newAnyRefMap[String, Symbol]()
-
- private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _)
- private def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0
- private def isRemote(s: Symbol) = s hasAnnotation RemoteAttr
-
- /**
- * Return the Java modifiers for the given symbol.
- * Java modifiers for classes:
- * - public, abstract, final, strictfp (not used)
- * for interfaces:
- * - the same as for classes, without 'final'
- * for fields:
- * - public, private (*)
- * - static, final
- * for methods:
- * - the same as for fields, plus:
- * - abstract, synchronized (not used), strictfp (not used), native (not used)
- *
- * (*) protected cannot be used, since inner classes 'see' protected members,
- * and they would fail verification after lifted.
- */
- def javaFlags(sym: Symbol): Int = {
- // constructors of module classes should be private
- // PP: why are they only being marked private at this stage and not earlier?
- val privateFlag =
- sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner))
-
- // Final: the only fields which can receive ACC_FINAL are eager vals.
- // Neither vars nor lazy vals can, because:
- //
- // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
- // "Another problem is that the specification allows aggressive
- // optimization of final fields. Within a thread, it is permissible to
- // reorder reads of a final field with those modifications of a final
- // field that do not take place in the constructor."
- //
- // A var or lazy val which is marked final still has meaning to the
- // scala compiler. The word final is heavily overloaded unfortunately;
- // for us it means "not overridable". At present you can't override
- // vars regardless; this may change.
- //
- // The logic does not check .isFinal (which checks flags for the FINAL flag,
- // and includes symbols marked lateFINAL) instead inspecting rawflags so
- // we can exclude lateFINAL. Such symbols are eligible for inlining, but to
- // avoid breaking proxy software which depends on subclassing, we do not
- // emit ACC_FINAL.
- // Nested objects won't receive ACC_FINAL in order to allow for their overriding.
-
- val finalFlag = (
- (((sym.rawflags & Flags.FINAL) != 0) || isTopLevelModule(sym))
- && !sym.enclClass.isInterface
- && !sym.isClassConstructor
- && !sym.isMutable // lazy vals and vars both
- )
-
- // Primitives are "abstract final" to prohibit instantiation
- // without having to provide any implementations, but that is an
- // illegal combination of modifiers at the bytecode level so
- // suppress final if abstract if present.
- import asm.Opcodes._
- mkFlags(
- if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
- if (sym.isDeferred || 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,
- if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
- if (sym.isArtifact) ACC_SYNTHETIC else 0,
- if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
- if (sym.hasJavaEnumFlag) ACC_ENUM else 0,
- if (sym.isVarargsMethod) ACC_VARARGS else 0,
- if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
- )
- }
-
- def javaFieldFlags(sym: Symbol) = {
- javaFlags(sym) | mkFlags(
- if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
- if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0,
- if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL
- )
- }
-
- def isTopLevelModule(sym: Symbol): Boolean =
- exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
-
- def isStaticModule(sym: Symbol): Boolean = {
- sym.isModuleClass && !sym.isImplClass && !sym.isLifted
- }
-
- // -----------------------------------------------------------------------------------------
- // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM)
- // Background:
- // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf
- // http://comments.gmane.org/gmane.comp.java.vm.languages/2293
- // https://issues.scala-lang.org/browse/SI-3872
- // -----------------------------------------------------------------------------------------
-
- /**
- * Given an internal name (eg "java/lang/Integer") returns the class symbol for it.
- *
- * Better not to need this method (an example where control flow arrives here is welcome).
- * This method is invoked only upon both (1) and (2) below happening:
- * (1) providing an asm.ClassWriter with an internal name by other means than javaName()
- * (2) forgetting to track the corresponding class-symbol in reverseJavaName.
- *
- * (The first item is already unlikely because we rely on javaName()
- * to do the bookkeeping for entries that should go in innerClassBuffer.)
- *
- * (We could do completely without this method at the expense of computing stack-map-frames ourselves and
- * invoking visitFrame(), but that would require another pass over all instructions.)
- *
- * Right now I can't think of any invocation of visitSomething() on MethodVisitor
- * where we hand an internal name not backed by a reverseJavaName.
- * However, I'm leaving this note just in case any such oversight is discovered.
- */
- def inameToSymbol(iname: String): Symbol = {
- val name = global.newTypeName(iname)
- val res0 =
- if (nme.isModuleName(name)) rootMirror.getModuleByName(name.dropModule)
- else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested).
- assert(res0 != NoSymbol)
- val res = jsymbol(res0)
- res
- }
-
- def jsymbol(sym: Symbol): Symbol = {
- if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass
- else if(sym.isModule) sym.moduleClass
- else sym // we track only module-classes and plain-classes
- }
-
- private def superClasses(s: Symbol): List[Symbol] = {
- assert(!s.isInterface)
- s.superClass match {
- case NoSymbol => List(s)
- case sc => s :: superClasses(sc)
- }
- }
-
- private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = {
- assert(!(as contains NoSymbol))
- assert(!(bs contains NoSymbol))
- var chainA = as
- var chainB = bs
- var fcs: Symbol = NoSymbol
- do {
- if (chainB contains chainA.head) fcs = chainA.head
- else if (chainA contains chainB.head) fcs = chainB.head
- else {
- chainA = chainA.tail
- chainB = chainB.tail
- }
- } while(fcs == NoSymbol)
- fcs
- }
-
- private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = {
- assert(a.isClass)
- assert(b.isClass)
-
- val res = (a.isInterface, b.isInterface) match {
- case (true, true) =>
- global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents
- case (true, false) =>
- if(b isSubClass a) a else ObjectClass
- case (false, true) =>
- if(a isSubClass b) b else ObjectClass
- case _ =>
- firstCommonSuffix(superClasses(a), superClasses(b))
- }
- assert(res != NoSymbol)
- res
- }
-
- /* The internal name of the least common ancestor of the types given by inameA and inameB.
- It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */
- def getCommonSuperClass(inameA: String, inameB: String): String = {
- val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA))
- val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB))
-
- // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString()
- // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType
- val lcaSym = jvmWiseLUB(a, b)
- val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer.
- val oldsym = reverseJavaName.put(lcaName, lcaSym)
- assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption")
- assert(lcaName != "scala/Any")
-
- lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching.
- }
-
- class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
- override def getCommonSuperClass(iname1: String, iname2: String): String = {
- GenASM.this.getCommonSuperClass(iname1, iname2)
- }
- }
-
- // -----------------------------------------------------------------------------------------
- // constants
- // -----------------------------------------------------------------------------------------
-
- 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
- }
-
- private val majorVersion: Int = (classfileVersion & 0xFF)
- private val emitStackMapFrame = (majorVersion >= 50)
-
- private val extraProc: Int = mkFlags(
- asm.ClassWriter.COMPUTE_MAXS,
- if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
- )
-
- val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object")
- val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String")
-
- /**
- * We call many Java varargs methods from ASM library that expect Arra[asm.Type] as argument so
- * we override default (compiler-generated) ClassTag so we can provide specialized newArray implementation.
- *
- * Examples of methods that should pick our definition are: JBuilder.javaType and JPlainBuilder.genMethod.
- */
- private implicit val asmTypeTag: scala.reflect.ClassTag[asm.Type] = new scala.reflect.ClassTag[asm.Type] {
- def runtimeClass: java.lang.Class[asm.Type] = classOf[asm.Type]
- final override def newArray(len: Int): Array[asm.Type] = new Array[asm.Type](len)
- }
-
- /** basic functionality for class file building */
- abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) {
-
- val EMPTY_STRING_ARRAY = Array.empty[String]
-
- val mdesc_arglessvoid = "()V"
-
- val CLASS_CONSTRUCTOR_NAME = "<clinit>"
- val INSTANCE_CONSTRUCTOR_NAME = "<init>"
-
- // -----------------------------------------------------------------------------------------
- // factory methods
- // -----------------------------------------------------------------------------------------
-
- /**
- * Returns a new ClassWriter for the class given by arguments.
- *
- * @param access the class's access flags. This parameter also indicates if the class is deprecated.
- *
- * @param name the internal name of the class.
- *
- * @param signature the signature of this class. May be <tt>null</tt> if
- * the class is not a generic one, and does not extend or implement
- * generic classes or interfaces.
- *
- * @param superName the internal of name of the super class. For interfaces,
- * the super class is [[Object]]. May be <tt>null</tt>, but
- * only for the [[Object]] class.
- *
- * @param interfaces the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}). May be
- * <tt>null</tt>.
- */
- def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = {
- val cw = new CClassWriter(extraProc)
- cw.visit(classfileVersion,
- access, name, signature,
- superName, interfaces)
-
- cw
- }
-
- def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = {
- val dest = new Array[Byte](len)
- System.arraycopy(b, offset, dest, 0, len)
- new asm.CustomAttr(name, dest)
- }
-
- // -----------------------------------------------------------------------------------------
- // utilities useful when emitting plain, mirror, and beaninfo classes.
- // -----------------------------------------------------------------------------------------
-
- def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
- try {
- val arr = jclass.toByteArray()
- val outF: scala.tools.nsc.io.AbstractFile = {
- if(needsOutfile) getFile(sym, jclassName, ".class") else null
- }
- bytecodeWriter.writeClass(label, jclassName, arr, outF)
- } catch {
- case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") =>
- reporter.error(sym.pos,
- s"Could not write class $jclassName because it exceeds JVM code size limits. ${e.getMessage}")
- case e: java.io.IOException if e.getMessage != null && (e.getMessage contains "File name too long") =>
- reporter.error(sym.pos, e.getMessage + "\n" +
- "This can happen on some encrypted or legacy file systems. Please see SI-3623 for more details.")
-
- }
- }
-
- /** Specialized array conversion to prevent calling
- * java.lang.reflect.Array.newInstance via TraversableOnce.toArray
- */
- def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a }
-
- // -----------------------------------------------------------------------------------------
- // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances).
- // These getters track behind the scenes the inner classes referred to in the class being emitted,
- // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()`
- // (which also adds as member classes those inner classes that have been declared,
- // thus also covering the case of inner classes declared but otherwise not referred).
- // -----------------------------------------------------------------------------------------
-
- val innerClassBuffer = mutable.LinkedHashSet[Symbol]()
-
- /** For given symbol return a symbol corresponding to a class that should be declared as inner class.
- *
- * For example:
- * class A {
- * class B
- * object C
- * }
- *
- * then method will return:
- * NoSymbol for A,
- * the same symbol for A.B (corresponding to A$B class), and
- * A$C$ symbol for A.C.
- */
- def innerClassSymbolFor(s: Symbol): Symbol =
- if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol
-
- /** Return the name of this symbol that can be used on the Java platform. It removes spaces from names.
- *
- * Special handling:
- * scala.Nothing erases to scala.runtime.Nothing$
- * scala.Null erases to scala.runtime.Null$
- *
- * This is needed because they are not real classes, and they mean
- * 'abrupt termination upon evaluation of that expression' or null respectively.
- * This handling is done already in GenICode, but here we need to remove
- * references from method signatures to these types, because such classes
- * cannot exist in the classpath: the type checker will be very confused.
- */
- def javaName(sym: Symbol): String = {
-
- /*
- * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer
- *
- * Note: This method is called recursively thus making sure that we add complete chain
- * of inner class all until root class.
- */
- def collectInnerClass(s: Symbol): Unit = {
- // TODO: some enteringFlatten { ... } which accounts for
- // being nested in parameterized classes (if we're going to selectively flatten.)
- val x = innerClassSymbolFor(s)
- if(x ne NoSymbol) {
- assert(x.isClass, "not an inner-class symbol")
- // impl classes are considered top-level, see comment in BTypes
- val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass
- if (isInner) {
- innerClassBuffer += x
- collectInnerClass(x.rawowner)
- }
- }
- }
-
- collectInnerClass(sym)
-
- val hasInternalName = sym.isClass || sym.isModuleNotMethod
- val cachedJN = javaNameCache.getOrElseUpdate(sym, {
- if (hasInternalName) { sym.javaBinaryName }
- else { sym.javaSimpleName }
- })
-
- if(emitStackMapFrame && hasInternalName) {
- val internalName = cachedJN.toString()
- val trackedSym = jsymbol(sym)
- reverseJavaName.get(internalName) match {
- case None =>
- reverseJavaName.put(internalName, trackedSym)
- case Some(oldsym) =>
- // TODO: `duplicateOk` seems pretty ad-hoc (a more aggressive version caused SI-9356 because it called oldSym.exists, which failed in the unpickler; see also SI-5031)
- def duplicateOk = oldsym == NoSymbol || trackedSym == NoSymbol || (syntheticCoreClasses contains oldsym) || (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule))
- if (oldsym != trackedSym && !duplicateOk)
- devWarning(s"""|Different class symbols have the same bytecode-level internal name:
- | name: $internalName
- | oldsym: ${oldsym.fullNameString}
- | tracked: ${trackedSym.fullNameString}""".stripMargin)
- }
- }
-
- cachedJN.toString
- }
-
- def descriptor(t: Type): String = { javaType(t).getDescriptor }
- def descriptor(k: TypeKind): String = { javaType(k).getDescriptor }
- def descriptor(s: Symbol): String = { javaType(s).getDescriptor }
-
- def javaType(tk: TypeKind): asm.Type = {
- if(tk.isValueType) {
- if(tk.isIntSizedType) {
- (tk: @unchecked) match {
- case BOOL => asm.Type.BOOLEAN_TYPE
- case BYTE => asm.Type.BYTE_TYPE
- case SHORT => asm.Type.SHORT_TYPE
- case CHAR => asm.Type.CHAR_TYPE
- case INT => asm.Type.INT_TYPE
- }
- } else {
- (tk: @unchecked) match {
- case UNIT => asm.Type.VOID_TYPE
- case LONG => asm.Type.LONG_TYPE
- case FLOAT => asm.Type.FLOAT_TYPE
- case DOUBLE => asm.Type.DOUBLE_TYPE
- }
- }
- } else {
- assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway)
- (tk: @unchecked) match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
- }
- }
- }
-
- def javaType(t: Type): asm.Type = javaType(toTypeKind(t))
-
- def javaType(s: Symbol): asm.Type = {
- if (s.isMethod) {
- val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType)
- asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _*)
- } else { javaType(s.tpe) }
- }
-
- def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) }
-
- def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }
-
- def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) {
- /* The outer name for this inner class. Note that it returns null
- * when the inner class should not get an index in the constant pool.
- * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS.
- */
- def outerName(innerSym: Symbol): String = {
- if (isAnonymousOrLocalClass(innerSym))
- null
- else {
- val outerName = javaName(innerSym.rawowner)
- if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule
- else outerName
- }
- }
-
- def innerName(innerSym: Symbol): String = {
- // phase travel necessary: after flatten, the name includes the name of outer classes.
- // if some outer name contains $anon, a non-anon class is considered anon.
- if (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null
- else innerSym.rawname + innerSym.moduleSuffix
- }
-
- val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases
-
- innerClassBuffer ++= {
- val members = exitingPickler(memberClassesForInnerClassTable(csym))
- // lambdalift makes all classes (also local, anonymous) members of their enclosing class
- val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym))
- val nested = {
- // Classes nested in value classes are nested in the companion at this point. For InnerClass /
- // EnclosingMethod, we use the value class as the outer class. So we remove nested classes
- // from the companion that were originally nested in the value class.
- if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass))
- else allNested
- }
-
- // for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala).
- // for module classes, we filter out those members.
- if (isMirror) members
- else if (isTopLevelModule(csym)) nested diff members
- else nested
- }
-
- if (!considerAsTopLevelImplementationArtifact(csym)) {
- // If this is a top-level non-impl class, add members of the companion object. These are the
- // classes for which we change the InnerClass entry to allow using them from Java.
- // We exclude impl classes: if the classfile for the impl class exists on the classpath, a
- // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching
- // members of that weird impl-class-module-class-symbol. that search probably cannot return
- // any classes, but it's better to exclude it.
- if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) {
- // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only
- // sees member classes, not local classes that were lifted by lambdalift.
- innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass))
- }
-
- // Classes nested in value classes are nested in the companion at this point. For InnerClass /
- // EnclosingMethod we use the value class as enclosing class. Here we search nested classes
- // in the companion that were originally nested in the value class, and we add them as nested
- // in the value class.
- if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) {
- val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass))
- innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym))
- }
- }
-
- val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures
-
- if (allInners.nonEmpty) {
- debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.")
-
- // entries ready to be serialized into the classfile, used to detect duplicates.
- val entries = mutable.Map.empty[String, String]
-
- // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler
- for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ??
- val flagsWithFinal: Int = mkFlags(
- // See comment in BTypes, when is a class marked static in the InnerClass table.
- if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0,
- (if (innerSym.isJava) javaClassfileFlags(innerSym) else javaFlags(innerSym)) & ~asm.Opcodes.ACC_STATIC,
- if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag
- ) & (BCodeAsmCommon.INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED)
- val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding.
- val jname = javaName(innerSym) // never null
- val oname = outerName(innerSym) // null when method-enclosed
- val iname = innerName(innerSym) // null for anonymous inner class
-
- // Mimicking javap inner class output
- debuglog(
- if (oname == null || iname == null) "//class " + jname
- else "//%s=class %s of class %s".format(iname, jname, oname)
- )
-
- assert(jname != null, "javaName is broken.") // documentation
- val doAdd = entries.get(jname) match {
- // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute)
- case Some(prevOName) =>
- // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State,
- // i.e. for them it must be the case that oname == java/lang/Thread
- assert(prevOName == oname, "duplicate")
- false
- case None => true
- }
-
- if(doAdd) {
- entries += (jname -> oname)
- jclass.visitInnerClass(jname, oname, iname, flags)
- }
-
- /*
- * TODO assert (JVMS 4.7.6 The InnerClasses attribute)
- * If a class file has a version number that is greater than or equal to 51.0, and
- * has an InnerClasses attribute in its attributes table, then for all entries in the
- * classes array of the InnerClasses attribute, the value of the
- * outer_class_info_index item must be zero if the value of the
- * inner_name_index item is zero.
- */
-
- }
- }
- }
-
- } // end of class JBuilder
-
-
- /** functionality for building plain and mirror classes */
- abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) {
-
- def debugLevel = settings.debuginfo.indexOfChoice
-
- val emitSource = debugLevel >= 1
- val emitLines = debugLevel >= 2
- val emitVars = debugLevel >= 3
-
- // -----------------------------------------------------------------------------------------
- // more constants
- // -----------------------------------------------------------------------------------------
-
- val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC
- val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL
-
- val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString
-
- // -----------------------------------------------------------------------------------------
- // Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only
- // i.e., the pickle is contained in a custom annotation, see:
- // (1) `addAnnotations()`,
- // (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10
- // (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5
- // That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9)
- // other than both ending up encoded as attributes (JVMS 4.7)
- // (with the caveat that the "ScalaSig" attribute is associated to some classes,
- // while the "Signature" attribute can be associated to classes, methods, and fields.)
- // -----------------------------------------------------------------------------------------
-
- val versionPickle = {
- val vp = new PickleBuffer(new Array[Byte](16), -1, 0)
- assert(vp.writeIndex == 0, vp)
- vp writeNat PickleFormat.MajorVersion
- vp writeNat PickleFormat.MinorVersion
- vp writeNat 0
- vp
- }
-
- def pickleMarkerLocal = {
- createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
- }
-
- def pickleMarkerForeign = {
- createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0)
- }
-
- /** Returns a ScalaSignature annotation if it must be added to this class, none otherwise.
- * This annotation must be added to the class' annotations list when generating them.
- *
- * Depending on whether the returned option is defined, it adds to `jclass` one of:
- * (a) the ScalaSig marker attribute
- * (indicating that a scala-signature-annotation aka pickle is present in this class); or
- * (b) the Scala marker attribute
- * (indicating that a scala-signature-annotation aka pickle is to be found in another file).
- *
- *
- * @param jclassName The class file that is being readied.
- * @param sym The symbol for which the signature has been entered in the symData map.
- * This is different than the symbol
- * that is being generated in the case of a mirror class.
- * @return An option that is:
- * - defined and contains an AnnotationInfo of the ScalaSignature type,
- * instantiated with the pickle signature for sym.
- * - empty if the jclass/sym pair must not contain a pickle.
- *
- */
- def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = {
- currentRun.symData get sym match {
- case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) =>
- val scalaAnnot = {
- val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex))
- AnnotationInfo(sigBytes.sigAnnot, Nil, List((nme.bytes, sigBytes)))
- }
- pickledBytes += pickle.writeIndex
- currentRun.symData -= sym
- currentRun.symData -= sym.companionSymbol
- Some(scalaAnnot)
- case _ =>
- None
- }
- }
-
- /**
- * Quoting from JVMS 4.7.5 The Exceptions Attribute
- * "The Exceptions attribute indicates which checked exceptions a method may throw.
- * There may be at most one Exceptions attribute in each method_info structure."
- *
- * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod()
- * This method returns such list of internal names.
- */
- def getExceptions(excs: List[AnnotationInfo]): List[String] =
- for (ThrownException(exc) <- excs.distinct)
- yield javaName(exc)
-
- def getCurrentCUnit(): CompilationUnit
-
- def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit())
-
- def emitArgument(av: asm.AnnotationVisitor,
- name: String,
- arg: ClassfileAnnotArg) {
- (arg: @unchecked) match {
-
- case LiteralAnnotArg(const) =>
- if(const.isNonUnitAnyVal) { av.visit(name, const.value) }
- else {
- const.tag match {
- case StringTag =>
- assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
- av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
- case ClazzTag => av.visit(name, javaType(const.typeValue))
- case EnumTag =>
- val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
- val evalue = const.symbolValue.name.toString // value the actual enumeration value.
- av.visitEnum(name, edesc, evalue)
- }
- }
-
- case sb@ScalaSigBytes(bytes) =>
- // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files)
- // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure.
- if (sb.fitsInOneString)
- av.visit(name, strEncode(sb))
- else {
- val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
- for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) }
- arrAnnotV.visitEnd()
- }
- // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape.
-
- case ArrayAnnotArg(args) =>
- val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name)
- for(arg <- args) { emitArgument(arrAnnotV, null, arg) }
- arrAnnotV.visitEnd()
-
- case NestedAnnotArg(annInfo) =>
- val AnnotationInfo(typ, args, assocs) = annInfo
- assert(args.isEmpty, args)
- val desc = descriptor(typ) // the class descriptor of the nested annotation class
- val nestedVisitor = av.visitAnnotation(name, desc)
- emitAssocs(nestedVisitor, assocs)
- }
- }
-
- def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
- for ((name, value) <- assocs) {
- emitArgument(av, name.toString(), value)
- }
- av.visitEnd()
- }
-
- def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) {
- for(annot <- annotations; if shouldEmitAnnotation(annot)) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(av, assocs)
- }
- }
-
- def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) {
- for(annot <- annotations; if shouldEmitAnnotation(annot)) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(av, assocs)
- }
- }
-
- def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) {
- for(annot <- annotations; if shouldEmitAnnotation(annot)) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(av, assocs)
- }
- }
-
- def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) {
- val annotationss = pannotss map (_ filter shouldEmitAnnotation)
- if (annotationss forall (_.isEmpty)) return
- for ((annots, idx) <- annotationss.zipWithIndex;
- annot <- annots) {
- val AnnotationInfo(typ, args, assocs) = annot
- assert(args.isEmpty, args)
- val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
- emitAssocs(pannVisitor, assocs)
- }
- }
-
- /** Adds a @remote annotation, actual use unknown.
- *
- * Invoked from genMethod() and addForwarder().
- */
- def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
- val needsAnnotation = (
- ( isRemoteClass ||
- isRemote(meth) && isJMethodPublic
- ) && !(meth.throwsAnnotations contains RemoteExceptionClass)
- )
- if (needsAnnotation) {
- val c = Constant(RemoteExceptionClass.tpe)
- val arg = Literal(c) setType c.tpe
- meth.addAnnotation(appliedType(ThrowsClass, c.tpe), arg)
- }
- }
-
- // -----------------------------------------------------------------------------------------
- // Static forwarders (related to mirror classes but also present in
- // a plain class lacking companion module, for details see `isCandidateForForwarders`).
- // -----------------------------------------------------------------------------------------
-
- /** Add a forwarder for method m. Used only from addForwarders(). */
- private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) {
- val moduleName = javaName(module)
- val methodInfo = module.thisType.memberInfo(m)
- val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType
- // val paramNames = 0 until paramJavaTypes.length map ("x_" + _)
-
- /* Forwarders must not be marked final,
- * as the JVM will not allow redefinition of a final static method,
- * and we don't know what classes might be subclassing the companion class. See SI-4827.
- */
- // TODO: evaluate the other flags we might be dropping on the floor here.
- // TODO: ACC_SYNTHETIC ?
- val flags = PublicStatic | (
- if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0
- )
-
- // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
- val jgensig = staticForwarderGenericSignature(m, module, getCurrentCUnit())
- addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
- val (throws, others) = m.annotations partition (_.symbol == ThrowsClass)
- val thrownExceptions: List[String] = getExceptions(throws)
-
- val jReturnType = javaType(methodInfo.resultType)
- val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*)
- val mirrorMethodName = javaName(m)
- val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
- flags,
- mirrorMethodName,
- mdesc,
- jgensig,
- mkArray(thrownExceptions)
- )
-
- // typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- emitAnnotations(mirrorMethod, others)
- emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations))
-
- // typestate: entering mode with valid call sequences:
- // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
-
- mirrorMethod.visitCode()
-
- mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
-
- var index = 0
- for(jparamType <- paramJavaTypes) {
- mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index)
- assert(jparamType.getSort() != asm.Type.METHOD, jparamType)
- index += jparamType.getSize()
- }
-
- mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(m).getDescriptor, false)
- mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN))
-
- mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- mirrorMethod.visitEnd()
-
- }
-
- /** Add forwarders for all methods defined in `module` that don't conflict
- * with methods in the companion class of `module`. A conflict arises when
- * a method with the same name is defined both in a class and its companion object:
- * method signature is not taken into account.
- */
- def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) {
- assert(moduleClass.isModuleClass, moduleClass)
- debuglog("Dumping mirror class for object: " + moduleClass)
-
- val linkedClass = moduleClass.companionClass
- lazy val conflictingNames: Set[Name] = {
- (linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet
- }
- debuglog("Potentially conflicting names for forwarders: " + conflictingNames)
-
- for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) {
- if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor)
- debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'")
- else if (conflictingNames(m.name))
- log(s"No forwarder for $m due to conflict with " + linkedClass.info.member(m.name))
- else if (m.hasAccessBoundary)
- log(s"No forwarder for non-public member $m")
- else {
- debuglog(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'")
- addForwarder(isRemoteClass, jclass, moduleClass, m)
- }
- }
- }
-
- } // end of class JCommonBuilder
-
-
- trait JAndroidBuilder {
- self: JPlainBuilder =>
-
- def isAndroidParcelableClass(sym: Symbol) =
- (AndroidParcelableInterface != NoSymbol) &&
- (sym.parentSymbols contains AndroidParcelableInterface)
-
- /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */
- def addCreatorCode(block: BasicBlock) {
- val fieldSymbol = (
- clasz.symbol.newValue(androidFieldName, NoPosition, Flags.STATIC | Flags.FINAL)
- setInfo AndroidCreatorClass.tpe
- )
- val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName)
- clasz addField new IField(fieldSymbol)
- block emit CALL_METHOD(methodSymbol, Static(onInstance = false))
- block emit STORE_FIELD(fieldSymbol, isStatic = true)
- }
-
- def legacyAddCreatorCode(clinit: asm.MethodVisitor) {
- val creatorType: asm.Type = javaType(AndroidCreatorClass)
- val tdesc_creator = creatorType.getDescriptor
-
- jclass.visitField(
- PublicStaticFinal,
- androidFieldName.toString,
- tdesc_creator,
- null, // no java-generic-signature
- null // no initial value
- ).visitEnd()
-
- val moduleName = javaName(clasz.symbol)+"$"
-
- // GETSTATIC `moduleName`.MODULE$ : `moduleName`;
- clinit.visitFieldInsn(
- asm.Opcodes.GETSTATIC,
- moduleName,
- strMODULE_INSTANCE_FIELD,
- asm.Type.getObjectType(moduleName).getDescriptor
- )
-
- // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator;
- clinit.visitMethodInsn(
- asm.Opcodes.INVOKEVIRTUAL,
- moduleName,
- androidFieldName.toString,
- asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*),
- false
- )
-
- // PUTSTATIC `thisName`.CREATOR;
- clinit.visitFieldInsn(
- asm.Opcodes.PUTSTATIC,
- thisName,
- androidFieldName.toString,
- tdesc_creator
- )
- }
-
- } // end of trait JAndroidBuilder
-
- /** Map from type kinds to the Java reference types.
- * It is used to push class literals onto the operand stack.
- * @see Predef.classOf
- * @see genConstant()
- */
- private val classLiteral = immutable.Map[TypeKind, asm.Type](
- UNIT -> asm.Type.getObjectType("java/lang/Void"),
- BOOL -> asm.Type.getObjectType("java/lang/Boolean"),
- BYTE -> asm.Type.getObjectType("java/lang/Byte"),
- SHORT -> asm.Type.getObjectType("java/lang/Short"),
- CHAR -> asm.Type.getObjectType("java/lang/Character"),
- INT -> asm.Type.getObjectType("java/lang/Integer"),
- LONG -> asm.Type.getObjectType("java/lang/Long"),
- FLOAT -> asm.Type.getObjectType("java/lang/Float"),
- DOUBLE -> asm.Type.getObjectType("java/lang/Double")
- )
-
- def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT }
-
- case class MethodNameAndType(mname: String, mdesc: String)
-
- private val jBoxTo: Map[TypeKind, MethodNameAndType] = {
- Map(
- BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) ,
- BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) ,
- CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") ,
- SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) ,
- INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) ,
- LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) ,
- FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) ,
- DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" )
- )
- }
-
- private val jUnboxTo: Map[TypeKind, MethodNameAndType] = {
- Map(
- BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") ,
- BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") ,
- CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") ,
- SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") ,
- INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") ,
- LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") ,
- FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") ,
- DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D")
- )
- }
-
- case class BlockInteval(start: BasicBlock, end: BasicBlock)
-
- /** builder of plain classes */
- class JPlainBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean)
- extends JCommonBuilder(bytecodeWriter, needsOutfile)
- with JAndroidBuilder {
-
- val MIN_SWITCH_DENSITY = 0.7
-
- val StringBuilderClassName = javaName(definitions.StringBuilderClass)
- val BoxesRunTime = "scala/runtime/BoxesRunTime"
-
- val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName)
- val mdesc_toString = "()Ljava/lang/String;"
- val mdesc_arrayClone = "()Ljava/lang/Object;"
-
- val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J"
-
- def isParcelableClass = isAndroidParcelableClass(clasz.symbol)
-
- def serialVUID: Option[Long] = genBCode.serialVUID(clasz.symbol)
-
- var clasz: IClass = _ // this var must be assigned only by genClass()
- var jclass: asm.ClassWriter = _ // the classfile being emitted
- var thisName: String = _ // the internal name of jclass
-
- def thisDescr: String = {
- assert(thisName != null, "thisDescr invoked too soon.")
- asm.Type.getObjectType(thisName).getDescriptor
- }
-
- def getCurrentCUnit(): CompilationUnit = { clasz.cunit }
-
- def genClass(c: IClass) {
- clasz = c
- innerClassBuffer.clear()
-
- thisName = javaName(c.symbol) // the internal name of the class being emitted
-
- val ps = c.symbol.info.parents
- val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol)
-
- val ifaces: Array[String] = implementedInterfaces(c.symbol).map(javaName)(collection.breakOut)
-
- val thisSignature = getGenericSignature(c.symbol, c.symbol.owner)
- val flags = mkFlags(
- javaFlags(c.symbol),
- if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
- jclass = createJClass(flags,
- thisName, thisSignature,
- superClass, ifaces)
-
- // typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- if(emitSource) {
- jclass.visitSource(c.cunit.source.toString,
- null /* SourceDebugExtension */)
- }
-
- enclosingMethodAttribute(clasz.symbol, javaName, javaType(_).getDescriptor) match {
- case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) =>
- jclass.visitOuterClass(className, methodName, methodDescriptor)
- case _ => ()
- }
-
- // typestate: entering mode with valid call sequences:
- // ( visitAnnotation | visitAttribute )*
-
- val ssa = getAnnotPickle(thisName, c.symbol)
- jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
- emitAnnotations(jclass, c.symbol.annotations ++ ssa)
-
- if (!settings.YskipInlineInfoAttribute.value)
- jclass.visitAttribute(InlineInfoAttribute(buildInlineInfoFromClassSymbol(c.symbol, javaName, javaType(_).getDescriptor)))
-
- // typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- if (isStaticModule(c.symbol) || isParcelableClass) {
-
- if (isStaticModule(c.symbol)) { addModuleInstanceField() }
- addStaticInit(c.lookupStaticCtor)
-
- } else {
-
- for (constructor <- c.lookupStaticCtor) {
- addStaticInit(Some(constructor))
- }
- val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders)
- if (!skipStaticForwarders) {
- val lmoc = c.symbol.companionModule
- // add static forwarders if there are no name conflicts; see bugs #363 and #1735
- if (lmoc != NoSymbol) {
- // it must be a top level class (name contains no $s)
- val isCandidateForForwarders = {
- exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass }
- }
- if (isCandidateForForwarders) {
- log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc))
- addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass)
- }
- }
- }
-
- }
-
- // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)`
- serialVUID foreach { value =>
- val fieldName = "serialVersionUID"
- jclass.visitField(
- PublicStaticFinal,
- fieldName,
- tdesc_long,
- null, // no java-generic-signature
- value
- ).visitEnd()
- }
-
- clasz.fields foreach genField
- clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) }
-
- addInnerClasses(clasz.symbol, jclass)
- jclass.visitEnd()
- writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol)
- }
-
- def genField(f: IField) {
- debuglog("Adding field: " + f.symbol.fullName)
-
- val javagensig = getGenericSignature(f.symbol, clasz.symbol)
-
- val flags = mkFlags(
- javaFieldFlags(f.symbol),
- if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- val jfield: asm.FieldVisitor = jclass.visitField(
- flags,
- javaName(f.symbol),
- javaType(f.symbol.tpe).getDescriptor(),
- javagensig,
- null // no initial value
- )
-
- emitAnnotations(jfield, f.symbol.annotations)
- jfield.visitEnd()
- }
-
- var method: IMethod = _
- var jmethod: asm.MethodVisitor = _
- var jMethodName: String = _
-
- final def emit(opc: Int) { jmethod.visitInsn(opc) }
-
- def genMethod(m: IMethod, isJInterface: Boolean) {
-
- def isClosureApply(sym: Symbol): Boolean = {
- (sym.name == nme.apply) &&
- sym.owner.isSynthetic &&
- sym.owner.tpe.parents.exists { t =>
- val TypeRef(_, sym, _) = t
- FunctionClass.seq contains sym
- }
- }
-
- if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return
-
- if (m.params.size > MaximumJvmParameters) {
- reporter.error(m.symbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.")
- return
- }
-
- debuglog("Generating method " + m.symbol.fullName)
- method = m
- computeLocalVarsIndex(m)
-
- var resTpe: asm.Type = javaType(m.symbol.tpe.resultType)
- if (m.symbol.isClassConstructor)
- resTpe = asm.Type.VOID_TYPE
-
- val flags = mkFlags(
- javaFlags(m.symbol),
- if (isJInterface) 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
- )
-
- // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
- val jgensig = getGenericSignature(m.symbol, clasz.symbol)
- addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol)
- val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass)
- val thrownExceptions: List[String] = getExceptions(excs)
-
- jMethodName = javaName(m.symbol)
- val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*)
- jmethod = jclass.visitMethod(
- flags,
- jMethodName,
- mdesc,
- jgensig,
- mkArray(thrownExceptions)
- )
-
- // TODO param names: (m.params map (p => javaName(p.sym)))
-
- // typestate: entering mode with valid call sequences: (see ASM Guide, 3.2.1)
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- emitAnnotations(jmethod, others)
- emitParamAnnotations(jmethod, m.params.map(_.sym.annotations))
-
- // typestate: entering mode with valid call sequences:
- // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
- // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code,
- // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and
- // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited.
-
- val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0)
- val hasCodeAttribute = (!hasAbstractBitSet && !method.native)
- if (hasCodeAttribute) {
-
- jmethod.visitCode()
-
- if (emitVars && isClosureApply(method.symbol)) {
- // add a fake local for debugging purposes
- val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL)
- if (outerField != NoSymbol) {
- log("Adding fake local to represent outer 'this' for closure " + clasz)
- val _this =
- new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS),
- toTypeKind(outerField.tpe),
- false)
- m.locals = m.locals ::: List(_this)
- computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes
- jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
- jmethod.visitFieldInsn(asm.Opcodes.GETFIELD,
- javaName(clasz.symbol), // field owner
- javaName(outerField), // field name
- descriptor(outerField) // field descriptor
- )
- assert(_this.kind.isReferenceType, _this.kind)
- jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this))
- }
- }
-
- assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals )
-
- val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
- genCode(m, emitVars, hasStaticBitSet)
-
- // visitMaxs needs to be called according to the protocol. The arguments will be ignored
- // since maximums (and stack map frames) are computed. See ASM Guide, Section 3.2.1,
- // section "ClassWriter options"
- jmethod.visitMaxs(0, 0)
- }
-
- jmethod.visitEnd()
-
- }
-
- def addModuleInstanceField() {
- val fv =
- jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
- strMODULE_INSTANCE_FIELD,
- thisDescr,
- null, // no java-generic-signature
- null // no initial value
- )
-
- // typestate: entering mode with valid call sequences:
- // ( visitAnnotation | visitAttribute )* visitEnd.
-
- fv.visitEnd()
- }
-
-
- /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */
- def addStaticInit(mopt: Option[IMethod]) {
-
- val clinitMethod: asm.MethodVisitor = jclass.visitMethod(
- PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
- CLASS_CONSTRUCTOR_NAME,
- mdesc_arglessvoid,
- null, // no java-generic-signature
- null // no throwable exceptions
- )
-
- mopt match {
-
- case Some(m) =>
-
- val oldLastBlock = m.lastBlock
- val lastBlock = m.newBlock()
- oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock))
-
- if (isStaticModule(clasz.symbol)) {
- // call object's private ctor from static ctor
- lastBlock emit NEW(REFERENCE(m.symbol.enclClass))
- lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(onInstance = true))
- }
-
- if (isParcelableClass) { addCreatorCode(lastBlock) }
-
- lastBlock emit RETURN(UNIT)
- lastBlock.close()
-
- method = m
- jmethod = clinitMethod
- jMethodName = CLASS_CONSTRUCTOR_NAME
- jmethod.visitCode()
- computeLocalVarsIndex(m)
- genCode(m, emitVars = false, isStatic = true)
- jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- jmethod.visitEnd()
-
- case None =>
- clinitMethod.visitCode()
- legacyStaticInitializer(clinitMethod)
- clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- clinitMethod.visitEnd()
-
- }
- }
-
- /* used only from addStaticInit() */
- private def legacyStaticInitializer(clinit: asm.MethodVisitor) {
- if (isStaticModule(clasz.symbol)) {
- clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
- clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
- thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid, false)
- }
-
- if (isParcelableClass) { legacyAddCreatorCode(clinit) }
-
- clinit.visitInsn(asm.Opcodes.RETURN)
- }
-
- // -----------------------------------------------------------------------------------------
- // Emitting bytecode instructions.
- // -----------------------------------------------------------------------------------------
-
- private def genConstant(mv: asm.MethodVisitor, const: Constant) {
- const.tag match {
-
- case BooleanTag => jcode.boolconst(const.booleanValue)
-
- case ByteTag => jcode.iconst(const.byteValue.toInt)
- case ShortTag => jcode.iconst(const.shortValue.toInt)
- case CharTag => jcode.iconst(const.charValue)
- case IntTag => jcode.iconst(const.intValue)
-
- case LongTag => jcode.lconst(const.longValue)
- case FloatTag => jcode.fconst(const.floatValue)
- case DoubleTag => jcode.dconst(const.doubleValue)
-
- case UnitTag => ()
-
- case StringTag =>
- assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
- mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag
-
- case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL)
-
- case ClazzTag =>
- val kind = toTypeKind(const.typeValue)
- val toPush: asm.Type =
- if (kind.isValueType) classLiteral(kind)
- else javaType(kind)
- mv.visitLdcInsn(toPush)
-
- case EnumTag =>
- val sym = const.symbolValue
- mv.visitFieldInsn(
- asm.Opcodes.GETSTATIC,
- javaName(sym.owner),
- javaName(sym),
- javaType(sym.tpe.underlying).getDescriptor()
- )
-
- case _ => abort("Unknown constant value: " + const)
- }
- }
-
- /** Just a namespace for utilities that encapsulate MethodVisitor idioms.
- * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role,
- * but the methods here allow choosing when to transition from ICode to ASM types
- * (including not at all, e.g. for performance).
- */
- object jcode {
-
- import asm.Opcodes
-
- final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) }
-
- def iconst(cst: Char) { iconst(cst.toInt) }
- def iconst(cst: Int) {
- if (cst >= -1 && cst <= 5) {
- jmethod.visitInsn(Opcodes.ICONST_0 + cst)
- } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) {
- jmethod.visitIntInsn(Opcodes.BIPUSH, cst)
- } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) {
- jmethod.visitIntInsn(Opcodes.SIPUSH, cst)
- } else {
- jmethod.visitLdcInsn(new Integer(cst))
- }
- }
-
- def lconst(cst: Long) {
- if (cst == 0L || cst == 1L) {
- jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Long(cst))
- }
- }
-
- def fconst(cst: Float) {
- val bits: Int = java.lang.Float.floatToIntBits(cst)
- if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2
- jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Float(cst))
- }
- }
-
- def dconst(cst: Double) {
- val bits: Long = java.lang.Double.doubleToLongBits(cst)
- if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d
- jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int])
- } else {
- jmethod.visitLdcInsn(new java.lang.Double(cst))
- }
- }
-
- def newarray(elem: TypeKind) {
- if(elem.isRefOrArrayType) {
- jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName)
- } else {
- val rand = {
- if(elem.isIntSizedType) {
- (elem: @unchecked) match {
- case BOOL => Opcodes.T_BOOLEAN
- case BYTE => Opcodes.T_BYTE
- case SHORT => Opcodes.T_SHORT
- case CHAR => Opcodes.T_CHAR
- case INT => Opcodes.T_INT
- }
- } else {
- (elem: @unchecked) match {
- case LONG => Opcodes.T_LONG
- case FLOAT => Opcodes.T_FLOAT
- case DOUBLE => Opcodes.T_DOUBLE
- }
- }
- }
- jmethod.visitIntInsn(Opcodes.NEWARRAY, rand)
- }
- }
-
-
- def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) }
- def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) }
-
- def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) }
- def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) }
-
- def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) }
- def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) }
- def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) }
- def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) }
- def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) }
- def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) }
-
- def invokespecial(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false)
- }
- def invokestatic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false)
- }
- def invokeinterface(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true)
- }
- def invokevirtual(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
- }
-
- def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
- def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF(), label) }
- def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP(), label) }
- def emitIF_ACMP(cond: TestOp, label: asm.Label) {
- assert((cond == EQ) || (cond == NE), cond)
- val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
- jmethod.visitJumpInsn(opc, label)
- }
- def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) }
- def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) }
-
- def emitRETURN(tk: TypeKind) {
- if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) }
- else { emitTypeBased(returnOpcodes, tk) }
- }
-
- /** Emits one of tableswitch or lookoupswitch. */
- def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) {
- assert(keys.length == branches.length)
-
- // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only.
- // Similar to what javac emits for a switch statement consisting only of a default case.
- if (keys.length == 0) {
- jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches)
- return
- }
-
- // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort
- var i = 1
- while (i < keys.length) {
- var j = 1
- while (j <= keys.length - i) {
- if (keys(j) < keys(j - 1)) {
- val tmp = keys(j)
- keys(j) = keys(j - 1)
- keys(j - 1) = tmp
- val tmpL = branches(j)
- branches(j) = branches(j - 1)
- branches(j - 1) = tmpL
- }
- j += 1
- }
- i += 1
- }
-
- // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011)
- i = 1
- while (i < keys.length) {
- if(keys(i-1) == keys(i)) {
- abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.")
- }
- i += 1
- }
-
- val keyMin = keys(0)
- val keyMax = keys(keys.length - 1)
-
- val isDenseEnough: Boolean = {
- /* Calculate in long to guard against overflow. TODO what overflow??? */
- val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double]
- val klenD: Double = keys.length.toDouble
- val kdensity: Double = (klenD / keyRangeD)
-
- kdensity >= minDensity
- }
-
- if (isDenseEnough) {
- // use a table in which holes are filled with defaultBranch.
- val keyRange = (keyMax - keyMin + 1)
- val newBranches = new Array[asm.Label](keyRange)
- var oldPos = 0
- var i = 0
- while(i < keyRange) {
- val key = keyMin + i
- if (keys(oldPos) == key) {
- newBranches(i) = branches(oldPos)
- oldPos += 1
- } else {
- newBranches(i) = defaultBranch
- }
- i += 1
- }
- assert(oldPos == keys.length, "emitSWITCH")
- jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*)
- } else {
- jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches)
- }
- }
-
- // internal helpers -- not part of the public API of `jcode`
- // don't make private otherwise inlining will suffer
-
- def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) {
- assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc)
- jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx)
- }
-
- // ---------------- array load and store ----------------
-
- val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) }
- val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) }
-
- val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) }
-
- def emitTypeBased(opcs: Array[Int], tk: TypeKind) {
- assert(tk != UNIT, tk)
- val opc = {
- if(tk.isRefOrArrayType) { opcs(0) }
- else if(tk.isIntSizedType) {
- (tk: @unchecked) match {
- case BOOL | BYTE => opcs(1)
- case SHORT => opcs(2)
- case CHAR => opcs(3)
- case INT => opcs(4)
- }
- } else {
- (tk: @unchecked) match {
- case LONG => opcs(5)
- case FLOAT => opcs(6)
- case DOUBLE => opcs(7)
- }
- }
- }
- jmethod.visitInsn(opc)
- }
-
- // ---------------- primitive operations ----------------
-
- val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) }
- val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) }
- val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) }
- val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) }
- val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) }
- val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) }
-
- def emitPrimitive(opcs: Array[Int], tk: TypeKind) {
- val opc = {
- if(tk.isIntSizedType) { opcs(0) }
- else {
- (tk: @unchecked) match {
- case LONG => opcs(1)
- case FLOAT => opcs(2)
- case DOUBLE => opcs(3)
- }
- }
- }
- jmethod.visitInsn(opc)
- }
-
- }
-
- /** Invoked from genMethod() and addStaticInit() */
- def genCode(m: IMethod,
- emitVars: Boolean, // this param name hides the instance-level var
- isStatic: Boolean) {
-
-
- newNormal.normalize(m)
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization`
- // ------------------------------------------------------------------------------------------------------------
-
- val linearization: List[BasicBlock] = linearizer.linearize(m)
- if(linearization.isEmpty) { return }
-
- var isModuleInitialized = false
-
- val labels: scala.collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*)
-
- val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted
-
- // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label.
- val linNext: scala.collection.Map[BasicBlock, asm.Label] = {
- val result = mutable.HashMap.empty[BasicBlock, asm.Label]
- var rest = linearization
- var prev = rest.head
- rest = rest.tail
- while(!rest.isEmpty) {
- result += (prev -> labels(rest.head))
- prev = rest.head
- rest = rest.tail
- }
- assert(!result.contains(prev))
- result += (prev -> onePastLast)
-
- result
- }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock())
- // ------------------------------------------------------------------------------------------------------------
-
- /* Generate exception handlers for the current method.
- *
- * Quoting from the JVMS 4.7.3 The Code Attribute
- * The items of the Code_attribute structure are as follows:
- * . . .
- * exception_table[]
- * Each entry in the exception_table array describes one
- * exception handler in the code array. The order of the handlers in
- * the exception_table array is significant.
- * Each exception_table entry contains the following four items:
- * start_pc, end_pc:
- * ... The value of end_pc either must be a valid index into
- * the code array of the opcode of an instruction or must be equal to code_length,
- * the length of the code array.
- * handler_pc:
- * The value of the handler_pc item indicates the start of the exception handler
- * catch_type:
- * ... If the value of the catch_type item is zero,
- * this exception handler is called for all exceptions.
- * This is used to implement finally
- */
- def genExceptionHandlers() {
-
- /* Return a list of pairs of intervals where the handler is active.
- * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end].
- * Preconditions:
- * - e.covered non-empty
- * Postconditions for the result:
- * - always non-empty
- * - intervals are sorted as per `linearization`
- * - the argument's `covered` blocks have been grouped into maximally contiguous intervals,
- * ie. between any two intervals in the result there is a non-empty gap.
- * - each of the `covered` blocks in the argument is contained in some interval in the result
- */
- def intervals(e: ExceptionHandler): List[BlockInteval] = {
- assert(e.covered.nonEmpty, e)
- var result: List[BlockInteval] = Nil
- var rest = linearization
-
- // find intervals
- while(!rest.isEmpty) {
- // find interval start
- var start: BasicBlock = null
- while(!rest.isEmpty && (start eq null)) {
- if(e.covered(rest.head)) { start = rest.head }
- rest = rest.tail
- }
- if(start ne null) {
- // find interval end
- var end = start // for the time being
- while(!rest.isEmpty && (e.covered(rest.head))) {
- end = rest.head
- rest = rest.tail
- }
- result = BlockInteval(start, end) :: result
- }
- }
-
- assert(result.nonEmpty, e)
-
- result
- }
-
- /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains
- * blocks not in the linearization (dead-code?). Is that well-formed or not?
- * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does).
- */
- for (e <- this.method.exh) {
- val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } )
- // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)")
- if(ignore.nonEmpty) {
- e.covered = e.covered filterNot ignore
- }
- }
-
- // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table.
- // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class?
- for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) {
- debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method +
- " from: " + p.start + " to: " + p.end + " catching: " + e.cls)
- val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null
- else javaName(e.cls)
- jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls)
- }
- } // end of genCode()'s genExceptionHandlers()
-
- if (m.exh.nonEmpty) { genExceptionHandlers() }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute).
- // ------------------------------------------------------------------------------------------------------------
-
- case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive.
-
- case class Interval(lstart: asm.Label, lend: asm.Label) {
- final def start = lstart.getOffset
- final def end = lend.getOffset
-
- def precedes(that: Interval): Boolean = { this.end < that.start }
-
- def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) }
-
- def mergeWith(that: Interval): Interval = {
- val newStart = if(this.start <= that.start) this.lstart else that.lstart
- val newEnd = if(this.end <= that.end) that.lend else this.lend
- Interval(newStart, newEnd)
- }
-
- def repOK: Boolean = { start <= end }
-
- }
-
- /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */
- object scoping {
-
- private val pending = mutable.Map.empty[Local, mutable.Stack[Label]]
- private var seen: List[LocVarEntry] = Nil
-
- private def fuse(ranges: List[Interval], added: Interval): List[Interval] = {
- assert(added.repOK, added)
- if(ranges.isEmpty) { return List(added) }
- // precond: ranges is sorted by increasing start
- var fused: List[Interval] = Nil
- var done = false
- var rest = ranges
- while(!done && rest.nonEmpty) {
- val current = rest.head
- assert(current.repOK, current)
- rest = rest.tail
- if(added precedes current) {
- fused = fused ::: ( added :: current :: rest )
- done = true
- } else if(current overlaps added) {
- fused = fused ::: ( added.mergeWith(current) :: rest )
- done = true
- }
- }
- if(!done) { fused = fused ::: List(added) }
- assert(repOK(fused), fused)
-
- fused
- }
-
- def pushScope(lv: Local, start: Label) {
- val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label])
- st.push(start)
- }
- def popScope(lv: Local, end: Label, iPos: Position) {
- pending.get(lv) match {
- case Some(st) if st.nonEmpty =>
- val start = st.pop()
- seen ::= LocVarEntry(lv, start, end)
- case _ =>
- // TODO SI-6049 track down the cause for these.
- devWarning(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191")
- }
- }
-
- def getMerged(): scala.collection.Map[Local, List[Interval]] = {
- // TODO should but isn't: unbalanced start(s) of scope(s)
- val shouldBeEmpty = pending filter { p => val (_, st) = p; st.nonEmpty }
- val merged = mutable.Map[Local, List[Interval]]()
- def addToMerged(lv: Local, start: Label, end: Label) {
- val intv = Interval(start, end)
- merged(lv) = if (merged contains lv) fuse(merged(lv), intv) else intv :: Nil
- }
- for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) }
-
- /* for each var with unbalanced start(s) of scope(s):
- (a) take the earliest start (among unbalanced and balanced starts)
- (b) take the latest end (onePastLast if none available)
- (c) merge the thus made-up interval
- */
- for((k, st) <- shouldBeEmpty) {
- var start = st.toList.sortBy(_.getOffset).head
- if(merged.isDefinedAt(k)) {
- val balancedStart = merged(k).head.lstart
- if(balancedStart.getOffset < start.getOffset) {
- start = balancedStart
- }
- }
- val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend
- val end = endOpt.getOrElse(onePastLast)
- addToMerged(k, start, end)
- }
-
- merged
- }
-
- private def repOK(fused: List[Interval]): Boolean = {
- fused match {
- case Nil => true
- case h :: Nil => h.repOK
- case h :: n :: rest =>
- h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest)
- }
- }
-
- }
-
- def genLocalVariableTable() {
- // adding `this` and method params.
- if (!isStatic) {
- jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0)
- }
- for(lv <- m.params) {
- jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv))
- }
- // adding non-param locals
- var anonCounter = 0
- var fltnd: List[Tuple3[String, Local, Interval]] = Nil
- for((local, ranges) <- scoping.getMerged()) {
- var name = javaName(local.sym)
- if (name == null) {
- anonCounter += 1
- name = "<anon" + anonCounter + ">"
- }
- for(intrvl <- ranges) {
- fltnd ::= (name, local, intrvl)
- }
- }
- // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain).
- val srtd = fltnd.sortBy { kr =>
- val (name: String, _, intrvl: Interval) = kr
-
- (intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name)
- }
-
- for((name, local, Interval(start, end)) <- srtd) {
- jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local))
- }
- // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute"
- }
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position.
- // ------------------------------------------------------------------------------------------------------------
-
- case class LineNumberEntry(line: Int, start: asm.Label)
- var lastLineNr: Int = -1
- var lnEntries: List[LineNumberEntry] = Nil
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()).
- // ------------------------------------------------------------------------------------------------------------
-
- var nextBlock: BasicBlock = linearization.head
-
- def genBlocks(l: List[BasicBlock]): Unit = l match {
- case Nil => ()
- case x :: Nil => nextBlock = null; genBlock(x)
- case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys)
- }
-
- def genCallMethod(call: CALL_METHOD) {
- val CALL_METHOD(method, style) = call
- val siteSymbol = clasz.symbol
- val hostSymbol = call.hostClass
- val methodOwner = method.owner
- // info calls so that types are up to date; erasure may add lateINTERFACE to traits
- hostSymbol.info ; methodOwner.info
-
- def needsInterfaceCall(sym: Symbol) = (
- sym.isInterface
- || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass)
- )
- // whether to reference the type of the receiver or
- // the type of the method owner
- val useMethodOwner = (
- style != Dynamic
- || hostSymbol.isBottomClass
- || methodOwner == ObjectClass
- )
- val receiver = if (useMethodOwner) methodOwner else hostSymbol
- val jowner = javaName(receiver)
- val jname = javaName(method)
- val jtype = javaType(method).getDescriptor()
-
- def dbg(invoke: String) {
- debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype))
- }
-
- def initModule() {
- // we initialize the MODULE$ field immediately after the super ctor
- if (isStaticModule(siteSymbol) && !isModuleInitialized &&
- jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
- jname == INSTANCE_CONSTRUCTOR_NAME) {
- isModuleInitialized = true
- jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
- jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr)
- }
- }
-
- style match {
- case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype)
- case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype)
- case Dynamic if needsInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype)
- case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype)
- case SuperCall(_) =>
- dbg("invokespecial")
- jcode.invokespecial(jowner, jname, jtype)
- initModule()
- }
- } // end of genCode()'s genCallMethod()
-
- def genBlock(b: BasicBlock) {
- jmethod.visitLabel(labels(b))
-
- debuglog("Generating code for block: " + b)
-
- // val lastInstr = b.lastInstruction
-
- for (instr <- b) {
-
- if(instr.pos.isDefined) {
- val iPos = instr.pos
- val currentLineNr = iPos.line
- val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else
- if(!skip) {
- lastLineNr = currentLineNr
- val lineLab = new asm.Label
- jmethod.visitLabel(lineLab)
- lnEntries ::= LineNumberEntry(iPos.finalPosition.line, lineLab)
- }
- }
-
- genInstr(instr, b)
-
- }
-
- }
-
- def genInstr(instr: Instruction, b: BasicBlock) {
- import asm.Opcodes
- (instr.category: @scala.annotation.switch) match {
-
-
- case icodes.localsCat =>
- def genLocalInstr() = (instr: @unchecked) match {
- case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind)
- case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind)
- case STORE_THIS(_) =>
- // this only works for impl classes because the self parameter comes first
- // in the method signature. If that changes, this code has to be revisited.
- jmethod.visitVarInsn(Opcodes.ASTORE, 0)
-
- case SCOPE_ENTER(lv) =>
- // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if (relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars?
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val start = new asm.Label
- jmethod.visitLabel(start)
- scoping.pushScope(lv, start)
- }
-
- case SCOPE_EXIT(lv) =>
- val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
- if (relevant) {
- // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
- // similarly, these labels aren't tracked in the `labels` map.
- val end = new asm.Label
- jmethod.visitLabel(end)
- scoping.popScope(lv, end, instr.pos)
- }
- }
- genLocalInstr()
-
- case icodes.stackCat =>
- def genStackInstr() = (instr: @unchecked) match {
-
- case LOAD_MODULE(module) =>
- // assert(module.isModule, "Expected module: " + module)
- debuglog("generating LOAD_MODULE for: " + module + " flags: " + module.flagString)
- def inStaticMethod = this.method != null && this.method.symbol.isStaticMember
- if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString && !inStaticMethod) {
- jmethod.visitVarInsn(Opcodes.ALOAD, 0)
- } else {
- jmethod.visitFieldInsn(
- Opcodes.GETSTATIC,
- javaName(module) /* + "$" */ ,
- strMODULE_INSTANCE_FIELD,
- descriptor(module))
- }
-
- case DROP(kind) => emit(if (kind.isWideType) Opcodes.POP2 else Opcodes.POP)
-
- case DUP(kind) => emit(if (kind.isWideType) Opcodes.DUP2 else Opcodes.DUP)
-
- case LOAD_EXCEPTION(_) => ()
- }
- genStackInstr()
-
- case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant)
-
- case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos)
-
- case icodes.castsCat =>
- def genCastInstr() = (instr: @unchecked) match {
-
- case IS_INSTANCE(tpe) =>
- val jtyp: asm.Type =
- tpe match {
- case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
- case ARRAY(elem) => javaArrayType(javaType(elem))
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
- jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName)
-
- case CHECK_CAST(tpe) =>
- tpe match {
-
- case REFERENCE(cls) =>
- if (cls != ObjectClass) { // No need to checkcast for Objects
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls))
- }
-
- case ARRAY(elem) =>
- val iname = javaArrayType(javaType(elem)).getInternalName
- jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname)
-
- case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
- }
-
- }
- genCastInstr()
-
- case icodes.objsCat =>
- def genObjsInstr() = (instr: @unchecked) match {
- case BOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jBoxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
-
- case UNBOX(kind) =>
- val MethodNameAndType(mname, mdesc) = jUnboxTo(kind)
- jcode.invokestatic(BoxesRunTime, mname, mdesc)
-
- case NEW(REFERENCE(cls)) =>
- val className = javaName(cls)
- jmethod.visitTypeInsn(Opcodes.NEW, className)
-
- case MONITOR_ENTER() => emit(Opcodes.MONITORENTER)
- case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT)
- }
- genObjsInstr()
-
- case icodes.fldsCat =>
- def genFldsInstr() = (instr: @unchecked) match {
-
- case lf @ LOAD_FIELD(field, isStatic) =>
- val owner = javaName(lf.hostClass)
- debuglog("LOAD_FIELD with owner: " + owner + " flags: " + field.owner.flagString)
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- case STORE_FIELD(field, isStatic) =>
- val owner = javaName(field.owner)
- val fieldJName = javaName(field)
- val fieldDescr = descriptor(field)
- val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD
- jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
-
- }
- genFldsInstr()
-
- case icodes.mthdsCat =>
- def genMethodsInstr() = (instr: @unchecked) match {
-
- /* Special handling to access native Array.clone() */
- case call @ CALL_METHOD(definitions.Array_clone, Dynamic) =>
- val target: String = javaType(call.targetTypeKind).getInternalName
- jcode.invokevirtual(target, "clone", mdesc_arrayClone)
-
- case call @ CALL_METHOD(method, style) => genCallMethod(call)
-
- }
- genMethodsInstr()
-
- case icodes.arraysCat =>
- def genArraysInstr() = (instr: @unchecked) match {
- case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind)
- case STORE_ARRAY_ITEM(kind) => jcode.astore(kind)
- case CREATE_ARRAY(elem, 1) => jcode newarray elem
- case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims)
- }
- genArraysInstr()
-
- case icodes.jumpsCat =>
- def genJumpInstr() = (instr: @unchecked) match {
-
- case sw @ SWITCH(tagss, branches) =>
- assert(branches.length == tagss.length + 1, sw)
- val flatSize = sw.flatTagsCount
- val flatKeys = new Array[Int](flatSize)
- val flatBranches = new Array[asm.Label](flatSize)
-
- var restTagss = tagss
- var restBranches = branches
- var k = 0 // ranges over flatKeys and flatBranches
- while (restTagss.nonEmpty) {
- val currLabel = labels(restBranches.head)
- for (cTag <- restTagss.head) {
- flatKeys(k) = cTag
- flatBranches(k) = currLabel
- k += 1
- }
- restTagss = restTagss.tail
- restBranches = restBranches.tail
- }
- val defaultLabel = labels(restBranches.head)
- assert(restBranches.tail.isEmpty)
- debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches)
- jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY)
-
- case JUMP(whereto) =>
- if (nextBlock != whereto)
- jcode goTo labels(whereto)
- // SI-6102: Determine whether eliding this JUMP results in an empty range being covered by some EH.
- // If so, emit a NOP in place of the elided JUMP, to avoid "java.lang.ClassFormatError: Illegal exception table range"
- else if (newNormal.isJumpOnly(b) && m.exh.exists(eh => eh.covers(b))) {
- devWarning("Had a jump only block that wasn't collapsed")
- emit(asm.Opcodes.NOP)
- }
-
- case CJUMP(success, failure, cond, kind) =>
- if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF_ICMP(cond.negate(), labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ICMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- if (nextBlock == success) {
- jcode.emitIF_ACMP(cond.negate(), labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF_ACMP(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else {
- (kind: @unchecked) match {
- case LONG => emit(Opcodes.LCMP)
- case FLOAT =>
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate(), labels(failure))
- // .. and fall through to success label
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
-
- case CZJUMP(success, failure, cond, kind) =>
- if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
- if (nextBlock == success) {
- jcode.emitIF(cond.negate(), labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
- val Success = success
- val Failure = failure
- // @unchecked because references aren't compared with GT, GE, LT, LE.
- ((cond, nextBlock): @unchecked) match {
- case (EQ, Success) => jcode emitIFNONNULL labels(failure)
- case (NE, Failure) => jcode emitIFNONNULL labels(success)
- case (EQ, Failure) => jcode emitIFNULL labels(success)
- case (NE, Success) => jcode emitIFNULL labels(failure)
- case (EQ, _) =>
- jcode emitIFNULL labels(success)
- jcode goTo labels(failure)
- case (NE, _) =>
- jcode emitIFNONNULL labels(success)
- jcode goTo labels(failure)
- }
- } else {
- (kind: @unchecked) match {
- case LONG =>
- emit(Opcodes.LCONST_0)
- emit(Opcodes.LCMP)
- case FLOAT =>
- emit(Opcodes.FCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
- else emit(Opcodes.FCMPL)
- case DOUBLE =>
- emit(Opcodes.DCONST_0)
- if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
- else emit(Opcodes.DCMPL)
- }
- if (nextBlock == success) {
- jcode.emitIF(cond.negate(), labels(failure))
- } else {
- jcode.emitIF(cond, labels(success))
- if (nextBlock != failure) { jcode goTo labels(failure) }
- }
- }
-
- }
- genJumpInstr()
-
- case icodes.retCat =>
- def genRetInstr() = (instr: @unchecked) match {
- case RETURN(kind) => jcode emitRETURN kind
- case THROW(_) => emit(Opcodes.ATHROW)
- }
- genRetInstr()
- }
- }
-
- /*
- * Emits one or more conversion instructions based on the types given as arguments.
- *
- * @param from The type of the value to be converted into another type.
- * @param to The type the value will be converted into.
- */
- def emitT2T(from: TypeKind, to: TypeKind) {
- assert(isNonUnitValueTK(from) && isNonUnitValueTK(to), s"Cannot emit primitive conversion from $from to $to")
-
- def pickOne(opcs: Array[Int]) {
- val chosen = (to: @unchecked) match {
- case BYTE => opcs(0)
- case SHORT => opcs(1)
- case CHAR => opcs(2)
- case INT => opcs(3)
- case LONG => opcs(4)
- case FLOAT => opcs(5)
- case DOUBLE => opcs(6)
- }
- if(chosen != -1) { emit(chosen) }
- }
-
- if(from == to) { return }
- // the only conversion involving BOOL that is allowed is (BOOL -> BOOL)
- assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to")
-
- if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already)
-
- val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT)
- val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing
- val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing
- val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) }
-
- (from: @unchecked) match {
- case BYTE => pickOne(fromByte)
- case SHORT => pickOne(fromShort)
- case CHAR => pickOne(fromChar)
- case INT => pickOne(fromInt)
- }
-
- } else { // FLOAT, LONG, DOUBLE
-
- (from: @unchecked) match {
- case FLOAT =>
- import asm.Opcodes.{ F2L, F2D, F2I }
- (to: @unchecked) match {
- case LONG => emit(F2L)
- case DOUBLE => emit(F2D)
- case _ => emit(F2I); emitT2T(INT, to)
- }
-
- case LONG =>
- import asm.Opcodes.{ L2F, L2D, L2I }
- (to: @unchecked) match {
- case FLOAT => emit(L2F)
- case DOUBLE => emit(L2D)
- case _ => emit(L2I); emitT2T(INT, to)
- }
-
- case DOUBLE =>
- import asm.Opcodes.{ D2L, D2F, D2I }
- (to: @unchecked) match {
- case FLOAT => emit(D2F)
- case LONG => emit(D2L)
- case _ => emit(D2I); emitT2T(INT, to)
- }
- }
- }
- } // end of genCode()'s emitT2T()
-
- def genPrimitive(primitive: Primitive, pos: Position) {
-
- import asm.Opcodes
-
- primitive match {
-
- case Negation(kind) => jcode.neg(kind)
-
- case Arithmetic(op, kind) =>
- def genArith() = {
- op match {
-
- case ADD => jcode.add(kind)
- case SUB => jcode.sub(kind)
- case MUL => jcode.mul(kind)
- case DIV => jcode.div(kind)
- case REM => jcode.rem(kind)
-
- case NOT =>
- if(kind.isIntSizedType) {
- emit(Opcodes.ICONST_M1)
- emit(Opcodes.IXOR)
- } else if(kind == LONG) {
- jmethod.visitLdcInsn(new java.lang.Long(-1))
- jmethod.visitInsn(Opcodes.LXOR)
- } else {
- abort("Impossible to negate an " + kind)
- }
-
- case _ =>
- abort("Unknown arithmetic primitive " + primitive)
- }
- }
- genArith()
-
- // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey)
- // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead.
- // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed).
- case Logical(op, kind) =>
- def genLogical() = op match {
- case AND =>
- kind match {
- case LONG => emit(Opcodes.LAND)
- case INT => emit(Opcodes.IAND)
- case _ =>
- emit(Opcodes.IAND)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
- case OR =>
- kind match {
- case LONG => emit(Opcodes.LOR)
- case INT => emit(Opcodes.IOR)
- case _ =>
- emit(Opcodes.IOR)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
- case XOR =>
- kind match {
- case LONG => emit(Opcodes.LXOR)
- case INT => emit(Opcodes.IXOR)
- case _ =>
- emit(Opcodes.IXOR)
- if (kind != BOOL) { emitT2T(INT, kind) }
- }
- }
- genLogical()
-
- case Shift(op, kind) =>
- def genShift() = op match {
- case LSL =>
- kind match {
- case LONG => emit(Opcodes.LSHL)
- case INT => emit(Opcodes.ISHL)
- case _ =>
- emit(Opcodes.ISHL)
- emitT2T(INT, kind)
- }
- case ASR =>
- kind match {
- case LONG => emit(Opcodes.LSHR)
- case INT => emit(Opcodes.ISHR)
- case _ =>
- emit(Opcodes.ISHR)
- emitT2T(INT, kind)
- }
- case LSR =>
- kind match {
- case LONG => emit(Opcodes.LUSHR)
- case INT => emit(Opcodes.IUSHR)
- case _ =>
- emit(Opcodes.IUSHR)
- emitT2T(INT, kind)
- }
- }
- genShift()
-
- case Comparison(op, kind) =>
- def genCompare() = op match {
- case CMP =>
- (kind: @unchecked) match {
- case LONG => emit(Opcodes.LCMP)
- }
- case CMPL =>
- (kind: @unchecked) match {
- case FLOAT => emit(Opcodes.FCMPL)
- case DOUBLE => emit(Opcodes.DCMPL)
- }
- case CMPG =>
- (kind: @unchecked) match {
- case FLOAT => emit(Opcodes.FCMPG)
- case DOUBLE => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se6/html/Instructions2.doc3.html
-
- }
- }
- genCompare()
-
- case Conversion(src, dst) =>
- debuglog("Converting from: " + src + " to: " + dst)
- emitT2T(src, dst)
-
- case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH)
-
- case StartConcat =>
- jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
- jmethod.visitInsn(Opcodes.DUP)
- jcode.invokespecial(
- StringBuilderClassName,
- INSTANCE_CONSTRUCTOR_NAME,
- mdesc_arglessvoid
- )
-
- case StringConcat(el) =>
- val jtype = el match {
- case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT
- case _ => javaType(el)
- }
- jcode.invokevirtual(
- StringBuilderClassName,
- "append",
- asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*)
- )
-
- case EndConcat =>
- jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString)
-
- case _ => abort("Unimplemented primitive " + primitive)
- }
- } // end of genCode()'s genPrimitive()
-
- // ------------------------------------------------------------------------------------------------------------
- // Part 6 of genCode(): the executable part of genCode() starts here.
- // ------------------------------------------------------------------------------------------------------------
-
- genBlocks(linearization)
-
- jmethod.visitLabel(onePastLast)
-
- if(emitLines) {
- for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) }
- }
- if(emitVars) { genLocalVariableTable() }
-
- } // end of BytecodeGenerator.genCode()
-
-
- ////////////////////// local vars ///////////////////////
-
- def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1
-
- final def indexOf(local: Local): Int = {
- assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ")
- local.index
- }
-
- /**
- * Compute the indexes of each local variable of the given method.
- * *Does not assume the parameters come first!*
- */
- def computeLocalVarsIndex(m: IMethod) {
- var idx = if (m.symbol.isStaticMember) 0 else 1
-
- for (l <- m.params) {
- debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
- l.index = idx
- idx += sizeOf(l.kind)
- }
-
- for (l <- m.locals if !l.arg) {
- debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
- l.index = idx
- idx += sizeOf(l.kind)
- }
- }
-
- } // end of class JPlainBuilder
-
-
- /** builder of mirror classes */
- class JMirrorBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JCommonBuilder(bytecodeWriter, needsOutfile) {
-
- private var cunit: CompilationUnit = _
- def getCurrentCUnit(): CompilationUnit = cunit
-
- /** Generate a mirror class for a top-level module. A mirror class is a class
- * containing only static methods that forward to the corresponding method
- * on the MODULE instance of the given Scala object. It will only be
- * generated if there is no companion class: if there is, an attempt will
- * instead be made to add the forwarder methods to the companion class.
- */
- def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) {
- assert(modsym.companionClass == NoSymbol, modsym)
- innerClassBuffer.clear()
- this.cunit = cunit
- val moduleName = javaName(modsym) // + "$"
- val mirrorName = moduleName.substring(0, moduleName.length() - 1)
-
- val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL)
- val mirrorClass = createJClass(flags,
- mirrorName,
- null /* no java-generic-signature */,
- JAVA_LANG_OBJECT.getInternalName,
- EMPTY_STRING_ARRAY)
-
- log(s"Dumping mirror class for '$mirrorName'")
-
- // typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- if(emitSource) {
- mirrorClass.visitSource("" + cunit.source,
- null /* SourceDebugExtension */)
- }
-
- val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol)
- mirrorClass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
- emitAnnotations(mirrorClass, modsym.annotations ++ ssa)
-
- // typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym)
-
- addInnerClasses(modsym, mirrorClass, isMirror = true)
- mirrorClass.visitEnd()
- writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym)
- }
- } // end of class JMirrorBuilder
-
-
- /** builder of bean info classes */
- class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) {
-
- /**
- * Generate a bean info class that describes the given class.
- *
- * @author Ross Judson (ross.judson@soletta.com)
- */
- def genBeanInfoClass(clasz: IClass) {
-
- // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip")
- // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName")
- // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription")
- // val description = c.symbol getAnnotation BeanDescriptionAttr
- // informProgress(description.toString)
- innerClassBuffer.clear()
-
- val flags = mkFlags(
- javaFlags(clasz.symbol),
- if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
- )
-
- val beanInfoName = (javaName(clasz.symbol) + "BeanInfo")
- val beanInfoClass = createJClass(
- flags,
- beanInfoName,
- null, // no java-generic-signature
- "scala/beans/ScalaBeanInfo",
- EMPTY_STRING_ARRAY
- )
-
- // beanInfoClass typestate: entering mode with valid call sequences:
- // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
-
- beanInfoClass.visitSource(
- clasz.cunit.source.toString,
- null /* SourceDebugExtension */
- )
-
- var fieldList = List[String]()
-
- for (f <- clasz.fields if f.symbol.hasGetter;
- g = f.symbol.getterIn(clasz.symbol);
- s = f.symbol.setterIn(clasz.symbol)
- if g.isPublic && !(f.symbol.name startsWith "$")
- ) {
- // inserting $outer breaks the bean
- fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList
- }
-
- val methodList: List[String] =
- for (m <- clasz.methods
- if !m.symbol.isConstructor &&
- m.symbol.isPublic &&
- !(m.symbol.name startsWith "$") &&
- !m.symbol.isGetter &&
- !m.symbol.isSetter)
- yield javaName(m.symbol)
-
- // beanInfoClass typestate: entering mode with valid call sequences:
- // ( visitInnerClass | visitField | visitMethod )* visitEnd
-
- val constructor = beanInfoClass.visitMethod(
- asm.Opcodes.ACC_PUBLIC,
- INSTANCE_CONSTRUCTOR_NAME,
- mdesc_arglessvoid,
- null, // no java-generic-signature
- EMPTY_STRING_ARRAY // no throwable exceptions
- )
-
- // constructor typestate: entering mode with valid call sequences:
- // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
-
- val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING)
- val conJType: asm.Type =
- asm.Type.getMethodType(
- asm.Type.VOID_TYPE,
- Array(javaType(ClassClass), stringArrayJType, stringArrayJType): _*
- )
-
- def push(lst: List[String]) {
- var fi = 0
- for (f <- lst) {
- constructor.visitInsn(asm.Opcodes.DUP)
- constructor.visitLdcInsn(new java.lang.Integer(fi))
- if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) }
- else { constructor.visitLdcInsn(f) }
- constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE))
- fi += 1
- }
- }
-
- // constructor typestate: entering mode with valid call sequences:
- // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
-
- constructor.visitCode()
-
- constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
- // push the class
- constructor.visitLdcInsn(javaType(clasz.symbol))
-
- // push the string array of field information
- constructor.visitLdcInsn(new java.lang.Integer(fieldList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName)
- push(fieldList)
-
- // push the string array of method information
- constructor.visitLdcInsn(new java.lang.Integer(methodList.length))
- constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName)
- push(methodList)
-
- // invoke the superclass constructor, which will do the
- // necessary java reflection and create Method objects.
- constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor, false)
- constructor.visitInsn(asm.Opcodes.RETURN)
-
- constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments
- constructor.visitEnd()
-
- addInnerClasses(clasz.symbol, beanInfoClass)
- beanInfoClass.visitEnd()
-
- writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol)
- }
-
- } // end of class JBeanInfoBuilder
-
- /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for.
- * In particular, IMethod.normalize() doesn't collapseJumpChains().
- *
- * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them).
- */
- object newNormal {
- /**
- * True if a block is "jump only" which is defined
- * as being a block that consists only of 0 or more instructions that
- * won't make it to the JVM followed by a JUMP.
- */
- def isJumpOnly(b: BasicBlock): Boolean = {
- val nonICode = firstNonIcodeOnlyInstructions(b)
- // by definition a block has to have a jump, conditional jump, return, or throw
- assert(nonICode.hasNext, "empty block")
- nonICode.next.isInstanceOf[JUMP]
- }
-
- /**
- * Returns the list of instructions in a block that follow all ICode only instructions,
- * where an ICode only instruction is one that won't make it to the JVM
- */
- private def firstNonIcodeOnlyInstructions(b: BasicBlock): Iterator[Instruction] = {
- def isICodeOnlyInstruction(i: Instruction) = i match {
- case LOAD_EXCEPTION(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) => true
- case _ => false
- }
- b.iterator dropWhile isICodeOnlyInstruction
- }
-
- /**
- * Returns the target of a block that is "jump only" which is defined
- * as being a block that consists only of 0 or more instructions that
- * won't make it to the JVM followed by a JUMP.
- *
- * @param b The basic block to examine
- * @return Some(target) if b is a "jump only" block or None if it's not
- */
- private def getJumpOnlyTarget(b: BasicBlock): Option[BasicBlock] = {
- val nonICode = firstNonIcodeOnlyInstructions(b)
- // by definition a block has to have a jump, conditional jump, return, or throw
- assert(nonICode.nonEmpty, "empty block")
- nonICode.next match {
- case JUMP(whereto) =>
- assert(!nonICode.hasNext, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)")
- Some(whereto)
- case _ => None
- }
- }
-
- /**
- * Collapse a chain of "jump-only" blocks such as:
- *
- * JUMP b1;
- * b1: JUMP b2;
- * b2: JUMP ... etc.
- *
- * by re-wiring predecessors to target directly the "final destination".
- * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed.
-
- * Returns true if any replacement was made, false otherwise.
- *
- * In more detail:
- * Starting at each of the entry points (m.startBlock, the start block of each exception handler)
- * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D.
- * The blocks thus skipped become eligible to removed by the reachability analyzer
- *
- * Rationale for this normalization:
- * test/files/run/private-inline.scala after -optimize is chock full of
- * BasicBlocks containing just JUMP(whereto), where no exception handler straddles them.
- * They should be collapsed by IMethod.normalize() but aren't.
- * That was fine in FJBG times when by the time the exception table was emitted,
- * it already contained "anchored" labels (ie instruction offsets were known)
- * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question)
- * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range"
- * Now that visitTryCatchBlock() must be called before Labels are resolved,
- * renders the BasicBlocks described above (to recap, consisting of just a JUMP) unreachable.
- */
- private def collapseJumpOnlyBlocks(m: IMethod) {
- assert(m.hasCode, "code-less method")
-
- def rephraseGotos(detour: mutable.Map[BasicBlock, BasicBlock]) {
- def lookup(b: BasicBlock) = detour.getOrElse(b, b)
-
- m.code.startBlock = lookup(m.code.startBlock)
-
- for(eh <- m.exh)
- eh.setStartBlock(lookup(eh.startBlock))
-
- for (b <- m.blocks) {
- def replaceLastInstruction(i: Instruction) = {
- if (b.lastInstruction != i) {
- val idxLast = b.size - 1
- debuglog(s"In block $b, replacing last instruction ${b.lastInstruction} with ${i}")
- b.replaceInstruction(idxLast, i)
- }
- }
-
- b.lastInstruction match {
- case JUMP(whereto) =>
- replaceLastInstruction(JUMP(lookup(whereto)))
- case CJUMP(succ, fail, cond, kind) =>
- replaceLastInstruction(CJUMP(lookup(succ), lookup(fail), cond, kind))
- case CZJUMP(succ, fail, cond, kind) =>
- replaceLastInstruction(CZJUMP(lookup(succ), lookup(fail), cond, kind))
- case SWITCH(tags, labels) =>
- val newLabels = (labels map lookup)
- replaceLastInstruction(SWITCH(tags, newLabels))
- case _ => ()
- }
- }
- }
-
- /*
- * Computes a mapping from jump only block to its
- * final destination which is either a non-jump-only
- * block or, if it's in a jump-only block cycle, is
- * itself
- */
- def computeDetour: mutable.Map[BasicBlock, BasicBlock] = {
- // fetch the jump only blocks and their immediate destinations
- val pairs = for {
- block <- m.blocks.toIterator
- target <- getJumpOnlyTarget(block)
- } yield(block, target)
-
- // mapping from a jump-only block to our current knowledge of its
- // final destination. Initially it's just jump block to immediate jump
- // target
- val detour = mutable.Map[BasicBlock, BasicBlock](pairs.toSeq:_*)
-
- // for each jump-only block find its final destination
- // taking advantage of the destinations we found for previous
- // blocks
- for (key <- detour.keySet) {
- // we use the Robert Floyd's classic Tortoise and Hare algorithm
- @tailrec
- def findDestination(tortoise: BasicBlock, hare: BasicBlock): BasicBlock = {
- if (tortoise == hare)
- // cycle detected, map key to key
- key
- else if (detour contains hare) {
- // advance hare once
- val hare1 = detour(hare)
- // make sure we can advance hare a second time
- if (detour contains hare1)
- // advance tortoise once and hare a second time
- findDestination(detour(tortoise), detour(hare1))
- else
- // hare1 is not in the map so it's not a jump-only block, it's the destination
- hare1
- } else
- // hare is not in the map so it's not a jump-only block, it's the destination
- hare
- }
- // update the mapping for key based on its final destination
- detour(key) = findDestination(key, detour(key))
- }
- detour
- }
-
- val detour = computeDetour
- rephraseGotos(detour)
-
- if (settings.debug) {
- val (remappings, cycles) = detour partition {case (source, target) => source != target}
- for ((source, target) <- remappings) {
- debuglog(s"Will elide jump only block $source because it can be jumped around to get to $target.")
- if (m.startBlock == source) devWarning("startBlock should have been re-wired by now")
- }
- val sources = remappings.keySet
- val targets = remappings.values.toSet
- val intersection = sources intersect targets
-
- if (intersection.nonEmpty) devWarning(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}")
-
- for ((source, _) <- cycles) {
- debuglog(s"Block $source is in a do-nothing infinite loop. Did the user write 'while(true){}'?")
- }
- }
- }
-
- /**
- * Removes all blocks that are unreachable in a method using a standard reachability analysis.
- */
- def elimUnreachableBlocks(m: IMethod) {
- assert(m.hasCode, "code-less method")
-
- // assume nothing is reachable until we prove it can be reached
- val reachable = mutable.Set[BasicBlock]()
-
- // the set of blocks that we know are reachable but have
- // yet to be marked reachable, initially only the start block
- val worklist = mutable.Set(m.startBlock)
-
- while (worklist.nonEmpty) {
- val block = worklist.head
- worklist remove block
- // we know that one is reachable
- reachable add block
- // so are its successors, so go back around and add the ones we still
- // think are unreachable
- worklist ++= (block.successors filterNot reachable)
- }
-
- // exception handlers need to be told not to cover unreachable blocks
- // and exception handlers that no longer cover any blocks need to be
- // removed entirely
- val unusedExceptionHandlers = mutable.Set[ExceptionHandler]()
- for (exh <- m.exh) {
- exh.covered = exh.covered filter reachable
- if (exh.covered.isEmpty) {
- unusedExceptionHandlers += exh
- }
- }
-
- // remove the unused exception handler references
- if (settings.debug)
- for (exh <- unusedExceptionHandlers) debuglog(s"eliding exception handler $exh because it does not cover any reachable blocks")
- m.exh = m.exh filterNot unusedExceptionHandlers
-
- // everything not in the reachable set is unreachable, unused, and unloved. buh bye
- for (b <- m.blocks filterNot reachable) {
- debuglog(s"eliding block $b because it is unreachable")
- m.code removeBlock b
- }
- }
-
- def normalize(m: IMethod) {
- if(!m.hasCode) { return }
- collapseJumpOnlyBlocks(m)
- if (settings.optimise)
- elimUnreachableBlocks(m)
- icodes checkValid m
- }
-
- }
-
- // @M don't generate java generics sigs for (members of) implementation
- // classes, as they are monomorphic (TODO: ok?)
- private def needsGenericSignature(sym: Symbol) = !(
- // PP: This condition used to include sym.hasExpandedName, but this leads
- // to the total loss of generic information if a private member is
- // accessed from a closure: both the field and the accessor were generated
- // without it. This is particularly bad because the availability of
- // generic information could disappear as a consequence of a seemingly
- // unrelated change.
- settings.Ynogenericsig
- || sym.isArtifact
- || sym.isLiftedMethod
- || sym.isBridge
- || (sym.ownerChain exists (_.isImplClass))
- )
-
- final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol, unit: CompilationUnit): String = {
- if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745
- else {
- // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to.
- // By rights, it should use the signature as-seen-from the module class, and add suitable
- // primitive and value-class boxing/unboxing.
- // But for now, just like we did in mixin, we just avoid writing a wrong generic signature
- // (one that doesn't erase to the actual signature). See run/t3452b for a test case.
- val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym))
- val erasedMemberType = erasure.erasure(sym)(memberTpe)
- if (erasedMemberType =:= sym.info)
- getGenericSignature(sym, moduleClass, memberTpe, unit)
- else null
- }
- }
-
- /** @return
- * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases).
- * - otherwise the signature in question
- */
- def getGenericSignature(sym: Symbol, owner: Symbol, unit: CompilationUnit): String = {
- val memberTpe = enteringErasure(owner.thisType.memberInfo(sym))
- getGenericSignature(sym, owner, memberTpe, unit)
- }
- def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type, unit: CompilationUnit): String = {
- if (!needsGenericSignature(sym)) { return null }
-
- val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe)
- if (jsOpt.isEmpty) { return null }
-
- val sig = jsOpt.get
- log(sig) // This seems useful enough in the general case.
-
- def wrap(op: => Unit) = {
- try { op; true }
- catch { case _: Throwable => false }
- }
-
- if (settings.Xverify) {
- // Run the signature parser to catch bogus signatures.
- val isValidSignature = wrap {
- // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser)
- import scala.tools.asm.util.CheckClassAdapter
- if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar
- else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig }
- else { CheckClassAdapter checkClassSignature sig }
- }
-
- if(!isValidSignature) {
- reporter.warning(sym.pos,
- """|compiler bug: created invalid generic signature for %s in %s
- |signature: %s
- |if this is reproducible, please report bug at https://issues.scala-lang.org/
- """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig))
- return null
- }
- }
-
- if ((settings.check containsName phaseName)) {
- val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe))
- val bytecodeTpe = owner.thisType.memberInfo(sym)
- if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) {
- reporter.warning(sym.pos,
- """|compiler bug: created generic signature for %s in %s that does not conform to its erasure
- |signature: %s
- |original type: %s
- |normalized type: %s
- |erasure type: %s
- |if this is reproducible, please report bug at http://issues.scala-lang.org/
- """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe))
- return null
- }
- }
-
- sig
- }
-
- def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
- val ca = new Array[Char](bytes.length)
- var idx = 0
- while(idx < bytes.length) {
- val b: Byte = bytes(idx)
- assert((b & ~0x7f) == 0)
- ca(idx) = b.asInstanceOf[Char]
- idx += 1
- }
-
- ca
- }
-
- final def arrEncode(sb: ScalaSigBytes): Array[String] = {
- var strs: List[String] = Nil
- val bSeven: Array[Byte] = sb.sevenBitsMayBeZero
- // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure)
- var prevOffset = 0
- var offset = 0
- var encLength = 0
- while(offset < bSeven.length) {
- val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1)
- val newEncLength = encLength.toLong + deltaEncLength
- if(newEncLength >= 65535) {
- val ba = bSeven.slice(prevOffset, offset)
- strs ::= new java.lang.String(ubytesToCharArray(ba))
- encLength = 0
- prevOffset = offset
- } else {
- encLength += deltaEncLength
- offset += 1
- }
- }
- if(prevOffset < offset) {
- assert(offset == bSeven.length)
- val ba = bSeven.slice(prevOffset, offset)
- strs ::= new java.lang.String(ubytesToCharArray(ba))
- }
- assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict?
- strs.reverse.toArray
- }
-
- private def strEncode(sb: ScalaSigBytes): String = {
- val ca = ubytesToCharArray(sb.sevenBitsMayBeZero)
- new java.lang.String(ca)
- // debug val bvA = new asm.ByteVector; bvA.putUTF8(s)
- // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes)
- // debug assert(enc(idx) == bvA.getByte(idx + 2))
- // debug assert(bvA.getLength == enc.size + 2)
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index af962c4ce0..6593d4b725 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.
@@ -76,15 +77,16 @@ abstract class GenBCode extends BCodeSyncAndTry {
/* ---------------- q2 ---------------- */
- case class Item2(arrivalPos: Int,
- mirror: asm.tree.ClassNode,
- plain: asm.tree.ClassNode,
- bean: asm.tree.ClassNode,
- outFolder: scala.tools.nsc.io.AbstractFile) {
+ case class Item2(arrivalPos: Int,
+ mirror: asm.tree.ClassNode,
+ plain: asm.tree.ClassNode,
+ bean: asm.tree.ClassNode,
+ sourceFilePath: String,
+ outFolder: scala.tools.nsc.io.AbstractFile) {
def isPoison = { arrivalPos == Int.MaxValue }
}
- private val poison2 = Item2(Int.MaxValue, null, null, null, null)
+ private val poison2 = Item2(Int.MaxValue, null, null, null, null, null)
private val q2 = new _root_.java.util.LinkedList[Item2]
/* ---------------- q3 ---------------- */
@@ -134,7 +136,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
return
}
else {
- try { withCurrentUnit(item.cunit)(visit(item)) }
+ try { withCurrentUnitNoLog(item.cunit)(visit(item)) }
catch {
case ex: Throwable =>
ex.printStackTrace()
@@ -186,7 +188,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.thisBType.internalName, cunit) else null
val plainC = pcb.cnode
// -------------- bean info class, if needed --------------
@@ -204,6 +206,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
val item2 =
Item2(arrivalPos,
mirrorC, plainC, beanC,
+ cunit.source.file.canonicalPath,
outF)
q2 add item2 // at the very end of this method so that no Worker2 thread starts mutating before we're done.
@@ -220,17 +223,24 @@ 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)
- }
+ import scala.collection.JavaConverters._
+
+ // 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.optAddToBytecodeRepository) q2.asScala foreach {
+ case Item2(_, mirror, plain, bean, sourceFilePath, _) =>
+ val someSourceFilePath = Some(sourceFilePath)
+ if (mirror != null) byteCodeRepository.add(mirror, someSourceFilePath)
+ if (plain != null) byteCodeRepository.add(plain, someSourceFilePath)
+ if (bean != null) byteCodeRepository.add(bean, someSourceFilePath)
+ }
+ if (settings.optBuildCallGraph) 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)
+ if (settings.optInlinerEnabled)
bTypes.inliner.runInliner()
- if (settings.YoptClosureElimination)
+ if (settings.optClosureInvocations)
closureOptimizer.rewriteClosureApplyInvocations()
}
@@ -238,6 +248,11 @@ abstract class GenBCode extends BCodeSyncAndTry {
BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode))
}
+ def setInnerClasses(classNode: ClassNode): Unit = if (classNode != null) {
+ classNode.innerClasses.clear()
+ addInnerClasses(classNode, bTypes.backendUtils.collectNestedClasses(classNode))
+ }
+
def run() {
runGlobalOptimizations()
@@ -250,8 +265,17 @@ abstract class GenBCode extends BCodeSyncAndTry {
else {
try {
localOptimizations(item.plain)
+ setInnerClasses(item.plain)
+ val lambdaImplMethods = getIndyLambdaImplMethods(item.plain.name)
+ if (lambdaImplMethods.nonEmpty)
+ backendUtils.addLambdaDeserialize(item.plain, lambdaImplMethods)
+ setInnerClasses(item.mirror)
+ setInnerClasses(item.bean)
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}")
@@ -268,7 +292,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
cw.toByteArray
}
- val Item2(arrivalPos, mirror, plain, bean, outFolder) = item
+ val Item2(arrivalPos, mirror, plain, bean, _, outFolder) = item
val mirrorC = if (mirror == null) null else SubItem3(mirror.name, getByteArray(mirror))
val plainC = SubItem3(plain.name, getByteArray(plain))
@@ -313,7 +337,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
bTypes.initializeCoreBTypes()
bTypes.javaDefinedClasses.clear()
bTypes.javaDefinedClasses ++= currentRun.symSource collect {
- case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString
+ case (sym, _) if sym.isJavaDefined => sym.javaBinaryNameString
}
Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart)
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..086946e4e3 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._
@@ -23,63 +28,80 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
init(src)
}
- /**
- * 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)
+ override def toString: String = super.toString + " - " + aliases.toList.filter(s => s != null && s.size > 1).map(_.toString).distinct.mkString(",")
/**
- * 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)
- // the val pattern `val (p, c) = f` still allocates a tuple (https://github.com/scala-opt/scala/issues/28)
- val prodCons = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc
- val consumed = prodCons._1
- val produced = prodCons._2
+ val prodCons = InstructionStackEffect.forAsmAnalysis(insn, this) // needs to be called before super.execute, see its doc
+ val consumed = InstructionStackEffect.cons(prodCons)
+ val produced = InstructionStackEffect.prod(prodCons)
super.execute(insn, interpreter)
(insn.getOpcode: @switch) match {
- case ALOAD =>
+ case ILOAD | LLOAD | FLOAD | DLOAD | ALOAD =>
newAlias(assignee = stackTop, source = insn.asInstanceOf[VarInsnNode].`var`)
case DUP =>
@@ -166,31 +188,54 @@ 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
- case opcode =>
- if (opcode == ASTORE) {
- // Not a separate case because we need to remove the consumed stack value from alias sets after.
- val stackTopBefore = stackTop - produced + consumed
- val local = insn.asInstanceOf[VarInsnNode].`var`
- newAlias(assignee = local, source = stackTopBefore)
- // if the value written is size 2, it overwrites the subsequent slot, which is then no
- // longer an alias of anything. see the corresponding case in `Frame.execute`.
- if (getLocal(local).getSize == 2)
- removeAlias(local + 1)
-
- // if the value at the preceding index is size 2, it is no longer valid, so we remove its
- // aliasing. see corresponding case in `Frame.execute`
- if (local > 0) {
- val precedingValue = getLocal(local - 1)
- if (precedingValue != null && precedingValue.getSize == 2)
- removeAlias(local - 1)
+ 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 =>
+ (opcode: @switch) match {
+ case ISTORE | LSTORE | FSTORE | DSTORE | ASTORE =>
+ // 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)
+ // if the value written is size 2, it overwrites the subsequent slot, which is then no
+ // longer an alias of anything. see the corresponding case in `Frame.execute`.
+ if (getLocal(local).getSize == 2)
+ removeAlias(local + 1)
+
+ // if the value at the preceding index is size 2, it is no longer valid, so we remove its
+ // aliasing. see corresponding case in `Frame.execute`
+ if (local > 0) {
+ val precedingValue = getLocal(local - 1)
+ if (precedingValue != null && precedingValue.getSize == 2)
+ removeAlias(local - 1)
+ }
+
+ case _ =>
+ }
+
// Remove consumed stack values from aliasing sets.
// Example: iadd
// - before: local1, local2, stack1, consumed1, consumed2
@@ -198,10 +243,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 +274,131 @@ 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) {
+ if (otherAliases == null) {
+ if (thisAliases.size > 1) {
+ aliasesChanged = true
+ removeAlias(i)
+ }
+ } else {
+ // 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 +407,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 = iterator.toSet.mkString("<", ",", ">")
+
+ /**
+ * 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/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
new file mode 100644
index 0000000000..90da570f01
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
@@ -0,0 +1,514 @@
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import java.lang.invoke.LambdaMetafactory
+
+import scala.annotation.switch
+import scala.collection.JavaConverters._
+import scala.collection.mutable
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+import scala.tools.asm.{Handle, Type}
+import scala.tools.nsc.backend.jvm.BTypes._
+import scala.tools.nsc.backend.jvm.GenBCode._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+/**
+ * This component hosts tools and utilities used in the backend that require access to a `BTypes`
+ * instance.
+ *
+ * One example is the AsmAnalyzer class, which 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 BackendUtils[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import btypes.coreBTypes._
+ import callGraph.ClosureInstantiation
+
+ /**
+ * 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)) {
+ computeMaxLocalsMaxStack(methodNode)
+ try {
+ analyzer.analyze(classInternalName, methodNode)
+ } catch {
+ case ae: AnalyzerException =>
+ throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae)
+ }
+ 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 sizeOKForAliasing(method: MethodNode): Boolean = size(method) < nullnessSizeLimit
+ 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
+
+ class NonLubbingTypeFlowAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new NonLubbingTypeFlowInterpreter))
+
+ /**
+ * Add:
+ * private static Object $deserializeLambda$(SerializedLambda l) {
+ * return indy[scala.runtime.LambdaDeserialize.bootstrap](l)
+ * }
+ *
+ * We use invokedynamic here to enable caching within the deserializer without needing to
+ * host a static field in the enclosing class. This allows us to add this method to interfaces
+ * that define lambdas in default methods.
+ */
+ def addLambdaDeserialize(classNode: ClassNode, implMethods: Iterable[Handle]): Unit = {
+ val cw = classNode
+
+ // Make sure to reference the ClassBTypes of all types that are used in the code generated
+ // here (e.g. java/util/Map) are initialized. Initializing a ClassBType adds it to the
+ // `classBTypeFromInternalName` map. When writing the classfile, the asm ClassWriter computes
+ // stack map frames and invokes the `getCommonSuperClass` method. This method expects all
+ // ClassBTypes mentioned in the source code to exist in the map.
+
+ val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor
+ val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor
+
+ {
+ val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null)
+ mv.visitCode()
+ mv.visitVarInsn(ALOAD, 0)
+ mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle, implMethods.toArray: _*)
+ mv.visitInsn(ARETURN)
+ mv.visitEnd()
+ }
+ }
+
+ /**
+ * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to
+ * the `labelMap`. Returns the new instruction list and a map from old to new instructions, and
+ * a list of lambda implementation methods references by invokedynamic[LambdaMetafactory] for a
+ * serializable SAM types.
+ */
+ def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], keepLineNumbers: Boolean): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], List[Handle]) = {
+ val javaLabelMap = labelMap.asJava
+ val result = new InsnList
+ var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
+ var inlinedTargetHandles = mutable.ListBuffer[Handle]()
+ for (ins <- methodNode.instructions.iterator.asScala) {
+ ins match {
+ case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => indy.bsmArgs match {
+ case Array(_, targetHandle: Handle, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 =>
+ inlinedTargetHandles += targetHandle
+ case _ =>
+ }
+ case _ =>
+ }
+ if (keepLineNumbers || !ins.isInstanceOf[LineNumberNode]) {
+ val cloned = ins.clone(javaLabelMap)
+ result add cloned
+ map += ((ins, cloned))
+ }
+ }
+ (result, map, inlinedTargetHandles.toList)
+ }
+
+ def getBoxedUnit: FieldInsnNode = new FieldInsnNode(GETSTATIC, srBoxedUnitRef.internalName, "UNIT", srBoxedUnitRef.descriptor)
+
+ private val anonfunAdaptedName = """.*\$anonfun\$.*\$\d+\$adapted""".r
+ def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = {
+ anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches
+ }
+
+ private def primitiveAsmTypeToBType(primitiveType: Type): PrimitiveBType = (primitiveType.getSort: @switch) match {
+ case Type.BOOLEAN => BOOL
+ case Type.BYTE => BYTE
+ case Type.CHAR => CHAR
+ case Type.SHORT => SHORT
+ case Type.INT => INT
+ case Type.LONG => LONG
+ case Type.FLOAT => FLOAT
+ case Type.DOUBLE => DOUBLE
+ case _ => null
+ }
+
+ def isScalaBox(insn: MethodInsnNode): Boolean = {
+ insn.owner == srBoxesRunTimeRef.internalName && {
+ val args = Type.getArgumentTypes(insn.desc)
+ args.length == 1 && (srBoxesRuntimeBoxToMethods.get(primitiveAsmTypeToBType(args(0))) match {
+ case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc
+ case _ => false
+ })
+ }
+ }
+
+ def getScalaBox(primitiveType: Type): MethodInsnNode = {
+ val bType = primitiveAsmTypeToBType(primitiveType)
+ val MethodNameAndType(name, methodBType) = srBoxesRuntimeBoxToMethods(bType)
+ new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false)
+ }
+
+ def isScalaUnbox(insn: MethodInsnNode): Boolean = {
+ insn.owner == srBoxesRunTimeRef.internalName && (srBoxesRuntimeUnboxToMethods.get(primitiveAsmTypeToBType(Type.getReturnType(insn.desc))) match {
+ case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc
+ case _ => false
+ })
+ }
+
+ def getScalaUnbox(primitiveType: Type): MethodInsnNode = {
+ val bType = primitiveAsmTypeToBType(primitiveType)
+ val MethodNameAndType(name, methodBType) = srBoxesRuntimeUnboxToMethods(bType)
+ new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false)
+ }
+
+ private def calleeInMap(insn: MethodInsnNode, map: Map[InternalName, MethodNameAndType]): Boolean = map.get(insn.owner) match {
+ case Some(MethodNameAndType(name, tp)) => insn.name == name && insn.desc == tp.descriptor
+ case _ => false
+ }
+
+ def isJavaBox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaBoxMethods)
+ def isJavaUnbox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaUnboxMethods)
+
+ def isPredefAutoBox(insn: MethodInsnNode): Boolean = {
+ insn.owner == PredefRef.internalName && (predefAutoBoxMethods.get(insn.name) match {
+ case Some(tp) => insn.desc == tp.descriptor
+ case _ => false
+ })
+ }
+
+ def isPredefAutoUnbox(insn: MethodInsnNode): Boolean = {
+ insn.owner == PredefRef.internalName && (predefAutoUnboxMethods.get(insn.name) match {
+ case Some(tp) => insn.desc == tp.descriptor
+ case _ => false
+ })
+ }
+
+ def isRefCreate(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefCreateMethods)
+ def isRefZero(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefZeroMethods)
+
+ def runtimeRefClassBoxedType(refClass: InternalName): Type = Type.getArgumentTypes(srRefCreateMethods(refClass).methodType.descriptor)(0)
+
+ def isSideEffectFreeCall(insn: MethodInsnNode): Boolean = {
+ isScalaBox(insn) || isScalaUnbox(insn) ||
+ isJavaBox(insn) || // not java unbox, it may NPE
+ isSideEffectFreeConstructorCall(insn)
+ }
+
+ def isNonNullMethodInvocation(mi: MethodInsnNode): Boolean = {
+ isJavaBox(mi) || isScalaBox(mi) || isPredefAutoBox(mi) || isRefCreate(mi) || isRefZero(mi)
+ }
+
+ def isModuleLoad(insn: AbstractInsnNode, moduleName: InternalName): Boolean = insn match {
+ case fi: FieldInsnNode => fi.getOpcode == GETSTATIC && fi.owner == moduleName && fi.name == "MODULE$" && fi.desc == ("L" + moduleName + ";")
+ case _ => false
+ }
+
+ def isPredefLoad(insn: AbstractInsnNode) = isModuleLoad(insn, PredefRef.internalName)
+
+ def isPrimitiveBoxConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, primitiveBoxConstructors)
+ def isRuntimeRefConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefConstructors)
+ def isTupleConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, tupleClassConstructors)
+
+ // unused objects created by these constructors are eliminated by pushPop
+ private lazy val sideEffectFreeConstructors: Set[(String, String)] = {
+ val ownerDesc = (p: (InternalName, MethodNameAndType)) => (p._1, p._2.methodType.descriptor)
+ primitiveBoxConstructors.map(ownerDesc).toSet ++
+ srRefConstructors.map(ownerDesc) ++
+ tupleClassConstructors.map(ownerDesc) ++ Set(
+ (ObjectRef.internalName, MethodBType(Nil, UNIT).descriptor),
+ (StringRef.internalName, MethodBType(Nil, UNIT).descriptor),
+ (StringRef.internalName, MethodBType(List(StringRef), UNIT).descriptor),
+ (StringRef.internalName, MethodBType(List(ArrayBType(CHAR)), UNIT).descriptor))
+ }
+
+ def isSideEffectFreeConstructorCall(insn: MethodInsnNode): Boolean = {
+ insn.name == INSTANCE_CONSTRUCTOR_NAME && sideEffectFreeConstructors((insn.owner, insn.desc))
+ }
+
+ private lazy val classesOfSideEffectFreeConstructors = sideEffectFreeConstructors.map(_._1)
+
+ def isNewForSideEffectFreeConstructor(insn: AbstractInsnNode) = {
+ insn.getOpcode == NEW && {
+ val ti = insn.asInstanceOf[TypeInsnNode]
+ classesOfSideEffectFreeConstructors.contains(ti.desc)
+ }
+ }
+
+ def isBoxedUnit(insn: AbstractInsnNode) = {
+ insn.getOpcode == GETSTATIC && {
+ val fi = insn.asInstanceOf[FieldInsnNode]
+ fi.owner == srBoxedUnitRef.internalName && fi.name == "UNIT" && fi.desc == srBoxedUnitRef.descriptor
+ }
+ }
+
+ /**
+ * Visit the class node and collect all referenced nested classes.
+ */
+ def collectNestedClasses(classNode: ClassNode): List[ClassBType] = {
+ val innerClasses = mutable.Set.empty[ClassBType]
+
+ def visitInternalName(internalName: InternalName): Unit = if (internalName != null) {
+ val t = classBTypeFromParsedClassfile(internalName)
+ if (t.isNestedClass.get) innerClasses += t
+ }
+
+ // either an internal/Name or [[Linternal/Name; -- there are certain references in classfiles
+ // that are either an internal name (without the surrounding `L;`) or an array descriptor
+ // `[Linternal/Name;`.
+ def visitInternalNameOrArrayReference(ref: String): Unit = if (ref != null) {
+ val bracket = ref.lastIndexOf('[')
+ if (bracket == -1) visitInternalName(ref)
+ else if (ref.charAt(bracket + 1) == 'L') visitInternalName(ref.substring(bracket + 2, ref.length - 1))
+ }
+
+ // we are only interested in the class references in the descriptor, so we can skip over
+ // primitives and the brackets of array descriptors
+ def visitDescriptor(desc: String): Unit = (desc.charAt(0): @switch) match {
+ case '(' =>
+ val internalNames = mutable.ListBuffer.empty[String]
+ var i = 1
+ while (i < desc.length) {
+ if (desc.charAt(i) == 'L') {
+ val start = i + 1 // skip the L
+ while (desc.charAt(i) != ';') i += 1
+ internalNames append desc.substring(start, i)
+ }
+ // skips over '[', ')', primitives
+ i += 1
+ }
+ internalNames foreach visitInternalName
+
+ case 'L' =>
+ visitInternalName(desc.substring(1, desc.length - 1))
+
+ case '[' =>
+ visitInternalNameOrArrayReference(desc)
+
+ case _ => // skip over primitive types
+ }
+
+ def visitConstant(const: AnyRef): Unit = const match {
+ case t: Type => visitDescriptor(t.getDescriptor)
+ case _ =>
+ }
+
+ // in principle we could references to annotation types, as they only end up as strings in the
+ // constant pool, not as class references. however, the java compiler still includes nested
+ // annotation classes in the innerClass table, so we do the same. explained in detail in the
+ // large comment in class BTypes.
+ def visitAnnotation(annot: AnnotationNode): Unit = {
+ visitDescriptor(annot.desc)
+ if (annot.values != null) annot.values.asScala foreach visitConstant
+ }
+
+ def visitAnnotations(annots: java.util.List[_ <: AnnotationNode]) = if (annots != null) annots.asScala foreach visitAnnotation
+ def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations
+
+ def visitHandle(handle: Handle): Unit = {
+ visitInternalNameOrArrayReference(handle.getOwner)
+ visitDescriptor(handle.getDesc)
+ }
+
+ visitInternalName(classNode.name)
+ innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses
+
+ visitInternalName(classNode.superName)
+ classNode.interfaces.asScala foreach visitInternalName
+ visitInternalName(classNode.outerClass)
+
+ visitAnnotations(classNode.visibleAnnotations)
+ visitAnnotations(classNode.visibleTypeAnnotations)
+ visitAnnotations(classNode.invisibleAnnotations)
+ visitAnnotations(classNode.invisibleTypeAnnotations)
+
+ for (f <- classNode.fields.asScala) {
+ visitDescriptor(f.desc)
+ visitAnnotations(f.visibleAnnotations)
+ visitAnnotations(f.visibleTypeAnnotations)
+ visitAnnotations(f.invisibleAnnotations)
+ visitAnnotations(f.invisibleTypeAnnotations)
+ }
+
+ for (m <- classNode.methods.asScala) {
+ visitDescriptor(m.desc)
+
+ visitAnnotations(m.visibleAnnotations)
+ visitAnnotations(m.visibleTypeAnnotations)
+ visitAnnotations(m.invisibleAnnotations)
+ visitAnnotations(m.invisibleTypeAnnotations)
+ visitAnnotationss(m.visibleParameterAnnotations)
+ visitAnnotationss(m.invisibleParameterAnnotations)
+ visitAnnotations(m.visibleLocalVariableAnnotations)
+ visitAnnotations(m.invisibleLocalVariableAnnotations)
+
+ m.exceptions.asScala foreach visitInternalName
+ for (tcb <- m.tryCatchBlocks.asScala) visitInternalName(tcb.`type`)
+
+ val iter = m.instructions.iterator()
+ while (iter.hasNext) iter.next() match {
+ case ti: TypeInsnNode => visitInternalNameOrArrayReference(ti.desc)
+ case fi: FieldInsnNode => visitInternalNameOrArrayReference(fi.owner); visitDescriptor(fi.desc)
+ case mi: MethodInsnNode => visitInternalNameOrArrayReference(mi.owner); visitDescriptor(mi.desc)
+ case id: InvokeDynamicInsnNode => visitDescriptor(id.desc); visitHandle(id.bsm); id.bsmArgs foreach visitConstant
+ case ci: LdcInsnNode => visitConstant(ci.cst)
+ case ma: MultiANewArrayInsnNode => visitDescriptor(ma.desc)
+ case _ =>
+ }
+ }
+ innerClasses.toList
+ }
+
+ /**
+ * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
+ * framework only computes these values during bytecode generation.
+ *
+ * NOTE 1: as explained in the `analysis` package object, the maxStack value used by the Analyzer
+ * may be smaller than the correct maxStack value in the classfile (Analyzers only use a single
+ * slot for long / double values). The maxStack computed here are correct for running an analyzer,
+ * but not for writing in the classfile. We let the ClassWriter recompute max's.
+ *
+ * NOTE 2: the maxStack value computed here may be larger than the smallest correct value
+ * that would allow running an analyzer, see `InstructionStackEffect.forAsmAnalysis` and
+ * `InstructionStackEffect.maxStackGrowth`.
+ *
+ * NOTE 3: the implementation doesn't look at instructions that cannot be reached, it computes
+ * the max local / stack size in the reachable code. These max's work just fine for running an
+ * Analyzer: its implementation also skips over unreachable code in the same way.
+ */
+ def computeMaxLocalsMaxStack(method: MethodNode): Unit = {
+ if (isAbstractMethod(method) || isNativeMethod(method)) {
+ method.maxLocals = 0
+ method.maxStack = 0
+ } else if (!maxLocalsMaxStackComputed(method)) {
+ val size = method.instructions.size
+
+ var maxLocals = parametersSize(method)
+ var maxStack = 0
+
+ // queue of instruction indices where analysis should start
+ var queue = new Array[Int](8)
+ var top = -1
+ def enq(i: Int): Unit = {
+ if (top == queue.length - 1) {
+ val nq = new Array[Int](queue.length * 2)
+ Array.copy(queue, 0, nq, 0, queue.length)
+ queue = nq
+ }
+ top += 1
+ queue(top) = i
+ }
+ def deq(): Int = {
+ val r = queue(top)
+ top -= 1
+ r
+ }
+
+ val subroutineRetTargets = new mutable.Stack[AbstractInsnNode]
+
+ // for each instruction in the queue, contains the stack height at this instruction.
+ // once an instruction has been treated, contains -1 to prevent re-enqueuing
+ val stackHeights = new Array[Int](size)
+
+ def enqInsn(insn: AbstractInsnNode, height: Int): Unit = {
+ enqInsnIndex(method.instructions.indexOf(insn), height)
+ }
+
+ def enqInsnIndex(insnIndex: Int, height: Int): Unit = {
+ if (insnIndex < size && stackHeights(insnIndex) != -1) {
+ stackHeights(insnIndex) = height
+ enq(insnIndex)
+ }
+ }
+
+ val tcbIt = method.tryCatchBlocks.iterator()
+ while (tcbIt.hasNext) {
+ val tcb = tcbIt.next()
+ enqInsn(tcb.handler, 1)
+ if (maxStack == 0) maxStack = 1
+ }
+
+ enq(0)
+ while (top != -1) {
+ val insnIndex = deq()
+ val insn = method.instructions.get(insnIndex)
+ val initHeight = stackHeights(insnIndex)
+ stackHeights(insnIndex) = -1 // prevent i from being enqueued again
+
+ if (insn.getOpcode == -1) { // frames, labels, line numbers
+ enqInsnIndex(insnIndex + 1, initHeight)
+ } else {
+ val stackGrowth = InstructionStackEffect.maxStackGrowth(insn)
+ val heightAfter = initHeight + stackGrowth
+ if (heightAfter > maxStack) maxStack = heightAfter
+
+ // update maxLocals
+ insn match {
+ case v: VarInsnNode =>
+ val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0
+ maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 because local numbers are 0-based
+
+ case i: IincInsnNode =>
+ maxLocals = math.max(maxLocals, i.`var` + 1)
+
+ case _ =>
+ }
+
+ insn match {
+ case j: JumpInsnNode =>
+ if (j.getOpcode == JSR) {
+ val jsrTargetHeight = heightAfter + 1
+ if (jsrTargetHeight > maxStack) maxStack = jsrTargetHeight
+ subroutineRetTargets.push(j.getNext)
+ enqInsn(j.label, jsrTargetHeight)
+ } else {
+ enqInsn(j.label, heightAfter)
+ val opc = j.getOpcode
+ if (opc != GOTO) enqInsnIndex(insnIndex + 1, heightAfter) // jump is conditional, so the successor is also a possible control flow target
+ }
+
+ case l: LookupSwitchInsnNode =>
+ var j = 0
+ while (j < l.labels.size) {
+ enqInsn(l.labels.get(j), heightAfter); j += 1
+ }
+ enqInsn(l.dflt, heightAfter)
+
+ case t: TableSwitchInsnNode =>
+ var j = 0
+ while (j < t.labels.size) {
+ enqInsn(t.labels.get(j), heightAfter); j += 1
+ }
+ enqInsn(t.dflt, heightAfter)
+
+ case r: VarInsnNode if r.getOpcode == RET =>
+ enqInsn(subroutineRetTargets.pop(), heightAfter)
+
+ case _ =>
+ val opc = insn.getOpcode
+ if (opc != ATHROW && !isReturn(insn))
+ enqInsnIndex(insnIndex + 1, heightAfter)
+ }
+ }
+ }
+
+ method.maxLocals = maxLocals
+ method.maxStack = maxStack
+
+ maxLocalsMaxStackComputed += method
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
index 8d8ea839e6..dd19ad594f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
@@ -5,35 +5,74 @@ package analysis
import scala.annotation.switch
import scala.tools.asm.Opcodes._
import scala.tools.asm.Type
-import scala.tools.asm.tree.{MultiANewArrayInsnNode, InvokeDynamicInsnNode, MethodInsnNode, AbstractInsnNode}
+import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis.{Frame, Value}
import opt.BytecodeUtils._
-import collection.immutable
object InstructionStackEffect {
- private var cache: immutable.IntMap[(Int, Int)] = immutable.IntMap.empty
- private def t(x: Int, y: Int): (Int, Int) = {
- // x can go up to 255 (number of parameters of a method, dimensions in multianewarray) we cache
- // x up to 10, which covers most cases and limits the cache. y doesn't go above 6 (see cases).
- if (x > 10 || y > 6) (x, y)
- else {
- val key = (x << 8) + y // this would work for any x < 256
- if (cache contains key) {
- cache(key)
- } else {
- val r = (x, y)
- cache += key -> r
- r
- }
- }
+ val consShift = 3
+ val prodMask = (1 << consShift) - 1
+
+ def cons(i: Int) = i >>> consShift
+ def prod(i: Int) = i & prodMask
+
+ private def t(x: Int, y: Int): Int = (x << consShift) | y
+
+ /**
+ * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int`
+ * (the `cons` / `prod` extract individual values). The returned values are correct for use in
+ * asm's Analyzer framework. For example, a LLOAD instruction produces one stack value. See also
+ * doc in `analysis` package object.
+ *
+ * This method requires the `frame` to be in the state **before** executing / interpreting the
+ * `insn`.
+ */
+ def forAsmAnalysis[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): Int = computeConsProd(insn, forClassfile = false, conservative = false, frame = frame)
+
+ /**
+ * Returns the maximal possible growth of the stack when executing `insn`. The returned value
+ * is usually the same as expected by asm's Analyzer framework, but it may be larger. For
+ * example, consider a POP2 instruction:
+ * - if two size-1 values are popped, then the asm Analyzer consumes two values
+ * - if a size-2 value is popped, the asm Analyzer consumes only one stack slot (see doc in the
+ * `analysis` package object)
+ *
+ * If a precise result is needed, invoke the `forAsmAnalysis` and provide a `frame` value that
+ * allows looking up the sizes of values on the stack.
+ */
+ def maxStackGrowth(insn: AbstractInsnNode): Int = {
+ val prodCons = computeConsProd(insn, forClassfile = false, conservative = true)
+ prod(prodCons) - cons(prodCons)
}
/**
- * Returns a pair with the number of stack values consumed and produced by `insn`.
- * This method requires the `frame` to be in the state **before** executing / interpreting
- * the `insn`.
+ * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int`
+ * (the `cons` / `prod` extract individual values). The returned values are correct for writing
+ * into a classfile (see doc on the `analysis` package object).
*/
- def apply[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): (Int, Int) = {
+ def forClassfile(insn: AbstractInsnNode): Int = computeConsProd(insn, forClassfile = true, conservative = false)
+
+ private def invokeConsProd(methodDesc: String, insn: AbstractInsnNode, forClassfile: Boolean): Int = {
+ val consumesReceiver = insn.getOpcode != INVOKESTATIC && insn.getOpcode != INVOKEDYNAMIC
+ if (forClassfile) {
+ val sizes = Type.getArgumentsAndReturnSizes(methodDesc)
+ val cons = (sizes >> 2) - (if (consumesReceiver) 0 else 1)
+ val prod = sizes & 0x03
+ t(cons, prod)
+ } else {
+ val cons = Type.getArgumentTypes(methodDesc).length + (if (consumesReceiver) 1 else 0)
+ val prod = if (Type.getReturnType(methodDesc) == Type.VOID_TYPE) 0 else 1
+ t(cons, prod)
+ }
+ }
+
+ private def fieldInsnIsLongOrDouble(insn: AbstractInsnNode) = {
+ val d = insn.asInstanceOf[FieldInsnNode].desc
+ d == "J" || d == "D"
+ }
+
+ private def computeConsProd[V <: Value](insn: AbstractInsnNode, forClassfile: Boolean, conservative: Boolean, frame: Frame[V] = null): Int = {
+ // not used if `forClassfile || conservative`: in these cases, `frame` is allowed to be `null`
def peekStack(n: Int): V = frame.peekStack(n)
(insn.getOpcode: @switch) match {
@@ -48,142 +87,176 @@ object InstructionStackEffect {
ICONST_3 |
ICONST_4 |
ICONST_5 |
- LCONST_0 |
- LCONST_1 |
FCONST_0 |
FCONST_1 |
FCONST_2 |
- DCONST_0 |
- DCONST_1 |
BIPUSH |
SIPUSH |
- LDC |
ILOAD |
- LLOAD |
FLOAD |
- DLOAD |
ALOAD => t(0, 1)
+ case LDC =>
+ if (forClassfile) insn.asInstanceOf[LdcInsnNode].cst match {
+ case _: java.lang.Long | _: java.lang.Double => t(0, 2)
+ case _ => t(0, 1)
+ } else
+ t(0, 1)
+
+ case LCONST_0 |
+ LCONST_1 |
+ DCONST_0 |
+ DCONST_1 |
+ LLOAD |
+ DLOAD => if (forClassfile) t(0, 2) else t(0, 1)
+
case IALOAD |
- LALOAD |
FALOAD |
- DALOAD |
AALOAD |
BALOAD |
CALOAD |
SALOAD => t(2, 1)
+ case LALOAD |
+ DALOAD => if (forClassfile) t(2, 2) else t(2, 1)
+
case ISTORE |
- LSTORE |
FSTORE |
- DSTORE |
ASTORE => t(1, 0)
+ case LSTORE |
+ DSTORE => if (forClassfile) t(2, 0) else t(1, 0)
+
case IASTORE |
- LASTORE |
FASTORE |
- DASTORE |
AASTORE |
BASTORE |
CASTORE |
SASTORE => t(3, 0)
+ case LASTORE |
+ DASTORE => if (forClassfile) t(4, 0) else t(3, 0)
+
case POP => t(1, 0)
case POP2 =>
- val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(1, 0) else t(2, 0)
+ if (forClassfile) t(2, 0)
+ else if (conservative) t(1, 0)
+ else {
+ val isSize2 = peekStack(0).getSize == 2
+ if (isSize2) t(1, 0) else t(2, 0)
+ }
case DUP => t(1, 2)
case DUP_X1 => t(2, 3)
case DUP_X2 =>
- val isSize2 = peekStack(1).getSize == 2
- if (isSize2) t(2, 3) else t(3, 4)
+ if (forClassfile || conservative) t(3, 4)
+ else {
+ val isSize2 = peekStack(1).getSize == 2
+ if (isSize2) t(2, 3) else t(3, 4)
+ }
case DUP2 =>
- val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(1, 2) else t(2, 4)
+ if (forClassfile || conservative) t(2, 4)
+ else {
+ val isSize2 = peekStack(0).getSize == 2
+ if (isSize2) t(1, 2) else t(2, 4)
+ }
case DUP2_X1 =>
- val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(2, 3) else t(3, 4)
+ if (forClassfile || conservative) t(3, 5)
+ else {
+ val isSize2 = peekStack(0).getSize == 2
+ if (isSize2) t(2, 3) else t(3, 5)
+ }
case DUP2_X2 =>
- val v1isSize2 = peekStack(0).getSize == 2
- if (v1isSize2) {
- val v2isSize2 = peekStack(1).getSize == 2
- if (v2isSize2) t(2, 3) else t(3, 4)
- } else {
- val v3isSize2 = peekStack(2).getSize == 2
- if (v3isSize2) t(3, 5) else t(4, 6)
+ if (forClassfile || conservative) t(4, 6)
+ else {
+ val v1isSize2 = peekStack(0).getSize == 2
+ if (v1isSize2) {
+ val v2isSize2 = peekStack(1).getSize == 2
+ if (v2isSize2) t(2, 3) else t(3, 4)
+ } else {
+ val v3isSize2 = peekStack(2).getSize == 2
+ if (v3isSize2) t(3, 5) else t(4, 6)
+ }
}
case SWAP => t(2, 2)
case IADD |
- LADD |
FADD |
- DADD |
ISUB |
- LSUB |
FSUB |
- DSUB |
IMUL |
- LMUL |
FMUL |
- DMUL |
IDIV |
- LDIV |
FDIV |
- DDIV |
IREM |
+ FREM => t(2, 1)
+
+ case LADD |
+ DADD |
+ LSUB |
+ DSUB |
+ LMUL |
+ DMUL |
+ LDIV |
+ DDIV |
LREM |
- FREM |
- DREM => t(2, 1)
+ DREM => if (forClassfile) t(4, 2) else t(2, 1)
case INEG |
- LNEG |
- FNEG |
- DNEG => t(1, 1)
+ FNEG => t(1, 1)
+
+ case LNEG |
+ DNEG => if (forClassfile) t(2, 2) else t(1, 1)
case ISHL |
- LSHL |
ISHR |
- LSHR |
IUSHR |
- LUSHR |
IAND |
- LAND |
IOR |
+ IXOR => t(2, 1)
+
+ case LSHL |
+ LSHR |
+ LUSHR => if (forClassfile) t(3, 2) else t(2, 1)
+
+ case LAND |
LOR |
- IXOR |
- LXOR => t(2, 1)
+ LXOR => if (forClassfile) t(4, 2) else t(2, 1)
case IINC => t(0, 0)
- case I2L |
- I2F |
- I2D |
- L2I |
- L2F |
- L2D |
+ case I2F |
F2I |
- F2L |
- F2D |
- D2I |
- D2L |
- D2F |
I2B |
I2C |
I2S => t(1, 1)
+ case I2L |
+ I2D |
+ F2L |
+ F2D => if (forClassfile) t(1, 2) else t(1, 1)
+
+ case L2I |
+ L2F |
+ D2I |
+ D2F => if (forClassfile) t(2, 1) else t(1, 1)
+
+ case L2D |
+ D2L => if (forClassfile) t(2, 2) else t(1, 1)
+
+ case FCMPL |
+ FCMPG => t(2, 1)
+
case LCMP |
- FCMPL |
- FCMPG |
DCMPL |
- DCMPG => t(2, 1)
+ DCMPG => if (forClassfile) t(4, 1) else t(2, 1)
case IFEQ |
IFNE |
@@ -211,35 +284,36 @@ object InstructionStackEffect {
LOOKUPSWITCH => t(1, 0)
case IRETURN |
- LRETURN |
FRETURN |
- DRETURN |
ARETURN => t(1, 0) // Frame.execute consumes one stack value
+ case LRETURN |
+ DRETURN => if (forClassfile) t(2, 0) else t(1, 0)
+
case RETURN => t(0, 0) // Frame.execute does not change the stack
- case GETSTATIC => t(0, 1)
+ case GETSTATIC =>
+ val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1
+ t(0, prod)
- case PUTSTATIC => t(1, 0)
+ case PUTSTATIC =>
+ val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1
+ t(cons, 0)
- case GETFIELD => t(1, 1)
+ case GETFIELD =>
+ val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1
+ t(1, prod)
- case PUTFIELD => t(2, 0)
+ case PUTFIELD =>
+ val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 3 else 2
+ t(cons, 0)
case INVOKEVIRTUAL |
INVOKESPECIAL |
INVOKESTATIC |
- INVOKEINTERFACE =>
- val desc = insn.asInstanceOf[MethodInsnNode].desc
- val cons = Type.getArgumentTypes(desc).length + (if (insn.getOpcode == INVOKESTATIC) 0 else 1)
- val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1
- t(cons, prod)
-
- case INVOKEDYNAMIC =>
- val desc = insn.asInstanceOf[InvokeDynamicInsnNode].desc
- val cons = Type.getArgumentTypes(desc).length
- val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1
- t(cons, prod)
+ INVOKEINTERFACE => invokeConsProd(insn.asInstanceOf[MethodInsnNode].desc, insn, forClassfile)
+
+ case INVOKEDYNAMIC => invokeConsProd(insn.asInstanceOf[InvokeDynamicInsnNode].desc, insn, forClassfile)
case NEW => t(0, 1)
@@ -261,5 +335,4 @@ object InstructionStackEffect {
IFNONNULL => t(1, 0)
}
}
-
}
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..01afd0d2ef 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala
@@ -5,68 +5,14 @@ package analysis
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.{Opcodes, Type}
+import scala.tools.asm.tree.{AbstractInsnNode, LdcInsnNode, MethodInsnNode, MethodNode}
+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,59 +33,37 @@ 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) {
+final class NullnessInterpreter(bTypes: BTypes, method: MethodNode) extends Interpreter[NullnessValue](Opcodes.ASM5) {
def newValue(tp: Type): NullnessValue = {
// ASM loves giving semantics to null. The behavior here is the same as in SourceInterpreter,
// which is provided by the framework.
@@ -151,29 +75,31 @@ 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)
+ val isThis = local == 0 && (isInstanceMethod || {
+ method.parameters != null && !method.parameters.isEmpty && {
+ val p = method.parameters.get(0)
+ (p.access & Opcodes.ACC_SYNTHETIC) != 0 && p.name == "$this"
+ }
+ })
+ if (isThis) 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 +108,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)
+ def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = insn match {
+ case mi: MethodInsnNode if bTypes.backendUtils.isNonNullMethodInvocation(mi) =>
+ NotNullValue
case _ =>
- // TODO: use a list of methods that are known to return non-null values
- NullnessValue(Unknown, insn)
+ if (insn.getOpcode == Opcodes.MULTIANEWARRAY) NotNullValue
+ else NullnessValue.unknown(insn)
}
def returnOperation(insn: AbstractInsnNode, value: NullnessValue, expected: NullnessValue): Unit = ()
@@ -219,8 +143,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 +155,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 +165,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)
}
}
}
@@ -276,7 +203,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
* This class is required to override the `newFrame` methods, which makes makes sure the analyzer
* uses NullnessFrames.
*/
-class NullnessAnalyzer extends Analyzer[NullnessValue](new NullnessInterpreter) {
+class NullnessAnalyzer(bTypes: BTypes, method: MethodNode) extends Analyzer[NullnessValue](new NullnessInterpreter(bTypes, method)) {
override def newFrame(nLocals: Int, nStack: Int): NullnessFrame = new NullnessFrame(nLocals, nStack)
override def newFrame(src: Frame[_ <: NullnessValue]): NullnessFrame = new NullnessFrame(src)
}
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 594fd8923c..8af4bd4d5d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala
@@ -15,11 +15,10 @@ import scala.tools.asm.{Type, MethodVisitor}
import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
import opt.BytecodeUtils._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
/**
* This class provides additional queries over ASM's built-in `SourceValue` analysis.
@@ -55,24 +54,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`.
@@ -102,8 +93,13 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
inputValues(insn).iterator.flatMap(v => v.insns.asScala).toSet
}
- def consumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] =
- _consumersOfOutputsFrom.get(insn).map(v => v.indices.flatMap(v.apply)(collection.breakOut): Set[AbstractInsnNode]).getOrElse(Set.empty)
+ def consumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = insn match {
+ case _: UninitializedLocalProducer => Set.empty
+ case ParameterProducer(local) => consumersOfValueAt(methodNode.instructions.getFirst, local)
+ case ExceptionProducer(handlerLabel, handlerFrame) => consumersOfValueAt(handlerLabel, handlerFrame.stackTop)
+ case _ =>
+ _consumersOfOutputsFrom.get(insn).map(v => v.indices.flatMap(v.apply)(collection.breakOut): Set[AbstractInsnNode]).getOrElse(Set.empty)
+ }
/**
* Returns the potential initial producer instructions of a value in the frame of `insn`.
@@ -159,13 +155,19 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
inputValueSlots(insn).flatMap(slot => initialProducersForValueAt(insn, slot)).toSet
}
- def ultimateConsumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = {
- lazy val next = insn.getNext
- outputValueSlots(insn).flatMap(slot => ultimateConsumersOfValueAt(next, slot)).toSet
+ def ultimateConsumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = insn match {
+ case _: UninitializedLocalProducer => Set.empty
+ case _ =>
+ lazy val next = insn match {
+ case _: ParameterProducer => methodNode.instructions.getFirst
+ case ExceptionProducer(handlerLabel, _) => handlerLabel
+ case _ => insn.getNext
+ }
+ outputValueSlots(insn).flatMap(slot => ultimateConsumersOfValueAt(next, slot)).toSet
}
private def isCopyOperation(insn: AbstractInsnNode): Boolean = {
- isVarInstruction(insn) || {
+ isLoadOrStore(insn) || {
(insn.getOpcode: @switch) match {
case DUP | DUP_X1 | DUP_X2 | DUP2 | DUP2_X1 | DUP2_X2 | SWAP | CHECKCAST => true
case _ => false
@@ -376,9 +378,9 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
Seq(insn.asInstanceOf[IincInsnNode].`var`)
} else {
val frame = frameAt(insn)
- val stackEffect = InstructionStackEffect(insn, frame)
+ val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame)
val stackSize = frame.getLocals + frame.getStackSize
- (stackSize - stackEffect._1) until stackSize
+ (stackSize - InstructionStackEffect.cons(prodCons)) until stackSize
}
}
@@ -386,7 +388,7 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
private def outputValueSlots(insn: AbstractInsnNode): Seq[Int] = insn match {
case ParameterProducer(local) => Seq(local)
case UninitializedLocalProducer(local) => Seq(local)
- case ExceptionProducer(frame) => Seq(frame.stackTop)
+ case ExceptionProducer(_, frame) => Seq(frame.stackTop)
case _ =>
if (insn.getOpcode == -1) return Seq.empty
if (isStore(insn)) {
@@ -395,16 +397,15 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
Seq(insn.asInstanceOf[IincInsnNode].`var`)
} else {
val frame = frameAt(insn)
- val stackEffect = InstructionStackEffect(insn, frame)
+ val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame)
val nextFrame = frameAt(insn.getNext)
val stackSize = nextFrame.getLocals + nextFrame.getStackSize
- (stackSize - stackEffect._2) until stackSize
+ (stackSize - InstructionStackEffect.prod(prodCons)) until stackSize
}
}
/** 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 +418,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 +425,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:
@@ -446,10 +440,10 @@ object ProdConsAnalyzer {
* return a;
* }
*
- * In the first frame of the method, the SoruceValue for parameter `a` gives an empty set of
+ * In the first frame of the method, the SourceValue for parameter `a` gives an empty set of
* producer instructions.
*
- * In the frame of the `IRETURN` instruction, the SoruceValue for parameter `a` lists a single
+ * In the frame of the `IRETURN` instruction, the SourceValue for parameter `a` lists a single
* producer instruction: the `ISTORE 1`. This makes it look as if there was a single producer for
* `a`, where in fact it might still hold the parameter's initial value.
*/
@@ -459,9 +453,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](handlerLabel: LabelNode, handlerFrame: Frame[V]) extends InitialProducer
class InitialProducerSourceInterpreter extends SourceInterpreter {
override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): SourceValue = {
@@ -473,6 +467,6 @@ class InitialProducerSourceInterpreter extends SourceInterpreter {
}
override def newExceptionValue(tryCatchBlockNode: TryCatchBlockNode, handlerFrame: Frame[_ <: Value], exceptionType: Type): SourceValue = {
- new SourceValue(1, ExceptionProducer(handlerFrame))
+ new SourceValue(1, ExceptionProducer(tryCatchBlockNode.handler, handlerFrame))
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala
new file mode 100644
index 0000000000..bcf9978c16
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala
@@ -0,0 +1,36 @@
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import scala.tools.asm.Type
+import scala.tools.asm.tree.analysis.{BasicValue, BasicInterpreter}
+
+abstract class TypeFlowInterpreter extends BasicInterpreter {
+ override def newValue(tp: Type) = {
+ if (tp == null) super.newValue(tp)
+ else if (isRef(tp)) new BasicValue(tp)
+ else super.newValue(tp)
+ }
+
+ def isRef(tp: Type) = tp != null && (tp.getSort match {
+ case Type.OBJECT | Type.ARRAY => true
+ case _ => false
+ })
+
+ def refLub(a: BasicValue, b: BasicValue): BasicValue
+
+ override def merge(a: BasicValue, b: BasicValue): BasicValue = {
+ if (a == b) a
+ else if (isRef(a.getType) && isRef(b.getType)) refLub(a, b)
+ else BasicValue.UNINITIALIZED_VALUE
+ }
+}
+
+/**
+ * A [[TypeFlowInterpreter]] which collapses LUBs of non-equal reference types to Object.
+ * This could be made more precise by looking up ClassBTypes for the two reference types and using
+ * the `jvmWiseLUB` method.
+ */
+class NonLubbingTypeFlowInterpreter extends TypeFlowInterpreter {
+ def refLub(a: BasicValue, b: BasicValue): BasicValue = BasicValue.REFERENCE_VALUE // java/lang/Object
+}
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..999c686aac
--- /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 (fixpoint).
+ * - 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 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/BoxUnbox.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala
new file mode 100644
index 0000000000..78fc7e1ecf
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala
@@ -0,0 +1,907 @@
+/* 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
+import scala.tools.asm.Type
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.collection.mutable
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+class BoxUnbox[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import backendUtils._
+
+ /**
+ * Eliminate box-unbox pairs within `method`. Such appear commonly after closure elimination:
+ *
+ * def t2 = {
+ * val f = (b: Byte, i: Int) => i + b // no specialized variant for this function type
+ * f(1, 2) // invokes the generic `apply`
+ * }
+ *
+ * The closure optimizer re-writes the `apply` call to `anonfun$adapted` method, which takes
+ * boxed arguments. After inlining this method, we get
+ *
+ * def t2 = {
+ * val a = boxByte(1)
+ * val b = boxInteger(2)
+ * val r = boxInteger(anonfun$(unboxByte(a), unboxInt(b)))
+ * unboxInt(r)
+ * }
+ *
+ * All these box/unbox operations are eliminated here.
+ *
+ * Implementation: for every box operation, find all consumers of the boxed value, then all
+ * producers of these consumers, repeat until reaching a fixpoint. If this results in a set of
+ * boxing and unboxing operations, the box can be eliminated.
+ *
+ * There are two methods for eliminating boxes:
+ * M1: If there is a single boxing operation, the boxed value(s) are stored into new local
+ * variable(s) at the allocation site. Accesses to the boxed value are re-written to reads /
+ * writes of these locals. Advantages:
+ * - supports mutable boxes (IntRef and friends)
+ * - supports eliminating unbox operations even if the box object needs to be created
+ * because it escapes (see E4)
+ * - works by keeping the unboxed value(s) in locals AND the box in its original form
+ * - only for immutable boxes: modifications to the escaped box cannot be applied to
+ * the local variable(s) holding the boxed value(s).
+ * Restriction:
+ * - does not work if there are multiple boxing operations (see E1)
+ *
+ * M2: If there are multiple boxing operations, the boxing operations are simply eliminated,
+ * leaving the unboxed value(s) on the stack. Store / load operations that previously
+ * acted on the box are adapted to handle the boxed type(s). If the box contains multiple
+ * values (or a size-2 value, which doesn't fit into locals that were used for the box),
+ * new local slots are used for store / load operations. Restrictions:
+ * - does not support re-writing writes to (mutable) boxes (see E2)
+ * - does not support re-writing reads of boxes that also escape (see E3)
+ *
+ *
+ * E1: M1 only works if there's a single boxing operation.
+ * def e1(b: Boolean) = {
+ * val i: Integer = box(10) // 10 is stored into a new local, box operation and i removed
+ * val j: Integer = box(20) // 20 is stored into a new local, box operation and j removed
+ * val r = if (b) i else j // loads and stores of the box are eliminated, r no longer exists
+ * unbox(r) // cannot rewrite: we don't know which local to load
+ * }
+ * Note: the example has no write and the box does not escape, so M2 works here.
+ *
+ * E2: mutable boxes with multiple boxing operations cannot be eliminated.
+ * M1: see E1
+ * M2: cannot replace an `IntRef` on the stack by an `Int` value on the stack, an Int on the
+ * stack cannot be modified.
+ *
+ * def e2(b: Boolean) = {
+ * val r1 = new IntRef(0)
+ * val r2 = new IntRef(1)
+ * val modRef = if (b) r1 else r2
+ * modRef.elem += 10 // M1: cannot rewrite: which local to write? same as E1.
+ * (if (b) r1 else r2).elem += 10 // M2: cannot change an Int on the stack
+ * (r1.elem, r2.elem)
+ * }
+ *
+ *
+ * E3: escaping boxes with multiple boxing operations cannot be rewritten.
+ * M1: see E1.
+ * M2: at *, instead of an Integer, an Int is on the stack, but the escape method expects an
+ * Integer. We cannot just create a box at this point: if there are multiple escapes (or
+ * an escape is executed more than once), the difference could be observed (reference
+ * equality).
+ *
+ * def e3(b: Boolean) = {
+ * val i: Integer = box(1)
+ * val j: Integer = box(2)
+ * escape(if (b) i else j) // *
+ * unbox(if (b) i else j)
+ * }
+ *
+ *
+ * E4: M1 supports rewriting unbox operations of immutable boxes that escape
+ * def e4 = {
+ * val i: Integer = box(10) // 10 is stored into a new local, loaded as argument for the box call
+ * escape(i) // not changed, still loads the local i holding the box
+ * unbox(i) // rewritten to a pop (of the box) and a load of the local variable
+ * }
+ *
+ *
+ * E4 seems to be a bit of a corner case, but it's necessary to unblock box eliminations with
+ * mutual dependencies. Example:
+ *
+ * val ((a, b), c) = ((1, 2), 3)
+ * a + b + c
+ *
+ * generates (after a few cleanups) the following (pseudo-bytecode, ignoring primitive boxing, specialization):
+ *
+ * load 1, load 2, new Tuple2 // stack: Tuple2
+ * load 3 // stack: Tuple2; Int
+ * val local1 = new Tuple2
+ * val local2 = local1._1.asInstanceOf[Tuple2]
+ * val c = local1._2.asInstanceOf[Int]
+ * if (local2 == null) throw new MatchError(local1)
+ * val a = local2._1
+ * val b = local2._2
+ * a + b + c
+ *
+ * In order to eliminate the tuples, we first need to eliminate the outer tuple (stored in local1)
+ * - single box operation, so we use M1
+ * - there are three consumers of the outer tuple: `local1._1`, `local1._2` and
+ * `new MatchError(local1)`. in the last one, the tuple escapes.
+ * - note that the MatchError creation is dead code: local2 is never null. However, our nullness
+ * analysis cannot identify this: it does not track nullness through tuple stores and loads.
+ * - if we re-write the non-escaping consumers of the outer tuple, but keep the tuple allocation
+ * and the escaping consumer, we get the following:
+ *
+ * load 1, load 2
+ * val newLocal1 = new Tuple2; load newLocal1 // stack: Tuple2
+ * val newLocal2 = 3; load newLocal2 // stack: Tuple2; Int
+ * val local1 = new Tuple2
+ * val local2 = newLocal1
+ * val c = newLocal2
+ * if (local2 == null) throw new MatchError(local1)
+ * val a = local2._1
+ * val b = local2._2
+ * a + b + c
+ *
+ * At this point, the nullness analysis sees that `local2 == null` is false, dead code elimination
+ * removes the `throw new MatchError(local1)`. After eliminating the allocation of the outer tuple,
+ * the inner tuple (stored in newLocal1) can also be eliminated.
+ *
+ *
+ * Special case for tuples wrt specialization: a tuple getter may box or unbox the value stored
+ * in the tuple: calling `_1` on a `Tuple2$mcII$sp` boxes the primitive Int stored in the tuple.
+ * Similarly, calling `_1$mcI$sp` on a non-specialized `Tuple2` unboxes the Integer in the tuple.
+ * When eliminating such getters, we have to introduce appropriate box / unbox calls.
+ *
+ *
+ * TODO: add new calls (box / unbox) to the call graph (not urgent)
+ * TODO: update the call graph because stack heights change (not urgent).
+ * this may also affect other optimizations, we ignored the issue so far. check how stack
+ * heights stored in the call graph are used.
+ * Note: these tasks are not urgent because the call graph is not currently used during / after
+ * method-local optimizations, only before to perform inlining and closure rewriting.
+ */
+ def boxUnboxElimination(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForSourceValue(method) && {
+ val toInsertBefore = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+ val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+ val toDelete = mutable.Set.empty[AbstractInsnNode]
+
+ val knownHandled = mutable.Set.empty[AbstractInsnNode]
+
+ lazy val prodCons = new ProdConsAnalyzer(method, owner)
+
+ var nextLocal = method.maxLocals
+ def getLocal(size: Int) = {
+ val r = nextLocal
+ nextLocal += size
+ r
+ }
+
+ var maxStackGrowth = 0
+
+ /** Method M1 for eliminating box-unbox pairs (see doc comment in the beginning of this file) */
+ def replaceBoxOperationsSingleCreation(creation: BoxCreation, finalCons: Set[BoxConsumer], boxKind: BoxKind, keepBox: Boolean): Unit = {
+ /**
+ * If the box is eliminated, all copy operations (loads, stores, others) of the box need to
+ * be removed. This method returns all copy operations that should be removed.
+ *
+ * Returns `None` in case some exotic copy operation is found that cannot be removed
+ * (DUP2_X1 and friends - these are never emitted by scalac). In this case, the box cannot
+ * be eliminated.
+ */
+ def copyOpsToEliminate: Option[Set[AbstractInsnNode]] = {
+ var elidableCopyOps = Set.empty[AbstractInsnNode]
+ var replaceOK = true
+ val copyOps = new CopyOpsIterator(Set(creation), finalCons, prodCons)
+ while (replaceOK && copyOps.hasNext) copyOps.next() match {
+ case vi: VarInsnNode =>
+ elidableCopyOps += vi
+
+ case copyOp if copyOp.getOpcode == DUP =>
+ elidableCopyOps += copyOp
+
+ case _ =>
+ replaceOK = false
+ }
+ if (replaceOK) Some(elidableCopyOps) else None
+ }
+
+ val canRewrite = keepBox || (copyOpsToEliminate match {
+ case Some(copyOps) =>
+ toDelete ++= copyOps
+ true
+
+ case _ => false
+ })
+
+ if (canRewrite) {
+ val localSlots: Vector[(Int, Type)] = boxKind.boxedTypes.map(tp => (getLocal(tp.getSize), tp))(collection.breakOut)
+
+ // store boxed value(s) into localSlots
+ val storeOps = localSlots.toList reverseMap { case (slot, tp) =>
+ new VarInsnNode(tp.getOpcode(ISTORE), slot)
+ }
+ val storeInitialValues = creation.loadInitialValues match {
+ case Some(ops) => ops ::: storeOps
+ case None => storeOps
+ }
+ if (keepBox) {
+ val loadOps: List[VarInsnNode] = localSlots.map({ case (slot, tp) =>
+ new VarInsnNode(tp.getOpcode(ILOAD), slot)
+ })(collection.breakOut)
+ toInsertBefore(creation.valuesConsumer) = storeInitialValues ::: loadOps
+ } else {
+ toReplace(creation.valuesConsumer) = storeInitialValues
+ toDelete ++= creation.allInsns - creation.valuesConsumer
+ }
+
+ // rewrite consumers
+ finalCons foreach {
+ case write: StaticSetterOrInstanceWrite =>
+ assert(!keepBox, s"cannot eliminate box write if the box remains (and escapes): $write")
+ val (slot, tp) = localSlots(boxKind.extractedValueIndex(write))
+ val storeOp = new VarInsnNode(tp.getOpcode(ISTORE), slot)
+ toReplace(write.consumer) = List(storeOp)
+
+ case c: EscapingConsumer =>
+ assert(keepBox, s"found escaping consumer, but box is eliminated: $c")
+
+ case extraction =>
+ val (slot, tp) = localSlots(boxKind.extractedValueIndex(extraction))
+ val loadOps = new VarInsnNode(tp.getOpcode(ILOAD), slot) :: extraction.postExtractionAdaptationOps(tp)
+ if (keepBox) toReplace(extraction.consumer) = getPop(1) :: loadOps
+ else toReplace(extraction.consumer) = loadOps
+ toDelete ++= extraction.allInsns - extraction.consumer
+ }
+ }
+ }
+
+ /** Method M2 for eliminating box-unbox pairs (see doc comment in the beginning of this file) */
+ def replaceBoxOperationsMultipleCreations(allCreations: Set[BoxCreation], allConsumers: Set[BoxConsumer], boxKind: BoxKind): Unit = {
+ /**
+ * If a single-value size-1 box is eliminated, local variables slots holding the box are
+ * reused to hold the unboxed value. In case there's an entry for that local variable in the
+ * method's local variables table (debug info), adapt the type.
+ *
+ * If there are multiple entries for a local variable that's changing types, then all
+ * entries for that variable are deleted - it's not obvious how to find the correct entry.
+ * Note that scalac never re-uses local variable slots for non-overlapping locals. Also note
+ * that all locals that are newly created during the optimizer don't have an entry either.
+ *
+ * Finally, note that variables that become unused are removed later from the table by
+ * removeUnusedLocalVariableNodes in LocalOpt.
+ *
+ * Unlike modifications that affect the method's instructions (which uses toReplace etc),
+ * we can directly modify the local variable table - it does not affect the frames of the
+ * ProdCons analysis.
+ */
+ def updateLocalVariableTypes(reTypedLocals: Map[Int, Type]): Unit = {
+ lazy val localsByIndex = method.localVariables.asScala.groupBy(_.index)
+ for ((index, tp) <- reTypedLocals) localsByIndex.get(index).map(_.toList) match {
+ case Some(List(local)) =>
+ local.desc = tp.getDescriptor
+ case Some(locals) =>
+ locals foreach method.localVariables.remove
+ case _ =>
+ }
+ }
+
+ /** Remove box creations - leave the boxed value(s) on the stack instead. */
+ def replaceCreationOps(): Unit = {
+ for (creation <- allCreations) creation.loadInitialValues match {
+ case None =>
+ toDelete ++= creation.allInsns
+
+ case Some(ops) =>
+ toReplace(creation.valuesConsumer) = ops
+ toDelete ++= (creation.allInsns - creation.valuesConsumer)
+ }
+ }
+
+ /**
+ * Replace a value extraction operation. For a single-value box, the extraction operation can
+ * just be removed. An extraction from a multi-value box is replaced by POP operations for the
+ * non-used values, and an xSTORE / xLOAD for the extracted value. Example: tuple3._2 becomes
+ * POP; xSTORE n; POP; xLOAD n.
+ */
+ def replaceExtractionOps(): Unit = {
+ if (boxKind.boxedTypes.lengthCompare(1) == 0) {
+ // fast path for single-value boxes
+ allConsumers.foreach(extraction => extraction.postExtractionAdaptationOps(boxKind.boxedTypes.head) match {
+ case Nil =>
+ toDelete ++= extraction.allInsns
+ case ops =>
+ toReplace(extraction.consumer) = ops
+ toDelete ++= extraction.allInsns - extraction.consumer
+ })
+ } else {
+ for (extraction <- allConsumers) {
+ val valueIndex = boxKind.extractedValueIndex(extraction)
+ val replacementOps = if (valueIndex == 0) {
+ val pops = boxKind.boxedTypes.tail.map(t => getPop(t.getSize))
+ pops ::: extraction.postExtractionAdaptationOps(boxKind.boxedTypes.head)
+ } else {
+ var loadOps: List[AbstractInsnNode] = null
+ val consumeStack = boxKind.boxedTypes.zipWithIndex reverseMap {
+ case (tp, i) =>
+ if (i == valueIndex) {
+ val resultSlot = getLocal(tp.getSize)
+ loadOps = new VarInsnNode(tp.getOpcode(ILOAD), resultSlot) :: extraction.postExtractionAdaptationOps(tp)
+ new VarInsnNode(tp.getOpcode(ISTORE), resultSlot)
+ } else {
+ getPop(tp.getSize)
+ }
+ }
+ consumeStack ::: loadOps
+ }
+ toReplace(extraction.consumer) = replacementOps
+ toDelete ++= extraction.allInsns - extraction.consumer
+ }
+ }
+ }
+
+ checkCopyOpReplacements(allCreations, allConsumers, boxKind.boxedTypes, nextLocal, prodCons) match {
+ case Some((replacements, nextCopyOpLocal, reTypedLocals)) =>
+ toReplace ++= replacements
+ updateLocalVariableTypes(reTypedLocals)
+ nextLocal = nextCopyOpLocal
+ replaceCreationOps()
+ replaceExtractionOps()
+ // Conservative (safe) value for stack growth. In every frame that initially has a multi-value
+ // box on the stack, the stack now contains all of the individual values. So for every eliminated
+ // box, the maxStack may be up to N-1 slots larger.
+ maxStackGrowth += boxKind.boxedTypes.length - 1
+
+ case None =>
+ }
+ }
+
+ val it = method.instructions.iterator
+ while (it.hasNext) {
+ val insn = it.next()
+ if (!knownHandled(insn)) BoxKind.valueCreationKind(insn, prodCons) match {
+ case Some((boxCreation, boxKind)) =>
+ allCreationsConsumers(boxCreation, boxKind, prodCons) match {
+ case Some((allCreations, allConsumers)) =>
+ val (escapingConsumers, boxConsumers) = allConsumers.partition(_.isEscaping)
+ if (boxConsumers.nonEmpty) {
+ for (c <- allCreations) knownHandled ++= c.allInsns
+ for (e <- allConsumers) knownHandled ++= e.allInsns
+
+ val hasEscaping = escapingConsumers.nonEmpty
+ val hasWrite = allConsumers.exists(_.isWrite)
+ if (!hasEscaping && !hasWrite) {
+ // M2 -- see doc comment in the beginning of this file
+ // If both M1 and M2 can be applied, we prefer M2 because it doesn't introduce new locals.
+ replaceBoxOperationsMultipleCreations(allCreations, allConsumers, boxKind)
+ } else if (allCreations.size == 1 && (!hasEscaping || !boxKind.isMutable)) {
+ // M1 -- see doc comment in the beginning of this file
+ replaceBoxOperationsSingleCreation(allCreations.head, allConsumers, boxKind, keepBox = hasEscaping)
+ }
+ }
+
+ case None =>
+ }
+
+ case None =>
+ }
+ }
+
+ def removeFromCallGraph(insn: AbstractInsnNode): Unit = insn match {
+ case mi: MethodInsnNode => callGraph.removeCallsite(mi, method)
+ case _ =>
+ }
+
+ for ((location, ops) <- toInsertBefore; op <- ops)
+ method.instructions.insertBefore(location, op)
+
+ for ((oldOp, newOps) <- toReplace) {
+ for (newOp <- newOps) method.instructions.insertBefore(oldOp, newOp)
+ method.instructions.remove(oldOp)
+ removeFromCallGraph(oldOp)
+ }
+
+ for (op <- toDelete) {
+ method.instructions.remove(op)
+ removeFromCallGraph(op)
+ }
+
+ method.maxLocals = nextLocal
+ method.maxStack += maxStackGrowth
+ toInsertBefore.nonEmpty || toReplace.nonEmpty || toDelete.nonEmpty
+ }
+ }
+
+ /**
+ * Given a box creations operation
+ * - find all ultimate consumers for the produced value. then:
+ * - for all consumed values, find all producer operations. check that all are box creations
+ * - recurse until reaching a fixpoint
+ *
+ * Returns a set of box creations and a set of box consumers. Note that the box consumers may
+ * contain [[EscapingConsumer]]s, even if there are multiple box creation operations. The callee
+ * will handle this case (and not attempt to eliminate the box).
+ */
+ def allCreationsConsumers(initialCreation: BoxCreation, boxKind: BoxKind, prodCons: ProdConsAnalyzer): Option[(Set[BoxCreation], Set[BoxConsumer])] = {
+ var creations = Set(initialCreation)
+ var consumers = Set.empty[BoxConsumer]
+
+ def addCreations(boxConsumer: BoxConsumer): Boolean = {
+ val newProds = boxConsumer.boxProducers(prodCons).filterNot(prod => creations.exists(_.producer == prod))
+ newProds.forall(prod => boxKind.checkBoxCreation(prod, prodCons) match {
+ case Some(boxCreation) =>
+ creations += boxCreation
+ addBoxConsumers(boxCreation)
+
+ case _ => false
+ })
+ }
+
+ def addBoxConsumers(creation: BoxCreation): Boolean = {
+ val newCons = creation.boxConsumers(prodCons, ultimate = true).filterNot(cons => consumers.exists(_.consumer == cons))
+ newCons.forall(cons => boxKind.checkBoxConsumer(cons, prodCons) match {
+ case Some(boxConsumer) =>
+ consumers += boxConsumer
+ addCreations(boxConsumer)
+
+ case _ =>
+ creations.size <= 1 && {
+ // If there's a single box creation, the box operations can still be rewritten
+ consumers += EscapingConsumer(cons)
+ true
+ }
+ })
+ }
+
+ if (addBoxConsumers(initialCreation)) Some((creations, consumers))
+ else None
+ }
+
+ /**
+ * Takes two sets `initialProds` and `finalCons` such that all boxes produced by the first set
+ * are only consumed by an operation in the second set.
+ *
+ * Returns a map that replaces copy operations (ALOAD / ASTORE) between the producers and
+ * consumers with corresponding copy operations for the values stored in the box. The returned
+ * `Int` value returns the next free local variable slot.
+ *
+ * Examples:
+ * - for an Integer box, an ASTORE x is simply replaced by ISTORE x
+ * - for a pair of two references, an ASTORE x is replaced by `ASTORE x1; ASTORE x2` where x1
+ * and x2 are fresh locals
+ *
+ * Not all copy operations can be supported: DUP only works for single-value boxes, the more
+ * exotic copy operations (DUP2_X2) are not supported (note that Scalac never emits them). If a
+ * copy operation cannot be replaced, this method returns `None`.
+ */
+ def checkCopyOpReplacements(initialProds: Set[BoxCreation], finalCons: Set[BoxConsumer], valueTypes: List[Type], nextLocal: Int, prodCons: ProdConsAnalyzer): Option[(Map[AbstractInsnNode, List[AbstractInsnNode]], Int, Map[Int, Type])] = {
+ var replacements = Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+ var reTypedLocals = Map.empty[Int, Type]
+
+ var nextCopyOpLocal = nextLocal
+ val newLocalsMap: mutable.LongMap[List[(Type, Int)]] = mutable.LongMap.empty
+ def newLocals(index: Int) = newLocalsMap.getOrElseUpdate(index, valueTypes match {
+ case List(t) if t.getSize == 1 =>
+ reTypedLocals += index -> t
+ List((t, index))
+ case _ => valueTypes.map(t => {
+ val newIndex = nextCopyOpLocal
+ nextCopyOpLocal += t.getSize
+ (t, newIndex)
+ })
+ })
+
+ var replaceOK = true
+ val copyOps = new CopyOpsIterator(initialProds, finalCons, prodCons)
+ while (replaceOK && copyOps.hasNext) copyOps.next() match {
+ case vi: VarInsnNode =>
+ val isLoad = vi.getOpcode == ALOAD
+ val typedVarOp = (tp: (Type, Int)) => {
+ val opc = tp._1.getOpcode(if (isLoad) ILOAD else ISTORE)
+ new VarInsnNode(opc, tp._2)
+ }
+ val locs = newLocals(vi.`var`)
+ replacements += vi -> (if (isLoad) locs.map(typedVarOp) else locs.reverseMap(typedVarOp))
+
+ case copyOp =>
+ if (copyOp.getOpcode == DUP && valueTypes.lengthCompare(1) == 0) {
+ if (valueTypes.head.getSize == 2)
+ replacements += copyOp -> List(new InsnNode(DUP2))
+ } else {
+ replaceOK = false
+ }
+ }
+ if (replaceOK) Some((replacements, nextCopyOpLocal, reTypedLocals)) else None
+ }
+
+ /**
+ * For a set of box creation operations and a corresponding set of box consumer operations,
+ * this iterator returns all copy operations (load, store, dup) that are in between.
+ */
+ class CopyOpsIterator(initialCreations: Set[BoxCreation], finalCons: Set[BoxConsumer], prodCons: ProdConsAnalyzer) extends Iterator[AbstractInsnNode] {
+ private var queue = mutable.Queue.empty[AbstractInsnNode] ++ initialCreations.iterator.flatMap(_.boxConsumers(prodCons, ultimate = false))
+
+ // a single copy operation can consume multiple producers: val a = if (b) box(1) else box(2).
+ // the `ASTORE a` has two producers (the two box operations). we need to handle it only once.
+ private val visited = mutable.Set.empty[AbstractInsnNode]
+
+ private val boxConsumingOps = finalCons.map(_.consumer)
+
+ @tailrec private def advanceToNextCopyOp(): Unit = {
+ if (queue.nonEmpty) {
+ val h = queue.front
+ if (visited(h) || boxConsumingOps(h)) {
+ queue.dequeue()
+ advanceToNextCopyOp()
+ }
+ }
+ }
+
+ def hasNext: Boolean = {
+ advanceToNextCopyOp()
+ queue.nonEmpty
+ }
+
+ def next(): AbstractInsnNode = {
+ advanceToNextCopyOp()
+ val r = queue.dequeue()
+ visited += r
+ queue ++= prodCons.consumersOfOutputsFrom(r)
+ r
+ }
+ }
+
+ trait BoxKind {
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation]
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer]
+ def boxedTypes: List[Type]
+ def extractedValueIndex(extraction: BoxConsumer): Int
+ def isMutable: Boolean
+ }
+
+ object BoxKind {
+ def valueCreationKind(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[(BoxCreation, BoxKind)] = {
+ PrimitiveBox.checkPrimitiveBox(insn, None, prodCons) orElse
+ Ref.checkRefCreation(insn, None, prodCons) orElse
+ Tuple.checkTupleCreation(insn, None, prodCons)
+ }
+
+ /**
+ * Check if `newOp` is part of a standard object construction pattern in which:
+ *
+ * NEW T
+ * DUP
+ * [load constructor args]
+ * INVOKESPECIAL T.init
+ *
+ * The method ensures that the entire construction pattern is closed in itself, without any
+ * branches going in or out. This is checked by looking at producers / consumers:
+ * - `DUP` is the only consumer of `NEW`, and vice versa
+ * - `DUP` the only producer for the receiver of the constructor call
+ * - The set of consumers of `DUP` without the constructor call is the same as
+ * the set of consumers of the value on the stack top after the constructor call
+ */
+ def checkInstanceCreation(newOp: TypeInsnNode, prodCons: ProdConsAnalyzer): Option[(InsnNode, MethodInsnNode)] = {
+ val newCons = prodCons.consumersOfOutputsFrom(newOp)
+ if (newCons.size == 1 && newCons.head.getOpcode == DUP) {
+ val dupOp = newCons.head.asInstanceOf[InsnNode]
+ if (prodCons.producersForInputsOf(dupOp) == Set(newOp)) {
+ val dupCons = prodCons.consumersOfOutputsFrom(dupOp)
+ val initCalls = dupCons collect {
+ case mi: MethodInsnNode if mi.name == GenBCode.INSTANCE_CONSTRUCTOR_NAME && mi.owner == newOp.desc => mi
+ }
+ if (initCalls.size == 1) {
+ val initCall = initCalls.head
+ val numArgs = Type.getArgumentTypes(initCall.desc).length
+ val receiverProds = prodCons.producersForValueAt(initCall, prodCons.frameAt(initCall).stackTop - numArgs)
+ if (receiverProds == Set(dupOp)) {
+ val dupConsWithoutInit = dupCons - initCall
+ val afterInit = initCall.getNext
+ val stackTopAfterInit = prodCons.frameAt(afterInit).stackTop
+ val initializedInstanceCons = prodCons.consumersOfValueAt(afterInit, stackTopAfterInit)
+ if (initializedInstanceCons == dupConsWithoutInit && prodCons.producersForValueAt(afterInit, stackTopAfterInit) == Set(dupOp)) {
+ return Some((dupOp, initCall))
+ }
+ }
+ }
+ }
+ }
+ None
+ }
+
+ /**
+ * If `mi` is an invocation of a method on Predef, check if the receiver is a GETSTATIC of
+ * Predef.MODULE$ and return it.
+ */
+ def checkReceiverPredefLoad(mi: MethodInsnNode, prodCons: ProdConsAnalyzer): Option[AbstractInsnNode] = {
+ val numArgs = Type.getArgumentTypes(mi.desc).length
+ val receiverProds = prodCons.producersForValueAt(mi, prodCons.frameAt(mi).stackTop - numArgs)
+ if (receiverProds.size == 1) {
+ val prod = receiverProds.head
+ if (isPredefLoad(prod) && prodCons.consumersOfOutputsFrom(prod) == Set(mi)) return Some(prod)
+ }
+ None
+ }
+ }
+
+ case class PrimitiveBox(boxedType: Type, boxClass: InternalName) extends BoxKind {
+ import PrimitiveBox._
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkPrimitiveBox(insn, Some(this), prodCons).map(_._1)
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkPrimitiveUnbox(insn, this, prodCons)
+ def boxedTypes: List[Type] = List(boxedType)
+ def extractedValueIndex(extraction: BoxConsumer): Int = 0
+ def isMutable = false
+ }
+
+ object PrimitiveBox {
+ private def boxedType(mi: MethodInsnNode) = Type.getArgumentTypes(mi.desc)(0)
+
+ private def boxClass(mi: MethodInsnNode) = {
+ if (mi.name == GenBCode.INSTANCE_CONSTRUCTOR_NAME) mi.owner
+ else Type.getReturnType(mi.desc).getInternalName
+ }
+
+ def checkPrimitiveBox(insn: AbstractInsnNode, expectedKind: Option[PrimitiveBox], prodCons: ProdConsAnalyzer): Option[(BoxCreation, PrimitiveBox)] = {
+ // mi is either a box factory or a box constructor invocation
+ def checkKind(mi: MethodInsnNode) = expectedKind match {
+ case Some(kind) => if (kind.boxClass == boxClass(mi)) expectedKind else None
+ case None => Some(PrimitiveBox(boxedType(mi), boxClass(mi)))
+ }
+
+ insn match {
+ case mi: MethodInsnNode =>
+ if (isScalaBox(mi) || isJavaBox(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = None), _))
+ else if (isPredefAutoBox(mi))
+ for (predefLoad <- BoxKind.checkReceiverPredefLoad(mi, prodCons); kind <- checkKind(mi))
+ yield (ModuleFactory(predefLoad, mi), kind)
+ else None
+
+ case ti: TypeInsnNode if ti.getOpcode == NEW =>
+ for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isPrimitiveBoxConstructor(initCall); kind <- checkKind(initCall))
+ yield (InstanceCreation(ti, dupOp, initCall), kind)
+
+ case _ => None
+ }
+ }
+
+ def checkPrimitiveUnbox(insn: AbstractInsnNode, kind: PrimitiveBox, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = {
+ def typeOK(mi: MethodInsnNode) = kind.boxedType == Type.getReturnType(mi.desc)
+ insn match {
+ case mi: MethodInsnNode =>
+ if ((isScalaUnbox(mi) || isJavaUnbox(mi)) && typeOK(mi)) Some(StaticGetterOrInstanceRead(mi))
+ else if (isPredefAutoUnbox(mi) && typeOK(mi)) BoxKind.checkReceiverPredefLoad(mi, prodCons).map(ModuleGetter(_, mi))
+ else None
+
+ case _ => None
+ }
+ }
+ }
+
+ case class Ref(boxedType: Type, refClass: InternalName) extends BoxKind {
+ import Ref._
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkRefCreation(insn, Some(this), prodCons).map(_._1)
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkRefConsumer(insn, this, prodCons)
+ def boxedTypes: List[Type] = List(boxedType)
+ def extractedValueIndex(extraction: BoxConsumer): Int = 0
+ def isMutable = true
+ }
+
+ object Ref {
+ private def boxedType(mi: MethodInsnNode): Type = runtimeRefClassBoxedType(mi.owner)
+ private def refClass(mi: MethodInsnNode): InternalName = mi.owner
+ private def loadZeroValue(refZeroCall: MethodInsnNode): List[AbstractInsnNode] = List(loadZeroForTypeSort(runtimeRefClassBoxedType(refZeroCall.owner).getSort))
+
+ def checkRefCreation(insn: AbstractInsnNode, expectedKind: Option[Ref], prodCons: ProdConsAnalyzer): Option[(BoxCreation, Ref)] = {
+ def checkKind(mi: MethodInsnNode): Option[Ref] = expectedKind match {
+ case Some(kind) => if (kind.refClass == refClass(mi)) expectedKind else None
+ case None => Some(Ref(boxedType(mi), refClass(mi)))
+ }
+
+ insn match {
+ case mi: MethodInsnNode =>
+ if (isRefCreate(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = None), _))
+ else if (isRefZero(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = Some(loadZeroValue(mi))), _))
+ else None
+
+ case ti: TypeInsnNode if ti.getOpcode == NEW =>
+ for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isRuntimeRefConstructor(initCall); kind <- checkKind(initCall))
+ yield (InstanceCreation(ti, dupOp, initCall), kind)
+
+ case _ => None
+ }
+ }
+
+ def checkRefConsumer(insn: AbstractInsnNode, kind: Ref, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = insn match {
+ case fi: FieldInsnNode if fi.owner == kind.refClass && fi.name == "elem" =>
+ if (fi.getOpcode == GETFIELD) Some(StaticGetterOrInstanceRead(fi))
+ else if (fi.getOpcode == PUTFIELD) Some(StaticSetterOrInstanceWrite(fi))
+ else None
+
+ case _ => None
+ }
+ }
+
+ case class Tuple(boxedTypes: List[Type], tupleClass: InternalName) extends BoxKind {
+ import Tuple._
+ def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkTupleCreation(insn, Some(this), prodCons).map(_._1)
+ def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkTupleExtraction(insn, this, prodCons)
+ def extractedValueIndex(extraction: BoxConsumer): Int = extraction match {
+ case StaticGetterOrInstanceRead(mi: MethodInsnNode) => tupleGetterIndex(mi.name)
+ case PrimitiveBoxingGetter(mi) => tupleGetterIndex(mi.name)
+ case PrimitiveUnboxingGetter(mi, _) => tupleGetterIndex(mi.name)
+ case _ => throw new AssertionError(s"Expected tuple getter, found $extraction")
+ }
+ def isMutable = false
+ }
+
+ object Tuple {
+ private def boxedTypes(mi: MethodInsnNode): List[Type] = Type.getArgumentTypes(mi.desc).toList
+ private def tupleClass(mi: MethodInsnNode): InternalName = mi.owner
+
+ def checkTupleCreation(insn: AbstractInsnNode, expectedKind: Option[Tuple], prodCons: ProdConsAnalyzer): Option[(BoxCreation, Tuple)] = {
+ def checkKind(mi: MethodInsnNode): Option[Tuple] = expectedKind match {
+ case Some(kind) => if (kind.tupleClass == tupleClass(mi)) expectedKind else None
+ case None => Some(Tuple(boxedTypes(mi), tupleClass(mi)))
+ }
+
+ insn match {
+ // no need to check for TupleN.apply: the compiler transforms case companion apply calls to constructor invocations
+ case ti: TypeInsnNode if ti.getOpcode == NEW =>
+ for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isTupleConstructor(initCall); kind <- checkKind(initCall))
+ yield (InstanceCreation(ti, dupOp, initCall), kind)
+
+ case _ => None
+ }
+ }
+
+ private val specializedTupleClassR = "scala/Tuple[12]\\$mc[IJDCZ]{1,2}\\$sp".r
+ private def isSpecializedTupleClass(tupleClass: InternalName) = specializedTupleClassR.pattern.matcher(tupleClass).matches
+
+ private val specializedTupleGetterR = "_[12]\\$mc[IJDCZ]\\$sp".r
+ private def isSpecializedTupleGetter(mi: MethodInsnNode) = specializedTupleGetterR.pattern.matcher(mi.name).matches
+
+ private val tupleGetterR = "_\\d\\d?".r
+ private def isTupleGetter(mi: MethodInsnNode) = tupleGetterR.pattern.matcher(mi.name).matches
+
+ def checkTupleExtraction(insn: AbstractInsnNode, kind: Tuple, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = {
+ val expectedTupleClass = kind.tupleClass
+ insn match {
+ case mi: MethodInsnNode =>
+ val tupleClass = mi.owner
+ if (isSpecializedTupleClass(expectedTupleClass)) {
+ val typeOK = tupleClass == expectedTupleClass || tupleClass == expectedTupleClass.substring(0, expectedTupleClass.indexOf('$'))
+ if (typeOK) {
+ if (isSpecializedTupleGetter(mi)) return Some(StaticGetterOrInstanceRead(mi))
+ else if (isTupleGetter(mi)) return Some(PrimitiveBoxingGetter(mi))
+ }
+ } else if (expectedTupleClass == tupleClass) {
+ if (isSpecializedTupleGetter(mi)) return Some(PrimitiveUnboxingGetter(mi, Type.getReturnType(mi.desc)))
+ else if (isTupleGetter(mi)) return Some(StaticGetterOrInstanceRead(mi))
+ }
+
+ case _ =>
+ }
+ None
+ }
+
+ private val getterIndexPattern = "_(\\d{1,2}).*".r
+ def tupleGetterIndex(getterName: String) = getterName match { case getterIndexPattern(i) => i.toInt - 1 }
+ }
+
+ // TODO: add more
+ // case class ValueClass(valueClass: Type, valueType: Type) extends BoxKind
+
+ sealed trait BoxCreation {
+ // to support box creation operations that don't consume an initial value from the stack, e.g., IntRef.zero
+ val loadInitialValues: Option[List[AbstractInsnNode]]
+
+ /**
+ * The instruction that produces the box value; for instance creations, the `NEW` operation.
+ */
+ def producer: AbstractInsnNode
+
+ /**
+ * The instruction that consumes the boxed values; for instance creations, the `init` call.
+ */
+ def valuesConsumer: MethodInsnNode = this match {
+ case StaticFactory(call, _) => call
+ case ModuleFactory(_, call) => call
+ case InstanceCreation(_, _, initCall) => initCall
+ }
+
+ def allInsns: Set[AbstractInsnNode] = this match {
+ case StaticFactory(c, _) => Set(c)
+ case ModuleFactory(m, c) => Set(m, c)
+ case InstanceCreation(n, d, i) => Set(n, d, i)
+ }
+
+ /**
+ * The consumers of the box produced by this box creation. If `ultimate` is true, then the
+ * final consumers are returned (e.g., an unbox operation), otherwise direct consumers (e.g.,
+ * a store operation).
+ */
+ def boxConsumers(prodCons: ProdConsAnalyzer, ultimate: Boolean): Set[AbstractInsnNode] = {
+ val startInsn = this match {
+ // for the non-transitive case (ultimate == false), it's important to start at the `dupOp`,
+ // not the `newOp` - look at the BoxCreation as a black box, get its consumers.
+ case InstanceCreation(_, dupOp, _) => dupOp
+ case _ => producer
+ }
+ val cons = if (ultimate) prodCons.ultimateConsumersOfOutputsFrom(startInsn) else prodCons.consumersOfOutputsFrom(startInsn)
+ this match {
+ case InstanceCreation(_, _, initCall) => cons - initCall
+ case _ => cons
+ }
+ }
+ }
+
+ case class StaticFactory(producer: MethodInsnNode, loadInitialValues: Option[List[AbstractInsnNode]]) extends BoxCreation
+ case class ModuleFactory(moduleLoad: AbstractInsnNode, producer: MethodInsnNode) extends BoxCreation {
+ val loadInitialValues: Option[List[AbstractInsnNode]] = None
+ }
+ case class InstanceCreation(newOp: TypeInsnNode, dupOp: InsnNode, initCall: MethodInsnNode) extends BoxCreation {
+ def producer = newOp
+ val loadInitialValues: Option[List[AbstractInsnNode]] = None
+ }
+
+ sealed trait BoxConsumer {
+ val consumer: AbstractInsnNode
+
+ def allInsns: Set[AbstractInsnNode] = this match {
+ case ModuleGetter(m, c) => Set(m, c)
+ case _ => Set(consumer)
+ }
+
+ /**
+ * The initial producers of the box value consumed by this box consumer
+ */
+ def boxProducers(prodCons: ProdConsAnalyzer): Set[AbstractInsnNode] = {
+ val stackTop = prodCons.frameAt(consumer).stackTop
+ val slot = if (isWrite) stackTop - 1 else stackTop
+ prodCons.initialProducersForValueAt(consumer, slot)
+ }
+
+ def isEscaping = this match {
+ case _: EscapingConsumer => true
+ case _ => false
+ }
+
+ def isWrite = this match {
+ case _: StaticSetterOrInstanceWrite => true
+ case _ => false
+ }
+
+ /**
+ * If this box consumer extracts a boxed value and applies a conversion, this method returns
+ * equivalent conversion operations. For example, invoking `_1$mcI$sp` on a non-specialized
+ * `Tuple2` extracts the Integer value and unboxes it.
+ */
+ def postExtractionAdaptationOps(typeOfExtractedValue: Type): List[AbstractInsnNode] = this match {
+ case PrimitiveBoxingGetter(_) => List(getScalaBox(typeOfExtractedValue))
+ case PrimitiveUnboxingGetter(_, unboxedPrimitive) => List(getScalaUnbox(unboxedPrimitive))
+ case _ => Nil
+ }
+ }
+
+ /** Static extractor (BoxesRunTime.unboxToInt) or GETFIELD or getter invocation */
+ case class StaticGetterOrInstanceRead(consumer: AbstractInsnNode) extends BoxConsumer
+ /** A getter that boxes the returned value, e.g., `Tuple2$mcII$sp._1` */
+ case class PrimitiveBoxingGetter(consumer: MethodInsnNode) extends BoxConsumer
+ /** A getter that unboxes the returned value, e.g., `Tuple2._1$mcI$sp` */
+ case class PrimitiveUnboxingGetter(consumer: MethodInsnNode, unboxedPrimitive: Type) extends BoxConsumer
+ /** An extractor method in a Scala module, e.g., `Predef.Integer2int` */
+ case class ModuleGetter(moduleLoad: AbstractInsnNode, consumer: MethodInsnNode) extends BoxConsumer
+ /** PUTFIELD or setter invocation */
+ case class StaticSetterOrInstanceWrite(consumer: AbstractInsnNode) extends BoxConsumer
+ /** An unknown box consumer */
+ case class EscapingConsumer(consumer: AbstractInsnNode) extends BoxConsumer
+}
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..f2ff73c44d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -9,13 +9,12 @@ package opt
import scala.tools.asm
import asm.tree._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
+import scala.collection.{concurrent, mutable}
import scala.tools.asm.Attribute
import scala.tools.nsc.backend.jvm.BackendReporting._
-import scala.tools.nsc.io.AbstractFile
-import scala.tools.nsc.util.ClassFileLookup
+import scala.tools.nsc.util.ClassPath
import BytecodeUtils._
-import ByteCodeRepository._
import BTypes.InternalName
import java.util.concurrent.atomic.AtomicLong
@@ -24,58 +23,91 @@ 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: ClassPath, val btypes: BT) {
+ import btypes._
+
+ /**
+ * Contains ClassNodes and the canonical path of the source file path of classes being compiled in
+ * the current compilation run.
+ */
+ val compilingClasses: concurrent.Map[InternalName, (ClassNode, String)] = 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()))
+ def add(classNode: ClassNode, sourceFilePath: Option[String]) = sourceFilePath match {
+ case Some(path) if path != "<no file>" => compilingClasses(classNode.name) = (classNode, path)
+ case _ => parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet()))
+ }
+
+ private def parsedClassNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
+ 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 class node and source for an internal name. If the class node is not yet available, it is
- * parsed from the classfile on the compile classpath.
+ * The class node and source file path (if the class is being compiled) for an internal name. If
+ * the class node is not yet available, it is parsed from the classfile on the compile classpath.
*/
- def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = {
- val r = classes.getOrElseUpdate(internalName, {
- limitCacheSize()
- parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
- })
- r.map(v => (v._1, v._2))
+ def classNodeAndSourceFilePath(internalName: InternalName): Either[ClassNotFound, (ClassNode, Option[String])] = {
+ compilingClasses.get(internalName) match {
+ case Some((c, p)) => Right((c, Some(p)))
+ case _ => parsedClassNode(internalName).map((_, None))
+ }
}
/**
* 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) match {
+ case Some((c, _)) => Right(c)
+ case None => parsedClassNode(internalName)
+ }
+ }
/**
* The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`.
@@ -86,7 +118,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,33 +136,135 @@ 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.
*
+ * Note that the JVM spec performs method lookup in two steps: resolution and selection.
+ *
+ * Method resolution, defined in jvms-5.4.3.3 and jvms-5.4.3.4, is the first step and is identical
+ * for all invocation styles (virtual, interface, special, static). If C is the receiver class
+ * in the invocation instruction:
+ * 1 find a matching method (name and descriptor) in C
+ * 2 then in C's superclasses
+ * 3 then find the maximally-specific matching superinterface methods, succeed if there's a
+ * single non-abstract one. static and private methods in superinterfaces are not considered.
+ * 4 then pick a random non-static, non-private superinterface method.
+ * 5 then fail.
+ *
+ * Note that for an `invokestatic` instruction, a method reference `B.m` may resolve to `A.m`, if
+ * class `B` doesn't specify a matching method `m`, but the parent `A` does.
+ *
+ * Selection depends on the invocation style and is defined in jvms-6.5.
+ * - invokestatic: invokes the resolved method
+ * - invokevirtual / invokeinterface: searches for an override of the resolved method starting
+ * at the dynamic receiver type. the search procedure is basically the same as in resolution,
+ * but it fails at 4 instead of picking a superinterface method at random.
+ * - invokespecial: if C is the receiver in the invocation instruction, searches for an override
+ * of the resolved method starting at
+ * - the superclass of the current class, if C is a superclass of the current class
+ * - C otherwise
+ * again, the search procedure is the same.
+ *
+ * In the method here we implement method *resolution*. Whether or not the returned method is
+ * actually invoked at runtime depends on the invocation instruction and the class hierarchy, so
+ * the users (e.g. the inliner) have to be aware of method selection.
+ *
+ * Note that the returned method may be abstract (ACC_ABSTRACT), native (ACC_NATIVE) or signature
+ * polymorphic (methods `invoke` and `invokeExact` in class `MethodHandles`).
+ *
* @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.
+ * class, or an error message if the method could not be found. An error message is also
+ * returned if method resolution results in multiple default methods.
*/
def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Either[MethodNotFound, (MethodNode, InternalName)] = {
- // on failure, returns a list of class names that could not be found on the classpath
- def methodNodeImpl(ownerInternalName: InternalName): Either[List[ClassNotFound], (MethodNode, InternalName)] = {
- classNode(ownerInternalName) match {
- case Left(e) => Left(List(e))
- case Right(c) =>
- c.methods.asScala.find(m => m.name == name && m.desc == descriptor) match {
- case Some(m) => Right((m, ownerInternalName))
- case None => findInParents(Option(c.superName) ++: c.interfaces.asScala.toList, Nil)
- }
+ def findMethod(c: ClassNode): Option[MethodNode] = c.methods.asScala.find(m => m.name == name && m.desc == descriptor)
+
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9: "In Java SE 8, the only
+ // signature polymorphic methods are the invoke and invokeExact methods of the class MethodHandle.
+ def isSignaturePolymorphic(owner: InternalName) = owner == coreBTypes.jliMethodHandleRef.internalName && (name == "invoke" || name == "invokeExact")
+
+ // Note: if `owner` is an interface, in the first iteration we search for a matching member in the interface itself.
+ // If that fails, the recursive invocation checks in the superclass (which is Object) with `publicInstanceOnly == true`.
+ // This is specified in jvms-5.4.3.4: interface method resolution only returns public, non-static methods of Object.
+ def findInSuperClasses(owner: ClassNode, publicInstanceOnly: Boolean = false): Either[ClassNotFound, Option[(MethodNode, InternalName)]] = {
+ findMethod(owner) match {
+ case Some(m) if !publicInstanceOnly || (isPublicMethod(m) && !isStaticMethod(m)) => Right(Some((m, owner.name)))
+ case None =>
+ if (isSignaturePolymorphic(owner.name)) Right(Some((owner.methods.asScala.find(_.name == name).get, owner.name)))
+ else if (owner.superName == null) Right(None)
+ else classNode(owner.superName).flatMap(findInSuperClasses(_, isInterface(owner)))
}
}
- // find the MethodNode in one of the parent classes
- def findInParents(parents: List[InternalName], failedClasses: List[ClassNotFound]): Either[List[ClassNotFound], (MethodNode, InternalName)] = parents match {
- case x :: xs => methodNodeImpl(x).left.flatMap(failed => findInParents(xs, failed ::: failedClasses))
- case Nil => Left(failedClasses)
+ def findInInterfaces(initialOwner: ClassNode): Either[ClassNotFound, Option[(MethodNode, InternalName)]] = {
+ val visited = mutable.Set.empty[InternalName]
+ val found = mutable.ListBuffer.empty[(MethodNode, ClassNode)]
+
+ def findIn(owner: ClassNode): Option[ClassNotFound] = {
+ for (i <- owner.interfaces.asScala if !visited(i)) classNode(i) match {
+ case Left(e) => return Some(e)
+ case Right(c) =>
+ visited += i
+ // abstract and static methods are excluded, see jvms-5.4.3.3
+ for (m <- findMethod(c) if !isPrivateMethod(m) && !isStaticMethod(m)) found += ((m, c))
+ val recursionResult = findIn(c)
+ if (recursionResult.isDefined) return recursionResult
+ }
+ None
+ }
+
+ findIn(initialOwner)
+
+ val result =
+ if (found.size <= 1) found.headOption
+ else {
+ val maxSpecific = found.filterNot({
+ case (method, owner) =>
+ isAbstractMethod(method) || {
+ val ownerTp = classBTypeFromClassNode(owner)
+ found exists {
+ case (other, otherOwner) =>
+ (other ne method) && {
+ val otherTp = classBTypeFromClassNode(otherOwner)
+ otherTp.isSubtypeOf(ownerTp).get
+ }
+ }
+ }
+ })
+ // (*) note that if there's no single, non-abstract, maximally-specific method, the jvm
+ // method resolution (jvms-5.4.3.3) returns any of the non-private, non-static parent
+ // methods at random (abstract or concrete).
+ // we chose not to do this here, to prevent the inliner from potentially inlining the
+ // wrong method. in other words, we guarantee that a concrete method is only returned if
+ // it resolves deterministically.
+ // however, there may be multiple abstract methods inherited. in this case we *do* want
+ // to return a result to allow performing accessibility checks in the inliner. note that
+ // for accessibility it does not matter which of these methods is return, as they are all
+ // non-private (i.e., public, protected is not possible, jvms-4.1).
+ // the remaining case (when there's no max-specific method, but some non-abstract one)
+ // does not occur in bytecode generated by scalac or javac. we return no result in this
+ // case. this may at worst prevent some optimizations from happening.
+ if (maxSpecific.size == 1) maxSpecific.headOption
+ else if (found.forall(p => isAbstractMethod(p._1))) found.headOption // (*)
+ else None
+ }
+ Right(result.map(p => (p._1, p._2.name)))
}
// In a MethodInsnNode, the `owner` field may be an array descriptor, for example when invoking `clone`. We don't have a method node to return in this case.
- if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[')
- Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, Nil))
- else
- methodNodeImpl(ownerInternalNameOrArrayDescriptor).left.map(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, _))
+ if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') {
+ Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, None))
+ } else {
+ def notFound(cnf: Option[ClassNotFound]) = Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, cnf))
+ val res: Either[ClassNotFound, Option[(MethodNode, InternalName)]] = classNode(ownerInternalNameOrArrayDescriptor).flatMap(c =>
+ findInSuperClasses(c) flatMap {
+ case None => findInInterfaces(c)
+ case res => Right(res)
+ }
+ )
+ res match {
+ case Left(e) => notFound(Some(e))
+ case Right(None) => notFound(None)
+ case Right(Some(res)) => Right(res)
+ }
+ }
}
private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassNode] = {
@@ -157,17 +290,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)))
}
}
}
-
-object ByteCodeRepository {
- /**
- * The source of a ClassNode in the ByteCodeRepository. Can be either [[CompilationUnit]] if the
- * class is being compiled or [[Classfile]] if the class was parsed from the compilation classpath.
- */
- sealed trait Source
- object CompilationUnit extends Source
- object Classfile extends Source
-}
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 7aadd2c466..bfd92cac5c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -8,28 +8,29 @@ package backend.jvm
package opt
import scala.annotation.{tailrec, switch}
+
import scala.collection.mutable
import scala.reflect.internal.util.Collections._
import scala.tools.asm.commons.CodeSizeEvaluator
import scala.tools.asm.tree.analysis._
-import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type}
+import scala.tools.asm.{Label, Type}
+import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
import GenBCode._
-import scala.collection.convert.decorateAsScala._
-import scala.collection.convert.decorateAsJava._
-import scala.tools.nsc.backend.jvm.BTypes._
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.analysis.InstructionStackEffect
object BytecodeUtils {
// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.1
- final val maxJVMMethodSize = 65535
+ final val maxJVMMethodSize = 65535
// 5% margin, more than enough for the instructions added by the inliner (store / load args, null check for instance methods)
final val maxMethodSizeAfterInline = maxJVMMethodSize - (maxJVMMethodSize / 20)
object Goto {
def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = {
- if (instruction.getOpcode == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode])
+ if (instruction.getOpcode == GOTO) Some(instruction.asInstanceOf[JumpInsnNode])
else None
}
}
@@ -49,8 +50,9 @@ object BytecodeUtils {
}
object VarInstruction {
- def unapply(instruction: AbstractInsnNode): Option[VarInsnNode] = {
- if (isVarInstruction(instruction)) Some(instruction.asInstanceOf[VarInsnNode])
+ def unapply(instruction: AbstractInsnNode): Option[(AbstractInsnNode, Int)] = {
+ if (isLoadStoreOrRet(instruction)) Some((instruction, instruction.asInstanceOf[VarInsnNode].`var`))
+ else if (instruction.getOpcode == IINC) Some((instruction, instruction.asInstanceOf[IincInsnNode].`var`))
else None
}
@@ -59,30 +61,46 @@ object BytecodeUtils {
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)
+ op == 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
+ (op >= IFEQ && op <= IF_ACMPNE) || op == IFNULL || op == IFNONNULL
}
def isReturn(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- op >= Opcodes.IRETURN && op <= Opcodes.RETURN
+ op >= IRETURN && op <= RETURN
}
def isLoad(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- op >= Opcodes.ILOAD && op <= Opcodes.ALOAD
+ op >= ILOAD && op <= ALOAD
}
def isStore(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- op >= Opcodes.ISTORE && op <= Opcodes.ASTORE
+ op >= ISTORE && op <= ASTORE
+ }
+
+ def isLoadStoreOrRet(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) || instruction.getOpcode == RET
+
+ def isLoadOrStore(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction)
+
+ def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op == INVOKESPECIAL || op == INVOKESTATIC
}
- def isVarInstruction(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction)
+ def isVirtualCall(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op == INVOKEVIRTUAL || op == INVOKEINTERFACE
+ }
+
+ def isCall(instruction: AbstractInsnNode): Boolean = {
+ isNonVirtualCall(instruction) || isVirtualCall(instruction)
+ }
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
@@ -90,27 +108,40 @@ object BytecodeUtils {
methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME
}
- def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STATIC) != 0
+ def isPublicMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PUBLIC) != 0
- def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_ABSTRACT) != 0
+ def isPrivateMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_PRIVATE) != 0
- def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0
+ def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STATIC) != 0
- def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_NATIVE) != 0
+ def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_ABSTRACT) != 0
- def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0
+ def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_SYNCHRONIZED) != 0
- def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0
+ def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_NATIVE) != 0
- def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0
+ def hasCallerSensitiveAnnotation(methodNode: MethodNode): Boolean = methodNode.visibleAnnotations != null && methodNode.visibleAnnotations.asScala.exists(_.desc == "Lsun/reflect/CallerSensitive;")
+
+ def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & ACC_FINAL) != 0
+
+ def isInterface(classNode: ClassNode): Boolean = (classNode.access & ACC_INTERFACE) != 0
+
+ def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (ACC_FINAL | ACC_PRIVATE | ACC_STATIC)) != 0
+
+ def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STRICT) != 0
def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY
- 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)
+ @tailrec def nextExecutableInstruction(insn: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
+ val next = insn.getNext
+ if (next == null || isExecutable(next) || alsoKeep(next)) Option(next)
+ else nextExecutableInstruction(next, alsoKeep)
+ }
+
+ @tailrec def nextExecutableInstructionOrLabel(insn: AbstractInsnNode): Option[AbstractInsnNode] = {
+ val next = insn.getNext
+ if (next == null || isExecutable(next) || next.isInstanceOf[LabelNode]) Option(next)
+ else nextExecutableInstructionOrLabel(next)
}
def sameTargetExecutableInstruction(a: JumpInsnNode, b: JumpInsnNode): Boolean = {
@@ -124,14 +155,14 @@ object BytecodeUtils {
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) {
+ if ((op >= IFEQ && op <= IFLE) || op == IFNULL || op == IFNONNULL) {
instructions.insert(jump, getPop(1))
- } else if ((op >= Opcodes.IF_ICMPEQ && op <= Opcodes.IF_ICMPLE) || op == Opcodes.IF_ACMPEQ || op == Opcodes.IF_ACMPNE) {
+ } else if ((op >= IF_ICMPEQ && op <= IF_ICMPLE) || op == IF_ACMPEQ || op == 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)
+ assert(jump.getOpcode == GOTO)
}
instructions.remove(jump)
}
@@ -148,37 +179,61 @@ object BytecodeUtils {
}
def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match {
- case Opcodes.IFEQ => Opcodes.IFNE
- case Opcodes.IFNE => Opcodes.IFEQ
+ case IFEQ => IFNE
+ case IFNE => IFEQ
+
+ case IFLT => IFGE
+ case IFGE => IFLT
- case Opcodes.IFLT => Opcodes.IFGE
- case Opcodes.IFGE => Opcodes.IFLT
+ case IFGT => IFLE
+ case IFLE => IFGT
- case Opcodes.IFGT => Opcodes.IFLE
- case Opcodes.IFLE => Opcodes.IFGT
+ case IF_ICMPEQ => IF_ICMPNE
+ case IF_ICMPNE => IF_ICMPEQ
- case Opcodes.IF_ICMPEQ => Opcodes.IF_ICMPNE
- case Opcodes.IF_ICMPNE => Opcodes.IF_ICMPEQ
+ case IF_ICMPLT => IF_ICMPGE
+ case IF_ICMPGE => IF_ICMPLT
- case Opcodes.IF_ICMPLT => Opcodes.IF_ICMPGE
- case Opcodes.IF_ICMPGE => Opcodes.IF_ICMPLT
+ case IF_ICMPGT => IF_ICMPLE
+ case IF_ICMPLE => IF_ICMPGT
- case Opcodes.IF_ICMPGT => Opcodes.IF_ICMPLE
- case Opcodes.IF_ICMPLE => Opcodes.IF_ICMPGT
+ case IF_ACMPEQ => IF_ACMPNE
+ case IF_ACMPNE => IF_ACMPEQ
- case Opcodes.IF_ACMPEQ => Opcodes.IF_ACMPNE
- case Opcodes.IF_ACMPNE => Opcodes.IF_ACMPEQ
+ case IFNULL => IFNONNULL
+ case IFNONNULL => IFNULL
+ }
- case Opcodes.IFNULL => Opcodes.IFNONNULL
- case Opcodes.IFNONNULL => Opcodes.IFNULL
+ def isSize2LoadOrStore(opcode: Int): Boolean = (opcode: @switch) match {
+ case LLOAD | DLOAD | LSTORE | DSTORE => true
+ case _ => false
}
def getPop(size: Int): InsnNode = {
- val op = if (size == 1) Opcodes.POP else Opcodes.POP2
+ val op = if (size == 1) POP else POP2
new InsnNode(op)
}
- def instructionResultSize(instruction: AbstractInsnNode) = InstructionResultSize(instruction)
+ def instructionResultSize(insn: AbstractInsnNode) = InstructionStackEffect.prod(InstructionStackEffect.forClassfile(insn))
+
+ def loadZeroForTypeSort(sort: Int) = (sort: @switch) match {
+ case Type.BOOLEAN |
+ Type.BYTE |
+ Type.CHAR |
+ Type.SHORT |
+ Type.INT => new InsnNode(ICONST_0)
+ case Type.LONG => new InsnNode(LCONST_0)
+ case Type.FLOAT => new InsnNode(FCONST_0)
+ case Type.DOUBLE => new InsnNode(DCONST_0)
+ case Type.OBJECT => new InsnNode(ACONST_NULL)
+ }
+
+ /**
+ * The number of local variable slots used for parameters and for the `this` reference.
+ */
+ def parametersSize(methodNode: MethodNode): Int = {
+ (Type.getArgumentsAndReturnSizes(methodNode.desc) >> 2) - (if (isStaticMethod(methodNode)) 1 else 0)
+ }
def labelReferences(method: MethodNode): Map[LabelNode, Set[AnyRef]] = {
val res = mutable.Map.empty[LabelNode, Set[AnyRef]]
@@ -222,29 +277,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.
@@ -289,34 +321,36 @@ object BytecodeUtils {
}
/**
- * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to
- * the `labelMap`. Returns the new instruction list and a map from old to new instructions.
- */
- def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode]) = {
- val javaLabelMap = labelMap.asJava
- val result = new InsnList
- var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
- for (ins <- methodNode.instructions.iterator.asScala) {
- val cloned = ins.clone(javaLabelMap)
- result add cloned
- map += ((ins, cloned))
- }
- (result, map)
- }
-
- /**
* 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] = {
- methodNode.localVariables.iterator().asScala.map(localVariable => new LocalVariableNode(
- prefix + localVariable.name,
- localVariable.desc,
- localVariable.signature,
- labelMap(localVariable.start),
- labelMap(localVariable.end),
- localVariable.index
- )).toList
+ def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], calleeMethodName: String, shift: Int): List[LocalVariableNode] = {
+ methodNode.localVariables.iterator().asScala.map(localVariable => {
+ val name =
+ if (calleeMethodName.length + localVariable.name.length < BTypes.InlinedLocalVariablePrefixMaxLenght) {
+ calleeMethodName + "_" + localVariable.name
+ } else {
+ val parts = localVariable.name.split("_").toVector
+ val (methNames, varName) = (calleeMethodName +: parts.init, parts.last)
+ // keep at least 5 characters per method name
+ val maxNumMethNames = BTypes.InlinedLocalVariablePrefixMaxLenght / 5
+ val usedMethNames =
+ if (methNames.length < maxNumMethNames) methNames
+ else {
+ val half = maxNumMethNames / 2
+ methNames.take(half) ++ methNames.takeRight(half)
+ }
+ val charsPerMethod = BTypes.InlinedLocalVariablePrefixMaxLenght / usedMethNames.length
+ usedMethNames.foldLeft("")((res, methName) => res + methName.take(charsPerMethod) + "_") + varName
+ }
+ new LocalVariableNode(
+ name,
+ localVariable.desc,
+ localVariable.signature,
+ labelMap(localVariable.start),
+ labelMap(localVariable.end),
+ localVariable.index + shift)
+ }).toList
}
/**
@@ -344,23 +378,14 @@ object BytecodeUtils {
* method which explains the issue with such phantom values.
*/
def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = {
- if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) {
- methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW))
- } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) {
- methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL))
- methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP))
+ if (loadedType == bTypes.coreBTypes.srNothingRef.toASMType) {
+ methodNode.instructions.insert(loadInstr, new InsnNode(ATHROW))
+ } else if (loadedType == bTypes.coreBTypes.srNullRef.toASMType) {
+ methodNode.instructions.insert(loadInstr, new InsnNode(ACONST_NULL))
+ methodNode.instructions.insert(loadInstr, new InsnNode(POP))
}
}
- /**
- * 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..a740ca525c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -7,182 +7,320 @@ 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.{Handle, Opcodes, Type}
import scala.tools.asm.tree._
-import scala.collection.concurrent
-import scala.collection.convert.decorateAsScala._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.collection.{concurrent, mutable}
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo}
import scala.tools.nsc.backend.jvm.BackendReporting._
-import scala.tools.nsc.backend.jvm.analysis.{NotNull, NullnessAnalyzer}
-import ByteCodeRepository.{Source, CompilationUnit}
+import scala.tools.nsc.backend.jvm.analysis._
import BytecodeUtils._
class CallGraph[BT <: BTypes](val btypes: BT) {
import btypes._
+ import backendUtils._
- val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty)
+ /**
+ * 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 within 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)
+
+ /**
+ * 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.
+ */
+ 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)
+ }
+
+ def addCallsite(callsite: Callsite): Unit = {
+ val methodCallsites = callsites(callsite.callsiteMethod)
+ callsites(callsite.callsiteMethod) = methodCallsites + (callsite.callsiteInstruction -> callsite)
+ }
- val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, ClosureInstantiation] = recordPerRunCache(concurrent.TrieMap.empty)
+ def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction
+ def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call)
+
+ 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)
+ }
+
+ def addClosureInstantiation(closureInit: ClosureInstantiation) = {
+ val methodClosureInits = closureInstantiations(closureInit.ownerMethod)
+ closureInstantiations(closureInit.ownerMethod) = methodClosureInits + (closureInit.lambdaMetaFactoryCall.indy -> closureInit)
+ }
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))
- }
+ classNode.methods.asScala.foreach(addMethod(_, classType))
}
- /**
- * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
- */
- def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[LambdaMetaFactoryCall]) = {
+ def addIfMissing(methodNode: MethodNode, definingClass: ClassBType): Unit = {
+ if (!callsites.contains(methodNode)) addMethod(methodNode, definingClass)
+ }
- case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
- annotatedInline: Boolean, annotatedNoInline: Boolean,
- warning: Option[CalleeInfoWarning])
+ def addMethod(methodNode: MethodNode, definingClass: ClassBType): Unit = {
+ if (!BytecodeUtils.isAbstractMethod(methodNode) && !BytecodeUtils.isNativeMethod(methodNode)) {
+ // 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
+
+ // 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.
+
+ val analyzer = {
+ if (compilerSettings.optNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) {
+ Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer(btypes, methodNode)))
+ } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) {
+ Some(new AsmAnalyzer(methodNode, definingClass.internalName))
+ } else None
+ }
- /**
- * 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
+ // 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
+ }
- 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
+ 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, calleeSourceFilePath) <- byteCodeRepository.classNodeAndSourceFilePath(declarationClass): Either[OptimizerWarning, (ClassNode, Option[String])]
+ } yield {
+ val declarationClassBType = classBTypeFromClassNode(declarationClassNode)
+ val info = analyzeCallsite(method, declarationClassBType, call, calleeSourceFilePath)
+ import info._
+ Callee(
+ callee = method,
+ calleeDeclarationClass = declarationClassBType,
+ isStaticallyResolved = isStaticallyResolved,
+ sourceFilePath = sourceFilePath,
+ annotatedInline = annotatedInline,
+ annotatedNoInline = annotatedNoInline,
+ samParamTypes = info.samParamTypes,
+ calleeInfoWarning = warning)
+ }
- val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
+ val argInfos = computeArgInfos(callee, call, prodCons)
- // (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)
+ val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || {
+ val numArgs = Type.getArgumentTypes(call.desc).length
+ receiverNotNullByAnalysis(call, numArgs)
}
- 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,
- 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))
+ 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),
+ annotatedInline = inlineAnnotatedCallsites(call),
+ annotatedNoInline = noInlineAnnotatedCallsites(call)
+ )
+
+ 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 _ =>
}
- } catch {
- case Invalid(noInfo: NoClassBTypeInfo) =>
- val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
- CallsiteInfo(false, false, false, false, Some(warning))
+
+ callsites(methodNode) = methodCallsites
+ closureInstantiations(methodNode) = methodClosureInstantiations
}
}
+ }
- // 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
-
- // 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)
+ 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)
}
- 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
+ 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)
+ }
- case _ => false
+ 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
}
-
- 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)
+ 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, _))
}
+ }
+ }
- 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 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)
+ }
- val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || {
- val numArgs = Type.getArgumentTypes(call.desc).length
- receiverNotNullByAnalysis(call, numArgs)
- }
+ def capturedSamTypes(lmf: LambdaMetaFactoryCall): IntMap[ClassBType] = {
+ val capturedTypes = Type.getArgumentTypes(lmf.indy.desc).map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor))
+ samTypes(capturedTypes)
+ }
- 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 _ =>
- }
+ 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)
- (callsites.toList, closureInstantiations.toList)
+ case _ =>
+ }
+ }
+ res
}
/**
+ * Just a named tuple used as return type of `analyzeCallsite`.
+ */
+ private case class CallsiteInfo(isStaticallyResolved: Boolean, sourceFilePath: Option[String],
+ 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, call: MethodInsnNode, calleeSourceFilePath: Option[String]): 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 isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode)
+
+ val receiverType = classBTypeFromParsedClassfile(call.owner)
+ // (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 receiver 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 = {
+ isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined -- TODO: check if that's still needed, and if it's correct: scala-dev#143
+ methodInlineInfo.effectivelyFinal ||
+ receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1)
+ }
+
+ val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map(
+ MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _))
+
+ CallsiteInfo(
+ isStaticallyResolved = isStaticallyResolved,
+ sourceFilePath = calleeSourceFilePath,
+ 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, None, false, false, IntMap.empty, Some(warning))
+ }
+ } catch {
+ case Invalid(noInfo: NoClassBTypeInfo) =>
+ val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo)
+ CallsiteInfo(false, None, false, false, IntMap.empty, Some(warning))
+ }
+ }
+
+ /**
* A callsite in the call graph.
*
* @param callsiteInstruction The invocation instruction
@@ -197,21 +335,35 @@ 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],
- callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position) {
+ callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo],
+ callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position,
+ annotatedInline: Boolean, annotatedNoInline: Boolean) {
+ /**
+ * Contains callsites that were created during inlining by cloning this callsite. Used to find
+ * corresponding callsites when inlining post-inline requests.
+ */
+ val inlinedClones = mutable.Set.empty[ClonedCallsite]
+
+ // an annotation at the callsite takes precedence over an annotation at the definition site
+ def isInlineAnnotated = annotatedInline || (callee.get.annotatedInline && !annotatedNoInline)
+ def isNoInlineAnnotated = annotatedNoInline || (callee.get.annotatedNoInline && !annotatedInline)
+
override def toString =
"Invocation of" +
s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" +
s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" +
- s" in ${callsiteClass.internalName}.${callsiteMethod.name}"
+ s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}"
}
+ final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite)
+
/**
* 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
/**
@@ -221,46 +373,50 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* virtual calls, an override of the callee might be invoked. Also,
* the callee can be abstract.
* @param calleeDeclarationClass The class in which the callee is declared
- * @param safeToInline True if the callee can be safely inlined: it cannot be overridden,
- * and the inliner settings (project / global) allow inlining it.
- * @param safeToRewrite True if the callee is the interface method of a concrete trait method
- * that can be safely re-written to the static implementation method.
+ * @param isStaticallyResolved True if the callee cannot be overridden
* @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,
+ final case class Callee(callee: MethodNode, calleeDeclarationClass: btypes.ClassBType,
+ isStaticallyResolved: Boolean, sourceFilePath: Option[String],
annotatedInline: Boolean, annotatedNoInline: Boolean,
+ samParamTypes: IntMap[btypes.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})"
+
+ def canInlineFromSource = inlinerHeuristics.canInlineFromSource(sourceFilePath)
+ def isAbstract = isAbstractMethod(callee)
+ def isSpecialMethod = isConstructor(callee) || isNativeMethod(callee) || hasCallerSensitiveAnnotation(callee)
+
+ def safeToInline = isStaticallyResolved && canInlineFromSource && !isAbstract && !isSpecialMethod
}
- 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]) {
+ /**
+ * Contains closure instantiations that were created during inlining by cloning this instantiation.
+ */
+ val inlinedClones = mutable.Set.empty[ClosureInstantiation]
override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)"
}
final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type)
object LambdaMetaFactoryCall {
- private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory"
-
- private val metafactoryHandle = {
- val metafactoryMethodName: String = "metafactory"
- val metafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"
- new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc)
- }
-
- private val altMetafactoryHandle = {
- val altMetafactoryMethodName: String = "altMetafactory"
- val altMetafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
- new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
- }
-
def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match {
- case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle =>
+ case indy: InvokeDynamicInsnNode if indy.bsm == coreBTypes.lambdaMetaFactoryMetafactoryHandle || indy.bsm == coreBTypes.lambdaMetaFactoryAltMetafactoryHandle =>
indy.bsmArgs match {
- case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_*
+ case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, _@_*) =>
// LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
// implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
//
@@ -284,7 +440,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
// When re-writing the closure callsite to the implMethod, we have to insert a cast.
//
// The check below ensures that
- // (1) the implMethod type has the expected singature (captured types plus argument types
+ // (1) the implMethod type has the expected signature (captured types plus argument types
// from instantiatedMethodType)
// (2) the receiver of the implMethod matches the first captured type
// (3) all parameters that are not the same in samMethodType and instantiatedMethodType
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..2fca8991ab 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
@@ -8,21 +8,39 @@ package backend.jvm
package opt
import scala.annotation.switch
-import scala.collection.immutable
+import scala.collection.mutable
+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._
-import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.CompilationUnit
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
import btypes._
import callGraph._
+ import coreBTypes._
+ import backendUtils._
+ import ClosureOptimizer._
+
+ private object closureInitOrdering extends Ordering[ClosureInstantiation] {
+ override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = {
+ val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName
+ if (cls != 0) return cls
+
+ val mName = x.ownerMethod.name compareTo y.ownerMethod.name
+ if (mName != 0) return mName
+
+ val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc
+ if (mDesc != 0) return mDesc
+
+ def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy)
+ pos(x) - pos(y)
+ }
+ }
/**
* If a closure is allocated and invoked within the same method, re-write the invocation to the
@@ -54,55 +72,51 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
* [invoke the closure body method]
*/
def rewriteClosureApplyInvocations(): Unit = {
- implicit object closureInitOrdering extends Ordering[ClosureInstantiation] {
- override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = {
- val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName
- if (cls != 0) return cls
-
- val mName = x.ownerMethod.name compareTo y.ownerMethod.name
- if (mName != 0) return mName
- val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc
- if (mDesc != 0) return mDesc
-
- def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy)
- pos(x) - pos(y)
- }
+ // sort all closure invocations to rewrite to ensure bytecode stability
+ val toRewrite = mutable.TreeMap.empty[ClosureInstantiation, mutable.ArrayBuffer[(MethodInsnNode, Int)]](closureInitOrdering)
+ def addRewrite(init: ClosureInstantiation, invocation: MethodInsnNode, stackHeight: Int): Unit = {
+ val callsites = toRewrite.getOrElseUpdate(init, mutable.ArrayBuffer.empty[(MethodInsnNode, Int)])
+ callsites += ((invocation, stackHeight))
}
- // 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 find callsites of the closure and add them to the toRewrite
+ // buffer (cannot change a method's bytecode while still looking for further invocations to
+ // rewrite, the frame indices of the ProdCons analysis would get out of date). If a callsite
+ // cannot be rewritten, for example because the lambda body method is not accessible, issue a
+ // warning. The `toList` in the next line prevents modifying closureInstantiations while
+ // iterating it: minimalRemoveUnreachableCode (called in the loop) removes elements.
+ for (method <- closureInstantiations.keysIterator.toList if AsmAnalyzer.sizeOKForBasicValue(method)) closureInstantiations.get(method) match {
+ case Some(closureInitsBeforeDCE) if closureInitsBeforeDCE.nonEmpty =>
+ val ownerClass = closureInitsBeforeDCE.head._2.ownerClass.internalName
+
+ // Advanced ProdCons queries (initialProducersForValueAt) expect no unreachable code.
+ localOpt.minimalRemoveUnreachableCode(method, ownerClass)
+
+ if (AsmAnalyzer.sizeOKForSourceValue(method)) closureInstantiations.get(method) match {
+ case Some(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(method, ownerClass)
+
+ for (init <- closureInits.valuesIterator) closureCallsites(init, prodCons) foreach {
+ case Left(warning) =>
+ backendReporting.inlinerWarning(warning.pos, warning.toString)
+
+ case Right((invocation, stackHeight)) =>
+ addRewrite(init, invocation, stackHeight)
+ }
+
+ case _ =>
+ }
- // 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({
- 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
+ case _ =>
}
- // Rewrite all closure callsites (or issue inliner warnings for those that cannot be rewritten)
- for ((closureInit, callsites) <- callsitesToRewrite) {
+ for ((closureInit, invocations) <- toRewrite) {
// Local variables that hold the captured values and the closure invocation arguments.
- // They are lazy vals to ensure that locals for captured values are only allocated if there's
- // actually a callsite to rewrite (an not only warnings to be issued).
- lazy val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit)
- for (callsite <- callsites) callsite match {
- case Left(warning) =>
- backendReporting.inlinerWarning(warning.pos, warning.toString)
-
- case Right((invocation, stackHeight)) =>
- rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList)
- }
+ val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit)
+ for ((invocation, stackHeight) <- invocations)
+ rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList)
}
}
@@ -122,20 +136,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
val argTypes = closureInit.lambdaMetaFactoryCall.samMethodType.getArgumentTypes
val firstArgLocal = ownerMethod.maxLocals
- // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce
- // casts for arguments that have different types in samMethodType and instantiatedMethodType.
- val castLoadTypes = {
- val instantiatedMethodType = closureInit.lambdaMetaFactoryCall.instantiatedMethodType
- (argTypes, instantiatedMethodType.getArgumentTypes).zipped map {
- case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType =>
- // the LambdaMetaFactoryCall extractor ensures that the two types are reference types,
- // so we don't end up casting primitive values.
- Some(instantiatedArgType)
- case _ =>
- None
- }
- }
- val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes)
+ val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes)
ownerMethod.maxLocals = firstArgLocal + argLocals.size
(captureLocals, argLocals)
@@ -154,7 +155,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
// TODO: This is maybe over-cautious.
// We are checking if the closure body method is accessible at the closure callsite.
// If the closure allocation has access to the body method, then the callsite (in the same
- // method as the alloction) should have access too.
+ // method as the allocation) should have access too.
val bodyAccessible: Either[OptimizerWarning, Boolean] = for {
(bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass)
@@ -162,7 +163,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))
@@ -173,6 +174,28 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
}).toList
}
+ /**
+ * Check whether `invocation` invokes the SAM of the IndyLambda `closureInit`.
+ *
+ * In addition to a perfect match, we also identify cases where a generic FunctionN is created
+ * but the invocation is to a specialized variant apply$sp... Vice-versa, we also allow the
+ * case where a specialized FunctionN$sp.. is created but the generic apply is invoked. In
+ * these cases, the translation will introduce the necessary box / unbox invocations. Example:
+ *
+ * val f: Int => Any = (x: Int) => 1
+ * f(10)
+ *
+ * The IndyLambda creates a specialized `JFunction1$mcII$sp`, whose SAM is `apply$mcII$sp(I)I`.
+ * The invocation calls `apply(Object)Object`: the method name and type don't match.
+ * We identify these cases, insert the necessary unbox operation for the arguments, and invoke
+ * the `$anonfun(I)I` method.
+ *
+ * Tests in InlinerTest.optimizeSpecializedClosures. In that test, methods t4/t4a/t5/t8 show
+ * examples where the parameters have to be unboxed because generic `apply` is called, but the
+ * lambda body method takes primitive types.
+ * The opposite case is in t9: a the specialized `apply$sp..` is invoked, but the lambda body
+ * method takes boxed arguments, so we have to insert boxing operations.
+ */
private def isSamInvocation(invocation: MethodInsnNode, closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): Boolean = {
val indy = closureInit.lambdaMetaFactoryCall.indy
if (invocation.getOpcode == INVOKESTATIC) false
@@ -187,11 +210,85 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
receiverProducers.size == 1 && receiverProducers.head == indy
}
- invocation.name == indy.name && {
- val indySamMethodDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor
- indySamMethodDesc == invocation.desc
- } &&
- closureIsReceiver // most expensive check last
+ def isSpecializedVersion(specName: String, nonSpecName: String) = specName.startsWith(nonSpecName) && specializationSuffix.pattern.matcher(specName.substring(nonSpecName.length)).matches
+
+ def sameOrSpecializedType(specTp: Type, nonSpecTp: Type) = {
+ specTp == nonSpecTp || {
+ val specDesc = specTp.getDescriptor
+ val nonSpecDesc = nonSpecTp.getDescriptor
+ specDesc.length == 1 && primitives.contains(specDesc) && nonSpecDesc == ObjectRef.descriptor
+ }
+ }
+
+ def specializedDescMatches(specMethodDesc: String, nonSpecMethodDesc: String) = {
+ val specArgs = Type.getArgumentTypes(specMethodDesc)
+ val nonSpecArgs = Type.getArgumentTypes(nonSpecMethodDesc)
+ specArgs.corresponds(nonSpecArgs)(sameOrSpecializedType) && sameOrSpecializedType(Type.getReturnType(specMethodDesc), Type.getReturnType(nonSpecMethodDesc))
+ }
+
+ def nameAndDescMatch = {
+ val aName = invocation.name
+ val bName = indy.name
+ val aDesc = invocation.desc
+ val bDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor
+ if (aName == bName) aDesc == bDesc
+ else if (isSpecializedVersion(aName, bName)) specializedDescMatches(aDesc, bDesc)
+ else if (isSpecializedVersion(bName, aName)) specializedDescMatches(bDesc, aDesc)
+ else false
+ }
+
+ nameAndDescMatch && closureIsReceiver // most expensive check last
+ }
+ }
+
+ private def isPrimitiveType(asmType: Type) = {
+ val sort = asmType.getSort
+ Type.VOID <= sort && sort <= Type.DOUBLE
+ }
+
+ /**
+ * The argument types of the lambda body method may differ in two ways from the argument types of
+ * the closure member method that is invoked (and replaced by a call to the body).
+ * - The lambda body method may have more specific types than the invoked closure member, see
+ * comment in [[LambdaMetaFactoryCall.unapply]].
+ * - The invoked closure member might be a specialized variant of the SAM or vice-versa, see
+ * comment method [[isSamInvocation]].
+ */
+ private def adaptStoredArguments(closureInit: ClosureInstantiation, invocation: MethodInsnNode): Int => Option[AbstractInsnNode] = {
+ val invokeDesc = invocation.desc
+ // The lambda body method has additional parameters for captured values. Here we need to consider
+ // only those parameters of the body method that correspond to lambda parameters. This happens
+ // to be exactly LMF.instantiatedMethodType. In fact, `LambdaMetaFactoryCall.unapply` ensures
+ // that the body method signature is exactly (capturedParams + instantiatedMethodType).
+ val lambdaBodyMethodDescWithoutCaptures = closureInit.lambdaMetaFactoryCall.instantiatedMethodType.getDescriptor
+ if (invokeDesc == lambdaBodyMethodDescWithoutCaptures) {
+ _ => None
+ } else {
+ val invokeArgTypes = Type.getArgumentTypes(invokeDesc)
+ val implMethodArgTypes = Type.getArgumentTypes(lambdaBodyMethodDescWithoutCaptures)
+ val res = new Array[Option[AbstractInsnNode]](invokeArgTypes.length)
+ for (i <- invokeArgTypes.indices) {
+ if (invokeArgTypes(i) == implMethodArgTypes(i)) {
+ res(i) = None
+ } else if (isPrimitiveType(implMethodArgTypes(i)) && invokeArgTypes(i).getDescriptor == ObjectRef.descriptor) {
+ res(i) = Some(getScalaUnbox(implMethodArgTypes(i)))
+ } else if (isPrimitiveType(invokeArgTypes(i)) && implMethodArgTypes(i).getDescriptor == ObjectRef.descriptor) {
+ res(i) = Some(getScalaBox(invokeArgTypes(i)))
+ } else {
+ assert(!isPrimitiveType(invokeArgTypes(i)), invokeArgTypes(i))
+ assert(!isPrimitiveType(implMethodArgTypes(i)), implMethodArgTypes(i))
+ // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce
+ // casts for arguments that have different types in samMethodType and instantiatedMethodType.
+ //
+ // Note:
+ // - invokeArgTypes is the same as the argument types in the IndyLambda's samMethodType,
+ // this is ensured by the `isSamInvocation` filter in this file
+ // - implMethodArgTypes is the same as the arg types in the IndyLambda's instantiatedMethodType,
+ // this is ensured by the unapply method in LambdaMetaFactoryCall (file CallGraph)
+ res(i) = Some(new TypeInsnNode(CHECKCAST, implMethodArgTypes(i).getInternalName))
+ }
+ }
+ res
}
}
@@ -200,7 +297,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod
// store arguments
- insertStoreOps(invocation, ownerMethod, argumentLocalsList)
+ insertStoreOps(invocation, ownerMethod, argumentLocalsList, adaptStoredArguments(closureInit, invocation))
// drop the closure from the stack
ownerMethod.instructions.insertBefore(invocation, new InsnNode(POP))
@@ -210,8 +307,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
@@ -227,46 +325,75 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
insns.insertBefore(invocation, new InsnNode(DUP))
INVOKESPECIAL
}
- val isInterface = bodyOpcode == INVOKEINTERFACE
- val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
+ val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, lambdaBodyHandle.isInterface)
ownerMethod.instructions.insertBefore(invocation, bodyInvocation)
- val returnType = Type.getReturnType(lambdaBodyHandle.getDesc)
- fixLoadedNothingOrNullValue(returnType, bodyInvocation, ownerMethod, btypes) // see comment of that method
+ val bodyReturnType = Type.getReturnType(lambdaBodyHandle.getDesc)
+ val invocationReturnType = Type.getReturnType(invocation.desc)
+ if (isPrimitiveType(invocationReturnType) && bodyReturnType.getDescriptor == ObjectRef.descriptor) {
+ val op =
+ if (invocationReturnType.getSort == Type.VOID) getPop(1)
+ else getScalaUnbox(invocationReturnType)
+ ownerMethod.instructions.insertBefore(invocation, op)
+ } else if (isPrimitiveType(bodyReturnType) && invocationReturnType.getDescriptor == ObjectRef.descriptor) {
+ val op =
+ if (bodyReturnType.getSort == Type.VOID) getBoxedUnit
+ else getScalaBox(bodyReturnType)
+ ownerMethod.instructions.insertBefore(invocation, op)
+ } else {
+ // see comment of that method
+ fixLoadedNothingOrNullValue(bodyReturnType, bodyInvocation, ownerMethod, btypes)
+ }
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 sourceFilePath = byteCodeRepository.compilingClasses.get(lambdaBodyHandle.getOwner).map(_._2)
+ val callee = bodyMethod.map({
+ case (bodyMethodNode, bodyMethodDeclClass) =>
+ val bodyDeclClassType = classBTypeFromParsedClassfile(bodyMethodDeclClass)
+ Callee(
callee = bodyMethodNode,
- calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass),
- safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled,
- safeToRewrite = false, // the lambda body method is not a trait interface method
+ calleeDeclarationClass = bodyDeclClassType,
+ isStaticallyResolved = true,
+ sourceFilePath = sourceFilePath,
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)
+ callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition),
+ annotatedInline = false,
+ annotatedNoInline = false
)
// (*) The documentation in class LambdaMetafactory says:
// "if implMethod corresponds to an instance method, the first capture argument
// (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
+
+ if (hasAdaptedImplMethod(closureInit) && inliner.canInlineCallsite(bodyMethodCallsite).isEmpty)
+ inliner.inlineCallsite(bodyMethodCallsite)
}
/**
@@ -283,13 +410,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
// local. On the other hand, further optimizations (copy propagation, remove unused locals) will
// clean it up.
- // Captured variables don't need to be cast when loaded at the callsite (castLoadTypes are None).
- // This is checked in `isClosureInstantiation`: the types of the captured variables in the indy
- // instruction match exactly the corresponding parameter types in the body method.
- val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None)
+ val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes)
closureInit.ownerMethod.maxLocals = firstCaptureLocal + localsForCaptures.size
- insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures)
+ insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures, _ => None)
insertLoadOps(indy, closureInit.ownerMethod, localsForCaptures)
localsForCaptures
@@ -301,8 +425,16 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
*
* The lowest stack value is stored in the head of the locals list, so the last local is stored first.
*/
- private def insertStoreOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) =
- insertLocalValueOps(before, methodNode, localsList, store = true)
+ private def insertStoreOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList, beforeStore: Int => Option[AbstractInsnNode]) = {
+ // The first instruction needs to store into the last local of the `localsList`.
+ // To avoid reversing the list, we use `insert(previous)`.
+ val previous = before.getPrevious
+ def ins(op: AbstractInsnNode) = methodNode.instructions.insert(previous, op)
+ for ((l, i) <- localsList.locals.zipWithIndex) {
+ ins(new VarInsnNode(l.storeOpcode, l.local))
+ beforeStore(i) foreach ins
+ }
+ }
/**
* Insert load operations in front of the `before` instruction to copy the local values denoted
@@ -310,20 +442,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
*
* The head of the locals list will be the lowest value on the stack, so the first local is loaded first.
*/
- private def insertLoadOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) =
- insertLocalValueOps(before, methodNode, localsList, store = false)
-
- private def insertLocalValueOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList, store: Boolean): Unit = {
- // If `store` is true, the first instruction needs to store into the last local of the `localsList`.
- // Load instructions on the other hand are emitted in the order of the list.
- // To avoid reversing the list, we use `insert(previousInstr)` for stores and `insertBefore(before)` for loads.
- lazy val previous = before.getPrevious
+ private def insertLoadOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) = {
for (l <- localsList.locals) {
- val varOp = new VarInsnNode(if (store) l.storeOpcode else l.loadOpcode, l.local)
- if (store) methodNode.instructions.insert(previous, varOp)
- else methodNode.instructions.insertBefore(before, varOp)
- if (!store) for (castType <- l.castLoadedValue)
- methodNode.instructions.insert(varOp, new TypeInsnNode(CHECKCAST, castType.getInternalName))
+ val op = new VarInsnNode(l.loadOpcode, l.local)
+ methodNode.instructions.insertBefore(before, op)
}
}
@@ -345,12 +467,12 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
* Local(6, refOpOffset) ::
* Nil
*/
- def fromTypes(firstLocal: Int, types: Array[Type], castLoadTypes: Int => Option[Type]): LocalsList = {
+ def fromTypes(firstLocal: Int, types: Array[Type]): LocalsList = {
var sizeTwoOffset = 0
val locals: List[Local] = types.indices.map(i => {
// The ASM method `type.getOpcode` returns the opcode for operating on a value of `type`.
val offset = types(i).getOpcode(ILOAD) - ILOAD
- val local = Local(firstLocal + i + sizeTwoOffset, offset, castLoadTypes(i))
+ val local = Local(firstLocal + i + sizeTwoOffset, offset)
if (local.size == 2) sizeTwoOffset += 1
local
})(collection.breakOut)
@@ -364,10 +486,15 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
* The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for
* a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type]].
*/
- case class Local(local: Int, opcodeOffset: Int, castLoadedValue: Option[Type]) {
+ case class Local(local: Int, opcodeOffset: Int) {
def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD) 2 else 1
def loadOpcode = ILOAD + opcodeOffset
def storeOpcode = ISTORE + opcodeOffset
}
}
+
+object ClosureOptimizer {
+ val primitives = "BSIJCFDZV"
+ val specializationSuffix = s"(\\$$mc[$primitives]+\\$$sp)".r
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala
new file mode 100644
index 0000000000..518646812e
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala
@@ -0,0 +1,635 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.annotation.{switch, tailrec}
+import scala.tools.asm.tree.analysis.BasicInterpreter
+import scala.tools.asm.Type
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.collection.mutable
+import scala.collection.JavaConverters._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.analysis._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+class CopyProp[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import backendUtils._
+
+
+ /**
+ * For every `xLOAD n`, find all local variable slots that are aliases of `n` using an
+ * AliasingAnalyzer and change the instruction to `xLOAD m` where `m` is the smallest alias.
+ * This leaves behind potentially stale `xSTORE n` instructions, which are then eliminated
+ * by [[eliminateStaleStores]].
+ */
+ def copyPropagation(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForAliasing(method) && {
+ var changed = false
+ val numParams = parametersSize(method)
+ lazy val aliasAnalysis = new AsmAnalyzer(method, owner, new AliasingAnalyzer(new BasicInterpreter))
+
+ // Remember locals that are used in a `LOAD` instruction. Assume a program has two LOADs:
+ //
+ // ...
+ // LOAD 3 // aliases of 3 here: <3>
+ // ...
+ // LOAD 1 // aliases of 1 here: <1, 3>
+ //
+ // In this example, we should change the second load from 1 to 3, which might render the
+ // local variable 1 unused.
+ val knownUsed = new Array[Boolean](method.maxLocals)
+
+ def usedOrMinAlias(it: IntIterator, init: Int): Int = {
+ if (knownUsed(init)) init
+ else {
+ var r = init
+ while (it.hasNext) {
+ val n = it.next()
+ // knownUsed.length is the number of locals, `n` may be a stack slot
+ if (n < knownUsed.length && knownUsed(n)) return n
+ if (n < r) r = n
+ }
+ r
+ }
+ }
+
+ val it = method.instructions.iterator
+ while (it.hasNext) it.next() match {
+ case vi: VarInsnNode if vi.`var` >= numParams && isLoad(vi) =>
+ val aliases = aliasAnalysis.frameAt(vi).asInstanceOf[AliasingFrame[_]].aliasesOf(vi.`var`)
+ if (aliases.size > 1) {
+ val alias = usedOrMinAlias(aliases.iterator, vi.`var`)
+ if (alias != -1) {
+ changed = true
+ vi.`var` = alias
+ }
+ }
+ knownUsed(vi.`var`) = true
+
+ case _ =>
+ }
+
+ changed
+ }
+ }
+
+ /**
+ * Eliminate `xSTORE` instructions that have no consumer. If the instruction can be completely
+ * eliminated, it is replaced by a POP. The [[eliminatePushPop]] cleans up unnecessary POPs.
+ *
+ * Note that an `ASOTRE` can not always be eliminated: it removes a reference to the object that
+ * is currently stored in that local, which potentially frees it for GC (SI-5313). Therefore
+ * we replace such stores by `POP; ACONST_NULL; ASTORE x`.
+ */
+ def eliminateStaleStores(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForSourceValue(method) && {
+ lazy val prodCons = new ProdConsAnalyzer(method, owner)
+ def hasNoCons(varIns: AbstractInsnNode, slot: Int) = prodCons.consumersOfValueAt(varIns.getNext, slot).isEmpty
+
+ // insns to delete: IINC that have no consumer
+ val toDelete = mutable.ArrayBuffer.empty[IincInsnNode]
+
+ // xSTORE insns to be replaced by POP or POP2
+ val storesToDrop = mutable.ArrayBuffer.empty[VarInsnNode]
+
+ // ASTORE insn that have no consumer.
+ // - if the local is not live, the store is replaced by POP
+ // - otherwise, pop the argument value and store NULL instead. Unless the boolean field is
+ // `true`: then the store argument is already known to be ACONST_NULL.
+ val toNullOut = mutable.ArrayBuffer.empty[(VarInsnNode, Boolean)]
+
+ // `true` for variables that are known to be live
+ val liveVars = new Array[Boolean](method.maxLocals)
+
+ val it = method.instructions.iterator
+ while (it.hasNext) it.next() match {
+ case vi: VarInsnNode if isStore(vi) && hasNoCons(vi, vi.`var`) =>
+ val canElim = vi.getOpcode != ASTORE || {
+ val currentFieldValueProds = prodCons.initialProducersForValueAt(vi, vi.`var`)
+ currentFieldValueProds.size == 1 && (currentFieldValueProds.head match {
+ case ParameterProducer(0) => !isStaticMethod(method) // current field value is `this`, which won't be gc'd anyway
+ case _: UninitializedLocalProducer => true // field is not yet initialized, so current value cannot leak
+ case _ => false
+ })
+ }
+ if (canElim) storesToDrop += vi
+ else {
+ val prods = prodCons.producersForValueAt(vi, prodCons.frameAt(vi).stackTop)
+ val isStoreNull = prods.size == 1 && prods.head.getOpcode == ACONST_NULL
+ toNullOut += ((vi, isStoreNull))
+ }
+
+ case ii: IincInsnNode if hasNoCons(ii, ii.`var`) =>
+ toDelete += ii
+
+ case vi: VarInsnNode =>
+ liveVars(vi.`var`) = true
+
+ case ii: IincInsnNode =>
+ liveVars(ii.`var`) = true
+
+ case _ =>
+ }
+
+ def replaceByPop(vi: VarInsnNode): Unit = {
+ val size = if (isSize2LoadOrStore(vi.getOpcode)) 2 else 1
+ method.instructions.set(vi, getPop(size))
+ }
+
+ toDelete foreach method.instructions.remove
+
+ storesToDrop foreach replaceByPop
+
+ for ((vi, isStoreNull) <- toNullOut) {
+ if (!liveVars(vi.`var`)) replaceByPop(vi) // can drop `ASTORE x` where x has only dead stores
+ else {
+ if (!isStoreNull) {
+ val prev = vi.getPrevious
+ method.instructions.insert(prev, new InsnNode(ACONST_NULL))
+ method.instructions.insert(prev, getPop(1))
+ }
+ }
+ }
+
+ toDelete.nonEmpty || storesToDrop.nonEmpty || toNullOut.nonEmpty
+ }
+ }
+
+ /**
+ * When a POP instruction has a single producer, remove the POP and eliminate the producer by
+ * bubbling up the POPs. For example, given
+ * ILOAD 1; ILOAD 2; IADD; POP
+ * we first eliminate the POP, then the IADD, then its inputs, so the entire sequence goes away.
+ * If a producer cannot be eliminated (need to keep side-effects), a POP is inserted.
+ *
+ * A special case eliminates the creation of unused objects with side-effect-free constructors:
+ * NEW scala/Tuple1; DUP; ALOAD 0; INVOKESPECIAL scala/Tuple1.<init>; POP
+ * The POP has a single producer (the DUP), it's easy to eliminate these two. A special case
+ * is needed to eliminate the INVOKESPECIAL and NEW.
+ */
+ def eliminatePushPop(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForSourceValue(method) && {
+ // A queue of instructions producing a value that has to be eliminated. If possible, the
+ // instruction (and its inputs) will be removed, otherwise a POP is inserted after
+ val queue = mutable.Queue.empty[ProducedValue]
+ // Contains constructor invocations for values that can be eliminated if unused.
+ val sideEffectFreeConstructorCalls = mutable.ArrayBuffer.empty[MethodInsnNode]
+
+ // instructions to remove (we don't change the bytecode while analyzing it. this allows
+ // running the ProdConsAnalyzer only once.)
+ val toRemove = mutable.Set.empty[AbstractInsnNode]
+ // instructions to insert before some instruction
+ val toInsertBefore = mutable.Map.empty[AbstractInsnNode, List[InsnNode]]
+ // an instruction to insert after some instruction
+ val toInsertAfter = mutable.Map.empty[AbstractInsnNode, AbstractInsnNode]
+
+ lazy val prodCons = new ProdConsAnalyzer(method, owner)
+
+ /**
+ * Returns the producers for the stack value `inputSlot` consumed by `cons`, if the consumer
+ * instruction is the only consumer for all of these producers.
+ *
+ * If a producer has multiple consumers, or the value is the caught exception in a catch
+ * block, this method returns Set.empty.
+ */
+ def producersIfSingleConsumer(cons: AbstractInsnNode, inputSlot: Int): Set[AbstractInsnNode] = {
+ /**
+ * True if the values produced by `prod` are all the same. Most instructions produce a single
+ * value. DUP and DUP2 (with a size-2 input) produce two equivalent values. However, there
+ * are some exotic instructions that produce multiple non-equal values (DUP_X1, SWAP, ...).
+ *
+ * Assume we have `DUP_X2; POP`. In order to remove the `POP` we need to change the DUP_X2
+ * into something else, which is not straightforward.
+ *
+ * Since scalac never emits any of those exotic bytecodes, we don't optimize them.
+ */
+ def producerHasSingleOutput(prod: AbstractInsnNode): Boolean = prod match {
+ case _: ExceptionProducer[_] | _: UninitializedLocalProducer =>
+ // POP of an exception in a catch block cannot be removed. For an uninitialized local,
+ // there should not be a consumer. We are conservative and include it here, so the
+ // producer would not be removed.
+ false
+
+ case _: ParameterProducer =>
+ true
+
+ case _ => (prod.getOpcode: @switch) match {
+ case DUP => true
+ case DUP2 => prodCons.frameAt(prod).peekStack(0).getSize == 2
+ case _ => InstructionStackEffect.prod(InstructionStackEffect.forAsmAnalysis(prod, prodCons.frameAt(prod))) == 1
+ }
+ }
+
+ val prods = prodCons.producersForValueAt(cons, inputSlot)
+ val singleConsumer = prods forall { prod =>
+ producerHasSingleOutput(prod) && {
+ // for DUP / DUP2, we only consider the value that is actually consumed by cons
+ val conss = prodCons.consumersOfValueAt(prod.getNext, inputSlot)
+ conss.size == 1 && conss.head == cons
+ }
+ }
+ if (singleConsumer) prods else Set.empty
+ }
+
+ /**
+ * For a POP instruction that is the single consumer of its producers, remove the POP and
+ * enqueue the producers.
+ */
+ def handleInitialPop(pop: AbstractInsnNode): Unit = {
+ val prods = producersIfSingleConsumer(pop, prodCons.frameAt(pop).stackTop)
+ if (prods.nonEmpty) {
+ toRemove += pop
+ val size = if (pop.getOpcode == POP2) 2 else 1
+ queue ++= prods.map(ProducedValue(_, size))
+ }
+ }
+
+ /**
+ * Traverse the method in its initial state and collect all POP instructions and side-effect
+ * free constructor invocations that can be eliminated.
+ */
+ def collectInitialPopsAndPureConstrs(): Unit = {
+ val it = method.instructions.iterator
+ while (it.hasNext) {
+ val insn = it.next()
+ (insn.getOpcode: @switch) match {
+ case POP | POP2 =>
+ handleInitialPop(insn)
+
+ case INVOKESPECIAL =>
+ val mi = insn.asInstanceOf[MethodInsnNode]
+ if (isSideEffectFreeConstructorCall(mi)) sideEffectFreeConstructorCalls += mi
+
+ case _ =>
+ }
+ }
+ }
+
+ /**
+ * Eliminate the `numArgs` inputs of the instruction `prod` (which was eliminated). For
+ * each input value
+ * - if the `prod` instruction is the single consumer, enqueue the producers of the input
+ * - otherwise, insert a POP instruction to POP the input value
+ */
+ def handleInputs(prod: AbstractInsnNode, numArgs: Int): Unit = {
+ val frame = prodCons.frameAt(prod)
+ val pops = mutable.ListBuffer.empty[InsnNode]
+ @tailrec def handle(stackOffset: Int): Unit = {
+ if (stackOffset >= 0) {
+ val prods = producersIfSingleConsumer(prod, frame.stackTop - stackOffset)
+ val nSize = frame.peekStack(stackOffset).getSize
+ if (prods.isEmpty) pops append getPop(nSize)
+ else queue ++= prods.map(ProducedValue(_, nSize))
+ handle(stackOffset - 1)
+ }
+ }
+ handle(numArgs - 1) // handle stack offsets (numArgs - 1) to 0
+ if (pops.nonEmpty) toInsertBefore(prod) = pops.toList
+ }
+
+ /**
+ * Eliminate LMF `indy` and its inputs.
+ */
+ def handleClosureInst(indy: InvokeDynamicInsnNode): Unit = {
+ toRemove += indy
+ callGraph.removeClosureInstantiation(indy, method)
+ handleInputs(indy, Type.getArgumentTypes(indy.desc).length)
+ }
+
+ def runQueue(): Unit = while (queue.nonEmpty) {
+ val ProducedValue(prod, size) = queue.dequeue()
+
+ def prodString = s"Producer ${AsmUtils textify prod}@${method.instructions.indexOf(prod)}\n${AsmUtils textify method}"
+ def popAfterProd(): Unit = toInsertAfter(prod) = getPop(size)
+
+ (prod.getOpcode: @switch) match {
+ case ACONST_NULL | ICONST_M1 | ICONST_0 | ICONST_1 | ICONST_2 | ICONST_3 | ICONST_4 | ICONST_5 | LCONST_0 | LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | DCONST_0 | DCONST_1 |
+ BIPUSH | SIPUSH | ILOAD | LLOAD | FLOAD | DLOAD | ALOAD=>
+ toRemove += prod
+
+ case opc @ (DUP | DUP2) =>
+ assert(opc != 2 || size == 2, s"DUP2 for two size-1 values; $prodString") // ensured in method `producerHasSingleOutput`
+ if (toRemove(prod))
+ // the DUP is already scheduled for removal because one of its consumers is a POP.
+ // now the second consumer is also a POP, so we need to eliminate the DUP's input.
+ handleInputs(prod, 1)
+ else
+ toRemove += prod
+
+ case DUP_X1 | DUP_X2 | DUP2_X1 | DUP2_X2 | SWAP =>
+ // these are excluded in method `producerHasSingleOutput`
+ assert(false, s"Cannot eliminate value pushed by an instruction with multiple output values; $prodString")
+
+ case IDIV | LDIV | IREM | LREM =>
+ popAfterProd() // keep potential division by zero
+
+ case IADD | LADD | FADD | DADD | ISUB | LSUB | FSUB | DSUB | IMUL | LMUL | FMUL | DMUL | FDIV | DDIV | FREM | DREM |
+ LSHL | LSHR | LUSHR |
+ IAND | IOR | IXOR | LAND | LOR | LXOR |
+ LCMP | FCMPL | FCMPG | DCMPL | DCMPG =>
+ toRemove += prod
+ handleInputs(prod, 2)
+
+ case INEG | LNEG | FNEG | DNEG |
+ I2L | I2F | I2D | L2I | L2F | L2D | F2I | F2L | F2D | D2I | D2L | D2F | I2B | I2C | I2S =>
+ toRemove += prod
+ handleInputs(prod, 1)
+
+ case GETFIELD | GETSTATIC =>
+ // TODO eliminate side-effect free module loads (https://github.com/scala/scala-dev/issues/16)
+ if (isBoxedUnit(prod)) toRemove += prod
+ else popAfterProd() // keep potential class initialization (static field) or NPE (instance field)
+
+ case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | INVOKEINTERFACE =>
+ val methodInsn = prod.asInstanceOf[MethodInsnNode]
+ if (isSideEffectFreeCall(methodInsn)) {
+ toRemove += prod
+ callGraph.removeCallsite(methodInsn, method)
+ val receiver = if (methodInsn.getOpcode == INVOKESTATIC) 0 else 1
+ handleInputs(prod, Type.getArgumentTypes(methodInsn.desc).length + receiver)
+ } else
+ popAfterProd()
+
+ case INVOKEDYNAMIC =>
+ prod match {
+ case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => handleClosureInst(indy)
+ case _ => popAfterProd()
+ }
+
+ case NEW =>
+ if (isNewForSideEffectFreeConstructor(prod)) toRemove += prod
+ else popAfterProd()
+
+ case LDC => prod.asInstanceOf[LdcInsnNode].cst match {
+ case _: java.lang.Integer | _: java.lang.Float | _: java.lang.Long | _: java.lang.Double | _: String =>
+ toRemove += prod
+
+ case _ =>
+ // don't remove class literals, method types, method handles: keep a potential NoClassDefFoundError
+ popAfterProd()
+ }
+
+ case MULTIANEWARRAY =>
+ toRemove += prod
+ handleInputs(prod, prod.asInstanceOf[MultiANewArrayInsnNode].dims)
+
+ case _ =>
+ popAfterProd()
+ }
+ }
+
+ // there are two cases when we can eliminate a constructor call:
+ // - NEW T; INVOKESPECIAL T.<init> -- there's no DUP, the new object is consumed only by the constructor)
+ // - NEW T; DUP; INVOKESPECIAL T.<init>, where the DUP will be removed
+ def eliminateUnusedPureConstructorCalls(): Boolean = {
+ var changed = false
+
+ def removeConstructorCall(mi: MethodInsnNode): Unit = {
+ toRemove += mi
+ callGraph.removeCallsite(mi, method)
+ sideEffectFreeConstructorCalls -= mi
+ changed = true
+ }
+
+ for (mi <- sideEffectFreeConstructorCalls.toList) { // toList to allow removing elements while traversing
+ val frame = prodCons.frameAt(mi)
+ val stackTop = frame.stackTop
+ val numArgs = Type.getArgumentTypes(mi.desc).length
+ val receiverProds = producersIfSingleConsumer(mi, stackTop - numArgs)
+ if (receiverProds.size == 1) {
+ val receiverProd = receiverProds.head
+ if (receiverProd.getOpcode == NEW) {
+ removeConstructorCall(mi)
+ handleInputs(mi, numArgs + 1) // removes the producers of args and receiver
+ } else if (receiverProd.getOpcode == DUP && toRemove.contains(receiverProd)) {
+ val dupProds = producersIfSingleConsumer(receiverProd, prodCons.frameAt(receiverProd).stackTop)
+ if (dupProds.size == 1 && dupProds.head.getOpcode == NEW) {
+ removeConstructorCall(mi)
+ handleInputs(mi, numArgs) // removes the producers of args. the producer of the receiver is DUP and already in toRemove.
+ queue += ProducedValue(dupProds.head, 1) // removes the NEW (which is NOT the producer of the receiver!)
+ }
+ }
+ }
+ }
+ changed
+ }
+
+ collectInitialPopsAndPureConstrs()
+
+ // eliminating producers enables eliminating unused constructor calls (when a DUP gets removed).
+ // vice-versa, eliminating a constructor call adds producers of constructor parameters to the queue.
+ // so the two run in a loop.
+ runQueue()
+ while (eliminateUnusedPureConstructorCalls())
+ runQueue()
+
+ var changed = false
+ toInsertAfter foreach {
+ case (target, insn) =>
+ nextExecutableInstructionOrLabel(target) match {
+ // `insn` is of type `InsnNode`, so we only need to check the Opcode when comparing to another instruction
+ case Some(next) if next.getOpcode == insn.getOpcode && toRemove(next) =>
+ // Inserting and removing a POP at the same place should not enable `changed`. This happens
+ // when a POP directly follows a producer that cannot be eliminated, e.g. INVOKESTATIC A.m ()I; POP
+ // The POP is initially added to `toRemove`, and the `INVOKESTATIC` producer is added to the queue.
+ // Because the producer cannot be elided, a POP is added to `toInsertAfter`.
+ toRemove -= next
+
+ case _ =>
+ changed = true
+ method.instructions.insert(target, insn)
+ }
+ }
+ toInsertBefore foreach {
+ case (target, insns) =>
+ changed = true
+ insns.foreach(method.instructions.insertBefore(target, _))
+ }
+ toRemove foreach { insn =>
+ changed = true
+ method.instructions.remove(insn)
+ }
+ changed
+ }
+ }
+
+ case class ProducedValue(producer: AbstractInsnNode, size: Int) {
+ override def toString = s"<${AsmUtils textify producer}>"
+ }
+
+ /**
+ * Remove `xSTORE n; xLOAD n` pairs if
+ * - the local variable n is not used anywhere else in the method (1), and
+ * - there are no executable instructions and no live labels (jump targets) between the two (2)
+ *
+ * Note: store-load pairs that cannot be eliminated could be replaced by `DUP; xSTORE n`, but
+ * that's just cosmetic and doesn't help for anything.
+ *
+ * (1) This could be made more precise by running a prodCons analysis and checking that the load
+ * is the only user of the store. Then we could eliminate the pair even if the variable is live
+ * (except for ASTORE, SI-5313). Not needing an analyzer is more efficient, and catches most
+ * cases.
+ *
+ * (2) The implementation uses a conservative estimation for liveness (if some instruction uses
+ * local n, then n is considered live in the entire method). In return, it doesn't need to run an
+ * Analyzer on the method, making it more efficient.
+ *
+ * This method also removes `ACONST_NULL; ASTORE n` if the local n is not live. This pattern is
+ * introduced by [[eliminateStaleStores]].
+ *
+ * The implementation is a little tricky to support the following case:
+ * ISTORE 1; ISTORE 2; ILOAD 2; ACONST_NULL; ASTORE 3; ILOAD 1
+ * The outer store-load pair can be removed if two the inner pairs can be.
+ */
+ def eliminateStoreLoad(method: MethodNode): Boolean = {
+ val removePairs = mutable.Set.empty[RemovePair]
+ val liveVars = new Array[Boolean](method.maxLocals)
+ val liveLabels = mutable.Set.empty[LabelNode]
+
+ def mkRemovePair(store: VarInsnNode, other: AbstractInsnNode, depends: List[RemovePairDependency]): RemovePair = {
+ val r = RemovePair(store, other, depends)
+ removePairs += r
+ r
+ }
+
+ def registerLiveVarsLabels(insn: AbstractInsnNode): Unit = insn match {
+ case vi: VarInsnNode => liveVars(vi.`var`) = true
+ case ii: IincInsnNode => liveVars(ii.`var`) = true
+ case j: JumpInsnNode => liveLabels += j.label
+ case s: TableSwitchInsnNode => liveLabels += s.dflt; liveLabels ++= s.labels.asScala
+ case s: LookupSwitchInsnNode => liveLabels += s.dflt; liveLabels ++= s.labels.asScala
+ case _ =>
+ }
+
+ val pairStartStack = new mutable.Stack[(AbstractInsnNode, mutable.ListBuffer[RemovePairDependency])]
+
+ def push(insn: AbstractInsnNode) = {
+ pairStartStack push ((insn, mutable.ListBuffer.empty))
+ }
+
+ def addDepends(dependency: RemovePairDependency) = if (pairStartStack.nonEmpty) {
+ val (_, depends) = pairStartStack.top
+ depends += dependency
+ }
+
+ def completesStackTop(load: AbstractInsnNode) = isLoad(load) && pairStartStack.nonEmpty && {
+ pairStartStack.top match {
+ case (store: VarInsnNode, _) => store.`var` == load.asInstanceOf[VarInsnNode].`var`
+ case _ => false
+ }
+ }
+
+ /**
+ * Try to pair `insn` with its correspondent on the stack
+ * - if the stack top is a store and `insn` is a corresponding load, create a pair
+ * - otherwise, check the two top stack values for `null; store`. if it matches, create
+ * a pair and continue pairing `insn` on the remaining stack
+ * - otherwise, empty the stack and mark the local variables in it live
+ */
+ def tryToPairInstruction(insn: AbstractInsnNode): Unit = {
+ @tailrec def emptyStack(): Unit = if (pairStartStack.nonEmpty) {
+ registerLiveVarsLabels(pairStartStack.pop()._1)
+ emptyStack()
+ }
+
+ @tailrec def tryPairing(): Unit = {
+ if (completesStackTop(insn)) {
+ val (store: VarInsnNode, depends) = pairStartStack.pop()
+ addDepends(mkRemovePair(store, insn, depends.toList))
+ } else if (pairStartStack.nonEmpty) {
+ val (top, topDepends) = pairStartStack.pop()
+ if (pairStartStack.nonEmpty) {
+ (pairStartStack.top, top) match {
+ case ((ldNull: InsnNode, depends), store: VarInsnNode) if ldNull.getOpcode == ACONST_NULL && store.getOpcode == ASTORE =>
+ pairStartStack.pop()
+ addDepends(mkRemovePair(store, ldNull, depends.toList))
+ // example: store; (null; store;) (store; load;) load
+ // s1^ ^^^^^p1^^^^^ // p1 is added to s1's depends
+ // then: store; (null; store;) load
+ // s2^ ^^^^p2^^^^^ // p1 and p2 are added to s2's depends
+ topDepends foreach addDepends
+ tryPairing()
+
+ case _ =>
+ // empty the stack - a non-matching insn was found, cannot create any pairs to remove
+ registerLiveVarsLabels(insn)
+ registerLiveVarsLabels(top)
+ emptyStack()
+ }
+ } else {
+ // stack only has one element
+ registerLiveVarsLabels(insn)
+ registerLiveVarsLabels(top)
+ }
+ } else {
+ // stack is empty already
+ registerLiveVarsLabels(insn)
+ }
+ }
+
+ tryPairing()
+ }
+
+
+ var insn = method.instructions.getFirst
+
+ @tailrec def advanceToNextExecutableOrLabel(): Unit = {
+ insn = insn.getNext
+ if (insn != null && !isExecutable(insn) && !insn.isInstanceOf[LabelNode]) advanceToNextExecutableOrLabel()
+ }
+
+ while (insn != null) {
+ insn match {
+ case _ if insn.getOpcode == ACONST_NULL => push(insn)
+ case vi: VarInsnNode if isStore(vi) => push(insn)
+ case label: LabelNode if pairStartStack.nonEmpty => addDepends(LabelNotLive(label))
+ case _ => tryToPairInstruction(insn)
+ }
+ advanceToNextExecutableOrLabel()
+ }
+
+ // elide RemovePairs that depend on live labels or other RemovePair that have to be elided.
+ // example: store 1; store 2; label x; load 2; load 1
+ // if x is live, the inner pair has to be elided, causing the outer pair to be elided too.
+
+ var doneEliding = false
+
+ def elide(removePair: RemovePair) = {
+ doneEliding = false
+ liveVars(removePair.store.`var`) = true
+ removePairs -= removePair
+ }
+
+ while (!doneEliding) {
+ doneEliding = true
+ for (removePair <- removePairs.toList) {
+ val slot = removePair.store.`var`
+ if (liveVars(slot)) elide(removePair)
+ else removePair.depends foreach {
+ case LabelNotLive(label) => if (liveLabels(label)) elide(removePair)
+ case other: RemovePair => if (!removePairs(other)) elide(removePair)
+ }
+ }
+ }
+
+ for (removePair <- removePairs) {
+ method.instructions.remove(removePair.store)
+ method.instructions.remove(removePair.other)
+ }
+
+ removePairs.nonEmpty
+ }
+}
+
+trait RemovePairDependency
+case class RemovePair(store: VarInsnNode, other: AbstractInsnNode, depends: List[RemovePairDependency]) extends RemovePairDependency {
+ override def toString = s"<${AsmUtils textify store},${AsmUtils textify other}> [$depends]"
+}
+case class LabelNotLive(label: LabelNode) extends RemovePairDependency
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..7bc4ea2392 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -27,7 +27,7 @@ import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersio
* In principle we could encode the InlineInfo into a Java annotation (instead of a classfile attribute).
* However, an attribute allows us to save many bits. In particular, note that the strings in an
* InlineInfo are serialized as references to constants in the constant pool, and those strings
- * (traitImplClassSelfType, method names, method signatures) would exist in there anyway. So the
+ * (method names, method signatures) would exist in there anyway. So the
* ScalaInlineAttribute remains relatively compact.
*/
case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineInfoAttribute.attributeName) {
@@ -47,13 +47,16 @@ 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 flags = 0
+ if (inlineInfo.isEffectivelyFinal) flags |= 1
+ // flags |= 2 // no longer written
+ if (inlineInfo.sam.isDefined) flags |= 4
+ result.putByte(flags)
- 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)
@@ -68,10 +71,10 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
result.putShort(cw.newUTF8(desc))
var inlineInfo = 0
- if (info.effectivelyFinal) inlineInfo |= 1
- if (info.traitMethodWithStaticImplementation) inlineInfo |= 2
- if (info.annotatedInline) inlineInfo |= 4
- if (info.annotatedNoInline) inlineInfo |= 8
+ if (info.effectivelyFinal) inlineInfo |= 1
+ // inlineInfo |= 2 // no longer written
+ if (info.annotatedInline) inlineInfo |= 4
+ if (info.annotatedNoInline) inlineInfo |= 8
result.putByte(inlineInfo)
}
@@ -79,7 +82,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
}
/**
- * De-serialize the attribute into an InlineInfo. The attribute starts at cr.b(off), but we don't
+ * Deserialize the attribute into an InlineInfo. The attribute starts at cr.b(off), but we don't
* need to access that array directly, we can use the `read` methods provided by the ClassReader.
*
* `buf` is a pre-allocated character array that is guaranteed to be long enough to hold any
@@ -94,15 +97,17 @@ 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 self = if (hasSelf) {
- val selfName = nextUTF8()
- Some(selfName)
- } else {
- None
+ val flags = nextByte()
+ val isFinal = (flags & 1) != 0
+ val hasSelf = (flags & 2) != 0
+ val hasSam = (flags & 4) != 0
+
+ if (hasSelf) nextUTF8() // no longer used
+
+ val sam = if (!hasSam) None else {
+ val name = nextUTF8()
+ val desc = nextUTF8()
+ Some(name + desc)
}
val numEntries = nextShort()
@@ -111,14 +116,15 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
val desc = nextUTF8()
val inlineInfo = nextByte()
- val isFinal = (inlineInfo & 1) != 0
- val traitMethodWithStaticImplementation = (inlineInfo & 2) != 0
- val isInline = (inlineInfo & 4) != 0
- val isNoInline = (inlineInfo & 8) != 0
- (name + desc, MethodInlineInfo(isFinal, traitMethodWithStaticImplementation, isInline, isNoInline))
+ val isFinal = (inlineInfo & 1) != 0
+ // = (inlineInfo & 2) != 0 // no longer used
+ val isInline = (inlineInfo & 4) != 0
+ val isNoInline = (inlineInfo & 8) != 0
+ (name + desc, MethodInlineInfo(isFinal, isInline, isNoInline))
}).toMap
- InlineInfoAttribute(InlineInfo(self, isFinal, infos, None))
+ val info = InlineInfo(isFinal, sam, infos, None)
+ InlineInfoAttribute(info)
} else {
val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg)))
@@ -128,9 +134,18 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
object InlineInfoAttribute {
/**
+ * Notes:
+ * - `traitImplClassSelfType` is no longer emitted, `hasTraitImplClassSelfType` is always emitted
+ * as 0. Similarly, `traitMethodWithStaticImplementation` is always emitted 0.
+ * - When reading an existing attribute where `hasTraitImplClassSelfType` is 1, the
+ * `traitImplClassSelfType` is ignored. Also the value of `traitMethodWithStaticImplementation`
+ * is ignored.
+ *
* [u1] version
- * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1)
+ * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1), hasSam (<< 2), hasLateInterfaces (<< 3)
* [u2]? traitImplClassSelfType (reference)
+ * [u2]? samName (reference)
+ * [u2]? samDescriptor (reference)
* [u2] numMethodEntries
* [u2] name (reference)
* [u2] descriptor (reference)
@@ -142,7 +157,7 @@ object InlineInfoAttribute {
}
/**
- * In order to instruct the ASM framework to de-serialize the ScalaInlineInfo attribute, we need
+ * In order to instruct the ASM framework to deserialize 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(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..1c29859f46 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -9,59 +9,125 @@ package opt
import scala.annotation.tailrec
import scala.tools.asm
-import asm.Handle
import asm.Opcodes._
import asm.tree._
-import scala.collection.convert.decorateAsScala._
-import scala.collection.convert.decorateAsJava._
+import scala.collection.JavaConverters._
import AsmUtils._
import BytecodeUtils._
import collection.mutable
-import scala.tools.asm.tree.analysis.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 backendUtils._
- 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 _ =>
+ sealed trait InlineLog {
+ def request: InlineRequest
+ }
+ final case class InlineLogSuccess(request: InlineRequest, sizeBefore: Int, sizeInlined: Int) extends InlineLog {
+ var downstreamLog: mutable.Buffer[InlineLog] = mutable.ListBuffer.empty
+ }
+ final case class InlineLogFail(request: InlineRequest, warning: CannotInlineWarning) extends InlineLog
+ final case class InlineLogRollback(request: InlineRequest, warnings: List[CannotInlineWarning]) extends InlineLog
+
+ object InlineLog {
+ private def shouldLog(request: InlineRequest): Boolean = {
+ def logEnabled = compilerSettings.YoptLogInline.isSetByUser
+ def matchesName = {
+ val prefix = compilerSettings.YoptLogInline.value match {
+ case "_" => ""
+ case p => p
+ }
+ val name: String = request.callsite.callsiteClass.internalName + "." + request.callsite.callsiteMethod.name
+ name startsWith prefix
+ }
+ logEnabled && (upstream != null || (isTopLevel && matchesName))
+ }
+
+ // indexed by callsite method
+ private val logs = mutable.Map.empty[MethodNode, mutable.LinkedHashSet[InlineLog]]
+
+ private var upstream: InlineLogSuccess = _
+ private var isTopLevel = true
+
+ def withInlineLogging[T](request: InlineRequest)(inlineRequest: => Unit)(inlinePost: => T): T = {
+ def doInlinePost(): T = {
+ val savedIsTopLevel = isTopLevel
+ isTopLevel = false
+ try inlinePost
+ finally isTopLevel = savedIsTopLevel
+ }
+ if (shouldLog(request)) {
+ val sizeBefore = request.callsite.callsiteMethod.instructions.size
+ inlineRequest
+ val log = InlineLogSuccess(request, sizeBefore, request.callsite.callee.get.callee.instructions.size)
+ apply(log)
+
+ val savedUpstream = upstream
+ upstream = log
+ try doInlinePost()
+ finally upstream = savedUpstream
+ } else {
+ inlineRequest
+ doInlinePost()
+ }
+ }
+
+ def apply(log: => InlineLog): Unit = if (shouldLog(log.request)) {
+ if (upstream != null) upstream.downstreamLog += log
+ else {
+ val methodLogs = logs.getOrElseUpdate(log.request.callsite.callsiteMethod, mutable.LinkedHashSet.empty)
+ methodLogs += log
+ }
+ }
+
+ def entryString(log: InlineLog, indent: Int = 0): String = {
+ val callee = log.request.callsite.callee.get
+ val calleeString = callee.calleeDeclarationClass.internalName + "." + callee.callee.name
+ val indentString = " " * indent
+ log match {
+ case s @ InlineLogSuccess(_, sizeBefore, sizeInlined) =>
+ val self = s"${indentString}inlined $calleeString. Before: $sizeBefore ins, inlined: $sizeInlined ins."
+ if (s.downstreamLog.isEmpty) self
+ else s.downstreamLog.iterator.map(entryString(_, indent + 2)).mkString(self + "\n", "\n", "")
+
+ case InlineLogFail(_, w) =>
+ s"${indentString}failed $calleeString. ${w.toString.replace('\n', ' ')}"
+
+ case InlineLogRollback(_, _) =>
+ s"${indentString}rolling back, nested inline failed."
+ }
+ }
+
+ def print(): Unit = if (compilerSettings.YoptLogInline.isSetByUser) {
+ val byClassAndMethod: List[(InternalName, mutable.Map[MethodNode, mutable.LinkedHashSet[InlineLog]])] = {
+ logs.
+ groupBy(_._2.head.request.callsite.callsiteClass.internalName).
+ toList.sortBy(_._1)
+ }
+ for {
+ (c, methodLogs) <- byClassAndMethod
+ (m, mLogs) <- methodLogs.toList.sortBy(_._1.name)
+ mLog <- mLogs // insertion order
+ } {
+ println(s"Inline into $c.${m.name}: ${entryString(mLog)}")
+ }
}
}
def runInliner(): Unit = {
- rewriteFinalTraitMethodInvocations()
-
for (request <- collectAndOrderInlineRequests) {
- val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee
-
- // Inlining a method can create unreachable code. Example:
- // def f = throw e
- // def g = f; println() // println is unreachable after inlining f
- // If we have an inline request for a call to g, and f has been already inlined into g, we
- // need to run DCE before inlining g.
- eliminateUnreachableCodeAndUpdateCallGraph(callee.callee, callee.calleeDeclarationClass.internalName)
-
- // 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)
-
- for (warning <- r) {
- 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)
- }
- }
+ val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee
+ val warnings = inline(request)
+ for (warning <- warnings) {
+ if (warning.emitWarning(compilerSettings))
+ backendReporting.inlinerWarning(request.callsite.callsitePosition, warning.toString)
}
}
+ InlineLog.print()
}
/**
@@ -69,165 +135,21 @@ 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)
- }
- }
-
- /**
- * 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)
- }
-
- /**
- * 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 _ => false
- }
-
- /**
- * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the
- * corresponding method in the implementation class. This enables inlining final trait methods.
- *
- * In a final trait method callsite, the callee is safeToInline and the callee method is abstract
- * (the receiver type is the interface, so the method is abstract).
- */
- def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = {
- if (doRewriteTraitCallsite(callsite)) {
- val Right(Callee(callee, calleeDeclarationClass, _, _, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee
-
- val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc)
-
- val implClassInternalName = calleeDeclarationClass.internalName + "$class"
-
- val selfParamTypeV: Either[OptimizerWarning, ClassBType] = calleeDeclarationClass.info.map(_.inlineInfo.traitImplClassSelfType match {
- case Some(internalName) => classBTypeFromParsedClassfile(internalName)
- case None => calleeDeclarationClass
- })
-
- def implClassMethodV(implMethodDescriptor: String): Either[OptimizerWarning, MethodNode] = {
- byteCodeRepository.methodNode(implClassInternalName, callee.name, implMethodDescriptor).map(_._1)
- }
-
- // The rewrite reading the implementation class and the implementation method from the bytecode
- // repository. If either of the two fails, the rewrite is not performed.
- val res = for {
- selfParamType <- selfParamTypeV
- implMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType.toASMType +: traitMethodArgumentTypes: _*)
- implClassMethod <- implClassMethodV(implMethodDescriptor)
- implClassBType = classBTypeFromParsedClassfile(implClassInternalName)
- selfTypeOk <- calleeDeclarationClass.isSubtypeOf(selfParamType)
- } yield {
-
- // The self parameter type may be incompatible with the trait type.
- // trait T { self: S => def foo = 1 }
- // The $self parameter type of T$class.foo is S, which may be unrelated to T. If we re-write
- // a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a
- // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the
- // receiver value and add a cast to the self type after each.
- if (!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)
- val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekStack(traitMethodArgumentTypes.length)
- for (i <- receiverValue.insns.asScala) {
- val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName)
- callsite.callsiteMethod.instructions.insert(i, cast)
- }
- }
-
- val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implMethodDescriptor, false)
- callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction)
- callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction)
-
- callGraph.callsites.remove(callsite.callsiteInstruction)
- val staticCallsite = Callsite(
- callsiteInstruction = newCallsiteInstruction,
- callsiteMethod = callsite.callsiteMethod,
- callsiteClass = callsite.callsiteClass,
- callee = Right(Callee(
- callee = implClassMethod,
- calleeDeclarationClass = implClassBType,
- safeToInline = true,
- safeToRewrite = false,
- annotatedInline = annotatedInline,
- annotatedNoInline = annotatedNoInline,
- calleeInfoWarning = infoWarning)),
- argInfos = Nil,
- callsiteStackHeight = callsite.callsiteStackHeight,
- receiverKnownNotNull = callsite.receiverKnownNotNull,
- callsitePosition = callsite.callsitePosition
- )
- callGraph.callsites(newCallsiteInstruction) = 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))
- }
+ pos(xCs) - pos(yCs)
}
}
@@ -238,15 +160,13 @@ 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
+
+ val elided = mutable.Set.empty[InlineRequest]
+ def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided
- // 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
+ def allCallees(r: InlineRequest): Set[MethodNode] = r.post.flatMap(allCallees).toSet + r.callsite.callee.get.callee
/**
* Break cycles in the inline request graph by removing callsites.
@@ -254,236 +174,454 @@ 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 {
- case x :: xs =>
+ def isReachable(start: Set[MethodNode], goal: MethodNode): Boolean = {
+ @tailrec def reachableImpl(check: Set[MethodNode], visited: Set[MethodNode]): Boolean = {
+ if (check.isEmpty) false
+ else {
+ val x = check.head
if (x == goal) true
- else if (visited(x)) reachableImpl(xs, visited)
+ else if (visited(x)) reachableImpl(check - x, visited)
else {
- val callees = inlineRequestsForMethod(x).map(_.callee.get.callee)
- reachableImpl(xs ::: callees.toList, visited + x)
+ val callees = nonElidedRequests(x).flatMap(allCallees)
+ reachableImpl(check - x ++ callees, visited + x)
}
-
- case Nil =>
- false
+ }
}
- reachableImpl(List(start), Set.empty)
+ reachableImpl(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(allCallees(r), r.callsite.callsiteMethod))
+ elided += r
else
result += r
+ ()
}
result.toList
}
// 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)
- inlineRequestsForCallee.forall(visited)
+ val inlineRequestsForCallees = allCallees(r).flatMap(nonElidedRequests)
+ inlineRequestsForCallees.forall(visited)
})
assert(leaves.nonEmpty, requests)
leaves ::: leavesFirst(others, visited ++ leaves)
}
}
- leavesFirst(breakInlineCycles(requests))
+ leavesFirst(breakInlineCycles)
}
-
/**
- * Copy and adapt the instructions of a method to a callsite.
+ * Given an InlineRequest(mainCallsite, post = List(postCallsite)), the postCallsite is a callsite
+ * in the method `mainCallsite.callee`. Once the mainCallsite is inlined into the target method
+ * (mainCallsite.callsiteMethod), we need to find the cloned callsite that corresponds to the
+ * postCallsite so we can inline that into the target method as well.
*
- * Preconditions:
- * - 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
+ * However, it is possible that there is no cloned callsite at all that corresponds to the
+ * postCallsite, for example if the corresponding callsite already inlined. Example:
+ *
+ * def a() = 1
+ * def b() = a() + 2
+ * def c() = b() + 3
+ * def d() = c() + 4
+ *
+ * We have the following callsite objects in the call graph:
+ *
+ * c1 = a() in b
+ * c2 = b() in c
+ * c3 = c() in d
*
- * @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
+ * Assume we have the following inline request
+ * r = InlineRequest(c3,
+ * post = List(InlineRequest(c2,
+ * post = List(InlineRequest(c1, post = Nil)))))
+ *
+ * But before inlining r, assume a separate InlineRequest(c2, post = Nil) is inlined first. We get
+ *
+ * c1' = a() in c // added to the call graph
+ * c1.inlinedClones += (c1' at c2) // remember that c1' was created when inlining c2
+ * ~c2~ // c2 is removed from the call graph
+ *
+ * If we now inline r, we first inline c3. We get
+ *
+ * c1'' = a() in d // added to call graph
+ * c1'.inlinedClones += (c1'' at c3) // remember that c1'' was created when inlining c3
+ * ~c3~
+ *
+ * Now we continue with the post-requests for r, i.e. c2.
+ * - we try to find the clone of c2 that was created when inlining c3 - but there is none. c2
+ * was already inlined before
+ * - we continue with the post-request of c2: c1
+ * - we search for the callsite of c1 that was cloned when inlining c2, we find c1'
+ * - recursively we search for the callsite of c1' that was cloned when inlining c3, we find c1''
+ * - so we create an inline request for c1''
*/
- 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 adaptPostRequestForMainCallsite(post: InlineRequest, mainCallsite: Callsite): List[InlineRequest] = {
+ def impl(post: InlineRequest, at: Callsite): List[InlineRequest] = {
+ post.callsite.inlinedClones.find(_.clonedWhenInlining == at) match {
+ case Some(clonedCallsite) =>
+ List(InlineRequest(clonedCallsite.callsite, post.post, post.reason))
+ case None =>
+ post.post.flatMap(impl(_, post.callsite)).flatMap(impl(_, at))
}
+ }
+ impl(post, mainCallsite)
+ }
+
+ class UndoLog(active: Boolean = true) {
+ import java.util.{ ArrayList => JArrayList }
+
+ private var actions = List.empty[() => Unit]
+ private var methodStateSaved = false
+
+ def apply(a: => Unit): Unit = if (active) actions = (() => a) :: actions
+ def rollback(): Unit = if (active) actions.foreach(_.apply())
- // 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 _ => ()
+ def saveMethodState(methodNode: MethodNode): Unit = if (active && !methodStateSaved) {
+ methodStateSaved = true
+ val currentInstructions = methodNode.instructions.toArray
+ val currentLocalVariables = new JArrayList(methodNode.localVariables)
+ val currentTryCatchBlocks = new JArrayList(methodNode.tryCatchBlocks)
+ val currentMaxLocals = methodNode.maxLocals
+ val currentMaxStack = methodNode.maxStack
+
+ apply {
+ // `methodNode.instructions.clear()` doesn't work: it keeps the `prev` / `next` / `index` of
+ // instruction nodes. `instructions.removeAll(true)` would work, but is not public.
+ methodNode.instructions.iterator.asScala.toList.foreach(methodNode.instructions.remove)
+ for (i <- currentInstructions) methodNode.instructions.add(i)
+
+ methodNode.localVariables.clear()
+ methodNode.localVariables.addAll(currentLocalVariables)
+
+ methodNode.tryCatchBlocks.clear()
+ methodNode.tryCatchBlocks.addAll(currentTryCatchBlocks)
+
+ methodNode.maxLocals = currentMaxLocals
+ methodNode.maxStack = currentMaxStack
}
+ }
+ }
- // 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)
+ val NoUndoLogging = new UndoLog(active = false)
+
+ /**
+ * Inline the callsite 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, undo: UndoLog = NoUndoLogging): List[CannotInlineWarning] = {
+ def doInline(undo: UndoLog, callRollback: Boolean = false): List[CannotInlineWarning] = {
+ InlineLog.withInlineLogging(request) {
+ inlineCallsite(request.callsite, undo)
+ } {
+ val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite))
+ val warnings = postRequests.flatMap(inline(_, undo))
+ if (callRollback && warnings.nonEmpty) {
+ undo.rollback()
+ InlineLog(InlineLogRollback(request, warnings))
}
- argStores.add(new VarInsnNode(ASTORE, nextLocalIndex))
- nextLocalIndex += 1
+ warnings
}
+ }
- // We just use an asm.Type here, no need to create the MethodBType.
- val calleAsmType = asm.Type.getMethodType(callee.desc)
+ def inlinedByPost(insns: List[AbstractInsnNode]): Boolean =
+ insns.nonEmpty && insns.forall(ins => request.post.exists(_.callsite.callsiteInstruction == ins))
- 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
+ canInlineCallsite(request.callsite) match {
+ case None =>
+ doInline(undo)
+
+ case Some((_, illegalAccessInsns)) if inlinedByPost(illegalAccessInsns) =>
+ // speculatively inline, roll back if an illegalAccessInsn cannot be eliminated
+ if (undo == NoUndoLogging) doInline(new UndoLog(), callRollback = true)
+ else doInline(undo)
+
+ case Some((w, _)) =>
+ InlineLog(InlineLogFail(request, w))
+ List(w)
+ }
+ }
+
+ /**
+ * Copy and adapt the instructions of a method to a callsite.
+ *
+ * Preconditions:
+ * - The callsite can safely be inlined (canInlineBody is true)
+ * - The maxLocals and maxStack values of the callsite method are correctly computed
+ *
+ * @return A map associating instruction nodes of the callee with the corresponding cloned
+ * instruction in the callsite method.
+ */
+ def inlineCallsite(callsite: Callsite, undo: UndoLog = NoUndoLogging): Unit = {
+ import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass, sourceFilePath}
+
+ // Inlining requires the callee not to have unreachable code, the analyzer used below should not
+ // return any `null` frames. Note that inlining a method can create unreachable code. Example:
+ // def f = throw e
+ // def g = f; println() // println is unreachable after inlining f
+ // If we have an inline request for a call to g, and f has been already inlined into g, we
+ // need to run DCE on g's body before inlining g.
+ localOpt.minimalRemoveUnreachableCode(callee, calleeDeclarationClass.internalName)
+
+ // If the callsite was eliminated by DCE, do nothing.
+ if (!callGraph.containsCallsite(callsite)) return
+
+ // New labels for the cloned instructions
+ val labelsMap = cloneLabels(callee)
+ val sameSourceFile = sourceFilePath match {
+ case Some(calleeSource) => byteCodeRepository.compilingClasses.get(callsiteClass.internalName) match {
+ case Some((_, `calleeSource`)) => true
+ case _ => false
}
+ case _ => false
+ }
+ val (clonedInstructions, instructionMap, targetHandles) = cloneInstructions(callee, labelsMap, keepLineNumbers = sameSourceFile)
+
+ // 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 _ => ()
+ }
- 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)
+ // 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 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 just use an asm.Type here, no need to create the MethodBType.
+ val calleAsmType = asm.Type.getMethodType(callee.desc)
+ val calleeParamTypes = calleAsmType.getArgumentTypes
- for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
- val frame = analyzer.frameAt(originalReturn)
- var stackHeight = frame.getStackSize
+ 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
+ }
- val inlinedReturn = instructionMap(originalReturn)
- val returnReplacement = new InsnList
+ 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)
+ }
- def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize)
+ // 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 non-void methods, store the stack top into the return local variable
- if (hasReturnValue) {
- returnReplacement add returnValueStore(originalReturn)
- stackHeight -= 1
- }
+ for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) {
+ val frame = analyzer.frameAt(originalReturn)
+ var stackHeight = frame.getStackSize
- // drop the rest of the stack
- for (i <- 0 until stackHeight) drop(i)
+ val inlinedReturn = instructionMap(originalReturn)
+ val returnReplacement = new InsnList
- returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
- clonedInstructions.insert(inlinedReturn, returnReplacement)
- clonedInstructions.remove(inlinedReturn)
- }
+ def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize)
- // Load instruction for the return value
+ // for non-void methods, store the stack top into the return local variable
if (hasReturnValue) {
- val retVarLoad = {
- val opc = returnType.getOpcode(ILOAD)
- new VarInsnNode(opc, returnValueIndex)
- }
- clonedInstructions.insert(postCallLabel, retVarLoad)
+ returnReplacement add returnValueStore(originalReturn)
+ stackHeight -= 1
}
- 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 =>
- }
+ // drop the rest of the stack
+ for (i <- 0 until stackHeight) drop(i)
- 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)
-
- case None =>
- }
+ returnReplacement add new JumpInsnNode(GOTO, postCallLabel)
+ clonedInstructions.insert(inlinedReturn, returnReplacement)
+ clonedInstructions.remove(inlinedReturn)
+ }
- case _ =>
+ // Load instruction for the return value
+ if (hasReturnValue) {
+ val retVarLoad = {
+ val opc = returnType.getOpcode(ILOAD)
+ new VarInsnNode(opc, returnValueIndex)
}
- // Remove the elided invocation from the call graph
- callGraph.callsites.remove(callsiteInstruction)
+ clonedInstructions.insert(postCallLabel, retVarLoad)
+ }
- // Inlining a method body can render some code unreachable, see example above (in runInliner).
- unreachableCodeEliminated -= callsiteMethod
+ undo.saveMethodState(callsiteMethod)
- callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals
- callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight)
+ callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions)
+ callsiteMethod.instructions.remove(callsiteInstruction)
- None
+ 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
}
+
+ callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode))
+
+ val added = addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles)
+ undo { removeIndyLambdaImplMethod(callsiteClass.internalName, added) }
+
+ callGraph.addIfMissing(callee, calleeDeclarationClass)
+
+ 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, _))
+ }
+
+ // 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
+ val newCallsite = originalCallsite.copy(
+ callsiteInstruction = newCallsiteIns,
+ callsiteMethod = callsiteMethod,
+ callsiteClass = callsiteClass,
+ argInfos = argInfos,
+ callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight
+ )
+ val clonedCallsite = ClonedCallsite(newCallsite, callsite)
+ originalCallsite.inlinedClones += clonedCallsite
+ callGraph.addCallsite(newCallsite)
+ undo {
+ originalCallsite.inlinedClones -= clonedCallsite
+ callGraph.removeCallsite(newCallsite.callsiteInstruction, newCallsite.callsiteMethod)
+ }
+ }
+
+ callGraph.closureInstantiations(callee).valuesIterator foreach { originalClosureInit =>
+ val newIndy = instructionMap(originalClosureInit.lambdaMetaFactoryCall.indy).asInstanceOf[InvokeDynamicInsnNode]
+ val capturedArgInfos = originalClosureInit.capturedArgInfos flatMap mapArgInfo
+ val newClosureInit = ClosureInstantiation(
+ originalClosureInit.lambdaMetaFactoryCall.copy(indy = newIndy),
+ callsiteMethod,
+ callsiteClass,
+ capturedArgInfos)
+ originalClosureInit.inlinedClones += newClosureInit
+ callGraph.addClosureInstantiation(newClosureInit)
+ undo {
+ callGraph.removeClosureInstantiation(newClosureInit.lambdaMetaFactoryCall.indy, newClosureInit.ownerMethod)
+ }
+ }
+
+ // Remove the elided invocation from the call graph
+ callGraph.removeCallsite(callsiteInstruction, callsiteMethod)
+ undo { callGraph.addCallsite(callsite) }
+
+ // Inlining a method body can render some code unreachable, see example above in this method.
+ unreachableCodeEliminated -= callsiteMethod
}
/**
- * Check whether an inling can be performed. Parmeters are described in method [[inline]].
+ * Check whether an inlining can be performed. This method performs tests that don't change even
+ * if the body of the callee is changed by the inliner / optimizer, so it can be used early
+ * (when looking at the call graph and collecting inline requests for the program).
+ *
+ * The tests that inspect the callee's instructions are implemented in method `canInlineBody`,
+ * which is queried when performing an inline.
+ *
* @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 earlyCanInlineCheck(callsite: Callsite): Option[CannotInlineWarning] = {
+ import callsite.{callsiteMethod, callsiteClass}
+ val Right(callsiteCallee) = callsite.callee
+ import callsiteCallee.{callee, calleeDeclarationClass}
+
+ if (isSynchronizedMethod(callee)) {
+ // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking
+ // in finally. But it's probably not worth the effort, scala never emits synchronized methods.
+ Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated))
+ } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) {
+ Some(StrictfpMismatch(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
+ } else
+ None
+ }
+
+ /**
+ * Check whether the body of the callee contains any instructions that prevent the callsite from
+ * being inlined. See also method `earlyCanInlineCheck`.
+ *
+ * The result of this check depends on changes to the callee method's body. For example, if the
+ * callee initially invokes a private method, it cannot be inlined into a different class. If the
+ * private method is inlined into the callee, inlining the callee becomes possible. Therefore
+ * we don't query it while traversing the call graph and selecting callsites to inline - it might
+ * rule out callsites that can be inlined just fine.
+ *
+ * Returns
+ * - `None` if the callsite can be inlined
+ * - `Some((message, Nil))` if there was an issue performing the access checks, for example
+ * because of a missing classfile
+ * - `Some((message, instructions))` if inlining `instructions` into the callsite method would
+ * cause an IllegalAccessError
+ */
+ def canInlineCallsite(callsite: Callsite): Option[(CannotInlineWarning, List[AbstractInsnNode])] = {
+ 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"
@@ -511,31 +649,30 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
if (codeSizeOKForInlining(callsiteMethod, callee)) {
- Some(ResultingMethodTooLarge(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
- } else if (isSynchronizedMethod(callee)) {
- // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking
- // in finally. But it's probably not worth the effort, scala never emits synchronized methods.
- Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc))
- } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) {
- Some(StrictfpMismatch(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
+ val warning = ResultingMethodTooLarge(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)
+ Some((warning, Nil))
} else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) {
- Some(MethodWithHandlerCalledOnNonEmptyStack(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc))
- } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) map {
- case (illegalAccessIns, None) =>
- IllegalAccessInstruction(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, illegalAccessIns)
-
- case (illegalAccessIns, Some(warning)) =>
- IllegalAccessCheckFailed(
- calleeDeclarationClass.internalName, callee.name, callee.desc,
- callsiteClass.internalName, illegalAccessIns, warning)
+ val warning = MethodWithHandlerCalledOnNonEmptyStack(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)
+ Some((warning, Nil))
+ } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) match {
+ case Right(Nil) =>
+ None
+
+ case Right(illegalAccessInsns) =>
+ val warning = IllegalAccessInstruction(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, illegalAccessInsns.head)
+ Some((warning, illegalAccessInsns))
+
+ case Left((illegalAccessIns, cause)) =>
+ val warning = IllegalAccessCheckFailed(
+ calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated,
+ callsiteClass.internalName, illegalAccessIns, cause)
+ Some((warning, Nil))
}
}
@@ -545,7 +682,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* (A2) C and D are members of the same run-time package
*/
def classIsAccessible(accessed: BType, from: ClassBType): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match {
- // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ // TODO: A2 requires "same run-time package", which seems to be package + classloader (JVMS 5.3.). is the below ok?
case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName)
case a: ArrayBType => classIsAccessible(a.elementType, from)
case _: PrimitiveBType => Right(true)
@@ -587,7 +724,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
* type from there (https://github.com/scala-opt/scala/issues/13).
*/
def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType, from: ClassBType): Either[OptimizerWarning, Boolean] = {
- // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ // TODO: B3 requires "same run-time package", which seems to be package + classloader (JVMS 5.3.). is the below ok?
def samePackageAsDestination = memberDeclClass.packageInternalName == from.packageInternalName
def targetObjectConformsToDestinationClass = false // needs type propagation analysis, see above
@@ -624,13 +761,14 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
/**
- * Returns the first instruction in the `instructions` list that would cause a
- * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`.
- *
- * If validity of some instruction could not be checked because an error occurred, the instruction
- * is returned together with a warning message that describes the problem.
+ * Returns
+ * - `Right(Nil)` if all instructions can be safely inlined
+ * - `Right(insns)` if inlining any of `insns` would cause a [[java.lang.IllegalAccessError]]
+ * when inlined into the `destinationClass`
+ * - `Left((insn, warning))` if validity of some instruction could not be checked because an
+ * error occurred
*/
- def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
+ def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Either[(AbstractInsnNode, OptimizerWarning), List[AbstractInsnNode]] = {
/**
* Check if `instruction` can be transplanted to `destinationClass`.
*
@@ -759,17 +897,15 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
val it = instructions.iterator.asScala
- @tailrec def find: Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
- if (!it.hasNext) None // all instructions are legal
- else {
- val i = it.next()
- isLegal(i) match {
- case Left(warning) => Some((i, Some(warning))) // checking isLegal for i failed
- case Right(false) => Some((i, None)) // an illegal instruction was found
- case _ => find
- }
+ val illegalAccess = mutable.ListBuffer.empty[AbstractInsnNode]
+ while (it.hasNext) {
+ val i = it.next()
+ isLegal(i) match {
+ case Left(warning) => return Left((i, warning)) // checking isLegal for i failed
+ case Right(false) => illegalAccess += i // an illegal instruction was found
+ case _ =>
}
}
- find
+ Right(illegalAccess.toList)
}
}
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..63360e17ff
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
@@ -0,0 +1,339 @@
+/* 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
+import scala.collection.JavaConverters._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{AbstractInsnNode, MethodInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.BackendReporting.{CalleeNotFinal, OptimizerWarning}
+
+class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
+ import bTypes._
+ import callGraph._
+
+ final case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) {
+ // invariant: all post inline requests denote callsites in the callee of the main callsite
+ for (pr <- post) assert(pr.callsite.callsiteMethod == callsite.callee.get.callee, s"Callsite method mismatch: main $callsite - post ${pr.callsite}")
+ }
+
+ def canInlineFromSource(sourceFilePath: Option[String]) = compilerSettings.optInlineGlobal || sourceFilePath.isDefined
+
+ /**
+ * 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, _, _, _, _, _, _, callsiteWarning)), _, _, _, pos, _, _) =>
+ inlineRequest(callsite, requests) match {
+ case Some(Right(req)) => requests += req
+
+ case Some(Left(w)) =>
+ if (w.emitWarning(compilerSettings)) {
+ backendReporting.inlinerWarning(callsite.callsitePosition, w.toString)
+ }
+
+ case None =>
+ if (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings))
+ backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ callsiteWarning.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
+ }
+
+ private def isTraitStaticSuperAccessorName(s: String) = s.endsWith("$")
+ private def traitStaticSuperAccessorName(s: String) = s + "$"
+
+ private def isTraitSuperAccessor(method: MethodNode, owner: ClassBType): Boolean = {
+ owner.isInterface == Right(true) && BytecodeUtils.isStaticMethod(method) && isTraitStaticSuperAccessorName(method.name)
+ }
+
+ private def findSingleCall(method: MethodNode, such: MethodInsnNode => Boolean): Option[MethodInsnNode] = {
+ @tailrec def noMoreInvoke(insn: AbstractInsnNode): Boolean = {
+ insn == null || (!insn.isInstanceOf[MethodInsnNode] && noMoreInvoke(insn.getNext))
+ }
+ @tailrec def find(insn: AbstractInsnNode): Option[MethodInsnNode] = {
+ if (insn == null) None
+ else insn match {
+ case mi: MethodInsnNode =>
+ if (such(mi) && noMoreInvoke(insn.getNext)) Some(mi)
+ else None
+ case _ =>
+ find(insn.getNext)
+ }
+ }
+ find(method.instructions.getFirst)
+ }
+ private def superAccessorInvocation(method: MethodNode): Option[MethodInsnNode] =
+ findSingleCall(method, mi => mi.itf && mi.getOpcode == Opcodes.INVOKESTATIC && isTraitStaticSuperAccessorName(mi.name))
+
+ private def isMixinForwarder(method: MethodNode, owner: ClassBType): Boolean = {
+ owner.isInterface == Right(false) &&
+ !BytecodeUtils.isStaticMethod(method) &&
+ (superAccessorInvocation(method) match {
+ case Some(mi) => mi.name == traitStaticSuperAccessorName(method.name)
+ case _ => false
+ })
+ }
+
+ private def isTraitSuperAccessorOrMixinForwarder(method: MethodNode, owner: ClassBType): Boolean = {
+ isTraitSuperAccessor(method, owner) || isMixinForwarder(method, owner)
+ }
+
+
+ /**
+ * 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.
+ *
+ * @return `None` if this callsite should not be inlined according to the active heuristic
+ * `Some(Left)` if the callsite cannot be inlined (for example because that would cause
+ * an IllegalAccessError) but should be according to the heuristic
+ * TODO: what if a downstream inline request would cause an IAE and we don't create an
+ * InlineRequest for the original callsite? new subclass of OptimizerWarning.
+ * `Some(Right)` if the callsite should be and can be inlined
+ */
+ def inlineRequest(callsite: Callsite, selectedRequestsForCallee: Set[InlineRequest]): Option[Either[OptimizerWarning, InlineRequest]] = {
+ def requestIfCanInline(callsite: Callsite, reason: String): Option[Either[OptimizerWarning, InlineRequest]] = {
+ val callee = callsite.callee.get
+ if (!callee.safeToInline) {
+ if (callsite.isInlineAnnotated && callee.canInlineFromSource) {
+ // By default, we only emit inliner warnings for methods annotated @inline. However, we don't
+ // want to be unnecessarily noisy with `-opt-warnings:_`: for example, the inliner heuristic
+ // would attempt to inline `Function1.apply$sp$II`, as it's higher-order (the receiver is
+ // a function), and it's concrete (forwards to `apply`). But because it's non-final, it cannot
+ // be inlined. So we only create warnings here for methods annotated @inline.
+ Some(Left(CalleeNotFinal(
+ callee.calleeDeclarationClass.internalName,
+ callee.callee.name,
+ callee.callee.desc,
+ callsite.isInlineAnnotated)))
+ } else None
+ } else inliner.earlyCanInlineCheck(callsite) match {
+ case Some(w) => Some(Left(w))
+ case None =>
+ val postInlineRequest: List[InlineRequest] = {
+ val postCall =
+ if (isTraitSuperAccessor(callee.callee, callee.calleeDeclarationClass)) {
+ // scala-dev#259: when inlining a trait super accessor, also inline the callsite to the default method
+ val implName = callee.callee.name.dropRight(1)
+ findSingleCall(callee.callee, mi => mi.itf && mi.getOpcode == Opcodes.INVOKESPECIAL && mi.name == implName)
+ } else {
+ // scala-dev#259: when inlining a mixin forwarder, also inline the callsite to the static super accessor
+ superAccessorInvocation(callee.callee)
+ }
+ postCall.flatMap(call => {
+ callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass)
+ val maybeCallsite = callGraph.findCallSite(callee.callee, call)
+ maybeCallsite.flatMap(requestIfCanInline(_, reason).flatMap(_.right.toOption))
+ }).toList
+ }
+ Some(Right(InlineRequest(callsite, postInlineRequest, reason)))
+ }
+ }
+
+ // scala-dev#259: don't inline into static accessors and mixin forwarders
+ if (isTraitSuperAccessorOrMixinForwarder(callsite.callsiteMethod, callsite.callsiteClass)) None
+ else {
+ val callee = callsite.callee.get
+ compilerSettings.YoptInlineHeuristics.value match {
+ case "everything" =>
+ val reason = if (compilerSettings.YoptLogInline.isSetByUser) "the inline strategy is \"everything\"" else null
+ requestIfCanInline(callsite, reason)
+
+ case "at-inline-annotated" =>
+ def reason = if (!compilerSettings.YoptLogInline.isSetByUser) null else {
+ val what = if (callee.annotatedInline) "callee" else "callsite"
+ s"the $what is annotated `@inline`"
+ }
+ if (callsite.isInlineAnnotated && !callsite.isNoInlineAnnotated) requestIfCanInline(callsite, reason)
+ else None
+
+ case "default" =>
+ def reason = if (!compilerSettings.YoptLogInline.isSetByUser) null else {
+ if (callsite.isInlineAnnotated) {
+ val what = if (callee.annotatedInline) "callee" else "callsite"
+ s"the $what is annotated `@inline`"
+ } else {
+ val paramNames = Option(callee.callee.parameters).map(_.asScala.map(_.name).toVector)
+ def param(i: Int) = {
+ def syn = s"<param $i>"
+ paramNames.fold(syn)(v => v.applyOrElse(i, (_: Int) => syn))
+ }
+ def samInfo(i: Int, sam: String, arg: String) = s"the argument for parameter (${param(i)}: $sam) is a $arg"
+ val argInfos = for ((i, sam) <- callee.samParamTypes; info <- callsite.argInfos.get(i)) yield {
+ val argKind = info match {
+ case FunctionLiteral => "function literal"
+ case ForwardedParam(_) => "parameter of the callsite method"
+ }
+ samInfo(i, sam.internalName.split('/').last, argKind)
+ }
+ s"the callee is a higher-order method, ${argInfos.mkString(", ")}"
+ }
+ }
+ def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists {
+ case (index, _) => callsite.argInfos.contains(index)
+ })
+ if (!callsite.isNoInlineAnnotated && (callsite.isInlineAnnotated || shouldInlineHO)) requestIfCanInline(callsite, reason)
+ 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/InstructionResultSize.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala
deleted file mode 100644
index 8d744f6d13..0000000000
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala
+++ /dev/null
@@ -1,240 +0,0 @@
-package scala.tools.nsc.backend.jvm.opt
-
-import scala.annotation.switch
-import scala.tools.asm.{Handle, Type, Opcodes}
-import scala.tools.asm.tree._
-
-object InstructionResultSize {
- import Opcodes._
- def apply(instruction: AbstractInsnNode): Int = (instruction.getOpcode: @switch) match {
- // The order of opcodes is (almost) the same as in Opcodes.java
- case ACONST_NULL => 1
-
- case ICONST_M1 |
- ICONST_0 |
- ICONST_1 |
- ICONST_2 |
- ICONST_3 |
- ICONST_4 |
- ICONST_5 => 1
-
- case LCONST_0 |
- LCONST_1 => 2
-
- case FCONST_0 |
- FCONST_1 |
- FCONST_2 => 1
-
- case DCONST_0 |
- DCONST_1 => 2
-
- case BIPUSH |
- SIPUSH => 1
-
- case LDC =>
- instruction.asInstanceOf[LdcInsnNode].cst match {
- case _: java.lang.Integer |
- _: java.lang.Float |
- _: String |
- _: Type |
- _: Handle => 1
-
- case _: java.lang.Long |
- _: java.lang.Double => 2
- }
-
- case ILOAD |
- FLOAD |
- ALOAD => 1
-
- case LLOAD |
- DLOAD => 2
-
- case IALOAD |
- FALOAD |
- AALOAD |
- BALOAD |
- CALOAD |
- SALOAD => 1
-
- case LALOAD |
- DALOAD => 2
-
- case ISTORE |
- LSTORE |
- FSTORE |
- DSTORE |
- ASTORE => 0
-
- case IASTORE |
- LASTORE |
- FASTORE |
- DASTORE |
- AASTORE |
- BASTORE |
- CASTORE |
- SASTORE => 0
-
- case POP |
- POP2 => 0
-
- case DUP |
- DUP_X1 |
- DUP_X2 |
- DUP2 |
- DUP2_X1 |
- DUP2_X2 |
- SWAP => throw new IllegalArgumentException("Can't compute the size of DUP/SWAP without knowing what's on stack top")
-
- case IADD |
- FADD => 1
-
- case LADD |
- DADD => 2
-
- case ISUB |
- FSUB => 1
-
- case LSUB |
- DSUB => 2
-
- case IMUL |
- FMUL => 1
-
- case LMUL |
- DMUL => 2
-
- case IDIV |
- FDIV => 1
-
- case LDIV |
- DDIV => 2
-
- case IREM |
- FREM => 1
-
- case LREM |
- DREM => 2
-
- case INEG |
- FNEG => 1
-
- case LNEG |
- DNEG => 2
-
- case ISHL |
- ISHR => 1
-
- case LSHL |
- LSHR => 2
-
- case IUSHR => 1
-
- case LUSHR => 2
-
- case IAND |
- IOR |
- IXOR => 1
-
- case LAND |
- LOR |
- LXOR => 2
-
- case IINC => 1
-
- case I2F |
- L2I |
- L2F |
- F2I |
- D2I |
- D2F |
- I2B |
- I2C |
- I2S => 1
-
- case I2L |
- I2D |
- L2D |
- F2L |
- F2D |
- D2L => 2
-
- case LCMP |
- FCMPL |
- FCMPG |
- DCMPL |
- DCMPG => 1
-
- case IFEQ |
- IFNE |
- IFLT |
- IFGE |
- IFGT |
- IFLE => 0
-
- case IF_ICMPEQ |
- IF_ICMPNE |
- IF_ICMPLT |
- IF_ICMPGE |
- IF_ICMPGT |
- IF_ICMPLE |
- IF_ACMPEQ |
- IF_ACMPNE => 0
-
- case GOTO => 0
-
- case JSR => throw new IllegalArgumentException("Subroutines are not supported.")
-
- case RET => 0
-
- case TABLESWITCH |
- LOOKUPSWITCH => 0
-
- case IRETURN |
- FRETURN |
- ARETURN => 1
-
- case LRETURN |
- DRETURN => 2
-
- case RETURN => 0
-
- case GETSTATIC => Type.getType(instruction.asInstanceOf[FieldInsnNode].desc).getSize
-
- case PUTSTATIC => 0
-
- case GETFIELD => Type.getType(instruction.asInstanceOf[FieldInsnNode].desc).getSize
-
- case PUTFIELD => 0
-
- case INVOKEVIRTUAL |
- INVOKESPECIAL |
- INVOKESTATIC |
- INVOKEINTERFACE =>
- val desc = instruction.asInstanceOf[MethodInsnNode].desc
- Type.getReturnType(desc).getSize
-
- case INVOKEDYNAMIC =>
- val desc = instruction.asInstanceOf[InvokeDynamicInsnNode].desc
- Type.getReturnType(desc).getSize
-
- case NEW => 1
-
- case NEWARRAY |
- ANEWARRAY |
- ARRAYLENGTH => 1
-
- case ATHROW => 0
-
- case CHECKCAST |
- INSTANCEOF => 1
-
- case MONITORENTER |
- MONITOREXIT => 0
-
- case MULTIANEWARRAY => 1
-
- case IFNULL |
- IFNONNULL => 0
- }
-}
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..9c22b09cdd 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -7,79 +7,180 @@ package scala.tools.nsc
package backend.jvm
package opt
-import scala.annotation.switch
-import scala.tools.asm.Opcodes
-import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter}
+import scala.annotation.{tailrec, switch}
+
+import scala.tools.asm.Type
+import scala.tools.asm.tree.analysis.Frame
+import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.mutable
+import scala.collection.JavaConverters._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.analysis._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
/**
- * Optimizations within a single method.
+ * Optimizations within a single method. Certain optimizations enable others, for example removing
+ * unreachable code can render a `try` block empty and enable removeEmptyExceptionHandlers. The
+ * latter in turn enables more unreachable code to be eliminated (the `catch` block), so there is
+ * a cyclic dependency. Optimizations that depend on each other are therefore executed in a loop
+ * until reaching a fixpoint.
+ *
+ * The optimizations marked UPSTREAM enable optimizations that were already executed, so they cause
+ * another iteration in the fixpoint loop.
+ *
+ * nullness optimizations: rewrite null-checking branches to GOTO if nullness is known
+ * + enables downstream
+ * - unreachable code (null / non-null branch becomes unreachable)
+ * - box-unbox elimination (may render an escaping consumer of a box unreachable)
+ * - stale stores (aload x is replaced by aconst_null if it's known null)
+ * - simplify jumps (replaces conditional jumps by goto, so may enable goto chains)
+ *
+ * unreachable code / DCE (removes instructions of basic blocks to which there is no branch)
+ * + enables downstream:
+ * - stale stores (loads may be eliminated, removing consumers of a store)
+ * - empty handlers (try blocks may become empty)
+ * - simplify jumps (goto l; [dead code]; l: ..) => remove goto
+ * - stale local variable descriptors
+ * - (not box-unbox, which is implemented using prod-cons, so it doesn't consider dead code)
+ *
+ * note that eliminating empty handlers and stale local variable descriptors is required for
+ * correctness, see the comment in the body of `methodOptimizations`.
+ *
+ * box-unbox elimination (eliminates box-unbox pairs within the same method)
+ * + enables UPSTREAM:
+ * - nullness optimizations (a box extraction operation (unknown nullness) may be rewritten to
+ * a read of a non-null local. example in doc comment of box-unbox implementation)
+ * - further box-unbox elimination (e.g. an Integer stored in a Tuple; eliminating the tuple may
+ * enable eliminating the Integer)
+ * + enables downstream:
+ * - copy propagation (new locals are introduced, may be aliases of existing)
+ * - stale stores (multi-value boxes where not all values are used)
+ * - redundant casts (`("a", "b")._1`: the generic `_1` method returns `Object`, a cast
+ * to String is added. The cast is redundant after eliminating the tuple.)
+ * - empty local variable descriptors (local variables that were holding the box may become unused)
+ *
+ * copy propagation (replaces LOAD n to the LOAD m for the smallest m that is an alias of n)
+ * + enables downstream:
+ * - stale stores (a stored value may not be loaded anymore)
+ * - store-load pairs (a load n may now be right after a store n)
+ * + NOTE: copy propagation is only executed once, in the first fixpoint loop iteration. none of
+ * the other optimizations enables further copy prop. we still run it as part of the loop
+ * because it requires unreachable code to be eliminated.
+ *
+ * stale stores (replace STORE by POP)
+ * + enables downstream:
+ * - push-pop (the new pop may be the single consumer for an instruction)
+ *
+ * redundant casts: eliminates casts that are statically known to succeed (uses type propagation)
+ * + enables UPSTREAM:
+ * - box-unbox elimination (a removed checkcast may be a box consumer)
+ * + enables downstream:
+ * - push-pop for closure allocation elimination (every indyLambda is followed by a checkcast, see SI-9540)
+ *
+ * push-pop (when a POP is the only consumer of a value, remove the POP and its producer)
+ * + enables UPSTREAM:
+ * - stale stores (if a LOAD is removed, a corresponding STORE may become stale)
+ * - box-unbox elimination (push-pop may eliminate a closure allocation, rendering a captured
+ * box non-escaping)
+ * + enables downstream:
+ * - store-load pairs (a variable may become non-live)
+ * - stale handlers (push-pop removes code)
+ * - simplify jumps (push-pop removes code)
+ *
+ * store-load pairs (remove `STORE x; LOAD x` if x is otherwise not used in the method)
+ * + enables downstream:
+ * - empty handlers (code is removes, a try block may become empty
+ * - simplify jumps (code is removed, a goto may become redundant for example)
+ * - stale local variable descriptors
*
- * unreachable code
- * - removes instructions 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 handlers (removes exception handlers whose try block is empty)
+ * + enables UPSTREAM:
+ * - unreachable code (catch block becomes unreachable)
+ * - box-unbox (a box may be escape in an operation in a dead handler)
+ * + enables downstream:
+ * - simplify jumps
*
- * 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
- * unreachable. in this case "unreachable code" is invoked recursively until reaching a fixpoint.
- * > for try blocks that are unreachable, "unreachable code" removes also the instructions of the
- * catch block, and the recursive invocation is not necessary.
+ * simplify jumps (various, like `GOTO l; l: ...`, see doc comments of individual optimizations)
+ * + enables UPSTREAM
+ * - unreachable code (`GOTO a; a: GOTO b; b: ...`, the first jump is changed to `GOTO b`, the second becomes unreachable)
+ * - store-load pairs (a `GOTO l; l: ...` is removed between store and load)
+ * - push-pop (`IFNULL l; l: ...` is replaced by `POP`)
*
- * simplify jumps
- * - various simplifications, see doc comments 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)
+ * The following cleanup optimizations don't enable any upstream optimizations, so they can be
+ * executed once at the end, when the above optimizations reach a fixpoint.
*
- * 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.
+ * empty local variable descriptors (removes unused variables from the local variable table)
+ * + enables downstream:
+ * - stale labels (labels that the entry points to, if not otherwise referenced)
+ *
+ * empty line numbers (eliminates line number nodes that describe no executable instructions)
+ * + enables downstream:
+ * - stale labels (label of the line number node, if not otherwise referenced)
+ *
+ * stale labels (eliminate labels that are not referenced, merge sequences of label definitions)
+ *
+ *
+ * Note on a method's maxLocals / maxStack: the backend only uses those values for running
+ * Analyzers. The values can be conservative approximations: if an optimization removes code and
+ * the maximal stack size is now smaller, the larger maxStack value will still work fine for
+ * running an Analyzer (just that frames allocate more space than required). The correct max
+ * values written to the bytecode are re-computed during classfile serialization.
+ * To keep things simpler, we don't update the max values in every optimization:
+ * - we do it in `removeUnreachableCodeImpl`, because it's quite straightforward
+ * - maxLocals is updated in `compactLocalVariables`, which runs at the end of method optimizations
+ *
+ *
+ * Note on updating the call graph: whenever an optimization eliminates a callsite or a closure
+ * instantiation, we eliminate the corresponding entry from the call graph.
*/
class LocalOpt[BT <: BTypes](val btypes: BT) {
import LocalOptImpls._
import btypes._
+ import coreBTypes._
+ import backendUtils._
+
+ val boxUnbox = new BoxUnbox(btypes)
+ import boxUnbox._
+
+ val copyProp = new CopyProp(btypes)
+ import copyProp._
/**
* 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
+ def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = {
+ // In principle, for the inliner, a single removeUnreachableCodeImpl would be enough. But that
+ // would potentially leave behind stale handlers (empty try block) which is not legal in the
+ // classfile. So we run both removeUnreachableCodeImpl and removeEmptyExceptionHandlers.
+ if (method.instructions.size == 0) return false // fast path for abstract methods
+ if (unreachableCodeEliminated(method)) return false // we know there is no unreachable code
+ if (!AsmAnalyzer.sizeOKForBasicValue(method)) return false // 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
// code unreachable and therefore requires running another round.
- def removalRound(): Set[AbstractInsnNode] = {
- val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
- val removedRecursively = if (removedInstructions.nonEmpty) {
+ def removalRound(): Boolean = {
+ val (insnsRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
+ if (insnsRemoved) {
val liveHandlerRemoved = removeEmptyExceptionHandlers(method).exists(h => liveLabels(h.start))
if (liveHandlerRemoved) removalRound()
- else Set.empty
- } else Set.empty
- removedInstructions ++ removedRecursively
+ }
+ insnsRemoved
}
- val removedInstructions = removalRound()
- if (removedInstructions.nonEmpty) removeUnusedLocalVariableNodes(method)()
+ val changed = removalRound()
+ if (changed) removeUnusedLocalVariableNodes(method)()
unreachableCodeEliminated += method
- removedInstructions
+ changed
}
/**
@@ -90,21 +191,13 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
* @return `true` if unreachable code was eliminated in some method, `false` otherwise.
*/
def methodOptimizations(clazz: ClassNode): Boolean = {
- !compilerSettings.YoptNone && clazz.methods.asScala.foldLeft(false) {
+ !compilerSettings.optNone && 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 analysis are unreachable.
- *
- * Also simplifies branching instructions, removes unused local variable descriptors, empty
- * exception handlers, unnecessary label declarations and empty line number nodes.
+ * Run method-level optimizations, see comment on class [[LocalOpt]].
*
* Returns `true` if the bytecode of `method` was changed.
*/
@@ -137,36 +230,151 @@ 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 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 (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName)
- val removedHandlers = removeEmptyExceptionHandlers(method)
- (removedInstructions.nonEmpty, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start)))
- } else {
- (false, false, false)
+ var currentTrace: String = null
+ val methodPrefix = {val p = compilerSettings.YoptTrace.value; if (p == "_") "" else p }
+ val doTrace = compilerSettings.YoptTrace.isSetByUser && s"$ownerClassName.${method.name}".startsWith(methodPrefix)
+ def traceIfChanged(optName: String): Unit = if (doTrace) {
+ val after = AsmUtils.textify(method)
+ if (currentTrace != after) {
+ println(s"after $optName")
+ println(after)
}
-
- val jumpsChanged = if (compilerSettings.YoptSimplifyJumps) simplifyJumps(method) else false
-
- // Eliminating live handlers and simplifying jump instructions may render more code
- // unreachable, so we need to run another round.
- if (liveHandlerRemoved || jumpsChanged) removalRound()
-
- codeRemoved || handlersRemoved || jumpsChanged
+ currentTrace = after
}
- val codeHandlersOrJumpsChanged = removalRound()
+ /**
+ * Runs the optimizations that depend on each other in a loop until reaching a fixpoint. See
+ * comment in class [[LocalOpt]].
+ *
+ * Returns a pair of booleans (codeChanged, requireEliminateUnusedLocals).
+ */
+ def removalRound(
+ requestNullness: Boolean,
+ requestDCE: Boolean,
+ requestBoxUnbox: Boolean,
+ requestStaleStores: Boolean,
+ requestPushPop: Boolean,
+ requestStoreLoad: Boolean,
+ firstIteration: Boolean,
+ maxRecursion: Int = 10): (Boolean, Boolean) = {
+ if (maxRecursion == 0) return (false, false)
+
+ traceIfChanged("beforeMethodOpt")
+
+ // NULLNESS OPTIMIZATIONS
+ val runNullness = compilerSettings.optNullnessTracking && requestNullness
+ val nullnessOptChanged = runNullness && nullnessOptimizations(method, ownerClassName)
+ traceIfChanged("nullness")
+
+ // UNREACHABLE CODE
+ // Both AliasingAnalyzer (used in copyProp) and ProdConsAnalyzer (used in eliminateStaleStores,
+ // boxUnboxElimination) require not having unreachable instructions (null frames).
+ val runDCE = (compilerSettings.optUnreachableCode && (requestDCE || nullnessOptChanged)) ||
+ compilerSettings.optBoxUnbox ||
+ compilerSettings.optCopyPropagation
+ val (codeRemoved, liveLabels) = if (runDCE) removeUnreachableCodeImpl(method, ownerClassName) else (false, Set.empty[LabelNode])
+ traceIfChanged("dce")
+
+ // BOX-UNBOX
+ val runBoxUnbox = compilerSettings.optBoxUnbox && (requestBoxUnbox || nullnessOptChanged)
+ val boxUnboxChanged = runBoxUnbox && boxUnboxElimination(method, ownerClassName)
+ traceIfChanged("boxUnbox")
+
+ // COPY PROPAGATION
+ val runCopyProp = compilerSettings.optCopyPropagation && (firstIteration || boxUnboxChanged)
+ val copyPropChanged = runCopyProp && copyPropagation(method, ownerClassName)
+ traceIfChanged("copyProp")
+
+ // STALE STORES
+ val runStaleStores = compilerSettings.optCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged)
+ val storesRemoved = runStaleStores && eliminateStaleStores(method, ownerClassName)
+ traceIfChanged("staleStores")
+
+ // REDUNDANT CASTS
+ val runRedundantCasts = compilerSettings.optRedundantCasts && (firstIteration || boxUnboxChanged)
+ val castRemoved = runRedundantCasts && eliminateRedundantCasts(method, ownerClassName)
+ traceIfChanged("redundantCasts")
+
+ // PUSH-POP
+ val runPushPop = compilerSettings.optCopyPropagation && (requestPushPop || firstIteration || storesRemoved || castRemoved)
+ val pushPopRemoved = runPushPop && eliminatePushPop(method, ownerClassName)
+ traceIfChanged("pushPop")
+
+ // STORE-LOAD PAIRS
+ val runStoreLoad = compilerSettings.optCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved)
+ val storeLoadRemoved = runStoreLoad && eliminateStoreLoad(method)
+ traceIfChanged("storeLoadPairs")
+
+ // STALE HANDLERS
+ val removedHandlers = if (runDCE) removeEmptyExceptionHandlers(method) else Set.empty[TryCatchBlockNode]
+ val handlersRemoved = removedHandlers.nonEmpty
+ val liveHandlerRemoved = removedHandlers.exists(h => liveLabels(h.start))
+ traceIfChanged("staleHandlers")
+
+ // SIMPLIFY JUMPS
+ // almost all of the above optimizations enable simplifying more jumps, so we just run it in every iteration
+ val runSimplifyJumps = compilerSettings.optSimplifyJumps
+ val jumpsChanged = runSimplifyJumps && simplifyJumps(method)
+ traceIfChanged("simplifyJumps")
+
+ // See doc comment in the beginning of this file (optimizations marked UPSTREAM)
+ val runNullnessAgain = boxUnboxChanged
+ val runDCEAgain = liveHandlerRemoved || jumpsChanged
+ val runBoxUnboxAgain = boxUnboxChanged || castRemoved || pushPopRemoved || liveHandlerRemoved
+ val runStaleStoresAgain = pushPopRemoved
+ val runPushPopAgain = jumpsChanged
+ val runStoreLoadAgain = jumpsChanged
+ val runAgain = runNullnessAgain || runDCEAgain || runBoxUnboxAgain || pushPopRemoved || runStaleStoresAgain || runPushPopAgain || runStoreLoadAgain
+
+ val downstreamRequireEliminateUnusedLocals = runAgain && removalRound(
+ requestNullness = runNullnessAgain,
+ requestDCE = runDCEAgain,
+ requestBoxUnbox = runBoxUnboxAgain,
+ requestStaleStores = runStaleStoresAgain,
+ requestPushPop = runPushPopAgain,
+ requestStoreLoad = runStoreLoadAgain,
+ firstIteration = false,
+ maxRecursion = maxRecursion - 1)._2
+
+ val requireEliminateUnusedLocals = downstreamRequireEliminateUnusedLocals ||
+ nullnessOptChanged || // nullness opt may eliminate stores / loads, rendering a local unused
+ codeRemoved || // see comment in method `methodOptimizations`
+ boxUnboxChanged || // box-unbox renders locals (holding boxes) unused
+ storesRemoved ||
+ storeLoadRemoved ||
+ handlersRemoved
+
+ val codeChanged = nullnessOptChanged || codeRemoved || boxUnboxChanged || castRemoved || copyPropChanged || storesRemoved || pushPopRemoved || storeLoadRemoved || handlersRemoved || jumpsChanged
+ (codeChanged, requireEliminateUnusedLocals)
+ }
- // (*) Removing stale local variable descriptors is required for correctness of unreachable-code
+ val (nullnessDceBoxesCastsCopypropPushpopOrJumpsChanged, requireEliminateUnusedLocals) = if (AsmAnalyzer.sizeOKForBasicValue(method)) {
+ // we run DCE even if the method is already in the `unreachableCodeEliminated` map: the DCE
+ // here is more thorough than `minimalRemoveUnreachableCode` that run before inlining.
+ val r = removalRound(
+ requestNullness = true,
+ requestDCE = true,
+ requestBoxUnbox = true,
+ requestStaleStores = true,
+ requestPushPop = true,
+ requestStoreLoad = true,
+ firstIteration = true)
+ if (compilerSettings.optUnreachableCode) unreachableCodeEliminated += method
+ r
+ } else (false, false)
+
+ // (*) Removing stale local variable descriptors is required for correctness, see comment in `methodOptimizations`
val localsRemoved =
- if (compilerSettings.YoptCompactLocals) compactLocalVariables(method) // also removes unused
- else if (compilerSettings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*)
+ if (compilerSettings.optCompactLocals) compactLocalVariables(method) // also removes unused
+ else if (requireEliminateUnusedLocals) removeUnusedLocalVariableNodes(method)() // (*)
else false
+ traceIfChanged("localVariables")
- val lineNumbersRemoved = if (compilerSettings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false
+ val lineNumbersRemoved = if (compilerSettings.optUnreachableCode) removeEmptyLineNumbers(method) else false
+ traceIfChanged("lineNumbers")
- val labelsRemoved = if (compilerSettings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false
+ val labelsRemoved = if (compilerSettings.optUnreachableCode) removeEmptyLabelNodes(method) else false
+ traceIfChanged("labels")
// 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.
@@ -174,53 +382,198 @@ class LocalOpt[BT <: BTypes](val btypes: BT) {
assert(nullOrEmpty(method.visibleLocalVariableAnnotations), method.visibleLocalVariableAnnotations)
assert(nullOrEmpty(method.invisibleLocalVariableAnnotations), method.invisibleLocalVariableAnnotations)
- unreachableCodeEliminated += method
-
- codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
+ nullnessDceBoxesCastsCopypropPushpopOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved
}
-}
+ /**
+ * Apply various optimizations based on nullness analysis information.
+ * - IFNULL / IFNONNULL are rewritten to GOTO if nullness is known
+ * - IF_ACMPEQ / IF_ACMPNE are rewritten to GOTO if the both references are known null, or if
+ * one is known null and the other known not-null
+ * - ALOAD is replaced by ACONST_NULL if the local is known to hold null
+ * - ASTORE of null is removed if the local is known to hold null
+ * - INSTANCEOF of null is replaced by `ICONST_0`
+ * - scala.runtime.BoxesRunTime.unboxToX(null) is rewritten to a zero-value load
+ */
+ def nullnessOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForNullness(method) && {
+ lazy val nullnessAnalyzer = new AsmAnalyzer(method, ownerClassName, new NullnessAnalyzer(btypes, method))
+
+ // When running nullness optimizations the method may still have unreachable code. Analyzer
+ // frames of unreachable instructions are `null`.
+ def frameAt(insn: AbstractInsnNode): Option[Frame[NullnessValue]] = Option(nullnessAnalyzer.frameAt(insn))
+
+ def nullness(insn: AbstractInsnNode, slot: Int): Option[NullnessValue] = {
+ frameAt(insn).map(_.getValue(slot))
+ }
+
+ def isNull(insn: AbstractInsnNode, slot: Int) = nullness(insn, slot).contains(NullValue)
+
+ // cannot change instructions while iterating, it gets the analysis out of synch (indexed by instructions)
+ val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]]
+
+ val it = method.instructions.iterator()
+ while (it.hasNext) it.next() match {
+ case vi: VarInsnNode if isNull(vi, vi.`var`) =>
+ if (vi.getOpcode == ALOAD)
+ toReplace(vi) = List(new InsnNode(ACONST_NULL))
+ else if (vi.getOpcode == ASTORE)
+ for (frame <- frameAt(vi) if frame.peekStack(0) == NullValue)
+ toReplace(vi) = List(getPop(1))
+
+ case ji: JumpInsnNode =>
+ val isIfNull = ji.getOpcode == IFNULL
+ val isIfNonNull = ji.getOpcode == IFNONNULL
+ if (isIfNull || isIfNonNull) for (frame <- frameAt(ji)) {
+ val nullness = frame.peekStack(0)
+ val taken = nullness == NullValue && isIfNull || nullness == NotNullValue && isIfNonNull
+ val avoided = nullness == NotNullValue && isIfNull || nullness == NullValue && isIfNonNull
+ if (taken || avoided) {
+ val jump = if (taken) List(new JumpInsnNode(GOTO, ji.label)) else Nil
+ toReplace(ji) = getPop(1) :: jump
+ }
+ } else {
+ val isIfEq = ji.getOpcode == IF_ACMPEQ
+ val isIfNe = ji.getOpcode == IF_ACMPNE
+ if (isIfEq || isIfNe) for (frame <- frameAt(ji)) {
+ val aNullness = frame.peekStack(1)
+ val bNullness = frame.peekStack(0)
+ val eq = aNullness == NullValue && bNullness == NullValue
+ val ne = aNullness == NullValue && bNullness == NotNullValue || aNullness == NotNullValue && bNullness == NullValue
+ val taken = isIfEq && eq || isIfNe && ne
+ val avoided = isIfEq && ne || isIfNe && eq
+ if (taken || avoided) {
+ val jump = if (taken) List(new JumpInsnNode(GOTO, ji.label)) else Nil
+ toReplace(ji) = getPop(1) :: getPop(1) :: jump
+ }
+ }
+ }
+
+ case ti: TypeInsnNode =>
+ if (ti.getOpcode == INSTANCEOF) for (frame <- frameAt(ti) if frame.peekStack(0) == NullValue) {
+ toReplace(ti) = List(getPop(1), new InsnNode(ICONST_0))
+ }
+
+ case mi: MethodInsnNode =>
+ if (isScalaUnbox(mi)) for (frame <- frameAt(mi) if frame.peekStack(0) == NullValue) {
+ toReplace(mi) = List(
+ getPop(1),
+ loadZeroForTypeSort(Type.getReturnType(mi.desc).getSort))
+ }
+
+ case _ =>
+ }
+
+ def removeFromCallGraph(insn: AbstractInsnNode): Unit = insn match {
+ case mi: MethodInsnNode => callGraph.removeCallsite(mi, method)
+ case _ =>
+ }
+
+ for ((oldOp, newOps) <- toReplace) {
+ for (newOp <- newOps) method.instructions.insertBefore(oldOp, newOp)
+ method.instructions.remove(oldOp)
+ removeFromCallGraph(oldOp)
+ }
+
+ toReplace.nonEmpty
+ }
+ }
-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
+ def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Boolean, Set[LabelNode]) = {
+ 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 changed = false
+ var maxLocals = parametersSize(method)
+ 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 because local numbers are 0-based
+
+ case i: IincInsnNode if isLive =>
+ maxLocals = math.max(maxLocals, i.`var` + 1)
+
+ case _ =>
+ if (!isLive || insn.getOpcode == NOP) {
// Instruction iterators allow removing during iteration.
// Removing is O(1): instructions are doubly linked list elements.
itr.remove()
- removedInstructions += ins
+ changed = true
+ insn match {
+ case invocation: MethodInsnNode => callGraph.removeCallsite(invocation, method)
+ case indy: InvokeDynamicInsnNode => callGraph.removeClosureInstantiation(indy, method)
+ case _ =>
+ }
}
}
i += 1
}
- (removedInstructions, liveLabels)
+ method.maxLocals = maxLocals
+ method.maxStack = maxStack
+ (changed, liveLabels)
}
/**
+ * Eliminate `CHECKCAST` instructions that are statically known to succeed. This is safe if the
+ * tested object is null: `null.asInstanceOf` always succeeds.
+ *
+ * The type of the tested object is determined using a NonLubbingTypeFlowAnalyzer. Note that this
+ * analysis collapses LUBs of non-equal references types to Object for simplicity. Example:
+ * given `B <: A <: Object`, the cast in `(if (..) new B else new A).asInstanceOf[A]` would not
+ * be eliminated.
+ *
+ * Note: we cannot replace `INSTANCEOF` tests by only looking at the types, `null.isInstanceOf`
+ * always returns false, so we'd also need nullness information.
+ */
+ def eliminateRedundantCasts(method: MethodNode, owner: InternalName): Boolean = {
+ AsmAnalyzer.sizeOKForBasicValue(method) && {
+ def isSubType(aRefDesc: String, bClass: InternalName): Boolean = aRefDesc == bClass || bClass == ObjectRef.internalName || {
+ (bTypeForDescriptorOrInternalNameFromClassfile(aRefDesc) conformsTo classBTypeFromParsedClassfile(bClass)).getOrElse(false)
+ }
+
+ lazy val typeAnalyzer = new NonLubbingTypeFlowAnalyzer(method, owner)
+
+ // cannot remove instructions while iterating, it gets the analysis out of synch (indexed by instructions)
+ val toRemove = mutable.Set.empty[TypeInsnNode]
+
+ val it = method.instructions.iterator()
+ while (it.hasNext) it.next() match {
+ case ti: TypeInsnNode if ti.getOpcode == CHECKCAST =>
+ val frame = typeAnalyzer.frameAt(ti)
+ val valueTp = frame.getValue(frame.stackTop)
+ if (valueTp.isReference && isSubType(valueTp.getType.getDescriptor, ti.desc)) {
+ toRemove += ti
+ }
+
+ case _ =>
+ }
+
+ toRemove foreach method.instructions.remove
+ toRemove.nonEmpty
+ }
+ }
+}
+
+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.
*
@@ -235,16 +588,16 @@ object LocalOptImpls {
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 : @switch) match {
+ start != end && ((start.getOpcode: @switch) match {
// FrameNode, LabelNode and LineNumberNode have opcode == -1.
- case -1 | Opcodes.GOTO => containsExecutableCode(start.getNext, end)
+ case -1 | GOTO => containsExecutableCode(start.getNext, end)
case _ => true
})
}
var removedHandlers = Set.empty[TryCatchBlockNode]
val handlersIter = method.tryCatchBlocks.iterator()
- while(handlersIter.hasNext) {
+ while (handlersIter.hasNext) {
val handler = handlersIter.next()
if (!containsExecutableCode(handler.start, handler.end)) {
removedHandlers += handler
@@ -263,9 +616,10 @@ object LocalOptImpls {
* same type or name.
*/
def removeUnusedLocalVariableNodes(method: MethodNode)(firstLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = {
- def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
+ @tailrec def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
start != end && (start match {
case v: VarInsnNode if v.`var` == varIndex => true
+ case i: IincInsnNode if i.`var` == varIndex => true
case _ => variableIsUsed(start.getNext, end, varIndex)
})
}
@@ -285,17 +639,6 @@ object LocalOptImpls {
}
/**
- * The number of local variable 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.
*
@@ -310,12 +653,9 @@ object LocalOptImpls {
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
- }
+ def addVar(varIns: AbstractInsnNode, slot: Int): Unit = {
+ val index = slot
+ 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
@@ -332,7 +672,7 @@ object LocalOptImpls {
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 VarInstruction(varIns, slot) => addVar(varIns, slot)
case _ =>
}
@@ -353,10 +693,12 @@ object LocalOptImpls {
// 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 VarInstruction(varIns, slot) =>
+ val oldIndex = slot
+ if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex) varIns match {
+ case vi: VarInsnNode => vi.`var` = renumber(slot)
+ case ii: IincInsnNode => ii.`var` = renumber(slot)
+ }
case _ =>
}
true
@@ -431,154 +773,181 @@ object LocalOptImpls {
// 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 jumpInsns = mutable.LinkedHashMap.empty[JumpInsnNode, Boolean]
- val iterator = method.instructions.iterator()
- while (iterator.hasNext) {
- val instruction = iterator.next()
+ for (insn <- method.instructions.iterator().asScala) insn match {
+ case l: LabelNode =>
+ activeHandlers ++= allHandlers.filter(_.start == l)
+ activeHandlers = activeHandlers.filter(_.end != l)
- instruction match {
- case l: LabelNode =>
- activeHandlers ++= allHandlers.filter(_.start == l)
- activeHandlers = activeHandlers.filter(_.end != l)
- case _ =>
+ case ji: JumpInsnNode =>
+ jumpInsns(ji) = activeHandlers.nonEmpty
+
+ case _ =>
+ }
+
+ var _jumpTargets: Set[AbstractInsnNode] = null
+ def jumpTargets = {
+ if (_jumpTargets == null) {
+ _jumpTargets = jumpInsns.keysIterator.map(_.label).toSet
}
+ _jumpTargets
+ }
- 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)
+ def removeJumpFromMap(jump: JumpInsnNode) = {
+ jumpInsns.remove(jump)
+ _jumpTargets = null
+ }
- if (!jumpRemoved) {
- changed = collapseJumpChains(instruction) || changed
- jumpRemoved = removeJumpToSuccessor(method, instruction)
+ def replaceJumpByPop(jump: JumpInsnNode) = {
+ removeJumpAndAdjustStack(method, jump)
+ removeJumpFromMap(jump)
+ }
- if (!jumpRemoved) {
- val staleGoto = simplifyBranchOverGoto(method, instruction)
- instructionsToRemove ++= staleGoto
- changed ||= staleGoto.nonEmpty
- changed = simplifyGotoReturn(method, instruction, inTryBlock = activeHandlers.nonEmpty) || 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.
+ */
+ def simplifyThenElseSameTarget(insn: AbstractInsnNode): Boolean = insn match {
+ case ConditionalJump(jump) =>
+ nextExecutableInstruction(insn) match {
+ case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) =>
+ replaceJumpByPop(jump)
+ true
+
+ case _ => false
}
- changed ||= jumpRemoved
- }
+
+ case _ => false
}
- 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)
+ /**
+ * 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 unchanged]
+ * }}}
+ *
+ * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop.
+ */
+ def collapseJumpChains(insn: AbstractInsnNode): Boolean = insn match {
+ case JumpNonJsr(jump) =>
+ val target = finalJumpTarget(jump)
+ if (jump.label == target) false else {
+ jump.label = target
+ _jumpTargets = null
true
+ }
- case _ => false
- }
- 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 unchanged]
- *
- * 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
+ /**
+ * 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.
+ */
+ def removeJumpToSuccessor(insn: AbstractInsnNode): Boolean = insn match {
+ case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) contains jump.label =>
+ replaceJumpByPop(jump)
true
- }
- case _ => false
- }
+ 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 jump targets]; GOTO m; [nops]; l: [...]
+ * => NegatedCondJump m; [nops, no jump targets]; [nops]; l: [...]
+ * }}}
+ *
+ * Note that no jump targets are allowed in the first [nops] section. Otherwise, there could
+ * be some other jump to the GOTO, and eliminating it would change behavior.
+ */
+ def simplifyBranchOverGoto(insn: AbstractInsnNode, inTryBlock: Boolean): Boolean = insn match {
+ case ConditionalJump(jump) =>
+ // don't skip over jump targets, see doc comment
+ nextExecutableInstruction(jump, alsoKeep = jumpTargets) match {
+ case Some(Goto(goto)) =>
+ if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) contains jump.label) {
+ val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label)
+ method.instructions.set(jump, newJump)
+ removeJumpFromMap(jump)
+ jumpInsns(newJump) = inTryBlock
+ replaceJumpByPop(goto)
+ true
+ } else false
+
+ case _ => false
+ }
+ 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. Otherwise, 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 instructions may throw
+ * an IllegalMonitorStateException, as described here:
+ * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return
+ */
+ def simplifyGotoReturn(instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match {
+ case Goto(jump) =>
+ nextExecutableInstruction(jump.label) match {
+ case Some(target) =>
+ if (isReturn(target) || target.getOpcode == ATHROW) {
+ method.instructions.set(jump, target.clone(null))
+ removeJumpFromMap(jump)
+ true
+ } else false
+
+ case _ => false
+ }
+ case _ => false
+ })
- /**
- * 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 instructions 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
+ def run(): Boolean = {
+ var changed = false
+
+ // `.toList` because we're modifying the map while iterating over it
+ for ((jumpInsn, inTryBlock) <- jumpInsns.toList if jumpInsns.contains(jumpInsn) && isJumpNonJsr(jumpInsn)) {
+ var jumpRemoved = simplifyThenElseSameTarget(jumpInsn)
+
+ if (!jumpRemoved) {
+ changed = collapseJumpChains(jumpInsn) || changed
+ jumpRemoved = removeJumpToSuccessor(jumpInsn)
+
+ if (!jumpRemoved) {
+ changed = simplifyBranchOverGoto(jumpInsn, inTryBlock) || changed
+ changed = simplifyGotoReturn(jumpInsn, inTryBlock) || changed
+ }
+ }
- case _ => false
+ changed ||= jumpRemoved
}
- case _ => false
- })
+
+ if (changed) run()
+ changed
+ }
+
+ run()
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala
deleted file mode 100644
index a866173a88..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala
+++ /dev/null
@@ -1,235 +0,0 @@
- /* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-package scala.tools.nsc
-package backend.opt
-
-import scala.tools.nsc.backend.icode.analysis.LubException
-
-/**
- * @author Iulian Dragos
- */
-abstract class ClosureElimination extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
-
- val phaseName = "closelim"
-
- override val enabled: Boolean = settings.Xcloselim
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new ClosureEliminationPhase(p)
-
- /** A simple peephole optimizer. */
- val peephole = new PeepholeOpt {
-
- def peep(bb: BasicBlock, i1: Instruction, i2: Instruction) = (i1, i2) match {
- case (CONSTANT(c), DROP(_)) =>
- if (c.tag == UnitTag) Some(List(i2)) else Some(Nil)
-
- case (LOAD_LOCAL(x), STORE_LOCAL(y)) =>
- if (x eq y) Some(Nil) else None
-
- case (STORE_LOCAL(x), LOAD_LOCAL(y)) if (x == y) =>
- var liveOut = liveness.out(bb)
- if (!liveOut(x)) {
- debuglog("store/load to a dead local? " + x)
- val instrs = bb.getArray
- var idx = instrs.length - 1
- while (idx > 0 && (instrs(idx) ne i2)) {
- liveOut = liveness.interpret(liveOut, instrs(idx))
- idx -= 1
- }
- if (!liveOut(x)) {
- log("Removing dead store/load of " + x.sym.initialize.defString)
- Some(Nil)
- } else None
- } else
- Some(List(DUP(x.kind), STORE_LOCAL(x)))
-
- case (LOAD_LOCAL(_), DROP(_)) | (DUP(_), DROP(_)) =>
- Some(Nil)
-
- case (BOX(t1), UNBOX(t2)) if (t1 == t2) =>
- Some(Nil)
-
- case (LOAD_FIELD(sym, /* isStatic */false), DROP(_)) if !sym.hasAnnotation(definitions.VolatileAttr) && inliner.isClosureClass(sym.owner) =>
- Some(DROP(REFERENCE(definitions.ObjectClass)) :: Nil)
-
- case _ => None
- }
- }
-
- /** The closure elimination phase.
- */
- class ClosureEliminationPhase(prev: Phase) extends ICodePhase(prev) {
-
- def name = phaseName
- val closser = new ClosureElim
-
- override def apply(c: IClass): Unit = {
- if (closser ne null)
- closser analyzeClass c
- }
- }
-
- /**
- * Remove references to the environment through fields of a closure object.
- * This has to be run after an 'apply' method has been inlined, but it still
- * references the closure object.
- *
- */
- class ClosureElim {
- def analyzeClass(cls: IClass): Unit = if (settings.Xcloselim) {
- log(s"Analyzing ${cls.methods.size} methods in $cls.")
- cls.methods foreach { m =>
- analyzeMethod(m)
- peephole(m)
- }}
-
- val cpp = new copyPropagation.CopyAnalysis
-
- import copyPropagation._
-
- /* Some embryonic copy propagation. */
- def analyzeMethod(m: IMethod): Unit = try {if (m.hasCode) {
- cpp.init(m)
- cpp.run()
-
- m.linearizedBlocks() foreach { bb =>
- var info = cpp.in(bb)
- debuglog("Cpp info at entry to block " + bb + ": " + info)
-
- for (i <- bb) {
- i match {
- case LOAD_LOCAL(l) if info.bindings isDefinedAt LocalVar(l) =>
- val t = info.getBinding(l)
- t match {
- case Deref(This) | Const(_) =>
- bb.replaceInstruction(i, valueToInstruction(t))
- debuglog(s"replaced $i with $t")
-
- case _ =>
- val t = info.getAlias(l)
- bb.replaceInstruction(i, LOAD_LOCAL(t))
- debuglog(s"replaced $i with $t")
- }
-
- case LOAD_FIELD(f, false) /* if accessible(f, m.symbol) */ =>
- def replaceFieldAccess(r: Record) {
- val Record(cls, _) = r
- info.getFieldNonRecordValue(r, f) foreach { v =>
- bb.replaceInstruction(i, DROP(REFERENCE(cls)) :: valueToInstruction(v) :: Nil)
- debuglog(s"replaced $i with $v")
- }
- }
-
- info.stack(0) match {
- case r @ Record(_, bindings) if bindings isDefinedAt f =>
- replaceFieldAccess(r)
-
- case Deref(LocalVar(l)) =>
- info.getBinding(l) match {
- case r @ Record(_, bindings) if bindings isDefinedAt f =>
- replaceFieldAccess(r)
- case _ =>
- }
- case Deref(Field(r1, f1)) =>
- info.getFieldValue(r1, f1) match {
- case Some(r @ Record(_, bindings)) if bindings isDefinedAt f =>
- replaceFieldAccess(r)
- case _ =>
- }
-
- case _ =>
- }
-
- case UNBOX(boxType) =>
- info.stack match {
- case Deref(LocalVar(loc1)) :: _ if info.bindings isDefinedAt LocalVar(loc1) =>
- val value = info.getBinding(loc1)
- value match {
- case Boxed(LocalVar(loc2)) if loc2.kind == boxType =>
- bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(info.getBinding(loc2)) :: Nil)
- debuglog("replaced " + i + " with " + info.getBinding(loc2))
- case _ =>
- ()
- }
- case Boxed(LocalVar(loc1)) :: _ if loc1.kind == boxType =>
- val loc2 = info.getAlias(loc1)
- bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(Deref(LocalVar(loc2))) :: Nil)
- debuglog("replaced " + i + " with " + LocalVar(loc2))
- case _ =>
- }
-
- case _ =>
- }
- info = cpp.interpret(info, i)
- }
- }
- }} catch {
- case e: LubException =>
- Console.println("In method: " + m)
- Console.println(e)
- e.printStackTrace
- }
-
- /* Partial mapping from values to instructions that load them. */
- def valueToInstruction(v: Value): Instruction = (v: @unchecked) match {
- case Deref(LocalVar(v)) =>
- LOAD_LOCAL(v)
- case Const(k) =>
- CONSTANT(k)
- case Deref(This) =>
- THIS(definitions.ObjectClass)
- case Boxed(LocalVar(v)) =>
- LOAD_LOCAL(v)
- }
- } /* class ClosureElim */
-
-
- /** Peephole optimization. */
- abstract class PeepholeOpt {
- /** Concrete implementations will perform their optimizations here */
- def peep(bb: BasicBlock, i1: Instruction, i2: Instruction): Option[List[Instruction]]
-
- var liveness: global.icodes.liveness.LivenessAnalysis = null
-
- def apply(m: IMethod): Unit = if (m.hasCode) {
- liveness = new global.icodes.liveness.LivenessAnalysis
- liveness.init(m)
- liveness.run()
- m foreachBlock transformBlock
- }
-
- def transformBlock(b: BasicBlock): Unit = if (b.size >= 2) {
- var newInstructions: List[Instruction] = b.toList
- var redo = false
-
- do {
- var h = newInstructions.head
- var t = newInstructions.tail
- var seen: List[Instruction] = Nil
- redo = false
-
- while (t != Nil) {
- peep(b, h, t.head) match {
- case Some(newInstrs) =>
- newInstructions = seen reverse_::: newInstrs ::: t.tail
- redo = true
- case None =>
- ()
- }
- seen = h :: seen
- h = t.head
- t = t.tail
- }
- } while (redo)
- b fromList newInstructions
- }
- }
-
-} /* class ClosureElimination */
diff --git a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
deleted file mode 100644
index eafaf41932..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala
+++ /dev/null
@@ -1,626 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author James Iry
- */
-
-package scala
-package tools.nsc
-package backend.opt
-
-import scala.annotation.tailrec
-
-/**
- * ConstantOptimization uses abstract interpretation to approximate for
- * each instruction what constants a variable or stack slot might hold
- * or cannot hold. From this it will eliminate unreachable conditionals
- * where only one branch is reachable, e.g. to eliminate unnecessary
- * null checks.
- *
- * With some more work it could be extended to
- * - cache stable values (final fields, modules) in locals
- * - replace the copy propagation in ClosureElimination
- * - fold constants
- * - eliminate unnecessary stores and loads
- * - propagate knowledge gathered from conditionals for further optimization
- */
-abstract class ConstantOptimization extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
-
- val phaseName = "constopt"
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new ConstantOptimizationPhase(p)
-
- override val enabled: Boolean = settings.YconstOptimization
-
- /**
- * The constant optimization phase.
- */
- class ConstantOptimizationPhase(prev: Phase) extends ICodePhase(prev) {
-
- def name = phaseName
-
- override def apply(c: IClass) {
- if (settings.YconstOptimization) {
- val analyzer = new ConstantOptimizer
- analyzer optimizeClass c
- }
- }
- }
-
- class ConstantOptimizer {
- def optimizeClass(cls: IClass) {
- log(s"Analyzing ${cls.methods.size} methods in $cls.")
- cls.methods foreach { m =>
- optimizeMethod(m)
- }
- }
-
- def optimizeMethod(m: IMethod) {
- if (m.hasCode) {
- log(s"Analyzing ${m.symbol}")
- val replacementInstructions = interpretMethod(m)
- for (block <- m.blocks) {
- if (replacementInstructions contains block) {
- val instructions = replacementInstructions(block)
- block.replaceInstruction(block.lastInstruction, instructions)
- }
- }
- }
- }
-
- /**
- * A single possible (or impossible) datum that can be held in Contents
- */
- private sealed abstract class Datum
- /**
- * A constant datum
- */
- private case class Const(c: Constant) extends Datum {
- def isIntAssignable = c.tag >= BooleanTag && c.tag <= IntTag
- def toInt = c.tag match {
- case BooleanTag => if (c.booleanValue) 1 else 0
- case _ => c.intValue
- }
-
- /**
- * True if this constant would compare to other as true under primitive eq
- */
- override def equals(other: Any) = other match {
- case oc @ Const(o) => (this eq oc) || (if (this.isIntAssignable && oc.isIntAssignable) this.toInt == oc.toInt else c.value == o.value)
- case _ => false
- }
-
- /**
- * Hash code consistent with equals
- */
- override def hashCode = if (this.isIntAssignable) this.toInt else c.hashCode
-
- }
- /**
- * A datum that has been Boxed via a BOX instruction
- */
- private case class Boxed(c: Datum) extends Datum
-
- /**
- * The knowledge we have about the abstract state of one location in terms
- * of what constants it might or cannot hold. Forms a lower
- * lattice where lower elements in the lattice indicate less knowledge.
- *
- * With the following partial ordering (where '>' indicates more precise knowledge)
- *
- * Possible(xs) > Possible(xs + y)
- * Possible(xs) > Impossible(ys)
- * Impossible(xs + y) > Impossible(xs)
- *
- * and the following merges, which indicate merging knowledge from two paths through
- * the code,
- *
- * // left must be 1 or 2, right must be 2 or 3 then we must have a 1, 2 or 3
- * Possible(xs) merge Possible(ys) => Possible(xs union ys)
- *
- * // Left says can't be 2 or 3, right says can't be 3 or 4
- * // then it's not 3 (it could be 2 from the right or 4 from the left)
- * Impossible(xs) merge Impossible(ys) => Impossible(xs intersect ys)
- *
- * // Left says it can't be 2 or 3, right says it must be 3 or 4, then
- * // it can't be 2 (left rules out 4 and right says 3 is possible)
- * Impossible(xs) merge Possible(ys) => Impossible(xs -- ys)
- *
- * Intuitively, Possible(empty) says that a location can't hold anything,
- * it's uninitialized. However, Possible(empty) never appears in the code.
- *
- * Conversely, Impossible(empty) says nothing is impossible, it could be
- * anything. Impossible(empty) is given a synonym UNKNOWN and is used
- * for, e.g., the result of an arbitrary method call.
- */
- private sealed abstract class Contents {
- /**
- * Join this Contents with another coming from another path. Join enforces
- * the lattice structure. It is symmetrical and never moves upward in the
- * lattice
- */
- final def merge(other: Contents): Contents = if (this eq other) this else (this, other) match {
- case (Possible(possible1), Possible(possible2)) =>
- Possible(possible1 union possible2)
- case (Impossible(impossible1), Impossible(impossible2)) =>
- Impossible(impossible1 intersect impossible2)
- case (Impossible(impossible), Possible(possible)) =>
- Impossible(impossible -- possible)
- case (Possible(possible), Impossible(impossible)) =>
- Impossible(impossible -- possible)
- }
- // TODO we could have more fine-grained knowledge, e.g. know that 0 < x < 3. But for now equality/inequality is a good start.
- def mightEqual(other: Contents): Boolean
- def mightNotEqual(other: Contents): Boolean
- }
- private def SingleImpossible(x: Datum) = new Impossible(Set(x))
-
- /**
- * The location is known to have one of a set of values.
- */
- private case class Possible(possible: Set[Datum]) extends Contents {
- assert(possible.nonEmpty, "Contradiction: had an empty possible set indicating an uninitialized location")
- def mightEqual(other: Contents): Boolean = (this eq other) || (other match {
- // two Possibles might be equal if they have any possible members in common
- case Possible(possible2) => (possible intersect possible2).nonEmpty
- // a possible can be equal to an impossible if the impossible doesn't rule
- // out all the possibilities
- case Impossible(possible2) => (possible -- possible2).nonEmpty
- })
- def mightNotEqual(other: Contents): Boolean = (other match {
- case Possible(possible2) =>
- // two Possibles must equal if each is known to be of the same, single value
- val mustEqual = possible.size == 1 && possible == possible2
- !mustEqual
- case Impossible(_) => true
- })
- }
- private def SinglePossible(x: Datum) = new Possible(Set(x))
-
- /**
- * The location is known to not have any of a set of values value (e.g null).
- */
- private case class Impossible(impossible: Set[Datum]) extends Contents {
- def mightEqual(other: Contents): Boolean = (this eq other) || (other match {
- case Possible(_) => other mightEqual this
- case _ => true
- })
- def mightNotEqual(other: Contents): Boolean = (this eq other) || (other match {
- case Possible(_) => other mightNotEqual this
- case _ => true
- })
- }
-
- /**
- * Our entire knowledge about the contents of all variables and the stack. It forms
- * a lattice primarily driven by the lattice structure of Contents.
- *
- * In addition to the rules of contents, State has the following properties:
- * - The merge of two sets of locals holds the merges of locals found in the intersection
- * of the two sets of locals. Locals not found in a
- * locals map are thus possibly uninitialized and attempting to load them results
- * in an error.
- * - The stack heights of two states must match otherwise it's an error to merge them
- *
- * State is immutable in order to aid in structure sharing of local maps and stacks
- */
- private case class State(locals: Map[Local, Contents], stack: List[Contents]) {
- def mergeLocals(olocals: Map[Local, Contents]): Map[Local, Contents] = if (locals eq olocals) locals else Map((for {
- key <- (locals.keySet intersect olocals.keySet).toSeq
- } yield (key, locals(key) merge olocals(key))): _*)
-
- def merge(other: State): State = if (this eq other) this else {
- @tailrec def mergeStacks(l: List[Contents], r: List[Contents], out: List[Contents]): List[Contents] = (l, r) match {
- case (Nil, Nil) => out.reverse
- case (l, r) if l eq r => out.reverse ++ l
- case (lhead :: ltail, rhead :: rtail) => mergeStacks(ltail, rtail, (lhead merge rhead) :: out)
- case _ => sys.error("Mismatched stack heights")
- }
-
- val newLocals = mergeLocals(other.locals)
-
- val newStack = if (stack eq other.stack) stack else mergeStacks(stack, other.stack, Nil)
- State(newLocals, newStack)
- }
-
- /**
- * Peek at the top of the stack without modifying it. Error if the stack is empty
- */
- def peek(n: Int): Contents = stack(n)
- /**
- * Push contents onto a stack
- */
- def push(contents: Contents): State = this copy (stack = contents :: stack)
- /**
- * Drop n elements from the stack
- */
- def drop(number: Int): State = this copy (stack = stack drop number)
- /**
- * Store the top of the stack into the specified local. An error if the stack
- * is empty
- */
- def store(variable: Local): State = {
- val contents = stack.head
- val newVariables = locals + ((variable, contents))
- new State(newVariables, stack.tail)
- }
- /**
- * Load the specified local onto the top of the stack. An error if the local is uninitialized.
- */
- def load(variable: Local): State = {
- val contents: Contents = locals.getOrElse(variable, sys.error(s"$variable is not initialized"))
- push(contents)
- }
- /**
- * A copy of this State with an empty stack
- */
- def cleanStack: State = if (stack.isEmpty) this else this copy (stack = Nil)
- }
-
- // some precomputed constants
- private val NULL = Const(Constant(null: Any))
- private val UNKNOWN = Impossible(Set.empty)
- private val NOT_NULL = SingleImpossible(NULL)
- private val CONST_UNIT = SinglePossible(Const(Constant(())))
- private val CONST_FALSE = SinglePossible(Const(Constant(false)))
- private val CONST_ZERO_BYTE = SinglePossible(Const(Constant(0: Byte)))
- private val CONST_ZERO_SHORT = SinglePossible(Const(Constant(0: Short)))
- private val CONST_ZERO_CHAR = SinglePossible(Const(Constant(0: Char)))
- private val CONST_ZERO_INT = SinglePossible(Const(Constant(0: Int)))
- private val CONST_ZERO_LONG = SinglePossible(Const(Constant(0: Long)))
- private val CONST_ZERO_FLOAT = SinglePossible(Const(Constant(0.0f)))
- private val CONST_ZERO_DOUBLE = SinglePossible(Const(Constant(0.0d)))
- private val CONST_NULL = SinglePossible(NULL)
-
- /**
- * Given a TypeKind, figure out what '0' for it means in order to interpret CZJUMP
- */
- private def getZeroOf(k: TypeKind): Contents = k match {
- case UNIT => CONST_UNIT
- case BOOL => CONST_FALSE
- case BYTE => CONST_ZERO_BYTE
- case SHORT => CONST_ZERO_SHORT
- case CHAR => CONST_ZERO_CHAR
- case INT => CONST_ZERO_INT
- case LONG => CONST_ZERO_LONG
- case FLOAT => CONST_ZERO_FLOAT
- case DOUBLE => CONST_ZERO_DOUBLE
- case REFERENCE(_) => CONST_NULL
- case ARRAY(_) => CONST_NULL
- case BOXED(_) => CONST_NULL
- case ConcatClass => abort("no zero of ConcatClass")
- }
-
- // normal locals can't be null, so we use null to mean the magic 'this' local
- private val THIS_LOCAL: Local = null
-
- /**
- * interpret a single instruction to find its impact on the abstract state
- */
- private def interpretInst(in: State, inst: Instruction): State = {
- // pop the consumed number of values off the `in` state's stack, producing a new state
- def dropConsumed: State = in drop inst.consumed
-
- inst match {
- case THIS(_) =>
- in load THIS_LOCAL
-
- case CONSTANT(k) =>
- // treat NaN as UNKNOWN because NaN must never equal NaN
- val const = if (k.isNaN) UNKNOWN
- else SinglePossible(Const(k))
- in push const
-
- case LOAD_ARRAY_ITEM(_) | LOAD_FIELD(_, _) | CALL_PRIMITIVE(_) =>
- dropConsumed push UNKNOWN
-
- case LOAD_LOCAL(local) =>
- // TODO if a local is known to hold a constant then we can replace this instruction with a push of that constant
- in load local
-
- case STORE_LOCAL(local) =>
- in store local
-
- case STORE_THIS(_) =>
- // if a local is already known to have a constant and we're replacing with the same constant then we can
- // replace this with a drop
- in store THIS_LOCAL
-
- case CALL_METHOD(_, _) =>
- // TODO we could special case implementations of equals that are known, e.g. String#equals
- // We could turn Possible(string constants).equals(Possible(string constants) into an eq check
- // We could turn nonConstantString.equals(constantString) into constantString.equals(nonConstantString)
- // and eliminate the null check that likely precedes this call
- val initial = dropConsumed
- (0 until inst.produced).foldLeft(initial) { case (know, _) => know push UNKNOWN }
-
- case BOX(_) =>
- val value = in peek 0
- // we simulate boxing by, um, boxing the possible/impossible contents
- // so if we have Possible(1,2) originally then we'll end up with
- // a Possible(Boxed(1), Boxed(2))
- // Similarly, if we know the input is not a 0 then we'll know the
- // output is not a Boxed(0)
- val newValue = value match {
- case Possible(values) => Possible(values map Boxed)
- case Impossible(values) => Impossible(values map Boxed)
- }
- dropConsumed push newValue
-
- case UNBOX(_) =>
- val value = in peek 0
- val newValue = value match {
- // if we have a Possible, then all the possibilities
- // should themselves be Boxes. In that
- // case we can merge them to figure out what the UNBOX will produce
- case Possible(inners) =>
- assert(inners.nonEmpty, "Empty possible set indicating an uninitialized location")
- val sanitized: Set[Contents] = (inners map {
- case Boxed(content) => SinglePossible(content)
- case _ => UNKNOWN
- })
- sanitized reduce (_ merge _)
- // if we have an impossible then the thing that's impossible
- // should be a box. We'll unbox that to see what we get
- case unknown@Impossible(inners) =>
- if (inners.isEmpty) {
- unknown
- } else {
- val sanitized: Set[Contents] = (inners map {
- case Boxed(content) => SingleImpossible(content)
- case _ => UNKNOWN
- })
- sanitized reduce (_ merge _)
- }
- }
- dropConsumed push newValue
-
- case LOAD_MODULE(_) | NEW(_) | LOAD_EXCEPTION(_) =>
- in push NOT_NULL
-
- case CREATE_ARRAY(_, _) =>
- dropConsumed push NOT_NULL
-
- case IS_INSTANCE(_) =>
- // TODO IS_INSTANCE is going to be followed by a C(Z)JUMP
- // and if IS_INSTANCE/C(Z)JUMP the branch for "true" can
- // know that whatever was checked was not a null
- // see the TODO on CJUMP for more information about propagating null
- // information
- // TODO if the top of stack is guaranteed null then we can eliminate this IS_INSTANCE check and
- // replace with a constant false, but how often is a knowable null checked for instanceof?
- // TODO we could track type information and statically know to eliminate IS_INSTANCE
- // which might be a nice win under specialization
- dropConsumed push UNKNOWN // it's actually a Possible(true, false) but since the following instruction
- // will be a conditional jump comparing to true or false there
- // nothing to be gained by being more precise
-
- case CHECK_CAST(_) =>
- // TODO we could track type information and statically know to eliminate CHECK_CAST
- // but that's probably not a huge win
- in
-
- case DUP(_) =>
- val value = in peek 0
- in push value
-
- case DROP(_) | MONITOR_ENTER() | MONITOR_EXIT() | STORE_ARRAY_ITEM(_) | STORE_FIELD(_, _) =>
- dropConsumed
-
- case SCOPE_ENTER(_) | SCOPE_EXIT(_) =>
- in
-
- case JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | RETURN(_) | THROW(_) | SWITCH(_, _) =>
- dumpClassesAndAbort("Unexpected block ending instruction: " + inst)
- }
- }
- /**
- * interpret the last instruction of a block which will be jump, a conditional branch, a throw, or a return.
- * It will result in a map from target blocks to the input state computed for that block. It
- * also computes a replacement list of instructions
- */
- private def interpretLast(in: State, inst: Instruction): (Map[BasicBlock, State], List[Instruction]) = {
- def canSwitch(in1: Contents, tagSet: List[Int]) = {
- in1 mightEqual Possible(tagSet.toSet map { tag: Int => Const(Constant(tag)) })
- }
-
- /* common code for interpreting CJUMP and CZJUMP */
- def interpretConditional(kind: TypeKind, val1: Contents, val2: Contents, success: BasicBlock, failure: BasicBlock, cond: TestOp): (Map[BasicBlock, State], List[Instruction]) = {
- // TODO use reaching analysis to update the state in the two branches
- // e.g. if the comparison was checking null equality on local x
- // then the in the success branch we know x is null and
- // on the failure branch we know it is not
- // in fact, with copy propagation we could propagate that knowledge
- // back through a chain of locations
- //
- // TODO if we do all that we need to be careful in the
- // case that success and failure are the same target block
- // because we're using a Map and don't want one possible state to clobber the other
- // alternative maybe we should just replace the conditional with a jump if both targets are the same
-
- def mightEqual = val1 mightEqual val2
- def mightNotEqual = val1 mightNotEqual val2
- def guaranteedEqual = mightEqual && !mightNotEqual
-
- def succPossible = cond match {
- case EQ => mightEqual
- case NE => mightNotEqual
- case LT | GT => !guaranteedEqual // if the two are guaranteed to be equal then they can't be LT/GT
- case LE | GE => true
- }
-
- def failPossible = cond match {
- case EQ => mightNotEqual
- case NE => mightEqual
- case LT | GT => true
- case LE | GE => !guaranteedEqual // if the two are guaranteed to be equal then they must be LE/GE
- }
-
- val out = in drop inst.consumed
-
- var result = Map[BasicBlock, State]()
- if (succPossible) {
- result += ((success, out))
- }
-
- if (failPossible) {
- result += ((failure, out))
- }
-
- val replacements = if (result.size == 1) List.fill(inst.consumed)(DROP(kind)) :+ JUMP(result.keySet.head)
- else inst :: Nil
-
- (result, replacements)
- }
-
- inst match {
- case JUMP(whereto) =>
- (Map((whereto, in)), inst :: Nil)
-
- case CJUMP(success, failure, cond, kind) =>
- val in1 = in peek 0
- val in2 = in peek 1
- interpretConditional(kind, in1, in2, success, failure, cond)
-
- case CZJUMP(success, failure, cond, kind) =>
- val in1 = in peek 0
- val in2 = getZeroOf(kind)
- interpretConditional(kind, in1, in2, success, failure, cond)
-
- case SWITCH(tags, labels) =>
- val in1 = in peek 0
- val reachableNormalLabels = tags zip labels collect { case (tagSet, label) if canSwitch(in1, tagSet) => label }
- val reachableLabels = if (tags.isEmpty) {
- assert(labels.size == 1, s"When SWITCH node has empty array of tags it should have just one (default) label: $labels")
- labels
- } else if (labels.lengthCompare(tags.length) > 0) {
- // if we've got an extra label then it's the default
- val defaultLabel = labels.last
- // see if the default is reachable by seeing if the input might be out of the set
- // of all tags
- val allTags = Possible(tags.flatten.toSet map { tag: Int => Const(Constant(tag)) })
- if (in1 mightNotEqual allTags) {
- reachableNormalLabels :+ defaultLabel
- } else {
- reachableNormalLabels
- }
- } else {
- reachableNormalLabels
- }
- // TODO similar to the comment in interpretConditional, we should update our the State going into each
- // branch based on which tag is being matched. Also, just like interpretConditional, if target blocks
- // are the same we need to merge State rather than clobber
-
- // alternative, maybe we should simplify the SWITCH to not have same target labels
- val newState = in drop inst.consumed
- val result = Map(reachableLabels map { label => (label, newState) }: _*)
- if (reachableLabels.size == 1) (result, DROP(INT) :: JUMP(reachableLabels.head) :: Nil)
- else (result, inst :: Nil)
-
- // these instructions don't have target blocks
- // (exceptions are assumed to be reachable from all instructions)
- case RETURN(_) | THROW(_) =>
- (Map.empty, inst :: Nil)
-
- case _ =>
- dumpClassesAndAbort("Unexpected non-block ending instruction: " + inst)
- }
- }
-
- /**
- * Analyze a single block to find how it transforms an input state into a states for its successor blocks
- * Also computes a list of instructions to be used to replace its last instruction
- */
- private def interpretBlock(in: State, block: BasicBlock): (Map[BasicBlock, State], Map[BasicBlock, State], List[Instruction]) = {
- debuglog(s"interpreting block $block")
- // number of instructions excluding the last one
- val normalCount = block.size - 1
-
- var exceptionState = in.cleanStack
- var normalExitState = in
- var idx = 0
- while (idx < normalCount) {
- val inst = block(idx)
- normalExitState = interpretInst(normalExitState, inst)
- if (normalExitState.locals ne exceptionState.locals)
- exceptionState = exceptionState.copy(locals = exceptionState mergeLocals normalExitState.locals)
- idx += 1
- }
-
- val pairs = block.exceptionSuccessors map { b => (b, exceptionState) }
- val exceptionMap = Map(pairs: _*)
-
- val (normalExitMap, newInstructions) = interpretLast(normalExitState, block.lastInstruction)
-
- (normalExitMap, exceptionMap, newInstructions)
- }
-
- /**
- * Analyze a single method to find replacement instructions
- */
- private def interpretMethod(m: IMethod): Map[BasicBlock, List[Instruction]] = {
- import scala.collection.mutable.{ Set => MSet, Map => MMap }
-
- debuglog(s"interpreting method $m")
- var iterations = 0
-
- // initially we know that 'this' is not null and the params are initialized to some unknown value
- val initThis: Iterator[(Local, Contents)] = if (m.isStatic) Iterator.empty else Iterator.single((THIS_LOCAL, NOT_NULL))
- val initOtherLocals: Iterator[(Local, Contents)] = m.params.iterator map { param => (param, UNKNOWN) }
- val initialLocals: Map[Local, Contents] = Map((initThis ++ initOtherLocals).toSeq: _*)
- val initialState = State(initialLocals, Nil)
-
- // worklist of basic blocks to process, initially the start block
- val worklist = MSet(m.startBlock)
- // worklist of exception basic blocks. They're kept in a separate set so they can be
- // processed after normal flow basic blocks. That's because exception basic blocks
- // are more likely to have multiple predecessors and queueing them for later
- // increases the chances that they'll only need to be interpreted once
- val exceptionlist = MSet[BasicBlock]()
- // our current best guess at what the input state is for each block
- // initially we only know about the start block
- val inputState = MMap[BasicBlock, State]((m.startBlock, initialState))
-
- // update the inputState map based on new information from interpreting a block
- // When the input state of a block changes, add it back to the work list to be
- // reinterpreted
- def updateInputStates(outputStates: Map[BasicBlock, State], worklist: MSet[BasicBlock]) {
- for ((block, newState) <- outputStates) {
- val oldState = inputState get block
- val updatedState = oldState map (x => x merge newState) getOrElse newState
- if (oldState != Some(updatedState)) {
- worklist add block
- inputState(block) = updatedState
- }
- }
- }
-
- // the instructions to be used as the last instructions on each block
- val replacements = MMap[BasicBlock, List[Instruction]]()
-
- while (worklist.nonEmpty || exceptionlist.nonEmpty) {
- if (worklist.isEmpty) {
- // once the worklist is empty, start processing exception blocks
- val block = exceptionlist.head
- exceptionlist remove block
- worklist add block
- } else {
- iterations += 1
- val block = worklist.head
- worklist remove block
- val (normalExitMap, exceptionMap, newInstructions) = interpretBlock(inputState(block), block)
-
- updateInputStates(normalExitMap, worklist)
- updateInputStates(exceptionMap, exceptionlist)
- replacements(block) = newInstructions
- }
- }
-
- debuglog(s"method $m with ${m.blocks.size} reached fixpoint in $iterations iterations")
- replacements.toMap
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
deleted file mode 100644
index 8911a3a28c..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
+++ /dev/null
@@ -1,450 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-
-package scala.tools.nsc
-package backend.opt
-
-import scala.collection.{ mutable, immutable }
-
-/**
- */
-abstract class DeadCodeElimination extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions.RuntimePackage
-
- /** The block and index where an instruction is located */
- type InstrLoc = (BasicBlock, Int)
-
- val phaseName = "dce"
-
- override val enabled: Boolean = settings.Xdce
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new DeadCodeEliminationPhase(p)
-
- /** Dead code elimination phase.
- */
- class DeadCodeEliminationPhase(prev: Phase) extends ICodePhase(prev) {
-
- def name = phaseName
- val dce = new DeadCode()
-
- override def apply(c: IClass) {
- if (settings.Xdce && (dce ne null))
- dce.analyzeClass(c)
- }
- }
-
- /** closures that are instantiated at least once, after dead code elimination */
- val liveClosures = perRunCaches.newSet[Symbol]()
-
- /** closures that are eliminated, populated by GenASM.AsmPhase.run()
- * these class symbols won't have a .class physical file, thus shouldn't be included in InnerClasses JVM attribute,
- * otherwise some tools get confused or slow (SI-6546)
- * */
- val elidedClosures = perRunCaches.newSet[Symbol]()
-
- /** Remove dead code.
- */
- class DeadCode {
-
- def analyzeClass(cls: IClass) {
- log(s"Analyzing ${cls.methods.size} methods in $cls.")
- cls.methods.foreach { m =>
- this.method = m
- dieCodeDie(m)
- global.closureElimination.peephole(m)
- }
- }
-
- val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis
-
- /** Use-def chain: give the reaching definitions at the beginning of given instruction. */
- var defs: immutable.Map[InstrLoc, immutable.Set[rdef.lattice.Definition]] = immutable.HashMap.empty
-
- /** Useful instructions which have not been scanned yet. */
- val worklist: mutable.Set[InstrLoc] = new mutable.LinkedHashSet
-
- /** what instructions have been marked as useful? */
- val useful: mutable.Map[BasicBlock, mutable.BitSet] = perRunCaches.newMap()
-
- /** what local variables have been accessed at least once? */
- var accessedLocals: List[Local] = Nil
-
- /** Map from a local and a basic block to the instructions that store to that local in that basic block */
- val localStores = mutable.Map[(Local, BasicBlock), mutable.BitSet]() withDefault {_ => mutable.BitSet()}
-
- /** Stores that clobber previous stores to array or ref locals. See SI-5313 */
- val clobbers = mutable.Set[InstrLoc]()
-
- /** the current method. */
- var method: IMethod = _
-
- /** Map instructions who have a drop on some control path, to that DROP instruction. */
- val dropOf: mutable.Map[InstrLoc, List[InstrLoc]] = perRunCaches.newMap()
-
- def dieCodeDie(m: IMethod) {
- if (m.hasCode) {
- debuglog("dead code elimination on " + m)
- dropOf.clear()
- localStores.clear()
- clobbers.clear()
- m.code.blocks.clear()
- m.code.touched = true
- accessedLocals = m.params.reverse
- m.code.blocks ++= linearizer.linearize(m)
- m.code.touched = true
- collectRDef(m)
- mark()
- sweep(m)
- accessedLocals = accessedLocals.distinct
- val diff = m.locals diff accessedLocals
- if (diff.nonEmpty) {
- val msg = diff.map(_.sym.name)mkString(", ")
- log(s"Removed ${diff.size} dead locals: $msg")
- m.locals = accessedLocals.reverse
- }
- }
- }
-
- /** collect reaching definitions and initial useful instructions for this method. */
- def collectRDef(m: IMethod): Unit = if (m.hasCode) {
- defs = immutable.HashMap.empty; worklist.clear(); useful.clear()
- rdef.init(m)
- rdef.run()
-
- m foreachBlock { bb =>
- useful(bb) = new mutable.BitSet(bb.size)
- var rd = rdef.in(bb)
- for ((i, idx) <- bb.toList.zipWithIndex) {
-
- // utility for adding to worklist
- def moveToWorkList() = moveToWorkListIf(cond = true)
-
- // utility for (conditionally) adding to worklist
- def moveToWorkListIf(cond: Boolean) =
- if (cond) {
- debuglog("in worklist: " + i)
- worklist += ((bb, idx))
- } else {
- debuglog("not in worklist: " + i)
- }
-
- // instruction-specific logic
- i match {
-
- case LOAD_LOCAL(_) =>
- defs = defs + (((bb, idx), rd.vars))
- moveToWorkListIf(cond = false)
-
- case STORE_LOCAL(l) =>
- /* SI-4935 Check whether a module is stack top, if so mark the instruction that loaded it
- * (otherwise any side-effects of the module's constructor go lost).
- * (a) The other two cases where a module's value is stored (STORE_FIELD and STORE_ARRAY_ITEM)
- * are already marked (case clause below).
- * (b) A CALL_METHOD targeting a method `m1` where the receiver is potentially a module (case clause below)
- * will have the module's load marked provided `isSideEffecting(m1)`.
- * TODO check for purity (the ICode?) of the module's constructor (besides m1's purity).
- * See also https://github.com/paulp/scala/blob/topic/purity-analysis/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
- */
- val necessary = rdef.findDefs(bb, idx, 1) exists { p =>
- val (bb1, idx1) = p
- bb1(idx1) match {
- case LOAD_MODULE(module) => isLoadNeeded(module)
- case _ => false
- }
- }
- moveToWorkListIf(necessary)
-
- // add it to the localStores map
- val key = (l, bb)
- val set = localStores(key)
- set += idx
- localStores(key) = set
-
- case RETURN(_) | JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | STORE_FIELD(_, _) |
- THROW(_) | LOAD_ARRAY_ITEM(_) | STORE_ARRAY_ITEM(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) | STORE_THIS(_) |
- LOAD_EXCEPTION(_) | SWITCH(_, _) | MONITOR_ENTER() | MONITOR_EXIT() | CHECK_CAST(_) | CREATE_ARRAY(_, _) =>
- moveToWorkList()
-
- case LOAD_FIELD(sym, isStatic) if isStatic || !inliner.isClosureClass(sym.owner) =>
- // static load may trigger static initialization.
- // non-static load can throw NPE (but we know closure fields can't be accessed via a
- // null reference.
- moveToWorkList()
- case CALL_METHOD(m1, _) if isSideEffecting(m1) =>
- moveToWorkList()
-
- case CALL_METHOD(m1, SuperCall(_)) =>
- moveToWorkList() // super calls to constructor
-
- case DROP(_) =>
- val necessary = rdef.findDefs(bb, idx, 1) exists { p =>
- val (bb1, idx1) = p
- bb1(idx1) match {
- case CALL_METHOD(m1, _) if isSideEffecting(m1) => true
- case LOAD_EXCEPTION(_) | DUP(_) | LOAD_MODULE(_) => true
- case _ =>
- dropOf((bb1, idx1)) = (bb,idx) :: dropOf.getOrElse((bb1, idx1), Nil)
- debuglog("DROP is inessential: " + i + " because of: " + bb1(idx1) + " at " + bb1 + ":" + idx1)
- false
- }
- }
- moveToWorkListIf(necessary)
- case LOAD_MODULE(sym) if isLoadNeeded(sym) =>
- moveToWorkList() // SI-4859 Module initialization might side-effect.
- case CALL_PRIMITIVE(Arithmetic(DIV | REM, INT | LONG) | ArrayLength(_)) =>
- moveToWorkList() // SI-8601 Might divide by zero
- case _ => ()
- moveToWorkListIf(cond = false)
- }
- rd = rdef.interpret(bb, idx, rd)
- }
- }
- }
-
- private def isLoadNeeded(module: Symbol): Boolean = {
- module.info.member(nme.CONSTRUCTOR).filter(isSideEffecting) != NoSymbol
- }
-
- /** Mark useful instructions. Instructions in the worklist are each inspected and their
- * dependencies are marked useful too, and added to the worklist.
- */
- def mark() {
-// log("Starting with worklist: " + worklist)
- while (!worklist.isEmpty) {
- val (bb, idx) = worklist.head
- worklist -= ((bb, idx))
- debuglog("Marking instr: \tBB_" + bb + ": " + idx + " " + bb(idx))
-
- val instr = bb(idx)
- // adds the instructions that define the stack values about to be consumed to the work list to
- // be marked useful
- def addDefs() = for ((bb1, idx1) <- rdef.findDefs(bb, idx, instr.consumed) if !useful(bb1)(idx1)) {
- debuglog(s"\t${bb1(idx1)} is consumed by $instr")
- worklist += ((bb1, idx1))
- }
-
- // DROP logic -- if an instruction is useful, its drops are also useful
- // and we don't mark the DROPs as useful directly but add them to the
- // worklist so we also mark their reaching defs as useful - see SI-7060
- if (!useful(bb)(idx)) {
- useful(bb) += idx
- dropOf.get((bb, idx)) foreach {
- for ((bb1, idx1) <- _) {
- /*
- * SI-7060: A drop that we now mark as useful can be reached via several paths,
- * so we should follow by marking all its reaching definition as useful too:
- */
- debuglog("\tAdding: " + bb1(idx1) + " to the worklist, as a useful DROP.")
- worklist += ((bb1, idx1))
- }
- }
-
- // per-instruction logic
- instr match {
- case LOAD_LOCAL(l1) =>
- for ((l2, bb1, idx1) <- defs((bb, idx)) if l1 == l2; if !useful(bb1)(idx1)) {
- debuglog("\tAdding " + bb1(idx1))
- worklist += ((bb1, idx1))
- }
-
- case STORE_LOCAL(l1) if l1.kind.isRefOrArrayType =>
- addDefs()
- // see SI-5313
- // search for clobbers of this store if we aren't doing l1 = null
- // this doesn't catch the second store in x=null;l1=x; but in practice this catches
- // a lot of null stores very cheaply
- if (idx == 0 || bb(idx - 1) != CONSTANT(Constant(null)))
- findClobbers(l1, bb, idx + 1)
-
- case nw @ NEW(REFERENCE(sym)) =>
- assert(nw.init ne null, "null new.init at: " + bb + ": " + idx + "(" + instr + ")")
- worklist += findInstruction(bb, nw.init)
- if (inliner.isClosureClass(sym)) {
- liveClosures += sym
- }
-
- // it may be better to move static initializers from closures to
- // the enclosing class, to allow the optimizer to remove more closures.
- // right now, the only static fields in closures are created when caching
- // 'symbol literals.
- case LOAD_FIELD(sym, true) if inliner.isClosureClass(sym.owner) =>
- log("added closure class for field " + sym)
- liveClosures += sym.owner
-
- case LOAD_EXCEPTION(_) =>
- ()
-
- case _ =>
- addDefs()
- }
- }
- }
- }
-
- /**
- * Finds and marks all clobbers of the given local starting in the given
- * basic block at the given index
- *
- * Storing to local variables of reference or array type may be indirectly
- * observable because it may remove a reference to an object which may allow the object
- * to be gc'd. See SI-5313. In this code I call the LOCAL_STORE(s) that immediately follow a
- * LOCAL_STORE and that store to the same local "clobbers." If a LOCAL_STORE is marked
- * useful then its clobbers must go into the set of clobbers, which will be
- * compensated for later
- */
- def findClobbers(l: Local, bb: BasicBlock, idx: Int) {
- // previously visited blocks tracked to prevent searching forever in a cycle
- val inspected = mutable.Set[BasicBlock]()
- // our worklist of blocks that still need to be checked
- val blocksToBeInspected = mutable.Set[BasicBlock]()
-
- // Tries to find the next clobber of l1 in bb1 starting at idx1.
- // if it finds one it adds the clobber to clobbers set for later
- // handling. If not it adds the direct successor blocks to
- // the uninspectedBlocks to try to find clobbers there. Either way
- // it adds the exception successor blocks for further search
- def findClobberInBlock(idx1: Int, bb1: BasicBlock) {
- val key = ((l, bb1))
- val foundClobber = (localStores contains key) && {
- def minIdx(s : mutable.BitSet) = if(s.isEmpty) -1 else s.min
-
- // find the smallest index greater than or equal to idx1
- val clobberIdx = minIdx(localStores(key) dropWhile (_ < idx1))
- if (clobberIdx == -1)
- false
- else {
- debuglog(s"\t${bb1(clobberIdx)} is a clobber of ${bb(idx)}")
- clobbers += ((bb1, clobberIdx))
- true
- }
- }
-
- // always need to look into the exception successors for additional clobbers
- // because we don't know when flow might enter an exception handler
- blocksToBeInspected ++= (bb1.exceptionSuccessors filterNot inspected)
- // If we didn't find a clobber here then we need to look at successor blocks.
- // if we found a clobber then we don't need to search in the direct successors
- if (!foundClobber) {
- blocksToBeInspected ++= (bb1.directSuccessors filterNot inspected)
- }
- }
-
- // first search starting at the current index
- // note we don't put bb in the inspected list yet because a loop may later force
- // us back around to search from the beginning of bb
- findClobberInBlock(idx, bb)
- // then loop until we've exhausted the set of uninspected blocks
- while(!blocksToBeInspected.isEmpty) {
- val bb1 = blocksToBeInspected.head
- blocksToBeInspected -= bb1
- inspected += bb1
- findClobberInBlock(0, bb1)
- }
- }
-
- def sweep(m: IMethod) {
- val compensations = computeCompensations(m)
-
- debuglog("Sweeping: " + m)
-
- m foreachBlock { bb =>
- debuglog(bb + ":")
- val oldInstr = bb.toList
- bb.open()
- bb.clear()
- for ((i, idx) <- oldInstr.zipWithIndex) {
- if (useful(bb)(idx)) {
- debuglog(" * " + i + " is useful")
- bb.emit(i, i.pos)
- compensations.get((bb, idx)) match {
- case Some(is) => is foreach bb.emit
- case None => ()
- }
- // check for accessed locals
- i match {
- case LOAD_LOCAL(l) if !l.arg =>
- accessedLocals = l :: accessedLocals
- case STORE_LOCAL(l) if !l.arg =>
- accessedLocals = l :: accessedLocals
- case _ => ()
- }
- } else {
- i match {
- case NEW(REFERENCE(sym)) =>
- log(s"Eliminated instantiation of $sym inside $m")
- case STORE_LOCAL(l) if clobbers contains ((bb, idx)) =>
- // if an unused instruction was a clobber of a used store to a reference or array type
- // then we'll replace it with the store of a null to make sure the reference is
- // eliminated. See SI-5313
- bb emit CONSTANT(Constant(null))
- bb emit STORE_LOCAL(l)
- case _ => ()
- }
- debuglog(" " + i + " [swept]")
- }
- }
-
- if (bb.nonEmpty) bb.close()
- else log(s"empty block encountered in $m")
- }
- }
-
- private def computeCompensations(m: IMethod): mutable.Map[InstrLoc, List[Instruction]] = {
- val compensations: mutable.Map[InstrLoc, List[Instruction]] = new mutable.HashMap
-
- m foreachBlock { bb =>
- assert(bb.closed, "Open block in computeCompensations")
- foreachWithIndex(bb.toList) { (i, idx) =>
- if (!useful(bb)(idx)) {
- foreachWithIndex(i.consumedTypes.reverse) { (consumedType, depth) =>
- debuglog("Finding definitions of: " + i + "\n\t" + consumedType + " at depth: " + depth)
- val defs = rdef.findDefs(bb, idx, 1, depth)
- for (d <- defs) {
- val (bb, idx) = d
- debuglog("rdef: "+ bb(idx))
- bb(idx) match {
- case DUP(_) if idx > 0 =>
- bb(idx - 1) match {
- case nw @ NEW(_) =>
- val init = findInstruction(bb, nw.init)
- log("Moving DROP to after <init> call: " + nw.init)
- compensations(init) = List(DROP(consumedType))
- case _ =>
- compensations(d) = List(DROP(consumedType))
- }
- case _ =>
- compensations(d) = List(DROP(consumedType))
- }
- }
- }
- }
- }
- }
- compensations
- }
-
- private def findInstruction(bb: BasicBlock, i: Instruction): InstrLoc = {
- for (b <- linearizer.linearizeAt(method, bb)) {
- val idx = b.toList indexWhere (_ eq i)
- if (idx != -1)
- return (b, idx)
- }
- abort("could not find init in: " + method)
- }
-
- private def isPure(sym: Symbol) = (
- (sym.isGetter && sym.isEffectivelyFinalOrNotOverridden && !sym.isLazy)
- || (sym.isPrimaryConstructor && (sym.enclosingPackage == RuntimePackage || inliner.isClosureClass(sym.owner)))
- )
- /** Is 'sym' a side-effecting method? TODO: proper analysis. */
- private def isSideEffecting(sym: Symbol) = !isPure(sym)
-
- } /* DeadCode */
-}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala b/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala
deleted file mode 100644
index 9f6883f03f..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala
+++ /dev/null
@@ -1,392 +0,0 @@
-/* NSC -- new scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- */
-
-package scala.tools.nsc
-package backend.opt
-
-import java.util.concurrent.TimeUnit
-
-/**
- * This optimization phase inlines the exception handlers so that further phases can optimize the code better
- *
- * {{{
- * try {
- * ...
- * if (condition)
- * throw IllegalArgumentException("sth")
- * } catch {
- * case e: IllegalArgumentException => <handler code>
- * case e: ... => ...
- * }
- * }}}
- *
- * will inline the exception handler code to:
- *
- * {{{
- * try {
- * ...
- * if (condition)
- * <handler code> // + jump to the end of the catch statement
- * } catch {
- * case e: IllegalArgumentException => <handler code>
- * case e: ... => ...
- * }
- * }}}
- *
- * Q: How does the inlining work, ICode level?
- * A: if a block contains a THROW(A) instruction AND there is a handler that takes A or a superclass of A we do:
- * 1. We duplicate the handler code such that we can transform THROW into a JUMP
- * 2. We analyze the handler to see what local it expects the exception to be placed in
- * 3. We place the exception that is thrown in the correct "local variable" slot and clean up the stack
- * 4. We finally JUMP to the duplicate handler
- * All the above logic is implemented in InlineExceptionHandlersPhase.apply(bblock: BasicBlock)
- *
- * Q: Why do we need to duplicate the handler?
- * A: An exception might be thrown in a method that we invoke in the function and we cannot see that THROW command
- * directly. In order to catch such exceptions, we keep the exception handler in place and duplicate it in order
- * to inline its code.
- *
- * @author Vlad Ureche
- */
-abstract class InlineExceptionHandlers extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
-
- val phaseName = "inlinehandlers"
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new InlineExceptionHandlersPhase(p)
-
- override def enabled = settings.inlineHandlers
-
- /**
- * Inlining Exception Handlers
- */
- class InlineExceptionHandlersPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
-
- /* This map is used to keep track of duplicated exception handlers
- * explanation: for each exception handler basic block, there is a copy of it
- * -some exception handler basic blocks might not be duplicated because they have an unknown format => Option[(...)]
- * -some exception handler duplicates expect the exception on the stack while others expect it in a local
- * => Option[Local]
- */
- private val handlerCopies = perRunCaches.newMap[BasicBlock, Option[(Option[Local], BasicBlock)]]()
- /* This map is the inverse of handlerCopies, used to compute the stack of duplicate blocks */
- private val handlerCopiesInverted = perRunCaches.newMap[BasicBlock, (BasicBlock, TypeKind)]()
- private def handlerLocal(bb: BasicBlock): Option[Local] =
- for (v <- handlerCopies get bb ; (local, block) <- v ; l <- local) yield l
-
- /* Type Flow Analysis */
- private val tfa: analysis.MethodTFA = new analysis.MethodTFA()
- private var tfaCache: Map[Int, tfa.lattice.Elem] = Map.empty
- private var analyzedMethod: IMethod = NoIMethod
-
- /* Blocks that need to be analyzed */
- private var todoBlocks: List[BasicBlock] = Nil
-
- /* Used only for warnings */
- private var currentClass: IClass = null
-
- /** Apply exception handler inlining to a class */
- override def apply(c: IClass): Unit =
- if (settings.inlineHandlers) {
- val startTime = System.nanoTime()
- currentClass = c
-
- debuglog("Starting InlineExceptionHandlers on " + c)
- c.methods foreach applyMethod
- debuglog("Finished InlineExceptionHandlers on " + c + "... " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + "ms")
- currentClass = null
- }
-
- /**
- * Apply exception handler inlining to a method
- *
- * Note: for each exception handling block, we (might) create duplicates. Therefore we iterate until we get to a
- * fixed point where all the possible handlers have been inlined.
- *
- * TODO: Should we have an inlining depth limit? A nested sequence of n try-catch blocks can lead to at most 2n
- * inlined blocks, so worst case scenario we double the size of the code
- */
- private def applyMethod(method: IMethod): Unit = {
- if (method.hasCode) {
- // create the list of starting blocks
- todoBlocks = global.icodes.linearizer.linearize(method)
-
- while (todoBlocks.nonEmpty) {
- val levelBlocks = todoBlocks
- todoBlocks = Nil
- levelBlocks foreach applyBasicBlock // new blocks will be added to todoBlocks
- }
- }
-
- // Cleanup the references after we finished the file
- handlerCopies.clear()
- handlerCopiesInverted.clear()
- todoBlocks = Nil
-
- // Type flow analysis cleanup
- analyzedMethod = NoIMethod
- tfaCache = Map.empty
- //TODO: Need a way to clear tfa structures
- }
-
- /** Apply exception handler inlining to a basic block */
- private def applyBasicBlock(bblock: BasicBlock): Unit = {
- /*
- * The logic of this entire method:
- * - for each basic block, we look at each instruction until we find a THROW instruction
- * - once we found a THROW instruction, we decide if it is DECIDABLE which of handler will catch the exception
- * (see method findExceptionHandler for more details)
- * - if we decided there is a handler that will catch the exception, we need to replace the THROW instruction by
- * a set of equivalent instructions:
- * * we need to compute the static types of the stack slots
- * * we need to clear the stack, everything but the exception instance on top (or in a local variable slot)
- * * we need to JUMP to the duplicate exception handler
- * - we compute the static types of the stack slots in function getTypesAtInstruction
- * - we duplicate the exception handler (and we get back the information of whether the duplicate expects the
- * exception instance on top of the stack or in a local variable slot)
- * - we compute the necessary code to put the exception in its place, clear the stack and JUMP
- * - we change the THROW exception to the new Clear stack + JUMP code
- */
- for {
- (instr @ THROW(clazz), index) <- bblock.iterator.zipWithIndex
- // Decide if any handler fits this exception
- // If not, then nothing to do, we cannot determine statically which handler will catch the exception
- (handler, caughtException) <- findExceptionHandler(toTypeKind(clazz.tpe), bblock.exceptionSuccessors)
- } {
- log(" Replacing " + instr + " in " + bblock + " to new handler")
-
- // Solve the stack and drop the element that we already stored, which should be the exception
- // needs to be done here to be the first thing before code becomes altered
- val typeInfo = getTypesAtInstruction(bblock, index)
-
- // Duplicate exception handler
- duplicateExceptionHandlerCache(handler) match {
- case None =>
- log(" Could not duplicate handler for " + instr + " in " + bblock)
-
- case Some((exceptionLocalOpt, newHandler)) =>
- val onStackException = typeInfo.head
- val thrownException = toTypeKind(clazz.tpe)
-
- // A couple of sanity checks, to make sure we don't touch code we can't safely handle
- val canReplaceHandler = (
- typeInfo.nonEmpty
- && (index == bblock.length - 1)
- && (onStackException <:< thrownException)
- )
- // in other words: what's on the stack MUST conform to what's in the THROW(..)!
-
- if (!canReplaceHandler) {
- reporter.warning(NoPosition, "Unable to inline the exception handler inside incorrect" +
- " block:\n" + bblock.iterator.mkString("\n") + "\nwith stack: " + typeInfo + " just " +
- "before instruction index " + index)
- }
- else {
- // Prepare the new code to replace the THROW instruction
- val newCode = exceptionLocalOpt match {
- // the handler duplicate expects the exception in a local: easy one :)
- case Some(local) =>
- // in the first cycle we remove the exception Type
- STORE_LOCAL(local) +: typeInfo.tail.map(x => DROP(x)) :+ JUMP(newHandler)
-
- // we already have the exception on top of the stack, only need to JUMP
- case None if typeInfo.length == 1 =>
- JUMP(newHandler) :: Nil
-
- // we have the exception on top of the stack but we have other stuff on the stack
- // create a local, load exception, clear the stack and finally store the exception on the stack
- case _ =>
- val exceptionType = typeInfo.head
- // Here we could create a single local for all exceptions of a certain type. TODO: try that.
- val localName = currentClass.cunit.freshTermName("exception$")
- val localType = exceptionType
- val localSymbol = bblock.method.symbol.newValue(localName).setInfo(localType.toType)
- val local = new Local(localSymbol, localType, false)
-
- bblock.method.addLocal(local)
-
- // Save the exception, drop the stack and place back the exception
- STORE_LOCAL(local) :: typeInfo.tail.map(x => DROP(x)) ::: List(LOAD_LOCAL(local), JUMP(newHandler))
- }
- // replace THROW by the new code
- bblock.replaceInstruction(instr, newCode)
-
- // notify the successors changed for the current block
- // notify the predecessors changed for the inlined handler block
- bblock.touched = true
- newHandler.touched = true
-
- log(" Replaced " + instr + " in " + bblock + " to new handler")
- log("OPTIMIZED class " + currentClass + " method " +
- bblock.method + " block " + bblock + " newhandler " +
- newHandler + ":\n\t\t" + onStackException + " <:< " +
- thrownException + " <:< " + caughtException)
-
- }
- }
- }
- }
-
- /**
- * Gets the types on the stack at a certain point in the program. Note that we want to analyze the method lazily
- * and therefore use the analyzedMethod variable
- */
- private def getTypesAtInstruction(bblock: BasicBlock, index: Int): List[TypeKind] = {
- // get the stack at the block entry
- var typeInfo = getTypesAtBlockEntry(bblock)
-
- // perform tfa to the current instruction
- log(" stack at the beginning of block " + bblock + " in function " +
- bblock.method + ": " + typeInfo.stack)
- for (i <- 0 to (index - 1)) {
- typeInfo = tfa.interpret(typeInfo, bblock(i))
- log(" stack after interpret: " + typeInfo.stack + " after instruction " +
- bblock(i))
- }
- log(" stack before instruction " + index + " of block " + bblock + " in function " +
- bblock.method + ": " + typeInfo.stack)
-
- // return the result
- typeInfo.stack.types
- }
-
- /**
- * Gets the stack at the block entry. Normally the typeFlowAnalysis should be run again, but we know how to compute
- * the stack for handler duplicates. For the locals, it's safe to assume the info from the original handler is
- * still valid (a more precise analysis can be done, but it's not necessary)
- */
- private def getTypesAtBlockEntry(bblock: BasicBlock): tfa.lattice.Elem = {
- // lazily perform tfa, because it's expensive
- // cache results by block label, as rewriting the code messes up the block's hashCode
- if (analyzedMethod eq NoIMethod) {
- analyzedMethod = bblock.method
- tfa.init(bblock.method)
- tfa.run()
- log(" performed tfa on method: " + bblock.method)
-
- for (block <- bblock.method.blocks.sortBy(_.label))
- tfaCache += block.label -> tfa.in(block)
- }
-
- log(" getting typeinfo at the beginning of block " + bblock)
-
- tfaCache.getOrElse(bblock.label, {
- // this block was not analyzed, but it's a copy of some other block so its stack should be the same
- log(" getting typeinfo at the beginning of block " + bblock + " as a copy of " +
- handlerCopiesInverted(bblock))
- val (origBlock, exception) = handlerCopiesInverted(bblock)
- val typeInfo = getTypesAtBlockEntry(origBlock)
- val stack =
- if (handlerLocal(origBlock).nonEmpty) Nil // empty stack, the handler copy expects an empty stack
- else List(exception) // one slot on the stack for the exception
-
- // If we use the mutability property, it crashes the analysis
- tfa.lattice.IState(new analysis.VarBinding(typeInfo.vars), new icodes.TypeStack(stack))
- })
- }
-
- /**
- * Finds the first exception handler that matches the current exception
- *
- * Note the following code:
- * {{{
- * try {
- * throw new IllegalArgumentException("...")
- * } catch {
- * case e: RuntimeException => log("RuntimeException")
- * case i: IllegalArgumentException => log("IllegalArgumentException")
- * }
- * }}}
- *
- * will print "RuntimeException" => we need the *first* valid handler
- *
- * There's a hidden catch here: say we have the following code:
- * {{{
- * try {
- * val exception: Throwable =
- * if (scala.util.Random.nextInt % 2 == 0)
- * new IllegalArgumentException("even")
- * else
- * new StackOverflowError("odd")
- * throw exception
- * } catch {
- * case e: IllegalArgumentException =>
- * println("Correct, IllegalArgumentException")
- * case e: StackOverflowError =>
- * println("Correct, StackOverflowException")
- * case t: Throwable =>
- * println("WROOOONG, not Throwable!")
- * }
- * }}}
- *
- * We don't want to select a handler if there's at least one that's more specific!
- */
- def findExceptionHandler(thrownException: TypeKind, handlers: List[BasicBlock]): Option[(BasicBlock, TypeKind)] = {
- for (handler <- handlers ; LOAD_EXCEPTION(clazz) <- handler take 1) {
- val caughtException = toTypeKind(clazz.tpe)
- // we'll do inlining here: createdException <:< thrownException <:< caughtException, good!
- if (thrownException <:< caughtException)
- return Some((handler, caughtException))
- // we can't do inlining here, the handling mechanism is more precise than we can reason about
- if (caughtException <:< thrownException)
- return None
- // no result yet, look deeper in the handler stack
- }
- None
- }
-
- /**
- * This function takes care of duplicating the basic block code for inlining the handler
- *
- * Note: This function does not duplicate the same basic block twice. It will contain a map of the duplicated
- * basic blocks
- */
- private def duplicateExceptionHandlerCache(handler: BasicBlock) =
- handlerCopies.getOrElseUpdate(handler, duplicateExceptionHandler(handler))
-
- /** This function takes care of actual duplication */
- private def duplicateExceptionHandler(handler: BasicBlock): Option[(Option[Local], BasicBlock)] = {
- log(" duplicating handler block " + handler)
-
- handler take 2 match {
- case Seq(LOAD_EXCEPTION(caughtClass), next) =>
- val (dropCount, exceptionLocal) = next match {
- case STORE_LOCAL(local) => (2, Some(local)) // we drop both LOAD_EXCEPTION and STORE_LOCAL
- case _ => (1, None) // we only drop the LOAD_EXCEPTION and expect the exception on the stack
- }
- val caughtException = toTypeKind(caughtClass.tpe)
- // copy the exception handler code once again, dropping the LOAD_EXCEPTION
- val copy = handler.code.newBlock()
- copy.emitOnly((handler.iterator drop dropCount).toSeq: _*)
-
- // extend the handlers of the handler to the copy
- for (parentHandler <- handler.method.exh ; if parentHandler covers handler) {
- parentHandler.addCoveredBlock(copy)
- // notify the parent handler that the successors changed
- parentHandler.startBlock.touched = true
- }
-
- // notify the successors of the inlined handler might have changed
- copy.touched = true
- handler.touched = true
- log(" duplicated handler block " + handler + " to " + copy)
-
- // announce the duplicate handler
- handlerCopiesInverted(copy) = ((handler, caughtException))
- todoBlocks ::= copy
-
- Some((exceptionLocal, copy))
-
- case _ =>
- reporter.warning(NoPosition, "Unable to inline the exception handler due to incorrect format:\n" +
- handler.iterator.mkString("\n"))
- None
- }
- }
- }
-}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala
deleted file mode 100644
index 8cd2a14066..0000000000
--- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala
+++ /dev/null
@@ -1,1075 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Iulian Dragos
- */
-
-
-package scala.tools.nsc
-package backend.opt
-
-import scala.collection.mutable
-import scala.tools.nsc.symtab._
-import scala.reflect.internal.util.NoSourceFile
-
-/**
- * Inliner balances two competing goals:
- * (a) aggressive inlining of:
- * (a.1) the apply methods of anonymous closures, so that their anon-classes can be eliminated;
- * (a.2) higher-order-methods defined in an external library, e.g. `Range.foreach()` among many others.
- * (b) circumventing the barrier to inter-library inlining that private accesses in the callee impose.
- *
- * Summing up the discussion in SI-5442 and SI-5891,
- * the current implementation achieves to a large degree both goals above, and
- * overcomes a problem exhibited by previous versions:
- *
- * (1) Problem: Attempting to access a private member `p` at runtime resulting in an `IllegalAccessError`,
- * where `p` is defined in a library L, and is accessed from a library C (for Client),
- * where C was compiled against L', an optimized version of L where the inliner made `p` public at the bytecode level.
- * The only such members are fields, either synthetic or isParamAccessor, and thus having a dollar sign in their name
- * (the accessibility of methods and constructors isn't touched by the inliner).
- *
- * Thus we add one more goal to our list:
- * (c) Compile C (either optimized or not) against any of L or L',
- * so that it runs with either L or L' (in particular, compile against L' and run with L).
- *
- * The chosen strategy is described in some detail in the comments for `accessRequirements()` and `potentiallyPublicized()`.
- * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2011Q4/Inliner.pdf
- *
- * @author Iulian Dragos
- */
-abstract class Inliners extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions.{
- NullClass, NothingClass, ObjectClass,
- PredefModule, RuntimePackage, ScalaInlineClass, ScalaNoInlineClass,
- isFunctionType, isByNameParamType
- }
-
- val phaseName = "inliner"
-
- override val enabled: Boolean = settings.inline
-
- /** Debug - for timing the inliner. */
- /****
- private def timed[T](s: String, body: => T): T = {
- val t1 = System.currentTimeMillis()
- val res = body
- val t2 = System.currentTimeMillis()
- val ms = (t2 - t1).toInt
- if (ms >= MAX_INLINE_MILLIS)
- println("%s: %d milliseconds".format(s, ms))
-
- res
- }
- ****/
-
- /** Look up implementation of method 'sym in 'clazz'.
- */
- def lookupImplFor(sym: Symbol, clazz: Symbol): Symbol = {
- // TODO: verify that clazz.superClass is equivalent here to clazz.tpe.parents(0).typeSymbol (.tpe vs .info)
- def needsLookup = (
- (clazz != NoSymbol)
- && (clazz != sym.owner)
- && !sym.isEffectivelyFinalOrNotOverridden
- && clazz.isEffectivelyFinalOrNotOverridden
- )
- def lookup(clazz: Symbol): Symbol = {
- // println("\t\tlooking up " + meth + " in " + clazz.fullName + " meth.owner = " + meth.owner)
- assert(clazz != NoSymbol, "Walked up past Object.superClass looking for " + sym +
- ", most likely this reveals the TFA at fault (receiver and callee don't match).")
- if (sym.owner == clazz || isBottomType(clazz)) sym
- else sym.overridingSymbol(clazz) orElse (
- if (sym.owner.isTrait) sym
- else lookup(clazz.superClass)
- )
- }
- if (needsLookup) {
- val concreteMethod = lookup(clazz)
- debuglog("\tlooked up method: " + concreteMethod.fullName)
-
- concreteMethod
- }
- else sym
- }
-
- /* A warning threshold */
- private final val MAX_INLINE_MILLIS = 2000
-
- /** The maximum size in basic blocks of methods considered for inlining. */
- final val MAX_INLINE_SIZE = 16
-
- /** Maximum loop iterations. */
- final val MAX_INLINE_RETRY = 15
-
- /** Small method size (in blocks) */
- val SMALL_METHOD_SIZE = 1
-
- /** Create a new phase */
- override def newPhase(p: Phase) = new InliningPhase(p)
-
- /** The Inlining phase.
- */
- class InliningPhase(prev: Phase) extends ICodePhase(prev) {
- def name = phaseName
- val inliner = new Inliner
-
- object iclassOrdering extends Ordering[IClass] {
- def compare(a: IClass, b: IClass) = {
- val sourceNamesComparison = (a.cunit.toString() compare b.cunit.toString())
- if(sourceNamesComparison != 0) sourceNamesComparison
- else {
- val namesComparison = (a.toString() compare b.toString())
- if(namesComparison != 0) namesComparison
- else {
- a.symbol.id compare b.symbol.id
- }
- }
- }
- }
- val queue = new mutable.PriorityQueue[IClass]()(iclassOrdering)
-
- override def apply(c: IClass) { queue += c }
-
- override def run() {
- knownLacksInline.clear()
- knownHasInline.clear()
- try {
- super.run()
- for(c <- queue) { inliner analyzeClass c }
- } finally {
- inliner.clearCaches()
- knownLacksInline.clear()
- knownHasInline.clear()
- }
- }
- }
-
- def isBottomType(sym: Symbol) = sym == NullClass || sym == NothingClass
-
- /** Is the given class a closure? */
- def isClosureClass(cls: Symbol): Boolean =
- cls.isFinal && cls.isSynthetic && !cls.isModuleClass && cls.isAnonymousFunction
-
- /*
- TODO now that Inliner runs faster we could consider additional "monadic methods" (in the limit, all those taking a closure as last arg)
- Any "monadic method" occurring in a given caller C that is not `isMonadicMethod()` will prevent CloseElim from eliminating
- any anonymous-closure-class any whose instances are given as argument to C invocations.
- */
- def isMonadicMethod(sym: Symbol) = {
- nme.unspecializedName(sym.name) match {
- case nme.foreach | nme.filter | nme.withFilter | nme.map | nme.flatMap => true
- case _ => false
- }
- }
-
- val knownLacksInline = mutable.Set.empty[Symbol] // cache to avoid multiple inliner.hasInline() calls.
- val knownHasInline = mutable.Set.empty[Symbol] // as above. Motivated by the need to warn on "inliner failures".
-
- def hasInline(sym: Symbol) = {
- if (knownLacksInline(sym)) false
- else if(knownHasInline(sym)) true
- else {
- val b = (sym hasAnnotation ScalaInlineClass)
- if(b) { knownHasInline += sym }
- else { knownLacksInline += sym }
-
- b
- }
- }
-
- def hasNoInline(sym: Symbol) = sym hasAnnotation ScalaNoInlineClass
-
- /**
- * Simple inliner.
- */
- class Inliner {
- object NonPublicRefs extends Enumeration {
- val Private, Protected, Public = Value
-
- /** Cache whether a method calls private members. */
- val usesNonPublics = mutable.Map.empty[IMethod, Value]
- }
- import NonPublicRefs._
-
- /** The current iclass */
- private var currentIClazz: IClass = _
- private def warn(pos: Position, msg: String) = currentRun.reporting.inlinerWarning(pos, msg)
-
- private def ownedName(sym: Symbol): String = exitingUncurry {
- val count = (
- if (!sym.isMethod) 1
- else if (sym.owner.isAnonymousFunction) 3
- else 2
- )
- (sym.ownerChain take count filterNot (_.isPackageClass)).reverseMap(_.nameString).mkString(".")
- }
- private def inlineLog(what: String, main: => String, comment: => String) {
- def cstr = comment match {
- case "" => ""
- case str => " // " + str
- }
- val width = if (currentIClazz eq null) 40 else currentIClazz.symbol.enclosingPackage.fullName.length + 25
- val fmt = "%8s %-" + width + "s" + cstr
- log(fmt.format(what, main))
- }
- private def inlineLog(what: String, main: Symbol, comment: => String) {
- inlineLog(what, ownedName(main), comment)
- }
-
- val recentTFAs = mutable.Map.empty[Symbol, Tuple2[Boolean, analysis.MethodTFA]]
-
- private def getRecentTFA(incm: IMethod, forceable: Boolean): (Boolean, analysis.MethodTFA) = {
-
- def containsRETURN(blocks: List[BasicBlock]) = blocks exists { bb => bb.lastInstruction.isInstanceOf[RETURN] }
-
- val opt = recentTFAs.get(incm.symbol)
- if(opt.isDefined) {
- // FYI val cachedBBs = opt.get._2.in.keySet
- // FYI assert(incm.blocks.toSet == cachedBBs)
- // incm.code.touched plays no role here
- return opt.get
- }
-
- val hasRETURN = containsRETURN(incm.code.blocksList) || (incm.exh exists { eh => containsRETURN(eh.blocks) })
- var a: analysis.MethodTFA = null
- if(hasRETURN) { a = new analysis.MethodTFA(incm); a.run() }
-
- if(forceable) { recentTFAs.put(incm.symbol, (hasRETURN, a)) }
-
- (hasRETURN, a)
- }
-
- def clearCaches() {
- // methods
- NonPublicRefs.usesNonPublics.clear()
- recentTFAs.clear()
- tfa.knownUnsafe.clear()
- tfa.knownSafe.clear()
- tfa.knownNever.clear()
- // basic blocks
- tfa.preCandidates.clear()
- tfa.relevantBBs.clear()
- // callsites
- tfa.remainingCALLs.clear()
- tfa.isOnWatchlist.clear()
- }
-
- object imethodOrdering extends Ordering[IMethod] {
- def compare(a: IMethod, b: IMethod) = {
- val namesComparison = (a.toString() compare b.toString())
- if(namesComparison != 0) namesComparison
- else {
- a.symbol.id compare b.symbol.id
- }
- }
- }
-
- def analyzeClass(cls: IClass): Unit =
- if (settings.inline) {
- inlineLog("class", s"${cls.symbol.decodedName}", s"analyzing ${cls.methods.size} methods in $cls")
-
- this.currentIClazz = cls
- val ms = cls.methods sorted imethodOrdering
- ms foreach { im =>
- if (hasInline(im.symbol)) {
- inlineLog("skip", im.symbol, "no inlining into @inline methods")
- }
- else if(im.hasCode && !im.symbol.isBridge) {
- analyzeMethod(im)
- }
- }
- }
-
- val tfa = new analysis.MTFAGrowable()
- tfa.stat = global.settings.YstatisticsEnabled
- val staleOut = new mutable.ListBuffer[BasicBlock]
- val splicedBlocks = mutable.Set.empty[BasicBlock]
- val staleIn = mutable.Set.empty[BasicBlock]
-
- /**
- * A transformation local to the body of the IMethod received as argument.
- * An inlining decision consists in replacing a callsite with the body of the callee.
- * Please notice that, because `analyzeMethod()` itself may modify a method body,
- * the particular callee bodies that end up being inlined depend on the particular order in which methods are visited
- * (no topological sorting over the call-graph is attempted).
- *
- * Making an inlining decision requires type-flow information for both caller and callee.
- * Regarding the caller, such information is needed only for basic blocks containing inlining candidates
- * (and their transitive predecessors). This observation leads to using a custom type-flow analysis (MTFAGrowable)
- * that can be re-inited, i.e. that reuses lattice elements (type-flow information computed in a previous iteration)
- * as starting point for faster convergence in a new iteration.
- *
- * The mechanics of inlining are iterative for a given invocation of `analyzeMethod(m)`,
- * and are affected by inlinings from previous iterations
- * (ie, "heuristic" rules are based on statistics tracked for that purpose):
- *
- * (1) before the iterations proper start, so-called preinlining is performed.
- * Those callsites whose (receiver, concreteMethod) are both known statically
- * can be analyzed for inlining before computing a type-flow. Details in `preInline()`
- *
- * (2) the first iteration computes type-flow information for basic blocks containing inlining candidates
- * (and their transitive predecessors), so called `relevantBBs` basic blocks.
- * The ensuing analysis of each candidate (performed by `analyzeInc()`)
- * may result in a CFG isomorphic to that of the callee being inserted in place of the callsite
- * (i.e. a CALL_METHOD instruction is replaced with a single-entry single-exit CFG,
- * a substitution we call "successful inlining").
- *
- * (3) following iterations have `relevantBBs` updated to focus on the inlined basic blocks and their successors only.
- * Details in `MTFAGrowable.reinit()`
- * */
- def analyzeMethod(m: IMethod): Unit = {
- // m.normalize
- if (settings.debug)
- inlineLog("caller", ownedName(m.symbol), "in " + m.symbol.owner.fullName)
-
- val sizeBeforeInlining = m.code.blockCount
- val instrBeforeInlining = m.code.instructionCount
- var retry = false
- var count = 0
-
- // fresh name counter
- val fresh = mutable.HashMap.empty[String, Int] withDefaultValue 0
- // how many times have we already inlined this method here?
- val inlinedMethodCount = mutable.HashMap.empty[Symbol, Int] withDefaultValue 0
- val caller = new IMethodInfo(m)
- def analyzeMessage = s"Analyzing ${caller.length} blocks of $m for inlining sites."
-
- def preInline(isFirstRound: Boolean): Int = {
- val inputBlocks = caller.m.linearizedBlocks()
- val callsites: Function1[BasicBlock, List[opcodes.CALL_METHOD]] = {
- if(isFirstRound) tfa.conclusives else tfa.knownBeforehand
- }
- inlineWithoutTFA(inputBlocks, callsites)
- }
-
- /*
- * Inline straightforward callsites (those that can be inlined without a TFA).
- *
- * To perform inlining, all we need to know is listed as formal params in `analyzeInc()`:
- * - callsite and block containing it
- * - actual (ie runtime) class of the receiver
- * - actual (ie runtime) method being invoked
- * - stack length just before the callsite (to check whether enough arguments have been pushed).
- * The assert below lists the conditions under which "no TFA is needed"
- * (the statically known receiver and method are both final, thus, at runtime they can't be any others than those).
- *
- */
- def inlineWithoutTFA(inputBlocks: Traversable[BasicBlock], callsites: Function1[BasicBlock, List[opcodes.CALL_METHOD]]): Int = {
- var inlineCount = 0
- import scala.util.control.Breaks._
- for(x <- inputBlocks; easyCake = callsites(x); if easyCake.nonEmpty) {
- breakable {
- for(ocm <- easyCake) {
- assert(ocm.method.isEffectivelyFinalOrNotOverridden && ocm.method.owner.isEffectivelyFinalOrNotOverridden)
- if(analyzeInc(ocm, x, ocm.method.owner, -1, ocm.method)) {
- inlineCount += 1
- break()
- }
- }
- }
- }
-
- inlineCount
- }
-
- /*
- * Decides whether it's feasible and desirable to inline the body of the method given by `concreteMethod`
- * at the program point given by `i` (a callsite). The boolean result indicates whether inlining was performed.
- *
- */
- def analyzeInc(i: CALL_METHOD, bb: BasicBlock, receiver: Symbol, stackLength: Int, concreteMethod: Symbol): Boolean = {
- assert(bb.toList contains i, "Candidate callsite does not belong to BasicBlock.")
- val shouldWarn = hasInline(i.method)
-
- def warnNoInline(reason: String): Boolean = {
- def msg = "Could not inline required method %s because %s.".format(i.method.unexpandedName.decode, reason)
- if (settings.debug)
- inlineLog("fail", i.method.fullName, reason)
- if (shouldWarn)
- warn(i.pos, msg)
-
- false
- }
-
- var isAvailable = icodes available concreteMethod.enclClass
-
- if (!isAvailable && shouldLoadImplFor(concreteMethod, receiver)) {
- // Until r22824 this line was:
- // icodes.icode(concreteMethod.enclClass, true)
- //
- // Changing it to
- // icodes.load(concreteMethod.enclClass)
- // was the proximate cause for SI-3882:
- // error: Illegal index: 0 overlaps List((variable par1,LONG))
- // error: Illegal index: 0 overlaps List((variable par1,LONG))
- isAvailable = icodes.load(concreteMethod.enclClass)
- }
-
- def isCandidate = (
- isClosureClass(receiver)
- || concreteMethod.isEffectivelyFinalOrNotOverridden
- || receiver.isEffectivelyFinalOrNotOverridden
- )
-
- def isApply = concreteMethod.name == nme.apply
-
- def isCountable = !(
- isClosureClass(receiver)
- || isApply
- || isMonadicMethod(concreteMethod)
- || receiver.enclosingPackage == definitions.RuntimePackage
- ) // only count non-closures
-
- debuglog("Treating " + i
- + "\n\treceiver: " + receiver
- + "\n\ticodes.available: " + isAvailable
- + "\n\tconcreteMethod.isEffectivelyFinalOrNotOverridden: " + concreteMethod.isEffectivelyFinalOrNotOverridden)
-
- if (!isCandidate) warnNoInline("it can be overridden")
- else if (!isAvailable) warnNoInline("bytecode unavailable")
- else lookupIMethod(concreteMethod, receiver) filter (callee => callee.hasCode || warnNoInline("callee has no code")) exists { callee =>
- val inc = new IMethodInfo(callee)
- val pair = new CallerCalleeInfo(caller, inc, fresh, inlinedMethodCount)
-
- if (inc.hasHandlers && (stackLength == -1)) {
- // no inlining is done, yet don't warn about it, stackLength == -1 indicates we're trying to inlineWithoutTFA.
- // Shortly, a TFA will be computed and an error message reported if indeed inlining not possible.
- false
- }
- else {
- val isSafe = pair isStampedForInlining stackLength match {
- case DontInlineHere(msg) => warnNoInline(msg)
- case NeverSafeToInline => false
- case InlineableAtThisCaller => true
- case FeasibleInline(required, toPublicize) =>
- for (f <- toPublicize) {
- inlineLog("access", f, "making public")
- f setFlag Flags.notPRIVATE
- f setFlag Flags.notPROTECTED
- }
- // only add to `knownSafe` after all `toPublicize` fields actually made public.
- if (required == NonPublicRefs.Public)
- tfa.knownSafe += inc.sym
-
- true
- }
- isSafe && {
- retry = true
- if (isCountable) count += 1
- pair.doInline(bb, i)
- if (!pair.isInlineForced || inc.isMonadic) caller.inlinedCalls += 1
- inlinedMethodCount(inc.sym) += 1
-
- // Remove the caller from the cache (this inlining might have changed its calls-private relation).
- usesNonPublics -= m
- recentTFAs -= m.symbol
- true
- }
- }
- }
- }
-
- /* Pre-inlining consists in invoking the usual inlining subroutine with (receiver class, concrete method) pairs as input
- * where both method and receiver are final, which implies that the receiver computed via TFA will always match `concreteMethod.owner`.
- *
- * As with any invocation of `analyzeInc()` the inlining outcome is based on heuristics which favor inlining an isMonadicMethod before other methods.
- * That's why preInline() is invoked twice: any inlinings downplayed by the heuristics during the first round get an opportunity to rank higher during the second.
- *
- * As a whole, both `preInline()` invocations amount to priming the inlining process,
- * so that the first TFA that is run afterwards is able to gain more information as compared to a cold-start.
- */
- /*val totalPreInlines = */ { // Val name commented out to emphasize it is never used
- val firstRound = preInline(isFirstRound = true)
- if(firstRound == 0) 0 else (firstRound + preInline(isFirstRound = false))
- }
- staleOut.clear()
- splicedBlocks.clear()
- staleIn.clear()
-
- do {
- retry = false
- debuglog(analyzeMessage)
-
- /* it's important not to inline in unreachable basic blocks. linearizedBlocks() returns only reachable ones. */
- tfa.callerLin = caller.m.linearizedBlocks()
- /* TODO Do we really want to inline inside exception handlers?
- * Seems counterproductive (the larger the method the less likely it will be JITed).
- * The alternative would be `linearizer.linearizeAt(caller.m, caller.m.startBlock)`.
- * And, we would cut down on TFA iterations, too.
- * See also comment on the same topic in TypeFlowAnalysis. */
-
- tfa.reinit(m, staleOut.toList, splicedBlocks, staleIn)
- tfa.run
-
- staleOut.clear()
- splicedBlocks.clear()
- staleIn.clear()
-
- import scala.util.control.Breaks._
- for(bb <- tfa.callerLin; if tfa.preCandidates(bb)) {
- val cms = bb.toList collect { case cm : CALL_METHOD => cm }
- breakable {
- for (cm <- cms; if tfa.remainingCALLs.isDefinedAt(cm)) {
- val analysis.CallsiteInfo(_, receiver, stackLength, concreteMethod) = tfa.remainingCALLs(cm)
- if (analyzeInc(cm, bb, receiver, stackLength, concreteMethod)) {
- break()
- }
- }
- }
- }
-
- /* As part of inlining, some instructions are moved to a new block.
- * In detail: the instructions moved to a new block originally appeared after a (by now inlined) callsite.
- * Their new home is an `afterBlock` created by `doInline()` to that effect.
- * Each block in staleIn is one such `afterBlock`.
- *
- * Some of those instructions may be CALL_METHOD possibly tracked in `remainingCALLs`
- * (with an entry still noting the old containing block). However, that causes no problem:
- *
- * (1) such callsites won't be analyzed for inlining by `analyzeInc()` (*in this iteration*)
- * because of the `break` that abandons the original basic block where it was contained.
- *
- * (2) Additionally, its new containing block won't be visited either (*in this iteration*)
- * because the new blocks don't show up in the linearization computed before inlinings started:
- * `for(bb <- tfa.callerLin; if tfa.preCandidates(bb)) {`
- *
- * For a next iteration, the new home of any instructions that have moved
- * will be tracked properly in `remainingCALLs` after `MTFAGrowable.reinit()` puts on radar their new homes.
- *
- */
- if(retry) {
- for(afterBlock <- staleIn) {
- val justCALLsAfter = afterBlock.toList collect { case c : opcodes.CALL_METHOD => c }
- for(ia <- justCALLsAfter) { tfa.remainingCALLs.remove(ia) }
- }
- }
-
- /*
- if(splicedBlocks.nonEmpty) { // TODO explore (saves time but leads to slightly different inlining decisions)
- // opportunistically perform straightforward inlinings before the next typeflow round
- val savedRetry = retry
- val savedStaleOut = staleOut.toSet; staleOut.clear()
- val savedStaleIn = staleIn.toSet ; staleIn.clear()
- val howmany = inlineWithoutTFA(splicedBlocks, tfa.knownBeforehand)
- splicedBlocks ++= staleIn
- staleOut.clear(); staleOut ++= savedStaleOut;
- staleIn.clear(); staleIn ++= savedStaleIn;
- retry = savedRetry
- }
- */
-
- if (tfa.stat)
- log(m.symbol.fullName + " iterations: " + tfa.iterations + " (size: " + caller.length + ")")
- }
- while (retry && count < MAX_INLINE_RETRY)
-
- for(inlFail <- tfa.warnIfInlineFails) {
- warn(inlFail.pos, "At the end of the day, could not inline @inline-marked method " + inlFail.method.unexpandedName.decode)
- }
-
- m.normalize()
- if (sizeBeforeInlining > 0) {
- val instrAfterInlining = m.code.instructionCount
- val inlinings = caller.inlinedCalls
- if (inlinings > 0) {
- val s1 = s"instructions $instrBeforeInlining -> $instrAfterInlining"
- val s2 = if (sizeBeforeInlining == m.code.blockCount) "" else s", blocks $sizeBeforeInlining -> ${m.code.blockCount}"
- val callees = inlinedMethodCount.toList map { case (k, v) => k.fullNameString + ( if (v == 1) "" else "/" + v ) }
-
- inlineLog("inlined", m.symbol.fullName, callees.sorted.mkString(inlinings + " inlined: ", ", ", ""))
- inlineLog("<<tldr>>", m.symbol.fullName, s"${m.symbol.nameString}: $s1$s2")
- }
- }
- }
-
- private def isHigherOrderMethod(sym: Symbol) = (
- sym.isMethod
- && enteringExplicitOuter(sym.info.paramTypes exists isFunctionType) // was "at erasurePhase.prev"
- )
-
- /** Should method 'sym' being called in 'receiver' be loaded from disk? */
- def shouldLoadImplFor(sym: Symbol, receiver: Symbol): Boolean = {
- def alwaysLoad = (receiver.enclosingPackage == RuntimePackage) || (receiver == PredefModule.moduleClass)
- def loadCondition = sym.isEffectivelyFinalOrNotOverridden && isMonadicMethod(sym) && isHigherOrderMethod(sym)
-
- val res = hasInline(sym) || alwaysLoad || loadCondition
- debuglog("shouldLoadImplFor: " + receiver + "." + sym + ": " + res)
- res
- }
-
- class IMethodInfo(val m: IMethod) {
- override def toString = m.toString
-
- val sym = m.symbol
- def owner = sym.owner
- def paramTypes = sym.info.paramTypes
- def minimumStack = paramTypes.length + 1
-
- def isBridge = sym.isBridge
- val isInClosure = isClosureClass(owner)
- val isHigherOrder = isHigherOrderMethod(sym)
- def isMonadic = isMonadicMethod(sym)
-
- def handlers = m.exh
- def blocks = m.blocks
- def locals = m.locals
- def length = blocks.length
- def openBlocks = blocks filterNot (_.closed)
- def instructions = m.code.instructions
-
- def isSmall = (length <= SMALL_METHOD_SIZE) && blocks(0).length < 10
- def isLarge = length > MAX_INLINE_SIZE
- def isRecursive = m.recursive
- def hasHandlers = handlers.nonEmpty || m.bytecodeHasEHs
-
- def isSynchronized = sym.hasFlag(Flags.SYNCHRONIZED)
- def hasNonFinalizerHandler = handlers exists {
- case _: Finalizer => true
- case _ => false
- }
-
- // the number of inlined calls in 'm', used by 'isScoreOK'
- var inlinedCalls = 0
-
- def addLocals(ls: List[Local]) = m.locals ++= ls
- def addLocal(l: Local) = addLocals(List(l))
- def addHandlers(exhs: List[ExceptionHandler]) = m.exh = exhs ::: m.exh
-
- /**
- * This method inspects the callee's instructions, finding out the most restrictive accessibility implied by them.
- *
- * Rather than giving up upon encountering an access to a private field `p`, it provisorily admits `p` as "can-be-made-public", provided:
- * - `p` is being compiled as part of this compilation run, and
- * - `p` is synthetic or param-accessor.
- *
- * This method is side-effect free, in particular it lets the invoker decide
- * whether the accessibility of the `toBecomePublic` fields should be changed or not.
- */
- def accessRequirements: AccessReq = {
-
- var toBecomePublic: List[Symbol] = Nil
-
- def check(sym: Symbol, cond: Boolean) =
- if (cond) Private
- else if (sym.isProtected) Protected
- else Public
-
- def canMakePublic(f: Symbol): Boolean =
- (m.sourceFile ne NoSourceFile) &&
- (f.isSynthetic || f.isParamAccessor) &&
- { toBecomePublic = f :: toBecomePublic; true }
-
- /* A safety check to consider as private, for the purposes of inlining, a public field that:
- * (1) is defined in an external library, and
- * (2) can be presumed synthetic (due to a dollar sign in its name).
- * Such field was made public by `doMakePublic()` and we don't want to rely on that,
- * because under other compilation conditions (ie no -optimize) that won't be the case anymore.
- *
- * This allows aggressive intra-library inlining (making public if needed)
- * that does not break inter-library scenarios (see comment for `Inliners`).
- *
- * TODO handle more robustly the case of a trait var changed at the source-level from public to private[this]
- * (eg by having ICodeReader use unpickler, see SI-5442).
-
- DISABLED
-
- def potentiallyPublicized(f: Symbol): Boolean = {
- (m.sourceFile eq NoSourceFile) && f.name.containsChar('$')
- }
- */
-
-
- def isPrivateForInlining(sym: Symbol): Boolean = {
- if (sym.isJavaDefined) {
- def check(sym: Symbol) = !(sym.isPublic || sym.isProtected)
- check(sym) || check(sym.owner) // SI-7582 Must check the enclosing class *and* the symbol for Java.
- }
- else sym.isPrivate // Scala never emits package-private bytecode
- }
-
- def checkField(f: Symbol) = check(f, isPrivateForInlining(f) && !canMakePublic(f))
- def checkSuper(n: Symbol) = check(n, isPrivateForInlining(n) || !n.isClassConstructor)
- def checkMethod(n: Symbol) = check(n, isPrivateForInlining(n))
-
- def getAccess(i: Instruction) = i match {
- case CALL_METHOD(n, SuperCall(_)) => checkSuper(n)
- case CALL_METHOD(n, _) => checkMethod(n)
- case LOAD_FIELD(f, _) => checkField(f)
- case STORE_FIELD(f, _) => checkField(f)
- case _ => Public
- }
-
- var seen = Public
- val iter = instructions.iterator
- while((seen ne Private) && iter.hasNext) {
- val i = iter.next()
- getAccess(i) match {
- case Private =>
- inlineLog("access", s"instruction $i requires private access", "pos=" + i.pos)
- toBecomePublic = Nil
- seen = Private
- case Protected => seen = Protected
- case _ => ()
- }
- }
-
- AccessReq(seen, toBecomePublic)
- }
-
- }
-
- /**
- * Classifies a pair (caller, callee) into one of four categories:
- *
- * (a) inlining should be performed, classified in turn into:
- * (a.1) `InlineableAtThisCaller`: unconditionally at this caller
- * (a.2) `FeasibleInline`: it only remains for certain access requirements to be met (see `IMethodInfo.accessRequirements()`)
- *
- * (b) inlining shouldn't be performed, classified in turn into:
- * (b.1) `DontInlineHere`: indicates that this particular occurrence of the callee at the caller shouldn't be inlined.
- * - Nothing is said about the outcome for other callers, or for other occurrences of the callee for the same caller.
- * - In particular inlining might be possible, but heuristics gave a low score for it.
- * (b.2) `NeverSafeToInline`: the callee can't be inlined anywhere, irrespective of caller.
- *
- * The classification above is computed by `isStampedForInlining()` based on which `analyzeInc()` goes on to:
- * - either log the reason for failure --- case (b) ---,
- * - or perform inlining --- case (a) ---.
- */
- sealed abstract class InlineSafetyInfo
- case object NeverSafeToInline extends InlineSafetyInfo
- case object InlineableAtThisCaller extends InlineSafetyInfo
- case class DontInlineHere(msg: String) extends InlineSafetyInfo
- case class FeasibleInline(accessNeeded: NonPublicRefs.Value, toBecomePublic: List[Symbol]) extends InlineSafetyInfo
-
- case class AccessReq(
- accessNeeded: NonPublicRefs.Value,
- toBecomePublic: List[Symbol]
- )
-
- final class CallerCalleeInfo(val caller: IMethodInfo, val inc: IMethodInfo, fresh: mutable.Map[String, Int], inlinedMethodCount: scala.collection.Map[Symbol, Int]) {
-
- assert(!caller.isBridge && inc.m.hasCode,
- "A guard in Inliner.analyzeClass() should have prevented from getting here.")
-
- def isLargeSum = caller.length + inc.length - 1 > SMALL_METHOD_SIZE
-
- private def freshName(s: String): TermName = {
- fresh(s) += 1
- newTermName(s + fresh(s))
- }
-
- private def isKnownToInlineSafely: Boolean = { tfa.knownSafe(inc.sym) }
-
- val isInlineForced = hasInline(inc.sym)
- val isInlineForbidden = hasNoInline(inc.sym)
- assert(!(isInlineForced && isInlineForbidden), "method ("+inc.m+") marked both @inline and @noinline.")
-
- /** Inline 'inc' into 'caller' at the given block and instruction.
- * The instruction must be a CALL_METHOD.
- */
- def doInline(block: BasicBlock, instr: CALL_METHOD) {
-
- staleOut += block
-
- tfa.remainingCALLs.remove(instr) // this bookkeeping is done here and not in MTFAGrowable.reinit due to (1st) convenience and (2nd) necessity.
- tfa.isOnWatchlist.remove(instr) // ditto
- tfa.warnIfInlineFails.remove(instr)
-
- val targetPos = instr.pos
-
- def blockEmit(i: Instruction) = block.emit(i, targetPos)
- def newLocal(baseName: String, kind: TypeKind) =
- new Local(caller.sym.newVariable(freshName(baseName), targetPos) setInfo kind.toType, kind, false)
-
- val (hasRETURN, a) = getRecentTFA(inc.m, isInlineForced)
-
- /* The exception handlers that are active at the current block. */
- val activeHandlers = caller.handlers filter (_ covered block)
-
- /* Map 'original' blocks to the ones inlined in the caller. */
- val inlinedBlock = mutable.Map[BasicBlock, BasicBlock]()
-
- val varsInScope = mutable.HashSet[Local]() ++= block.varsInScope
-
- /* Side effects varsInScope when it sees SCOPE_ENTERs. */
- def instrBeforeFilter(i: Instruction): Boolean = {
- i match { case SCOPE_ENTER(l) => varsInScope += l ; case _ => () }
- i ne instr
- }
- val instrBefore = block.toList takeWhile instrBeforeFilter
- val instrAfter = block.toList drop (instrBefore.length + 1)
-
- assert(!instrAfter.isEmpty, "CALL_METHOD cannot be the last instruction in block!")
-
- // store the '$this' into the special local
- val inlinedThis = newLocal("$inlThis", REFERENCE(ObjectClass))
-
- /* buffer for the returned value */
- val retVal = inc.m.returnType match {
- case UNIT => null
- case x => newLocal("$retVal", x)
- }
-
- val inlinedLocals = mutable.HashMap.empty[Local, Local]
-
- /* Add a new block in the current context. */
- def newBlock() = {
- val b = caller.m.code.newBlock()
- activeHandlers foreach (_ addCoveredBlock b)
- if (retVal ne null) b.varsInScope += retVal
- b.varsInScope += inlinedThis
- b.varsInScope ++= varsInScope
- b
- }
-
- def translateExh(e: ExceptionHandler) = {
- val handler: ExceptionHandler = e.dup
- handler.covered = handler.covered map inlinedBlock
- handler setStartBlock inlinedBlock(e.startBlock)
- handler
- }
-
- /* alfa-rename `l` in caller's context. */
- def dupLocal(l: Local): Local = {
- val sym = caller.sym.newVariable(freshName(l.sym.name.toString), l.sym.pos)
- // sym.setInfo(l.sym.tpe)
- val dupped = new Local(sym, l.kind, false)
- inlinedLocals(l) = dupped
- dupped
- }
-
- val afterBlock = newBlock()
-
- /* Map from nw.init instructions to their matching NEW call */
- val pending: mutable.Map[Instruction, NEW] = new mutable.HashMap
-
- /* Map an instruction from the callee to one suitable for the caller. */
- def map(i: Instruction): Instruction = {
- def assertLocal(l: Local) = {
- assert(caller.locals contains l, "Could not find local '" + l + "' in locals, nor in inlinedLocals: " + inlinedLocals)
- i
- }
- def isInlined(l: Local) = inlinedLocals isDefinedAt l
-
- val newInstr = i match {
- case THIS(clasz) => LOAD_LOCAL(inlinedThis)
- case STORE_THIS(_) => STORE_LOCAL(inlinedThis)
- case JUMP(whereto) => JUMP(inlinedBlock(whereto))
- case CJUMP(succ, fail, cond, kind) => CJUMP(inlinedBlock(succ), inlinedBlock(fail), cond, kind)
- case CZJUMP(succ, fail, cond, kind) => CZJUMP(inlinedBlock(succ), inlinedBlock(fail), cond, kind)
- case SWITCH(tags, labels) => SWITCH(tags, labels map inlinedBlock)
- case RETURN(_) => JUMP(afterBlock)
- case LOAD_LOCAL(l) if isInlined(l) => LOAD_LOCAL(inlinedLocals(l))
- case STORE_LOCAL(l) if isInlined(l) => STORE_LOCAL(inlinedLocals(l))
- case LOAD_LOCAL(l) => assertLocal(l)
- case STORE_LOCAL(l) => assertLocal(l)
- case SCOPE_ENTER(l) if isInlined(l) => SCOPE_ENTER(inlinedLocals(l))
- case SCOPE_EXIT(l) if isInlined(l) => SCOPE_EXIT(inlinedLocals(l))
-
- case nw @ NEW(sym) =>
- val r = NEW(sym)
- pending(nw.init) = r
- r
-
- case CALL_METHOD(meth, Static(true)) if meth.isClassConstructor =>
- CALL_METHOD(meth, Static(onInstance = true))
-
- case _ => i.clone()
- }
- // check any pending NEW's
- pending remove i foreach (_.init = newInstr.asInstanceOf[CALL_METHOD])
- newInstr
- }
-
- caller addLocals (inc.locals map dupLocal)
- caller addLocal inlinedThis
-
- if (retVal ne null)
- caller addLocal retVal
-
- inc.m foreachBlock { b =>
- inlinedBlock += (b -> newBlock())
- inlinedBlock(b).varsInScope ++= (b.varsInScope map inlinedLocals)
- }
-
- // re-emit the instructions before the call
- block.open()
- block.clear()
- block emit instrBefore
-
- // store the arguments into special locals
- inc.m.params.reverse foreach (p => blockEmit(STORE_LOCAL(inlinedLocals(p))))
- blockEmit(STORE_LOCAL(inlinedThis))
-
- // jump to the start block of the callee
- blockEmit(JUMP(inlinedBlock(inc.m.startBlock)))
- block.close()
-
- // duplicate the other blocks in the callee
- val calleeLin = inc.m.linearizedBlocks()
- calleeLin foreach { bb =>
- var info = if(hasRETURN) (a in bb) else null
- def emitInlined(i: Instruction) = inlinedBlock(bb).emit(i, targetPos)
- def emitDrops(toDrop: Int) = info.stack.types drop toDrop foreach (t => emitInlined(DROP(t)))
-
- for (i <- bb) {
- i match {
- case RETURN(UNIT) => emitDrops(0)
- case RETURN(kind) =>
- if (info.stack.length > 1) {
- emitInlined(STORE_LOCAL(retVal))
- emitDrops(1)
- emitInlined(LOAD_LOCAL(retVal))
- }
- case _ => ()
- }
- emitInlined(map(i))
- info = if(hasRETURN) a.interpret(info, i) else null
- }
- inlinedBlock(bb).close()
- }
-
- afterBlock emit instrAfter
- afterBlock.close()
-
- staleIn += afterBlock
- splicedBlocks ++= (calleeLin map inlinedBlock)
-
- // add exception handlers of the callee
- caller addHandlers (inc.handlers map translateExh)
- assert(pending.isEmpty, "Pending NEW elements: " + pending)
- if (settings.debug) icodes.checkValid(caller.m)
- }
-
- def isStampedForInlining(stackLength: Int): InlineSafetyInfo = {
-
- if(tfa.blackballed(inc.sym)) { return NeverSafeToInline }
-
- if(!isKnownToInlineSafely) {
-
- if(inc.openBlocks.nonEmpty) {
- val msg = ("Encountered " + inc.openBlocks.size + " open block(s) in isSafeToInline: this indicates a bug in the optimizer!\n" +
- " caller = " + caller.m + ", callee = " + inc.m)
- warn(inc.sym.pos, msg)
- tfa.knownNever += inc.sym
- return DontInlineHere("Open blocks in " + inc.m)
- }
-
- val reasonWhyNever: String = {
- var rs: List[String] = Nil
- if(inc.isRecursive) { rs ::= "is recursive" }
- if(isInlineForbidden) { rs ::= "is annotated @noinline" }
- if(inc.isSynchronized) { rs ::= "is synchronized method" }
- if(inc.m.bytecodeHasEHs) { rs ::= "bytecode contains exception handlers / finally clause" } // SI-6188
- if(inc.m.bytecodeHasInvokeDynamic) { rs ::= "bytecode contains invoke dynamic" }
- if(rs.isEmpty) null else rs.mkString("", ", and ", "")
- }
-
- if(reasonWhyNever != null) {
- tfa.knownNever += inc.sym
- inlineLog("never", inc.sym, reasonWhyNever)
- // next time around NeverSafeToInline is returned, thus skipping (duplicate) msg, this is intended.
- return DontInlineHere(inc.m + " " + reasonWhyNever)
- }
-
- if(sameSymbols) { // TODO but this also amounts to recursive, ie should lead to adding to tfa.knownNever, right?
- tfa.knownUnsafe += inc.sym
- return DontInlineHere("sameSymbols (ie caller == callee)")
- }
-
- }
-
- /*
- * From here on, two main categories of checks remain, (a) and (b) below:
- * (a.1) either the scoring heuristics give green light; or
- * (a.2) forced as candidate due to @inline.
- * After that, safety proper is checked:
- * (b.1) the callee does not contain calls to private methods when called from another class
- * (b.2) the callee is not going to be inlined into a position with non-empty stack,
- * while having a top-level finalizer (see liftedTry problem)
- * As a result of (b), some synthetic private members can be chosen to become public.
- */
-
- val score = inlinerScore
- val scoreStr = if (score > 0) "+" + score else "" + score
- val what = if (score > 0) "ok to" else "don't"
- inlineLog(scoreStr, inc.m.symbol, s"$what inline into ${ownedName(caller.m.symbol)}")
-
- if (!isInlineForced && score <= 0) {
- // During inlining retry, a previous caller-callee pair that scored low may pass.
- // Thus, adding the callee to tfa.knownUnsafe isn't warranted.
- return DontInlineHere(s"inliner heuristic")
- }
-
- if(inc.hasHandlers && (stackLength > inc.minimumStack)) {
- return DontInlineHere("callee contains exception handlers / finally clause, and is invoked with non-empty operand stack") // SI-6157
- }
-
- if(isKnownToInlineSafely) { return InlineableAtThisCaller }
-
- if(stackLength > inc.minimumStack && inc.hasNonFinalizerHandler) {
- val msg = "method " + inc.sym + " is used on a non-empty stack with finalizer."
- debuglog(msg)
- // FYI: not reason enough to add inc.sym to tfa.knownUnsafe (because at other callsite in this caller, inlining might be ok)
- return DontInlineHere(msg)
- }
-
- val accReq = inc.accessRequirements
- if(!canAccess(accReq.accessNeeded)) {
- tfa.knownUnsafe += inc.sym
- val msg = "access level required by callee not matched by caller"
- inlineLog("fail", inc.sym, msg)
- return DontInlineHere(msg)
- }
-
- FeasibleInline(accReq.accessNeeded, accReq.toBecomePublic)
-
- }
-
- def canAccess(level: NonPublicRefs.Value) = level match {
- case Private => caller.owner == inc.owner
- case Protected => caller.owner.tpe <:< inc.owner.tpe
- case Public => true
- }
- private def sameSymbols = caller.sym == inc.sym
-
- /** Gives green light for inlining (which may still be vetoed later). Heuristics:
- * - it's bad to make the caller larger (> SMALL_METHOD_SIZE) if it was small
- * - it's bad to inline large methods
- * - it's good to inline higher order functions
- * - it's good to inline closures functions.
- * - it's bad (useless) to inline inside bridge methods
- */
- def inlinerScore: Int = {
- var score = 0
-
- // better not inline inside closures, but hope that the closure itself is repeatedly inlined
- if (caller.isInClosure) score -= 2
- else if (caller.inlinedCalls < 1) score -= 1 // only monadic methods can trigger the first inline
-
- if (inc.isSmall) score += 1
- // if (inc.hasClosureParam) score += 2
- if (inc.isLarge) score -= 1
- if (caller.isSmall && isLargeSum) {
- score -= 1
- debuglog(s"inliner score decreased to $score because small caller $caller would become large")
- }
-
- if (inc.isMonadic) score += 3
- else if (inc.isHigherOrder) score += 1
-
- if (inc.isInClosure) score += 2
- if (inlinedMethodCount(inc.sym) > 2) score -= 2
- score
- }
- }
-
- def lookupIMethod(meth: Symbol, receiver: Symbol): Option[IMethod] = {
- def tryParent(sym: Symbol) = icodes icode sym flatMap (_ lookupMethod meth)
-
- (receiver.info.baseClasses.iterator map tryParent find (_.isDefined)).flatten
- }
- } /* class Inliner */
-} /* class Inliners */