diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc')
54 files changed, 1162 insertions, 16049 deletions
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index 6be1fda1b5..34b07a2651 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -117,9 +117,7 @@ trait CompilationUnits { global: Global => */ def targetPos: Position = NoPosition - /** The icode representation of classes in this compilation unit. - * It is empty up to phase 'icode'. - */ + /** For sbt compatibility (https://github.com/scala/scala/pull/4588) */ val icode: LinkedHashSet[icodes.IClass] = new LinkedHashSet @deprecated("Call global.reporter.echo directly instead.", "2.11.2") diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 422e2080f0..70582c82c8 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -7,12 +7,12 @@ package scala package tools package nsc -import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException } +import java.io.{ File, IOException, FileNotFoundException } import java.net.URL import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException } import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } -import reporters.{ Reporter, ConsoleReporter } +import reporters.Reporter import util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning } import scala.reflect.ClassTag import scala.reflect.internal.util.{ ScalaClassLoader, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile } @@ -25,12 +25,8 @@ import ast.parser._ import typechecker._ import transform.patmat.PatternMatching import transform._ -import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, JavaPlatform } import backend.jvm.GenBCode -import backend.jvm.GenASM -import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination } -import backend.icode.analysis._ import scala.language.postfixOps import scala.tools.nsc.ast.{TreeGen => AstTreeGen} import scala.tools.nsc.classpath.FlatClassPath @@ -140,12 +136,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val global: Global.this.type = Global.this } with ConstantFolder - /** ICode generator */ - object icodes extends { - val global: Global.this.type = Global.this - } with ICodes + /** For sbt compatibility (https://github.com/scala/scala/pull/4588) */ + object icodes { + class IClass(val symbol: Symbol) + } - /** Scala primitives, used in genicode */ + /** Scala primitives, used the backend */ object scalaPrimitives extends { val global: Global.this.type = Global.this } with ScalaPrimitives @@ -157,18 +153,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) type SymbolPair = overridingPairs.SymbolPair - // Optimizer components - - /** ICode analysis for optimization */ - object analysis extends { - val global: Global.this.type = Global.this - } with TypeFlowAnalysis - - /** Copy propagation for optimization */ - object copyPropagation extends { - val global: Global.this.type = Global.this - } with CopyPropagation - // Components for collecting and generating output /** Some statistics (normally disabled) set with -Ystatistics */ @@ -591,59 +575,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = None } with Delambdafy - // phaseName = "icode" - object genicode extends { - val global: Global.this.type = Global.this - val runsAfter = List("cleanup") - val runsRightAfter = None - } with GenICode - - // phaseName = "inliner" - object inliner extends { - val global: Global.this.type = Global.this - val runsAfter = List("icode") - val runsRightAfter = None - } with Inliners - - // phaseName = "inlinehandlers" - object inlineExceptionHandlers extends { - val global: Global.this.type = Global.this - val runsAfter = List("inliner") - val runsRightAfter = None - } with InlineExceptionHandlers - - // phaseName = "closelim" - object closureElimination extends { - val global: Global.this.type = Global.this - val runsAfter = List("inlinehandlers") - val runsRightAfter = None - } with ClosureElimination - - // phaseName = "constopt" - object constantOptimization extends { - val global: Global.this.type = Global.this - val runsAfter = List("closelim") - val runsRightAfter = None - } with ConstantOptimization - - // phaseName = "dce" - object deadCode extends { - val global: Global.this.type = Global.this - val runsAfter = List("closelim") - val runsRightAfter = None - } with DeadCodeElimination - - // phaseName = "jvm", ASM-based version - object genASM extends { - val global: Global.this.type = Global.this - val runsAfter = List("dce") - val runsRightAfter = None - } with GenASM - // phaseName = "bcode" object genBCode extends { val global: Global.this.type = Global.this - val runsAfter = List("dce") + val runsAfter = List("cleanup") val runsRightAfter = None } with GenBCode @@ -674,13 +609,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val global: Global.this.type = Global.this } with TreeCheckers - /** Icode verification */ - object icodeCheckers extends { - val global: Global.this.type = Global.this - } with ICodeCheckers - - object icodeChecker extends icodeCheckers.ICodeChecker() - object typer extends analyzer.Typer( analyzer.NoContext.make(EmptyTree, RootClass, newScope) ) @@ -713,12 +641,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) mixer -> "mixin composition", delambdafy -> "remove lambdas", cleanup -> "platform-specific cleanups, generate reflective calls", - genicode -> "generate portable intermediate code", - inliner -> "optimization: do inlining", - inlineExceptionHandlers -> "optimization: inline exception handlers", - closureElimination -> "optimization: eliminate uncalled closures", - constantOptimization -> "optimization: optimize null and other constants", - deadCode -> "optimization: eliminate dead code", terminal -> "the last phase during a compilation run" ) @@ -1057,9 +979,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) @inline final def enteringErasure[T](op: => T): T = enteringPhase(currentRun.erasurePhase)(op) @inline final def enteringExplicitOuter[T](op: => T): T = enteringPhase(currentRun.explicitouterPhase)(op) @inline final def enteringFlatten[T](op: => T): T = enteringPhase(currentRun.flattenPhase)(op) - @inline final def enteringIcode[T](op: => T): T = enteringPhase(currentRun.icodePhase)(op) @inline final def enteringMixin[T](op: => T): T = enteringPhase(currentRun.mixinPhase)(op) @inline final def enteringDelambdafy[T](op: => T): T = enteringPhase(currentRun.delambdafyPhase)(op) + @inline final def enteringJVM[T](op: => T): T = enteringPhase(currentRun.jvmPhase)(op) @inline final def enteringPickler[T](op: => T): T = enteringPhase(currentRun.picklerPhase)(op) @inline final def enteringSpecialize[T](op: => T): T = enteringPhase(currentRun.specializePhase)(op) @inline final def enteringTyper[T](op: => T): T = enteringPhase(currentRun.typerPhase)(op) @@ -1333,8 +1255,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // val superaccessorsPhase = phaseNamed("superaccessors") val picklerPhase = phaseNamed("pickler") val refchecksPhase = phaseNamed("refchecks") - // val selectiveanfPhase = phaseNamed("selectiveanf") - // val selectivecpsPhase = phaseNamed("selectivecps") val uncurryPhase = phaseNamed("uncurry") // val tailcallsPhase = phaseNamed("tailcalls") val specializePhase = phaseNamed("specialize") @@ -1348,20 +1268,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val mixinPhase = phaseNamed("mixin") val delambdafyPhase = phaseNamed("delambdafy") val cleanupPhase = phaseNamed("cleanup") - val icodePhase = phaseNamed("icode") - val inlinerPhase = phaseNamed("inliner") - val inlineExceptionHandlersPhase = phaseNamed("inlinehandlers") - val closelimPhase = phaseNamed("closelim") - val dcePhase = phaseNamed("dce") - // val jvmPhase = phaseNamed("jvm") + val jvmPhase = phaseNamed("jvm") def runIsAt(ph: Phase) = globalPhase.id == ph.id - def runIsAtOptimiz = { - runIsAt(inlinerPhase) || // listing phases in full for robustness when -Ystop-after has been given. - runIsAt(inlineExceptionHandlersPhase) || - runIsAt(closelimPhase) || - runIsAt(dcePhase) - } + def runIsAtOptimiz = runIsAt(jvmPhase) isDefined = true @@ -1424,8 +1334,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) if (canCheck) { phase = globalPhase - if (globalPhase.id >= icodePhase.id) icodeChecker.checkICodes() - else treeChecker.checkTrees() + if (globalPhase.id <= cleanupPhase.id) + treeChecker.checkTrees() } } @@ -1506,14 +1416,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // progress update informTime(globalPhase.description, startTime) - val shouldWriteIcode = ( - (settings.writeICode.isSetByUser && (settings.writeICode containsPhase globalPhase)) - || (!settings.Xprint.doAllPhases && (settings.Xprint containsPhase globalPhase) && runIsAtOptimiz) - ) - if (shouldWriteIcode) { - // Write *.icode files when -Xprint-icode or -Xprint:<some-optimiz-phase> was given. - writeICode() - } else if ((settings.Xprint containsPhase globalPhase) || settings.printLate && runIsAt(cleanupPhase)) { + if ((settings.Xprint containsPhase globalPhase) || settings.printLate && runIsAt(cleanupPhase)) { // print trees if (settings.Xshowtrees || settings.XshowtreesCompact || settings.XshowtreesStringified) nodePrinters.printAll() else printAllUnits() @@ -1678,30 +1581,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** Returns the file with the given suffix for the given class. Used for icode writing. */ def getFile(clazz: Symbol, suffix: String): File = getFile(clazz.sourceFile, clazz.fullName split '.', suffix) - private def writeICode() { - val printer = new icodes.TextPrinter(writer = null, icodes.linearizer) - icodes.classes.values foreach { cls => - val file = { - val module = if (cls.symbol.hasModuleFlag) "$" else "" - val faze = if (settings.debug) phase.name else f"${phase.id}%02d" // avoid breaking windows build with long filename - getFile(cls.symbol, s"$module-$faze.icode") - } - - try { - val stream = new FileOutputStream(file) - printer.setWriter(new PrintWriter(stream, true)) - try - printer.printClass(cls) - finally - stream.close() - informProgress(s"wrote $file") - } catch { - case e: IOException => - if (settings.debug) e.printStackTrace() - globalError(s"could not write file $file") - } - } - } def createJavadoc = false } diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 6bd123c51f..16f086e9e7 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -40,13 +40,9 @@ trait JavaPlatform extends Platform { def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) = currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst)) - private def classEmitPhase = - if (settings.isBCodeActive) genBCode - else genASM - 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/ScalaPrimitives.scala b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala index b8ddb65de9..9a53737554 100644 --- a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala +++ b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala @@ -31,7 +31,6 @@ abstract class ScalaPrimitives { import global._ import definitions._ - import global.icodes._ // Arithmetic unary operations final val POS = 1 // +x @@ -457,18 +456,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 +522,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 +539,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 +552,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 +561,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 +576,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 +591,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 a927097b62..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.packageObject match { - case NoSymbol => abort("Cannot use package as value: " + tree) - case s => - devWarning(s"Found ${tree.symbol} where a package object is required. Converting to ${s.moduleClass}") - 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.externalEqualsNumChar - 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 c0e0240210..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ /dev/null @@ -1,297 +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.Flags -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 && !symbol.hasFlag(Flags.JAVA_DEFAULTMETHOD)) || 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 27bf836484..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("ShitOp 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/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala deleted file mode 100644 index 42738d3e1c..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ /dev/null @@ -1,476 +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 - - val sam = { - if (classSym.isImplClass || classSym.isEffectivelyFinal) None - else { - // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an - // empty parameter list in later phases and would therefore be picked as SAM. - val samSym = exitingPickler(definitions.findSam(classSym.tpe)) - if (samSym == NoSymbol) None - else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) - } - } - - var warning = Option.empty[ClassSymbolInfoFailureSI9111] - - // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some - // 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, sam, 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 adaf870c46..790469d874 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -17,6 +17,7 @@ import scala.tools.asm import GenBCode._ import BackendReporting._ import scala.tools.asm.tree.MethodInsnNode +import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp} /* * @@ -28,16 +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. */ @@ -121,7 +118,7 @@ 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") } @@ -582,8 +579,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // 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) + case Apply(fun @ Select(Super(_, _), _), args) => + val invokeStyle = InvokeStyle.Super // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) genLoadArguments(args, paramTKs(app)) @@ -629,7 +626,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") @@ -667,9 +664,9 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { 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 (sym.isStaticMember) InvokeStyle.Static + else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special + else InvokeStyle.Virtual if (invokeStyle.hasInstance) { genLoadQualifier(fun) @@ -696,7 +693,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case _ => } - if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) { + if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isVirtual) { // 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 @@ -998,8 +995,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. case List(Literal(Constant("")), arg) => genLoad(arg, ObjectRef) - genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos) - + genCallMethod(String_valueOf, InvokeStyle.Static, arg.pos) case concatenations => bc.genStartConcat(tree.pos) for (elem <- concatenations) { @@ -1030,7 +1026,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // whether to reference the type of the receiver or // the type of the method owner val useMethodOwner = ( - style != icodes.opcodes.Dynamic + !style.isVirtual || hostSymbol.isBottomClass || methodOwner == definitions.ObjectClass ) @@ -1057,11 +1053,9 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } } - if (style.isStatic) { - if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) } - else { bc.invokestatic (jowner, jname, mdescr, pos) } - } - else if (style.isDynamic) { + if (style.isStatic) { bc.invokestatic (jowner, jname, mdescr, pos) } + else if (style.isSpecial) { bc.invokespecial (jowner, jname, mdescr, pos) } + else if (style.isVirtual) { if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) } else { bc.invokevirtual (jowner, jname, mdescr, pos) } } @@ -1077,8 +1071,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genScalaHash(tree: Tree, applyPos: Position): BType = { genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? genLoad(tree, ObjectRef) - genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos) - + genCallMethod(hashMethodSym, InvokeStyle.Static, applyPos) INT } @@ -1107,10 +1100,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { (tk: @unchecked) match { case LONG => emit(asm.Opcodes.LCMP) case FLOAT => - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) + if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG) else emit(asm.Opcodes.FCMPL) case DOUBLE => - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) + if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) else emit(asm.Opcodes.DCMPL) } bc.emitIF(op, success) @@ -1125,8 +1118,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } 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 + case TestOp.EQ => bc emitIFNULL success + case TestOp.NE => bc emitIFNONNULL success } } else { (tk: @unchecked) match { @@ -1135,11 +1128,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { emit(asm.Opcodes.LCMP) case FLOAT => emit(asm.Opcodes.FCONST_0) - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) + if (op == TestOp.LT || op == TestOp.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) + if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) else emit(asm.Opcodes.DCMPL) } bc.emitIF(op, success) @@ -1147,9 +1140,23 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { 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.GE => TestOp.GE + case scalaPrimitives.GT => TestOp.GT + } + + /** 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. @@ -1158,7 +1165,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) { def genComparisonOp(l: Tree, r: Tree, code: Int) { - val op: TestOp = testOpForPrimitive(code - scalaPrimitives.ID) + val op: TestOp = testOpForPrimitive(code) // special-case reference (in)equality test for null (null eq x, x eq null) var nonNullSide: Tree = null if (scalaPrimitives.isReferenceEqualityOp(code) && @@ -1177,7 +1184,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def default() = { genLoad(tree, BOOL) - genCZJUMP(success, failure, icodes.NE, BOOL) + genCZJUMP(success, failure, TestOp.NE, BOOL) } lineNumber(tree) @@ -1254,24 +1261,24 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } genLoad(l, ObjectRef) genLoad(r, ObjectRef) - genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos) - genCZJUMP(success, failure, icodes.NE, BOOL) + genCallMethod(equalsMethod, InvokeStyle.Static, pos) + genCZJUMP(success, failure, TestOp.NE, BOOL) } else { if (isNull(l)) { // null == expr -> expr eq null genLoad(r, ObjectRef) - genCZJUMP(success, failure, icodes.EQ, ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, ObjectRef) } else if (isNull(r)) { // expr == null -> expr eq null genLoad(l, ObjectRef) - genCZJUMP(success, failure, icodes.EQ, ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, ObjectRef) } else if (isNonNullExpr(l)) { // SI-7852 Avoid null check if L is statically non-null. genLoad(l, ObjectRef) genLoad(r, ObjectRef) - 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) } else { // l == r -> if (l eq null) r eq null else l.equals(r) val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.toString) @@ -1282,17 +1289,17 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genLoad(r, ObjectRef) locals.store(eqEqTempLocal) bc dup ObjectRef - genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectRef) + genCZJUMP(lNull, lNonNull, TestOp.EQ, ObjectRef) markProgramPoint(lNull) bc drop ObjectRef locals.load(eqEqTempLocal) - genCZJUMP(success, failure, icodes.EQ, ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, ObjectRef) 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) } } } @@ -1338,7 +1345,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { 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;") - + coreBTypes.jliLambdaMetafactoryRef.internalName, sn.AltMetafactory.toString, + MethodBType( + List( + coreBTypes.jliMethodHandlesLookupRef, + coreBTypes.StringRef, + coreBTypes.jliMethodTypeRef, + ArrayBType(ObjectRef)), + coreBTypes.jliCallSiteRef + ).descriptor) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index a48a437ed7..e1cee8861a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -22,8 +22,316 @@ 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.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) + + /** + * 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 + + val sam = { + if (classSym.isImplClass || classSym.isEffectivelyFinal) None + else { + // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an + // empty parameter list in later phases and would therefore be picked as SAM. + val samSym = exitingPickler(definitions.findSam(classSym.tpe)) + if (samSym == NoSymbol) None + else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) + } + } + + var warning = Option.empty[ClassSymbolInfoFailureSI9111] + + // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some + // 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, sam, methodInlineInfos, warning) + } /* * must-single-thread @@ -335,9 +643,79 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } // 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")) + + /** 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 + } - import genASM.{ubytesToCharArray, arrEncode} - import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible} + 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 + } + } + + 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 + } + + 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 @@ -384,7 +762,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. @@ -471,13 +849,87 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { 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 + || (sym.ownerChain exists (_.isImplClass)) + ) + /* @return * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). * - otherwise the signature in question * * must-single-thread */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit()) + def getGenericSignature(sym: Symbol, owner: Symbol): String = getGenericSignature(sym, owner, getCurrentCUnit()) + + 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 + } } // end of trait BCJGenSigGen @@ -490,11 +942,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 @@ -506,7 +962,23 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * * must-single-thread */ - private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol): Unit = { + 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 + } + } + val moduleName = internalName(module) val methodInfo = module.thisType.memberInfo(m) val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType @@ -523,7 +995,7 @@ 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(m, module, getCurrentCUnit()) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) @@ -578,7 +1050,7 @@ 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'") else if (conflictingNames(m.name)) @@ -603,8 +1075,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 = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) + internalName(erased.typeSymbol) + } } } // end of trait BCForwardersGen @@ -859,3 +1334,53 @@ 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 { + def opcodeIF = asm.Opcodes.IFEQ + op + def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op + } + + object TestOp { + 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 c78f422bf0..4a10756468 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. @@ -106,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 @@ -416,13 +396,13 @@ abstract class BCodeIdiomatic extends SubComponent { // 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 3fe56dd962..abd06b90a0 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -25,7 +25,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { import global._ import bTypes._ import coreBTypes._ - import bCodeAsmCommon._ /* * There's a dedicated PlainClassBuilder for each CompilationUnit, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index ef3fab7617..ab52bf72d8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -126,7 +126,7 @@ abstract class BTypes { /** * 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) @@ -932,7 +932,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 ) }) @@ -1192,4 +1192,4 @@ object BTypes { // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR val ScalaAttributeName = "Scala" val ScalaSigAttributeName = "ScalaSig" -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index a2bf8e5725..3696bb3daf 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -28,10 +28,7 @@ import scala.tools.nsc.settings.ScalaSettings class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { import global._ import definitions._ - - val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) - val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) - import bCodeAsmCommon._ + import genBCode._ val backendUtils: BackendUtils[this.type] = new BackendUtils(this) @@ -220,7 +217,101 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(!primitiveTypeMap.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) + + // 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) + } + + /** + * 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. + */ + 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) => // impl classes get the lateMODULE flag in mixin + val r = exitingPickler(sym.moduleClass) + assert(r != NoSymbol, sym.fullLocationString) + r + })(collection.breakOut) + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { + /** + * 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 isImplClass: trait implementation classes have NoSymbol as superClass // Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add // scala.annotation.Annotation as superclass to java annotations. In reality, java diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 79aa4308c5..028eea9da2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -117,6 +117,9 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { lazy val jliSerializedLambdaRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda]) lazy val jliMethodHandlesRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles]) lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(exitingPickler(rootMirror.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 srLambdaDeserializerRef : ClassBType = classBTypeFromSymbol(requiredModule[scala.runtime.LambdaDeserializer.type].moduleClass) lazy val srBoxesRunTimeRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) @@ -240,6 +243,9 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef 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 srLambdaDeserializerRef : ClassBType = _coreBTypes.srLambdaDeserializerRef def srBoxesRunTimeRef : ClassBType = _coreBTypes.srBoxesRunTimeRef 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 61abc55501..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ /dev/null @@ -1,3348 +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.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 isAbstractTraitMeth = isJInterface && !m.symbol.hasFlag(Flags.JAVA_DEFAULTMETHOD) - - val flags = mkFlags( - javaFlags(m.symbol), - if (isAbstractTraitMeth) asm.Opcodes.ACC_ABSTRACT else 0, - if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, - if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes - if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - // 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/analysis/AliasingFrame.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala index 9e5fbfcc0e..596ee55290 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala @@ -92,10 +92,9 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc 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) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index 1da32bc7a8..b02bc7c96e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -3,7 +3,7 @@ package backend.jvm package analysis import scala.annotation.switch -import scala.tools.asm.{Handle, Type, Label} +import scala.tools.asm.{Opcodes, Handle, Type, Label} import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis.{Frame, BasicInterpreter, Analyzer, Value} import scala.tools.nsc.backend.jvm.BTypes._ @@ -28,7 +28,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { * A wrapper to make ASM's Analyzer a bit easier to use. */ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) { - localOpt.computeMaxLocalsMaxStack(methodNode) + computeMaxLocalsMaxStack(methodNode) analyzer.analyze(classInternalName, methodNode) def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) } @@ -256,4 +256,145 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { } 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.forAsmAnalysisConservative`. + * + * 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 = { + import Opcodes._ + + if (isAbstractMethod(method) || isNativeMethod(method)) { + method.maxLocals = 0 + method.maxStack = 0 + } else if (!maxLocalsMaxStackComputed(method)) { + val size = method.instructions.size + + var maxLocals = (Type.getArgumentsAndReturnSizes(method.desc) >> 2) - (if (isStaticMethod(method)) 1 else 0) + 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 becauase 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..4e81018451 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, 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, 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) + + 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 = false, conservative: Boolean = false, 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) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala index 242171476a..c933341492 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala @@ -368,9 +368,9 @@ trait ProdConsAnalyzerImpl { 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 } } @@ -387,10 +387,10 @@ trait ProdConsAnalyzerImpl { 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 } } 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 66810176a1..b192e1b46a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -91,95 +91,94 @@ class CallGraph[BT <: BTypes](val btypes: BT) { if (!callsites.contains(methodNode)) addMethod(methodNode, definingClass) } - /** - * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions. - */ def addMethod(methodNode: MethodNode, definingClass: ClassBType): Unit = { - // TODO: run dataflow analyses to make the call graph more precise - // - producers to get forwarded parameters (ForwardedParam) - // - typeAnalysis for more precise argument types, more precise callee - - // 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.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { - Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer)) - } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) { - Some(new AsmAnalyzer(methodNode, definingClass.internalName)) - } else None - } - - // 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 + 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.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer)) + } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName)) + } else None } - var methodCallsites = Map.empty[MethodInsnNode, Callsite] - var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation] - - // lazy so it is only computed if actually used by computeArgInfos - lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName) - - methodNode.instructions.iterator.asScala foreach { - case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code - val callee: Either[OptimizerWarning, Callee] = for { - (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] - (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] - } yield { - val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) - Callee( - callee = method, - calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, - safeToRewrite = safeToRewrite, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline, - samParamTypes = samParamTypes, - calleeInfoWarning = warning) + // if the method is too large to run an analyzer, it is not added to the call graph + if (analyzer.nonEmpty) { + val Some(a) = analyzer + def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = a.analyzer match { + case nullnessAnalyzer: NullnessAnalyzer => + val frame = nullnessAnalyzer.frameAt(call, methodNode) + frame.getStack(frame.getStackSize - 1 - numArgs) eq NotNullValue + case _ => false + } + + var methodCallsites = Map.empty[MethodInsnNode, Callsite] + var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation] + + // lazy so it is only computed if actually used by computeArgInfos + lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName) + + methodNode.instructions.iterator.asScala foreach { + case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code + val callee: Either[OptimizerWarning, Callee] = for { + (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] + } yield { + val declarationClassBType = classBTypeFromClassNode(declarationClassNode) + val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, samParamTypes, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + safeToRewrite = safeToRewrite, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + samParamTypes = samParamTypes, + calleeInfoWarning = warning) + } + + val argInfos = computeArgInfos(callee, call, prodCons) + + val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || { + val numArgs = Type.getArgumentTypes(call.desc).length + receiverNotNullByAnalysis(call, numArgs) } - val argInfos = computeArgInfos(callee, call, prodCons) + 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) + ) - val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || { - val numArgs = Type.getArgumentTypes(call.desc).length - receiverNotNullByAnalysis(call, numArgs) - } + 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) - 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 _ => + } - case _ => + callsites(methodNode) = methodCallsites + closureInstantiations(methodNode) = methodClosureInstantiations } - - callsites(methodNode) = methodCallsites - closureInstantiations(methodNode) = methodClosureInstantiations } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala index 8d744f6d13..79e44a8503 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala @@ -33,14 +33,10 @@ object InstructionResultSize { 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 _ => 1 } case ILOAD | 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 38f3c51892..a80b3d0487 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -51,38 +51,6 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { import backendUtils._ /** - * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM - * framework only computes these values during bytecode generation. - * - * Since there's currently no better way, we run a bytecode generator on the method and extract - * the computed values. This required changes to the ASM codebase: - * - the [[MethodWriter]] class was made public - * - accessors for maxLocals / maxStack were added to the MethodWriter class - * - * We could probably make this faster (and allocate less memory) by hacking the ASM framework - * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be - * to create a separate visitor for computing those values, duplicating the functionality from the - * MethodWriter. - * - * NOTE: the maxStack value computed by this method allocates two slots for long / double values, - * as required by the JVM spec. For running an Analyzer, one slot per long / double would be fine. - * See comment in `analysis` package object. - */ - def computeMaxLocalsMaxStack(method: MethodNode): Unit = { - if (!maxLocalsMaxStackComputed(method)) { - method.maxLocals = 0 - method.maxStack = 0 - val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) - val excs = method.exceptions.asScala.toArray - val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] - method.accept(mw) - method.maxLocals = mw.getMaxLocals - method.maxStack = mw.getMaxStack - maxLocalsMaxStackComputed += method - } - } - - /** * Remove unreachable code from a method. * * This implementation only removes instructions that are unreachable for an ASM analyzer / @@ -225,7 +193,7 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { var i = 0 var liveLabels = Set.empty[LabelNode] var removedInstructions = Set.empty[AbstractInsnNode] - var maxLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2 - (if (BytecodeUtils.isStaticMethod(method)) 1 else 0) + var maxLocals = (Type.getArgumentsAndReturnSizes(method.desc) >> 2) - (if (BytecodeUtils.isStaticMethod(method)) 1 else 0) var maxStack = 0 val itr = method.instructions.iterator() while (itr.hasNext) { 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 fb1799e092..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 the 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 - - val 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.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 */ diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a9ec8c7b30..78e8c328b8 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -119,7 +119,6 @@ trait ScalaSettings extends AbsScalaSettings val require = MultiStringSetting ("-Xplugin-require", "plugin", "Abort if a named plugin is not loaded.") val pluginsDir = StringSetting ("-Xpluginsdir", "path", "Path to search for plugin archives.", Defaults.scalaPluginPath) val Xprint = PhasesSetting ("-Xprint", "Print out program after") - val writeICode = PhasesSetting ("-Xprint-icode", "Log internal icode to *.icode files after", "icode") val Xprintpos = BooleanSetting ("-Xprint-pos", "Print tree positions, as offsets.") val printtypes = BooleanSetting ("-Xprint-types", "Print tree types (debugging option).") val prompt = BooleanSetting ("-Xprompt", "Display a prompt after each error (debugging option).") @@ -222,8 +221,6 @@ trait ScalaSettings extends AbsScalaSettings val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method") - val YskipInlineInfoAttribute = BooleanSetting("-Yskip-inline-info-attribute", "Do not add the ScalaInlineInfo attribute to classfiles generated by -Ybackend:GenASM") - object YoptChoices extends MultiChoiceEnumeration { val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.") val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.") @@ -354,7 +351,7 @@ trait ScalaSettings extends AbsScalaSettings * Settings motivated by GenBCode */ val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.", - List("GenASM", "GenBCode"), + List("GenBCode"), "GenBCode") // Feature extensions val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.") diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala deleted file mode 100644 index b2f5a4119d..0000000000 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala +++ /dev/null @@ -1,1130 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Iulian Dragos - */ - -package scala -package tools.nsc -package symtab -package classfile - -import scala.collection.{ mutable, immutable } -import mutable.ListBuffer -import ClassfileConstants._ -import scala.reflect.internal.JavaAccFlags - -/** ICode reader from Java bytecode. - * - * @author Iulian Dragos - * @version 1.0 - */ -abstract class ICodeReader extends ClassfileParser { - val global: Global - val symbolTable: global.type - val loaders: global.loaders.type - import global._ - import icodes._ - - var instanceCode: IClass = null // the ICode class for the current symbol - var staticCode: IClass = null // the ICode class static members - var method: IMethod = NoIMethod // the current IMethod - var isScalaModule = false - - override protected type ThisConstantPool = ICodeConstantPool - override protected def newConstantPool = new ICodeConstantPool - - /** Try to force the chain of enclosing classes for the given name. Otherwise - * flatten would not lift classes that were not referenced in the source code. - */ - def forceMangledName(name: Name, module: Boolean): Symbol = { - val parts = name.decode.toString.split(Array('.', '$')) - var sym: Symbol = rootMirror.RootClass - - // was "at flatten.prev" - enteringFlatten { - for (part0 <- parts; if !(part0 == ""); part = newTermName(part0)) { - val sym1 = enteringIcode { - sym.linkedClassOfClass.info - sym.info.decl(part.encode) - }//.suchThat(module == _.isModule) - - sym = sym1 orElse sym.info.decl(part.encode.toTypeName) - } - } - sym - } - - protected class ICodeConstantPool extends ConstantPool { - /** Return the symbol of the class member at `index`. - * The following special cases exist: - * - If the member refers to special `MODULE$` static field, return - * the symbol of the corresponding module. - * - If the member is a field, and is not found with the given name, - * another try is made by appending `nme.LOCAL_SUFFIX_STRING` - * - If no symbol is found in the right tpe, a new try is made in the - * companion class, in case the owner is an implementation class. - */ - def getMemberSymbol(index: Int, static: Boolean): Symbol = { - if (index <= 0 || len <= index) errorBadIndex(index) - var f = values(index).asInstanceOf[Symbol] - if (f eq null) { - val start = starts(index) - val first = in.buf(start).toInt - if (first != CONSTANT_FIELDREF && - first != CONSTANT_METHODREF && - first != CONSTANT_INTFMETHODREF) errorBadTag(start) - val ownerTpe = getClassOrArrayType(in.getChar(start + 1).toInt) - debuglog("getMemberSymbol(static: " + static + "): owner type: " + ownerTpe + " " + ownerTpe.typeSymbol.unexpandedName) - val (name0, tpe0) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe) - debuglog("getMemberSymbol: name and tpe: " + name0 + ": " + tpe0) - - forceMangledName(tpe0.typeSymbol.name, module = false) - val (name, tpe) = getNameAndType(in.getChar(start + 3).toInt, ownerTpe) - if (name == nme.MODULE_INSTANCE_FIELD) { - val index = in.getChar(start + 1).toInt - val name = getExternalName(in.getChar(starts(index).toInt + 1).toInt) - //assert(name.endsWith("$"), "Not a module class: " + name) - f = forceMangledName(name dropRight 1, module = true) - if (f == NoSymbol) - f = rootMirror.getModuleByName(name dropRight 1) - } else { - val origName = nme.unexpandedName(name) - val owner = if (static) ownerTpe.typeSymbol.linkedClassOfClass else ownerTpe.typeSymbol - f = owner.info.findMember(origName, 0, 0, stableOnly = false).suchThat(_.tpe.widen =:= tpe) - if (f == NoSymbol) - f = owner.info.findMember(newTermName(origName + nme.LOCAL_SUFFIX_STRING), 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe) - if (f == NoSymbol) { - // if it's an impl class, try to find it's static member inside the class - if (ownerTpe.typeSymbol.isImplClass) { - f = ownerTpe.findMember(origName, 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe) - } else { - log("Couldn't find " + name + ": " + tpe + " inside: \n" + ownerTpe) - f = tpe match { - case MethodType(_, _) => owner.newMethod(name.toTermName, owner.pos) - case _ => owner.newVariable(name.toTermName, owner.pos) - } - f setInfo tpe - log("created fake member " + f.fullName) - } - } - } - assert(f != NoSymbol, - s"could not find $name: $tpe in $ownerTpe" + ( - if (settings.debug.value) ownerTpe.members.mkString(", members are:\n ", "\n ", "") else "" - ) - ) - values(index) = f - } - f - } - } - - /** Read back bytecode for the given class symbol. It returns - * two IClass objects, one for static members and one - * for non-static members. - */ - def readClass(cls: Symbol): (IClass, IClass) = { - cls.info // ensure accurate type information - - isScalaModule = cls.isModule && !cls.isJavaDefined - log("ICodeReader reading " + cls) - val name = cls.javaClassName - - classFileLookup.findClassFile(name) match { - case Some(classFile) => parse(classFile, cls) - case _ => MissingRequirementError.notFound("Could not find bytecode for " + cls) - } - - (staticCode, instanceCode) - } - - override def parseClass() { - this.instanceCode = new IClass(clazz) - this.staticCode = new IClass(staticModule) - - u2 - pool getClassSymbol u2 - parseInnerClasses() - - in.skip(2) // super class - in.skip(2 * u2) // interfaces - val fieldCount = u2 - for (i <- 0 until fieldCount) parseField() - val methodCount = u2 - for (i <- 0 until methodCount) parseMethod() - instanceCode.methods = instanceCode.methods.reverse - staticCode.methods = staticCode.methods.reverse - } - - override def parseField() { - val (jflags, sym) = parseMember(field = true) - getCode(jflags) addField new IField(sym) - skipAttributes() - } - - private def parseMember(field: Boolean): (JavaAccFlags, Symbol) = { - val jflags = JavaAccFlags(u2) - val name = pool getName u2 - /* If we're parsing a scala module, the owner of members is always - * the module symbol. - */ - val owner = ( - if (isScalaModule) staticModule - else if (jflags.isStatic) moduleClass - else clazz - ) - val dummySym = owner.newMethod(name.toTermName, owner.pos, jflags.toScalaFlags) - - try { - val ch = u2 - val tpe = pool.getType(dummySym, ch) - - if ("<clinit>" == name.toString) - (jflags, NoSymbol) - else { - var sym = owner.info.findMember(name, 0, 0, stableOnly = false).suchThat(old => sameType(old.tpe, tpe)) - if (sym == NoSymbol) - sym = owner.info.findMember(newTermName(name + nme.LOCAL_SUFFIX_STRING), 0, 0, stableOnly = false).suchThat(_.tpe =:= tpe) - if (sym == NoSymbol) { - sym = if (field) owner.newValue(name.toTermName, owner.pos, jflags.toScalaFlags) else dummySym - sym setInfoAndEnter tpe - log(s"ICodeReader could not locate ${name.decode} in $owner. Created ${sym.defString}.") - } - (jflags, sym) - } - } catch { - case e: MissingRequirementError => - (jflags, NoSymbol) - } - } - - /** Checks if `tp1` is the same type as `tp2`, modulo implicit methods. - * We don't care about the distinction between implicit and explicit - * methods as this point, and we can't get back the information from - * bytecode anyway. - */ - private def sameType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match { - case (mt1 @ MethodType(args1, resTpe1), mt2 @ MethodType(args2, resTpe2)) if mt1.isImplicit || mt2.isImplicit => - MethodType(args1, resTpe1) =:= MethodType(args2, resTpe2) - case _ => - tp1 =:= tp2 - } - - override def parseMethod() { - val (jflags, sym) = parseMember(field = false) - val beginning = in.bp - try { - if (sym != NoSymbol) { - this.method = new IMethod(sym) - this.method.returnType = toTypeKind(sym.tpe.resultType) - getCode(jflags).addMethod(this.method) - if (jflags.isNative) - this.method.native = true - val attributeCount = u2 - for (i <- 0 until attributeCount) parseAttribute() - } else { - debuglog("Skipping non-existent method.") - skipAttributes() - } - } catch { - case e: MissingRequirementError => - in.bp = beginning; skipAttributes() - debuglog("Skipping non-existent method. " + e.msg) - } - } - - def parseAttribute() { - val attrName = pool.getName(u2).toTypeName - val attrLen = u4 - attrName match { - case tpnme.CodeATTR => - parseByteCode() - case _ => - in.skip(attrLen) - } - } - - override def classNameToSymbol(name: Name) = { - val sym = if (name == fulltpnme.RuntimeNothing) - definitions.NothingClass - else if (name == fulltpnme.RuntimeNull) - definitions.NullClass - else if (nme.isImplClassName(name)) { - val iface = rootMirror.getClassByName(tpnme.interfaceName(name)) - log("forcing " + iface.owner + " at phase: " + phase + " impl: " + iface.implClass) - iface.owner.info // force the mixin type-transformer - rootMirror.getClassByName(name) - } - else if (nme.isModuleName(name)) { - val strippedName = name.dropModule - forceMangledName(newTermName(strippedName.decode), module = true) orElse rootMirror.getModuleByName(strippedName) - } - else { - forceMangledName(name, module = false) - exitingFlatten(rootMirror.getClassByName(name.toTypeName)) - } - if (sym.isModule) - sym.moduleClass - else - sym - } - - - var maxStack: Int = _ - var maxLocals: Int = _ - val JVM = ClassfileConstants // shorter, uppercase alias for use in case patterns - - def toUnsignedByte(b: Byte): Int = b.toInt & 0xff - var pc = 0 - - /** Parse java bytecode into ICode */ - def parseByteCode() { - maxStack = u2 - maxLocals = u2 - val codeLength = u4 - val code = new LinearCode - - def parseInstruction() { - import opcodes._ - import code._ - var size = 1 // instruction size - - /* Parse 16 bit jump target. */ - def parseJumpTarget = { - size += 2 - val offset = u2.toShort - val target = pc + offset - assert(target >= 0 && target < codeLength, "Illegal jump target: " + target) - target - } - - /* Parse 32 bit jump target. */ - def parseJumpTargetW: Int = { - size += 4 - val offset = u4 - val target = pc + offset - assert(target >= 0 && target < codeLength, "Illegal jump target: " + target + "pc: " + pc + " offset: " + offset) - target - } - - u1 match { - case JVM.nop => parseInstruction() - case JVM.aconst_null => code emit CONSTANT(Constant(null)) - case JVM.iconst_m1 => code emit CONSTANT(Constant(-1)) - case JVM.iconst_0 => code emit CONSTANT(Constant(0)) - case JVM.iconst_1 => code emit CONSTANT(Constant(1)) - case JVM.iconst_2 => code emit CONSTANT(Constant(2)) - case JVM.iconst_3 => code emit CONSTANT(Constant(3)) - case JVM.iconst_4 => code emit CONSTANT(Constant(4)) - case JVM.iconst_5 => code emit CONSTANT(Constant(5)) - - case JVM.lconst_0 => code emit CONSTANT(Constant(0l)) - case JVM.lconst_1 => code emit CONSTANT(Constant(1l)) - case JVM.fconst_0 => code emit CONSTANT(Constant(0.0f)) - case JVM.fconst_1 => code emit CONSTANT(Constant(1.0f)) - case JVM.fconst_2 => code emit CONSTANT(Constant(2.0f)) - case JVM.dconst_0 => code emit CONSTANT(Constant(0.0)) - case JVM.dconst_1 => code emit CONSTANT(Constant(1.0)) - - case JVM.bipush => code.emit(CONSTANT(Constant(s1))); size += 1 - case JVM.sipush => code.emit(CONSTANT(Constant(s2))); size += 2 - case JVM.ldc => code.emit(CONSTANT(pool.getConstant(u1))); size += 1 - case JVM.ldc_w => code.emit(CONSTANT(pool.getConstant(u2))); size += 2 - case JVM.ldc2_w => code.emit(CONSTANT(pool.getConstant(u2))); size += 2 - case JVM.iload => code.emit(LOAD_LOCAL(code.getLocal(u1, INT))); size += 1 - case JVM.lload => code.emit(LOAD_LOCAL(code.getLocal(u1, LONG))); size += 1 - case JVM.fload => code.emit(LOAD_LOCAL(code.getLocal(u1, FLOAT))); size += 1 - case JVM.dload => code.emit(LOAD_LOCAL(code.getLocal(u1, DOUBLE))); size += 1 - case JVM.aload => - val local = u1.toInt; size += 1 - if (local == 0 && !method.isStatic) - code.emit(THIS(method.symbol.owner)) - else - code.emit(LOAD_LOCAL(code.getLocal(local, ObjectReference))) - - case JVM.iload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, INT))) - case JVM.iload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, INT))) - case JVM.iload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, INT))) - case JVM.iload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, INT))) - case JVM.lload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, LONG))) - case JVM.lload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, LONG))) - case JVM.lload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, LONG))) - case JVM.lload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, LONG))) - case JVM.fload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, FLOAT))) - case JVM.fload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, FLOAT))) - case JVM.fload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, FLOAT))) - case JVM.fload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, FLOAT))) - case JVM.dload_0 => code.emit(LOAD_LOCAL(code.getLocal(0, DOUBLE))) - case JVM.dload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, DOUBLE))) - case JVM.dload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, DOUBLE))) - case JVM.dload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, DOUBLE))) - case JVM.aload_0 => - if (!method.isStatic) - code.emit(THIS(method.symbol.owner)) - else - code.emit(LOAD_LOCAL(code.getLocal(0, ObjectReference))) - case JVM.aload_1 => code.emit(LOAD_LOCAL(code.getLocal(1, ObjectReference))) - case JVM.aload_2 => code.emit(LOAD_LOCAL(code.getLocal(2, ObjectReference))) - case JVM.aload_3 => code.emit(LOAD_LOCAL(code.getLocal(3, ObjectReference))) - - case JVM.iaload => code.emit(LOAD_ARRAY_ITEM(INT)) - case JVM.laload => code.emit(LOAD_ARRAY_ITEM(LONG)) - case JVM.faload => code.emit(LOAD_ARRAY_ITEM(FLOAT)) - case JVM.daload => code.emit(LOAD_ARRAY_ITEM(DOUBLE)) - case JVM.aaload => code.emit(LOAD_ARRAY_ITEM(ObjectReference)) - case JVM.baload => code.emit(LOAD_ARRAY_ITEM(BYTE)) - case JVM.caload => code.emit(LOAD_ARRAY_ITEM(CHAR)) - case JVM.saload => code.emit(LOAD_ARRAY_ITEM(SHORT)) - - case JVM.istore => code.emit(STORE_LOCAL(code.getLocal(u1, INT))); size += 1 - case JVM.lstore => code.emit(STORE_LOCAL(code.getLocal(u1, LONG))); size += 1 - case JVM.fstore => code.emit(STORE_LOCAL(code.getLocal(u1, FLOAT))); size += 1 - case JVM.dstore => code.emit(STORE_LOCAL(code.getLocal(u1, DOUBLE))); size += 1 - case JVM.astore => code.emit(STORE_LOCAL(code.getLocal(u1, ObjectReference))); size += 1 - case JVM.istore_0 => code.emit(STORE_LOCAL(code.getLocal(0, INT))) - case JVM.istore_1 => code.emit(STORE_LOCAL(code.getLocal(1, INT))) - case JVM.istore_2 => code.emit(STORE_LOCAL(code.getLocal(2, INT))) - case JVM.istore_3 => code.emit(STORE_LOCAL(code.getLocal(3, INT))) - case JVM.lstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, LONG))) - case JVM.lstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, LONG))) - case JVM.lstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, LONG))) - case JVM.lstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, LONG))) - case JVM.fstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, FLOAT))) - case JVM.fstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, FLOAT))) - case JVM.fstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, FLOAT))) - case JVM.fstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, FLOAT))) - case JVM.dstore_0 => code.emit(STORE_LOCAL(code.getLocal(0, DOUBLE))) - case JVM.dstore_1 => code.emit(STORE_LOCAL(code.getLocal(1, DOUBLE))) - case JVM.dstore_2 => code.emit(STORE_LOCAL(code.getLocal(2, DOUBLE))) - case JVM.dstore_3 => code.emit(STORE_LOCAL(code.getLocal(3, DOUBLE))) - case JVM.astore_0 => - if (method.isStatic) - code.emit(STORE_LOCAL(code.getLocal(0, ObjectReference))) - else - code.emit(STORE_THIS(ObjectReference)) - case JVM.astore_1 => code.emit(STORE_LOCAL(code.getLocal(1, ObjectReference))) - case JVM.astore_2 => code.emit(STORE_LOCAL(code.getLocal(2, ObjectReference))) - case JVM.astore_3 => code.emit(STORE_LOCAL(code.getLocal(3, ObjectReference))) - case JVM.iastore => code.emit(STORE_ARRAY_ITEM(INT)) - case JVM.lastore => code.emit(STORE_ARRAY_ITEM(LONG)) - case JVM.fastore => code.emit(STORE_ARRAY_ITEM(FLOAT)) - case JVM.dastore => code.emit(STORE_ARRAY_ITEM(DOUBLE)) - case JVM.aastore => code.emit(STORE_ARRAY_ITEM(ObjectReference)) - case JVM.bastore => code.emit(STORE_ARRAY_ITEM(BYTE)) - case JVM.castore => code.emit(STORE_ARRAY_ITEM(CHAR)) - case JVM.sastore => code.emit(STORE_ARRAY_ITEM(SHORT)) - - case JVM.pop => code.emit(DROP(INT)) // any 1-word type would do - case JVM.pop2 => code.emit(DROP(LONG)) // any 2-word type would do - case JVM.dup => code.emit(DUP(ObjectReference)) // TODO: Is the kind inside DUP ever needed? - case JVM.dup_x1 => code.emit(DUP_X1) // sys.error("Unsupported JVM bytecode: dup_x1") - case JVM.dup_x2 => code.emit(DUP_X2) // sys.error("Unsupported JVM bytecode: dup_x2") - case JVM.dup2 => code.emit(DUP(LONG)) // TODO: Is the kind inside DUP ever needed? - case JVM.dup2_x1 => code.emit(DUP2_X1) // sys.error("Unsupported JVM bytecode: dup2_x1") - case JVM.dup2_x2 => code.emit(DUP2_X2) // sys.error("Unsupported JVM bytecode: dup2_x2") - case JVM.swap => sys.error("Unsupported JVM bytecode: swap") - - case JVM.iadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT))) - case JVM.ladd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, LONG))) - case JVM.fadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, FLOAT))) - case JVM.dadd => code.emit(CALL_PRIMITIVE(Arithmetic(ADD, DOUBLE))) - case JVM.isub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, INT))) - case JVM.lsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, LONG))) - case JVM.fsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, FLOAT))) - case JVM.dsub => code.emit(CALL_PRIMITIVE(Arithmetic(SUB, DOUBLE))) - case JVM.imul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, INT))) - case JVM.lmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, LONG))) - case JVM.fmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, FLOAT))) - case JVM.dmul => code.emit(CALL_PRIMITIVE(Arithmetic(MUL, DOUBLE))) - case JVM.idiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, INT))) - case JVM.ldiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, LONG))) - case JVM.fdiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, FLOAT))) - case JVM.ddiv => code.emit(CALL_PRIMITIVE(Arithmetic(DIV, DOUBLE))) - case JVM.irem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, INT))) - case JVM.lrem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, LONG))) - case JVM.frem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, FLOAT))) - case JVM.drem => code.emit(CALL_PRIMITIVE(Arithmetic(REM, DOUBLE))) - - case JVM.ineg => code.emit(CALL_PRIMITIVE(Negation(INT))) - case JVM.lneg => code.emit(CALL_PRIMITIVE(Negation(LONG))) - case JVM.fneg => code.emit(CALL_PRIMITIVE(Negation(FLOAT))) - case JVM.dneg => code.emit(CALL_PRIMITIVE(Negation(DOUBLE))) - - case JVM.ishl => code.emit(CALL_PRIMITIVE(Shift(LSL, INT))) - case JVM.lshl => code.emit(CALL_PRIMITIVE(Shift(LSL, LONG))) - case JVM.ishr => code.emit(CALL_PRIMITIVE(Shift(LSR, INT))) - case JVM.lshr => code.emit(CALL_PRIMITIVE(Shift(LSR, LONG))) - case JVM.iushr => code.emit(CALL_PRIMITIVE(Shift(ASR, INT))) - case JVM.lushr => code.emit(CALL_PRIMITIVE(Shift(ASR, LONG))) - case JVM.iand => code.emit(CALL_PRIMITIVE(Logical(AND, INT))) - case JVM.land => code.emit(CALL_PRIMITIVE(Logical(AND, LONG))) - case JVM.ior => code.emit(CALL_PRIMITIVE(Logical(OR, INT))) - case JVM.lor => code.emit(CALL_PRIMITIVE(Logical(OR, LONG))) - case JVM.ixor => code.emit(CALL_PRIMITIVE(Logical(XOR, INT))) - case JVM.lxor => code.emit(CALL_PRIMITIVE(Logical(XOR, LONG))) - case JVM.iinc => - size += 2 - val local = code.getLocal(u1, INT) - code.emit(LOAD_LOCAL(local)) - code.emit(CONSTANT(Constant(s1))) - code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT))) - code.emit(STORE_LOCAL(local)) - - case JVM.i2l => code.emit(CALL_PRIMITIVE(Conversion(INT, LONG))) - case JVM.i2f => code.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT))) - case JVM.i2d => code.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE))) - case JVM.l2i => code.emit(CALL_PRIMITIVE(Conversion(LONG, INT))) - case JVM.l2f => code.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT))) - case JVM.l2d => code.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE))) - case JVM.f2i => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT))) - case JVM.f2l => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG))) - case JVM.f2d => code.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE))) - case JVM.d2i => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT))) - case JVM.d2l => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG))) - case JVM.d2f => code.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT))) - case JVM.i2b => code.emit(CALL_PRIMITIVE(Conversion(INT, BYTE))) - case JVM.i2c => code.emit(CALL_PRIMITIVE(Conversion(INT, CHAR))) - case JVM.i2s => code.emit(CALL_PRIMITIVE(Conversion(INT, SHORT))) - - case JVM.lcmp => code.emit(CALL_PRIMITIVE(Comparison(CMP, LONG))) - case JVM.fcmpl => code.emit(CALL_PRIMITIVE(Comparison(CMPL, FLOAT))) - case JVM.fcmpg => code.emit(CALL_PRIMITIVE(Comparison(CMPG, FLOAT))) - case JVM.dcmpl => code.emit(CALL_PRIMITIVE(Comparison(CMPL, DOUBLE))) - case JVM.dcmpg => code.emit(CALL_PRIMITIVE(Comparison(CMPG, DOUBLE))) - - case JVM.ifeq => code.emit(LCZJUMP(parseJumpTarget, pc + size, EQ, INT)) - case JVM.ifne => code.emit(LCZJUMP(parseJumpTarget, pc + size, NE, INT)) - case JVM.iflt => code.emit(LCZJUMP(parseJumpTarget, pc + size, LT, INT)) - case JVM.ifge => code.emit(LCZJUMP(parseJumpTarget, pc + size, GE, INT)) - case JVM.ifgt => code.emit(LCZJUMP(parseJumpTarget, pc + size, GT, INT)) - case JVM.ifle => code.emit(LCZJUMP(parseJumpTarget, pc + size, LE, INT)) - - case JVM.if_icmpeq => code.emit(LCJUMP(parseJumpTarget, pc + size, EQ, INT)) - case JVM.if_icmpne => code.emit(LCJUMP(parseJumpTarget, pc + size, NE, INT)) - case JVM.if_icmplt => code.emit(LCJUMP(parseJumpTarget, pc + size, LT, INT)) - case JVM.if_icmpge => code.emit(LCJUMP(parseJumpTarget, pc + size, GE, INT)) - case JVM.if_icmpgt => code.emit(LCJUMP(parseJumpTarget, pc + size, GT, INT)) - case JVM.if_icmple => code.emit(LCJUMP(parseJumpTarget, pc + size, LE, INT)) - case JVM.if_acmpeq => code.emit(LCJUMP(parseJumpTarget, pc + size, EQ, ObjectReference)) - case JVM.if_acmpne => code.emit(LCJUMP(parseJumpTarget, pc + size, NE, ObjectReference)) - - case JVM.goto => emit(LJUMP(parseJumpTarget)) - case JVM.jsr => sys.error("Cannot handle jsr/ret") - case JVM.ret => sys.error("Cannot handle jsr/ret") - case JVM.tableswitch => - val padding = if ((pc + size) % 4 != 0) 4 - ((pc + size) % 4) else 0 - size += padding - in.bp += padding - assert((pc + size % 4) != 0, pc) -/* var byte1 = u1; size += 1; - while (byte1 == 0) { byte1 = u1; size += 1; } - val default = byte1 << 24 | u1 << 16 | u1 << 8 | u1; - size = size + 3 - */ - val default = pc + u4; size += 4 - val low = u4 - val high = u4 - size += 8 - assert(low <= high, "Value low not <= high for tableswitch.") - - val tags = List.tabulate(high - low + 1)(n => List(low + n)) - val targets = for (_ <- tags) yield parseJumpTargetW - code.emit(LSWITCH(tags, targets ::: List(default))) - - case JVM.lookupswitch => - val padding = if ((pc + size) % 4 != 0) 4 - ((pc + size) % 4) else 0 - size += padding - in.bp += padding - assert((pc + size % 4) != 0, pc) - val default = pc + u4; size += 4 - val npairs = u4; size += 4 - var tags: List[List[Int]] = Nil - var targets: List[Int] = Nil - var i = 0 - while (i < npairs) { - tags = List(u4) :: tags; size += 4 - targets = parseJumpTargetW :: targets; // parseJumpTargetW updates 'size' itself - i += 1 - } - targets = default :: targets - code.emit(LSWITCH(tags.reverse, targets.reverse)) - - case JVM.ireturn => code.emit(RETURN(INT)) - case JVM.lreturn => code.emit(RETURN(LONG)) - case JVM.freturn => code.emit(RETURN(FLOAT)) - case JVM.dreturn => code.emit(RETURN(DOUBLE)) - case JVM.areturn => code.emit(RETURN(ObjectReference)) - case JVM.return_ => code.emit(RETURN(UNIT)) - - case JVM.getstatic => - val field = pool.getMemberSymbol(u2, static = true); size += 2 - if (field.hasModuleFlag) - code emit LOAD_MODULE(field) - else - code emit LOAD_FIELD(field, isStatic = true) - case JVM.putstatic => - val field = pool.getMemberSymbol(u2, static = true); size += 2 - code.emit(STORE_FIELD(field, isStatic = true)) - case JVM.getfield => - val field = pool.getMemberSymbol(u2, static = false); size += 2 - code.emit(LOAD_FIELD(field, isStatic = false)) - case JVM.putfield => - val field = pool.getMemberSymbol(u2, static = false); size += 2 - code.emit(STORE_FIELD(field, isStatic = false)) - - case JVM.invokevirtual => - val m = pool.getMemberSymbol(u2, static = false); size += 2 - code.emit(CALL_METHOD(m, Dynamic)) - method.updateRecursive(m) - case JVM.invokeinterface => - val m = pool.getMemberSymbol(u2, static = false); size += 4 - in.skip(2) - code.emit(CALL_METHOD(m, Dynamic)) - // invokeinterface can't be recursive - case JVM.invokespecial => - val m = pool.getMemberSymbol(u2, static = false); size += 2 - val style = if (m.name == nme.CONSTRUCTOR || m.isPrivate) Static(onInstance = true) - else SuperCall(m.owner.name) - code.emit(CALL_METHOD(m, style)) - method.updateRecursive(m) - case JVM.invokestatic => - val m = pool.getMemberSymbol(u2, static = true); size += 2 - if (isBox(m)) - code.emit(BOX(toTypeKind(m.info.paramTypes.head))) - else if (isUnbox(m)) - code.emit(UNBOX(toTypeKind(m.info.resultType))) - else { - code.emit(CALL_METHOD(m, Static(onInstance = false))) - method.updateRecursive(m) - } - case JVM.invokedynamic => - // TODO, this is just a place holder. A real implementation must parse the class constant entry - debuglog("Found JVM invokedynamic instruction, inserting place holder ICode INVOKE_DYNAMIC.") - containsInvokeDynamic = true - val poolEntry = in.nextChar.toInt - in.skip(2) - code.emit(INVOKE_DYNAMIC(poolEntry)) - - case JVM.new_ => - code.emit(NEW(REFERENCE(pool.getClassSymbol(u2)))) - size += 2 - case JVM.newarray => - val kind = u1 match { - case T_BOOLEAN => BOOL - case T_CHAR => CHAR - case T_FLOAT => FLOAT - case T_DOUBLE => DOUBLE - case T_BYTE => BYTE - case T_SHORT => SHORT - case T_INT => INT - case T_LONG => LONG - } - size += 1 - code.emit(CREATE_ARRAY(kind, 1)) - - case JVM.anewarray => - val tpe = pool.getClassOrArrayType(u2); size += 2 - code.emit(CREATE_ARRAY(toTypeKind(tpe), 1)) - - case JVM.arraylength => code.emit(CALL_PRIMITIVE(ArrayLength(ObjectReference))); // the kind does not matter - case JVM.athrow => code.emit(THROW(definitions.ThrowableClass)) - case JVM.checkcast => - code.emit(CHECK_CAST(toTypeKind(pool.getClassOrArrayType(u2)))); size += 2 - case JVM.instanceof => - code.emit(IS_INSTANCE(toTypeKind(pool.getClassOrArrayType(u2)))); size += 2 - case JVM.monitorenter => code.emit(MONITOR_ENTER()) - case JVM.monitorexit => code.emit(MONITOR_EXIT()) - case JVM.wide => - size += 1 - u1 match { - case JVM.iload => code.emit(LOAD_LOCAL(code.getLocal(u2, INT))); size += 2 - case JVM.lload => code.emit(LOAD_LOCAL(code.getLocal(u2, LONG))); size += 2 - case JVM.fload => code.emit(LOAD_LOCAL(code.getLocal(u2, FLOAT))); size += 2 - case JVM.dload => code.emit(LOAD_LOCAL(code.getLocal(u2, DOUBLE))); size += 2 - case JVM.aload => code.emit(LOAD_LOCAL(code.getLocal(u2, ObjectReference))); size += 2 - case JVM.istore => code.emit(STORE_LOCAL(code.getLocal(u2, INT))); size += 2 - case JVM.lstore => code.emit(STORE_LOCAL(code.getLocal(u2, LONG))); size += 2 - case JVM.fstore => code.emit(STORE_LOCAL(code.getLocal(u2, FLOAT))); size += 2 - case JVM.dstore => code.emit(STORE_LOCAL(code.getLocal(u2, DOUBLE))); size += 2 - case JVM.astore => code.emit(STORE_LOCAL(code.getLocal(u2, ObjectReference))); size += 2 - case JVM.ret => sys.error("Cannot handle jsr/ret") - case JVM.iinc => - size += 4 - val local = code.getLocal(u2, INT) - code.emit(CONSTANT(Constant(u2))) - code.emit(CALL_PRIMITIVE(Arithmetic(ADD, INT))) - code.emit(STORE_LOCAL(local)) - case _ => sys.error("Invalid 'wide' operand") - } - - case JVM.multianewarray => - size += 3 - val tpe = toTypeKind(pool getClassOrArrayType u2) - val dim = u1 -// assert(dim == 1, "Cannot handle multidimensional arrays yet.") - code emit CREATE_ARRAY(tpe, dim) - - case JVM.ifnull => code emit LCZJUMP(parseJumpTarget, pc + size, EQ, ObjectReference) - case JVM.ifnonnull => code emit LCZJUMP(parseJumpTarget, pc + size, NE, ObjectReference) - case JVM.goto_w => code emit LJUMP(parseJumpTargetW) - case JVM.jsr_w => sys.error("Cannot handle jsr/ret") - -// case _ => sys.error("Unknown bytecode") - } - pc += size - } - - // add parameters - var idx = if (method.isStatic) 0 else 1 - for (t <- method.symbol.tpe.paramTypes) { - val kind = toTypeKind(t) - this.method addParam code.enterParam(idx, kind) - val width = if (kind.isWideType) 2 else 1 - idx += width - } - - pc = 0 - while (pc < codeLength) parseInstruction() - - val exceptionEntries = u2.toInt - code.containsEHs = (exceptionEntries != 0) - var i = 0 - while (i < exceptionEntries) { - // skip start end PC - in.skip(4) - // read the handler PC - code.jmpTargets += u2 - // skip the exception type - in.skip(2) - i += 1 - } - skipAttributes() - - code.toBasicBlock - assert(method.hasCode, method) - // reverse parameters, as they were prepended during code generation - method.params = method.params.reverse - - if (code.containsDUPX) - code.resolveDups() - - if (code.containsNEW) - code.resolveNEWs() - } - - /** Note: these methods are different from the methods of the same name found - * in Definitions. These test whether a symbol represents one of the boxTo/unboxTo - * methods found in BoxesRunTime. The others test whether a symbol represents a - * synthetic method from one of the fake companion classes of the primitive types, - * such as Int.box(5). - */ - def isBox(m: Symbol): Boolean = - (m.owner == definitions.BoxesRunTimeClass - && m.name.startsWith("boxTo")) - - def isUnbox(m: Symbol): Boolean = - (m.owner == definitions.BoxesRunTimeClass - && m.name.startsWith("unboxTo")) - - /** Return the icode class that should include members with the given flags. - * There are two possible classes, the static part and the instance part. - */ - def getCode(flags: JavaAccFlags): IClass = - if (isScalaModule || flags.isStatic) staticCode else instanceCode - - class LinearCode { - val instrs: ListBuffer[(Int, Instruction)] = new ListBuffer - val jmpTargets: mutable.Set[Int] = perRunCaches.newSet[Int]() - val locals: mutable.Map[Int, List[(Local, TypeKind)]] = perRunCaches.newMap() - - var containsDUPX = false - var containsNEW = false - var containsEHs = false - var containsInvokeDynamic = false - - def emit(i: Instruction) { - instrs += ((pc, i)) - if (i.isInstanceOf[DupX]) - containsDUPX = true - if (i.isInstanceOf[opcodes.NEW]) - containsNEW = true - } - - /** Break this linear code in basic block representation - * As a side effect, it sets the `code` field of the current - */ - def toBasicBlock: Code = { - import opcodes._ - - val code = new Code(method) - method.setCode(code) - method.bytecodeHasEHs = containsEHs - method.bytecodeHasInvokeDynamic = containsInvokeDynamic - var bb = code.startBlock - - def makeBasicBlocks: mutable.Map[Int, BasicBlock] = - mutable.Map(jmpTargets.toSeq map (_ -> code.newBlock): _*) - - val blocks = makeBasicBlocks - var otherBlock: BasicBlock = NoBasicBlock - - for ((pc, instr) <- instrs.iterator) { -// Console.println("> " + pc + ": " + instr); - if (jmpTargets(pc)) { - otherBlock = blocks(pc) - if (!bb.closed && otherBlock != bb) { - bb.emit(JUMP(otherBlock)) - bb.close() -// Console.println("\t> closing bb: " + bb) - } - bb = otherBlock -// Console.println("\t> entering bb: " + bb) - } - - if (bb.closed) { - // the basic block is closed, i.e. the previous instruction was a jump, return or throw, - // but the next instruction is not a jump target. this means that the next instruction is - // dead code. we can therefore advance until the next jump target. - debuglog(s"ICode reader skipping dead instruction $instr in classfile $instanceCode") - } else { - instr match { - case LJUMP(target) => - otherBlock = blocks(target) - bb.emitOnly(JUMP(otherBlock)) - - case LCJUMP(success, failure, cond, kind) => - otherBlock = blocks(success) - val failBlock = blocks(failure) - bb.emitOnly(CJUMP(otherBlock, failBlock, cond, kind)) - - case LCZJUMP(success, failure, cond, kind) => - otherBlock = blocks(success) - val failBlock = blocks(failure) - bb.emitOnly(CZJUMP(otherBlock, failBlock, cond, kind)) - - case LSWITCH(tags, targets) => - bb.emitOnly(SWITCH(tags, targets map blocks)) - - case RETURN(_) => - bb emitOnly instr - - case THROW(clasz) => - bb emitOnly instr - - case _ => - bb emit instr - } - } - } - - method.code - } - - def resolveDups() { - import opcodes._ - - val tfa = new analysis.MethodTFA() { - import analysis._ - - /** Abstract interpretation for one instruction. */ - override def mutatingInterpret(out: typeFlowLattice.Elem, i: Instruction): typeFlowLattice.Elem = { - val stack = out.stack - import stack.push - i match { - case DUP_X1 => - val (one, two) = stack.pop2 - push(one); push(two); push(one) - - case DUP_X2 => - val (one, two, three) = stack.pop3 - push(one); push(three); push(two); push(one) - - case DUP2_X1 => - val (one, two) = stack.pop2 - if (one.isWideType) { - push(one); push(two); push(one) - } else { - val three = stack.pop - push(two); push(one); push(three); push(two); push(one) - } - - case DUP2_X2 => - val (one, two) = stack.pop2 - if (one.isWideType && two.isWideType) { - push(one); push(two); push(one) - } else if (one.isWideType) { - val three = stack.pop - assert(!three.isWideType, "Impossible") - push(one); push(three); push(two); push(one) - } else { - val three = stack.pop - if (three.isWideType) { - push(two); push(one); push(one); push(three); push(two); push(one) - } else { - val four = stack.pop - push(two); push(one); push(four); push(one); push(three); push(two); push(one) - } - } - - case _ => - super.mutatingInterpret(out, i) - } - out - } - } - -// method.dump - tfa.init(method) - tfa.run() - for (bb <- linearizer.linearize(method)) { - var info = tfa.in(bb) - for (i <- bb.toList) { - i match { - case DUP_X1 => - val one = info.stack.types(0) - val two = info.stack.types(1) - assert(!one.isWideType, "DUP_X1 expects values of size 1 on top of stack " + info.stack) - val tmp1 = freshLocal(one) - val tmp2 = freshLocal(two) - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - - case DUP_X2 => - val one = info.stack.types(0) - val two = info.stack.types(1) - assert (!one.isWideType, "DUP_X2 expects values of size 1 on top of stack " + info.stack) - val tmp1 = freshLocal(one) - val tmp2 = freshLocal(two) - if (two.isWideType) - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - else { - val tmp3 = freshLocal(info.stack.types(2)) - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - STORE_LOCAL(tmp3), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp3), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } - - case DUP2_X1 => - val one = info.stack.types(0) - val two = info.stack.types(1) - val tmp1 = freshLocal(one) - val tmp2 = freshLocal(two) - if (one.isWideType) { - assert(!two.isWideType, "Impossible") - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } else { - val tmp3 = freshLocal(info.stack.types(2)) - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - STORE_LOCAL(tmp3), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp3), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } - - case DUP2_X2 => - val one = info.stack.types(0) - val two = info.stack.types(1) - val tmp1 = freshLocal(one) - val tmp2 = freshLocal(two) - if (one.isWideType && two.isWideType) { - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } else if (one.isWideType) { - val three = info.stack.types(2) - assert(!two.isWideType && !three.isWideType, "Impossible") - val tmp3 = freshLocal(three) - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - STORE_LOCAL(tmp3), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp3), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } else { - val three = info.stack.types(2) - val tmp3 = freshLocal(three) - if (three.isWideType) { - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - STORE_LOCAL(tmp3), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp3), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } else { - val four = info.stack.types(3) - val tmp4 = freshLocal(three) - assert(!four.isWideType, "Impossible") - bb.replaceInstruction(i, List(STORE_LOCAL(tmp1), - STORE_LOCAL(tmp2), - STORE_LOCAL(tmp3), - STORE_LOCAL(tmp4), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1), - LOAD_LOCAL(tmp4), - LOAD_LOCAL(tmp3), - LOAD_LOCAL(tmp2), - LOAD_LOCAL(tmp1))) - } - } - case _ => - } - info = tfa.interpret(info, i) - } - } - } - - /** Recover def-use chains for NEW and initializers. */ - def resolveNEWs() { - import opcodes._ - val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis - rdef.init(method) - rdef.run() - - for (bb <- method.code.blocks ; (i, idx) <- bb.toList.zipWithIndex) i match { - case cm @ CALL_METHOD(m, Static(true)) if m.isClassConstructor => - def loop(bb0: BasicBlock, idx0: Int, depth: Int): Unit = { - rdef.findDefs(bb0, idx0, 1, depth) match { - case ((bb1, idx1)) :: _ => - bb1(idx1) match { - case _: DUP => loop(bb1, idx1, 0) - case x: NEW => x.init = cm - case _: THIS => () // super constructor call - case producer => dumpMethodAndAbort(method, "producer: " + producer) - } - case _ => () - } - } - loop(bb, idx, m.info.paramTypes.length) - - case _ => () - } - } - - /** Return the local at given index, with the given type. */ - def getLocal(idx: Char, kind: TypeKind): Local = getLocal(idx.toInt, kind) - def getLocal(idx: Int, kind: TypeKind): Local = { - assert(idx < maxLocals, "Index too large for local variable.") - - def checkValidIndex() { - locals.get(idx - 1) match { - case Some(others) if others exists (_._2.isWideType) => - global.globalError("Illegal index: " + idx + " points in the middle of another local") - case _ => () - } - kind match { - case LONG | DOUBLE if (locals.isDefinedAt(idx + 1)) => - global.globalError("Illegal index: " + idx + " overlaps " + locals(idx + 1) + "\nlocals: " + locals) - case _ => () - } - } - - locals.get(idx) match { - case Some(ls) => - val l = ls find { loc => loc._2 isAssignabledTo kind } - l match { - case Some((loc, _)) => loc - case None => - val l = freshLocal(kind) - locals(idx) = (l, kind) :: locals(idx) - log("Expected kind " + kind + " for local " + idx + - " but only " + ls + " found. Added new local.") - l - } - case None => - checkValidIndex() - val l = freshLocal(idx, kind, isArg = false) - debuglog("Added new local for idx " + idx + ": " + kind) - locals += (idx -> List((l, kind))) - l - } - } - - override def toString(): String = instrs.toList.mkString("", "\n", "") - - /** Return a fresh Local variable for the given index. - */ - private def freshLocal(idx: Int, kind: TypeKind, isArg: Boolean) = { - val sym = method.symbol.newVariable(newTermName("loc" + idx)).setInfo(kind.toType) - val l = new Local(sym, kind, isArg) - method.addLocal(l) - l - } - - private var count = 0 - - /** Invent a new local, with a new index value outside the range of - * the original method. */ - def freshLocal(kind: TypeKind): Local = { - count += 1 - freshLocal(maxLocals + count, kind, isArg = false) - } - - /** add a method param with the given index. */ - def enterParam(idx: Int, kind: TypeKind) = { - val sym = method.symbol.newVariable(newTermName("par" + idx)).setInfo(kind.toType) - val l = new Local(sym, kind, true) - assert(!locals.isDefinedAt(idx), locals(idx)) - locals += (idx -> List((l, kind))) - l - } - - /** Base class for branch instructions that take addresses. */ - abstract class LazyJump(pc: Int) extends Instruction { - override def toString() = "LazyJump " + pc - jmpTargets += pc - } - - case class LJUMP(pc: Int) extends LazyJump(pc) - - case class LCJUMP(success: Int, failure: Int, cond: TestOp, kind: TypeKind) - extends LazyJump(success) { - override def toString(): String = "LCJUMP (" + kind + ") " + success + " : " + failure - - jmpTargets += failure - } - - case class LCZJUMP(success: Int, failure: Int, cond: TestOp, kind: TypeKind) - extends LazyJump(success) { - override def toString(): String = "LCZJUMP (" + kind + ") " + success + " : " + failure - - jmpTargets += failure - } - - case class LSWITCH(tags: List[List[Int]], targets: List[Int]) extends LazyJump(targets.head) { - override def toString(): String = "LSWITCH (tags: " + tags + ") targets: " + targets - - jmpTargets ++= targets.tail - } - - /** Duplicate and exchange pseudo-instruction. Should be later - * replaced by proper ICode */ - abstract class DupX extends Instruction - - case object DUP_X1 extends DupX - case object DUP_X2 extends DupX - case object DUP2_X1 extends DupX - case object DUP2_X2 extends DupX - } -} diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 833f25537c..266a422c53 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -71,7 +71,9 @@ abstract class Erasure extends AddInterfaces } override protected def verifyJavaErasure = settings.Xverify || settings.debug - def needsJavaSig(tp: Type) = !settings.Ynogenericsig && NeedsSigCollector.collect(tp) + def needsJavaSig(tp: Type, throwsArgs: List[Type]) = !settings.Ynogenericsig && { + NeedsSigCollector.collect(tp) || throwsArgs.exists(NeedsSigCollector.collect) + } // only refer to type params that will actually make it into the sig, this excludes: // * higher-order type parameters @@ -251,7 +253,7 @@ abstract class Erasure extends AddInterfaces // Anything which could conceivably be a module (i.e. isn't known to be // a type parameter or similar) must go through here or the signature is // likely to end up with Foo<T>.Empty where it needs Foo<T>.Empty$. - def fullNameInSig(sym: Symbol) = "L" + enteringIcode(sym.javaBinaryName) + def fullNameInSig(sym: Symbol) = "L" + enteringJVM(sym.javaBinaryName) def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, toplevel: Boolean = false, primitiveOK: Boolean = true): String = { val tp = tp0.dealias @@ -277,7 +279,7 @@ abstract class Erasure extends AddInterfaces val preRebound = pre.baseType(sym.owner) // #2585 dotCleanup( ( - if (needsJavaSig(preRebound)) { + if (needsJavaSig(preRebound, Nil)) { val s = jsig(preRebound, existentiallyBound) if (s.charAt(0) == 'L') s.substring(0, s.length - 1) + "." + sym.javaSimpleName else fullNameInSig(sym) @@ -356,8 +358,9 @@ abstract class Erasure extends AddInterfaces else jsig(etp) } } - if (needsJavaSig(info)) { - try Some(jsig(info, toplevel = true)) + val throwsArgs = sym0.annotations flatMap ThrownException.unapply + if (needsJavaSig(info, throwsArgs)) { + try Some(jsig(info, toplevel = true) + throwsArgs.map("^" + jsig(_, toplevel = true)).mkString("")) catch { case ex: UnknownSig => None } } else None diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index c9d3b3da96..b7c72f5373 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -1250,13 +1250,14 @@ trait ContextErrors { ) } - def treeTypeArgs(annotatedTree: Tree) = annotatedTree match { + def treeTypeArgs(annotatedTree: Tree): List[String] = annotatedTree match { case TypeApply(_, args) => args.map(_.toString) + case Block(_, Function(_, treeInfo.Applied(_, targs, _))) => targs.map(_.toString) // eta expansion, see neg/t9527b.scala case _ => Nil } context.issueAmbiguousError(AmbiguousImplicitTypeError(tree, - (tree1.symbol, tree2.symbol) match { + (info1.sym, info2.sym) match { case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1)) case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2)) case (_, _) if isView => viewMsg diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d06a4a5d80..3889d48213 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3660,7 +3660,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val annType = annTpt.tpe finish( - if (typedFun.isErroneous) + if (typedFun.isErroneous || annType == null) ErroneousAnnotation else if (annType.typeSymbol isNonBottomSubClass ClassfileAnnotationClass) { // annotation to be saved as java classfile annotation |