diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2016-01-24 13:03:22 +0100 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2016-01-24 13:03:22 +0100 |
commit | b0831e774e290148ce28cb7957794542dffe0366 (patch) | |
tree | 1380b224b4fe31598b07b3ddee9a46ff2e3eaf10 /src/compiler | |
parent | 1a9e649e34cda4306537d74ab425c33d5f6a77db (diff) | |
parent | 1081e718f8f8e174dbf615e42b157e187d3d3886 (diff) | |
download | scala-b0831e774e290148ce28cb7957794542dffe0366.tar.gz scala-b0831e774e290148ce28cb7957794542dffe0366.tar.bz2 scala-b0831e774e290148ce28cb7957794542dffe0366.zip |
Merge remote-tracking branch 'upstream/2.12.x' into opt/elimBoxes
Diffstat (limited to 'src/compiler')
118 files changed, 1376 insertions, 16484 deletions
diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala index b8384851da..a3bf894b25 100644 --- a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala +++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala @@ -12,7 +12,6 @@ abstract class DefaultMacroCompiler extends Resolvers import treeInfo._ import definitions._ val runDefinitions = currentRun.runDefinitions - import runDefinitions.Predef_??? val typer: global.analyzer.Typer val context = typer.context @@ -92,4 +91,4 @@ abstract class DefaultMacroCompiler extends Resolvers EmptyTree } } -}
\ No newline at end of file +} diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala index 47084fc317..63be500061 100644 --- a/src/compiler/scala/reflect/quasiquotes/Holes.scala +++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala @@ -1,7 +1,6 @@ package scala.reflect package quasiquotes -import scala.collection.{immutable, mutable} import scala.reflect.internal.Flags._ import scala.reflect.macros.TypecheckException @@ -222,7 +221,7 @@ trait Holes { self: Quasiquotes => else if (rank == NoDot) Some(unlifter) else { val idx = records.indexWhere { p => p._1 =:= tpe && p._2 == rank } - val resIdx = if (idx != -1) idx else { records +:= (tpe, rank); records.length - 1} + val resIdx = if (idx != -1) idx else { records +:= ((tpe, rank)); records.length - 1} Some(Ident(TermName(nme.QUASIQUOTE_UNLIFT_HELPER + resIdx))) } } diff --git a/src/compiler/scala/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/reflect/quasiquotes/Parsers.scala index 97ec7dbfc3..6972c4070c 100644 --- a/src/compiler/scala/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/reflect/quasiquotes/Parsers.scala @@ -3,10 +3,7 @@ package quasiquotes import scala.tools.nsc.ast.parser.{Parsers => ScalaParser} import scala.tools.nsc.ast.parser.Tokens._ -import scala.compat.Platform.EOL import scala.reflect.internal.util.{BatchSourceFile, SourceFile, FreshNameCreator} -import scala.collection.mutable.ListBuffer -import scala.util.Try /** Builds upon the vanilla Scala parser and teams up together with Placeholders.scala to emulate holes. * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. diff --git a/src/compiler/scala/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/reflect/quasiquotes/Placeholders.scala index a5b42f8a1f..bc4f954275 100644 --- a/src/compiler/scala/reflect/quasiquotes/Placeholders.scala +++ b/src/compiler/scala/reflect/quasiquotes/Placeholders.scala @@ -2,7 +2,7 @@ package scala.reflect package quasiquotes import java.util.UUID.randomUUID -import scala.collection.{immutable, mutable} +import scala.collection.mutable /** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala). * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. diff --git a/src/compiler/scala/tools/ant/FastScalac.scala b/src/compiler/scala/tools/ant/FastScalac.scala index 6f0a30aa9d..e0b7be8004 100644 --- a/src/compiler/scala/tools/ant/FastScalac.scala +++ b/src/compiler/scala/tools/ant/FastScalac.scala @@ -8,7 +8,7 @@ package scala.tools.ant -import org.apache.tools.ant.{AntClassLoader, Project} +import org.apache.tools.ant.AntClassLoader import org.apache.tools.ant.taskdefs.Java import org.apache.tools.ant.types.Path diff --git a/src/compiler/scala/tools/ant/Pack200Task.scala b/src/compiler/scala/tools/ant/Pack200Task.scala index 3c1bc8cad9..df162d734a 100644 --- a/src/compiler/scala/tools/ant/Pack200Task.scala +++ b/src/compiler/scala/tools/ant/Pack200Task.scala @@ -8,14 +8,10 @@ package scala.tools.ant -import java.io.{BufferedOutputStream, File, FileInputStream, - FileOutputStream, PipedInputStream, PipedOutputStream} -import java.util.jar.{JarFile, JarInputStream, JarOutputStream, Pack200} +import java.io.{BufferedOutputStream, File, FileOutputStream} +import java.util.jar.{JarFile, JarOutputStream, Pack200} import java.util.jar.Pack200.Packer._ -import org.apache.tools.ant.{BuildException, DirectoryScanner} -import org.apache.tools.ant.types.FileSet - /** An [[http://ant.apache.org Ant]] task that applies the pack200 encoding * to a JAR file. * diff --git a/src/compiler/scala/tools/ant/ScalaMatchingTask.scala b/src/compiler/scala/tools/ant/ScalaMatchingTask.scala index 68a84bed0c..43b9010509 100644 --- a/src/compiler/scala/tools/ant/ScalaMatchingTask.scala +++ b/src/compiler/scala/tools/ant/ScalaMatchingTask.scala @@ -8,11 +8,8 @@ package scala.tools.ant -import java.io.{ File, InputStream, FileWriter } - import org.apache.tools.ant.{ Task, BuildException } import org.apache.tools.ant.taskdefs.MatchingTask -import org.apache.tools.ant.types.{ Path, Reference } trait ScalaTask { self: Task => @@ -27,5 +24,4 @@ trait ScalaTask { throw new BuildException(message, getLocation()) } -abstract class ScalaMatchingTask extends MatchingTask with ScalaTask { -} +abstract class ScalaMatchingTask extends MatchingTask with ScalaTask diff --git a/src/compiler/scala/tools/ant/ScalaTool.scala b/src/compiler/scala/tools/ant/ScalaTool.scala index bb6a933d3f..67879d6de3 100644 --- a/src/compiler/scala/tools/ant/ScalaTool.scala +++ b/src/compiler/scala/tools/ant/ScalaTool.scala @@ -8,8 +8,7 @@ package scala.tools.ant -import java.io.{File, InputStream, FileWriter} -import org.apache.tools.ant.BuildException +import java.io.{File, FileWriter} import org.apache.tools.ant.types.{Path, Reference} /** An Ant task that generates a shell or batch script to execute a diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index f46f014096..fe9815be20 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -10,13 +10,11 @@ package scala.tools.ant import java.io.{File, PrintWriter, BufferedWriter, FileWriter} -import org.apache.tools.ant.{ BuildException, Project, AntClassLoader } +import org.apache.tools.ant.{ Project, AntClassLoader} import org.apache.tools.ant.taskdefs.Java import org.apache.tools.ant.types.{Path, Reference} -import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper, - SourceFileScanner, facade} -import org.apache.tools.ant.util.facade.{FacadeTaskHelper, - ImplementationSpecificArgument} +import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper, SourceFileScanner} +import org.apache.tools.ant.util.facade.{FacadeTaskHelper, ImplementationSpecificArgument} import scala.tools.nsc.{Global, Settings, CompilerCommand} import scala.tools.nsc.io.{Path => SPath} @@ -91,8 +89,8 @@ class Scalac extends ScalaMatchingTask with ScalacShared { val values = List("namer", "typer", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "lazyvals", "lambdalift", "constructors", - "flatten", "mixin", "delambdafy", "cleanup", "icode", "inliner", - "closelim", "dce", "jvm", "terminal") + "flatten", "mixin", "delambdafy", "cleanup", + "jvm", "terminal") } /** Defines valid values for the `target` property. */ diff --git a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala index cde827ba54..c31f55c9b6 100644 --- a/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala +++ b/src/compiler/scala/tools/ant/sabbus/ScalacFork.scala @@ -10,7 +10,7 @@ package scala package tools.ant package sabbus -import java.io.{ File, FileWriter } +import java.io.File import org.apache.tools.ant.Project import org.apache.tools.ant.taskdefs.Java import org.apache.tools.ant.util.{ GlobPatternMapper, SourceFileScanner } diff --git a/src/compiler/scala/tools/ant/sabbus/Use.scala b/src/compiler/scala/tools/ant/sabbus/Use.scala index a8736f228b..cb514e35b3 100644 --- a/src/compiler/scala/tools/ant/sabbus/Use.scala +++ b/src/compiler/scala/tools/ant/sabbus/Use.scala @@ -13,7 +13,6 @@ package sabbus import java.io.File -import org.apache.tools.ant.types.{Path, Reference} import org.apache.tools.ant.util.{GlobPatternMapper, SourceFileScanner} class Use extends ScalaMatchingTask { diff --git a/src/compiler/scala/tools/cmd/FromString.scala b/src/compiler/scala/tools/cmd/FromString.scala index 0b074efc0f..ab49c7507c 100644 --- a/src/compiler/scala/tools/cmd/FromString.scala +++ b/src/compiler/scala/tools/cmd/FromString.scala @@ -6,7 +6,7 @@ package scala.tools package cmd -import nsc.io.{ Path, File, Directory } +import nsc.io.Directory import scala.reflect.OptManifest /** A general mechanism for defining how a command line argument diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala index a1cb31f911..069a7a89a1 100644 --- a/src/compiler/scala/tools/cmd/Spec.scala +++ b/src/compiler/scala/tools/cmd/Spec.scala @@ -47,6 +47,6 @@ object Spec { } class EnvironmentVar(val name: String) { - override def toString = "${%s}" format name + override def toString = s"$${$name}" } } 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/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala index aa02957a6c..9cac497a85 100644 --- a/src/compiler/scala/tools/nsc/CompileServer.scala +++ b/src/compiler/scala/tools/nsc/CompileServer.scala @@ -8,7 +8,7 @@ package scala.tools.nsc import java.io.PrintStream import io.Directory import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} -import scala.reflect.internal.util.{FakePos, Position} +import scala.reflect.internal.util.FakePos import scala.tools.util.SocketServer import settings.FscSettings @@ -193,14 +193,14 @@ object CompileServer { val i = args.indexOf("-p") if (i >= 0 && args.length > i + 1) { scala.util.control.Exception.ignoring(classOf[NumberFormatException]) { - port = args(i + 1).toInt + port = args(i + 1).toInt } } - + // Create instance rather than extend to pass a port parameter. val server = new StandardCompileServer(port) val redirectDir = (server.compileSocket.tmpDir / "output-redirects").createDirectory() - + if (debug) { server.echo("Starting CompileServer on port " + server.port) server.echo("Redirect dir is " + redirectDir) diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala index 27a14141fa..01c7d72d4f 100644 --- a/src/compiler/scala/tools/nsc/CompileSocket.scala +++ b/src/compiler/scala/tools/nsc/CompileSocket.scala @@ -5,9 +5,9 @@ package scala.tools.nsc -import java.io.{ FileNotFoundException, PrintWriter, FileOutputStream } +import java.io.FileNotFoundException import java.security.SecureRandom -import io.{ File, Path, Directory, Socket } +import io.{ File, Path, Socket } import scala.tools.util.CompileOutputCommon import scala.reflect.internal.util.StringOps.splitWhere import scala.sys.process._ @@ -46,7 +46,7 @@ trait HasCompileSocket { class CompileSocket extends CompileOutputCommon { protected lazy val compileClient: StandardCompileClient = CompileClient def verbose = compileClient.verbose - + /* Fixes the port where to start the server, 0 yields some free port */ var fixPort = 0 @@ -67,7 +67,7 @@ class CompileSocket extends CompileOutputCommon { /** The class name of the scala compile server */ protected val serverClass = "scala.tools.nsc.CompileServer" - protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil) + protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil) /** A temporary directory to use */ val tmpDir = { @@ -196,7 +196,7 @@ class CompileSocket extends CompileOutputCommon { catch { case _: NumberFormatException => None } def getSocket(serverAdr: String): Option[Socket] = ( - for ((name, portStr) <- splitWhere(serverAdr, _ == ':', doDropIndex = true) ; port <- parseInt(portStr)) yield + for ((name, portStr) <- splitWhere(serverAdr, _ == ':', doDropIndex = true) ; port <- parseInt(portStr)) yield getSocket(name, port) ) getOrElse fatal("Malformed server address: %s; exiting" format serverAdr) @@ -205,7 +205,7 @@ class CompileSocket extends CompileOutputCommon { if (sock.isEmpty) warn("Unable to establish connection to server %s:%d".format(hostName, port)) sock } - + def getPassword(port: Int): String = { val ff = portFile(port) val f = ff.bufferedReader() diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index c6124e7177..c1d7176d0c 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 */ @@ -231,7 +215,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** Called from parser, which signals hereby that a method definition has been parsed. */ def signalParseProgress(pos: Position) {} - /** Called by ScalaDocAnalyzer when a doc comment has been parsed. */ + /** Called by ScaladocAnalyzer when a doc comment has been parsed. */ def signalParsedDocComment(comment: String, pos: Position) = { // TODO: this is all very broken (only works for scaladoc comments, not regular ones) // --> add hooks to parser and refactor Interactive global to handle comments directly @@ -400,15 +384,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) def apply(unit: CompilationUnit): Unit - private val isErased = prev.name == "erasure" || prev.erasedTypes - override def erasedTypes: Boolean = isErased - private val isFlat = prev.name == "flatten" || prev.flatClasses - override def flatClasses: Boolean = isFlat - private val isSpecialized = prev.name == "specialize" || prev.specialized - override def specialized: Boolean = isSpecialized - private val isRefChecked = prev.name == "refchecks" || prev.refChecked - override def refChecked: Boolean = isRefChecked - /** Is current phase cancelled on this unit? */ def cancelled(unit: CompilationUnit) = { // run the typer only if in `createJavadoc` mode @@ -591,59 +566,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 +600,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 +632,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 +970,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 +1246,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 +1259,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") 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 +1325,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 +1407,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() @@ -1534,7 +1428,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // move the pointer globalPhase = globalPhase.next - // run tree/icode checkers + // run tree checkers if (settings.check containsPhase globalPhase.prev) runCheckers() @@ -1678,30 +1572,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/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 4e7a527a5a..e11cba466e 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -7,7 +7,7 @@ package scala package tools package nsc -import scala.collection.{ mutable, immutable } +import scala.collection.mutable import scala.reflect.internal.util.StringOps.countElementsAsString /** Provides delegates to the reporter doing the actual work. @@ -78,7 +78,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w s"""| |This can be achieved by adding the import clause 'import $fqname' |or by setting the compiler option -language:$featureName. - |See the Scala docs for value $fqname for a discussion + |See the Scaladoc for value $fqname for a discussion |why the feature $req be explicitly enabled.""".stripMargin ) reportedFeature += featureTrait diff --git a/src/compiler/scala/tools/nsc/ast/Printers.scala b/src/compiler/scala/tools/nsc/ast/Printers.scala index f3def3c80c..8b37948e9b 100644 --- a/src/compiler/scala/tools/nsc/ast/Printers.scala +++ b/src/compiler/scala/tools/nsc/ast/Printers.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package ast -import java.io.{ OutputStream, PrintWriter, StringWriter, Writer } +import java.io.{ OutputStream, PrintWriter } trait Printers extends scala.reflect.internal.Printers { this: Global => diff --git a/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala b/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala index eafecf9462..105bdee256 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala @@ -7,7 +7,9 @@ package scala package tools.nsc package ast -import java.awt.{List => awtList, _} +import scala.language.implicitConversions + +import java.awt.{List => _, _} import java.awt.event._ import java.io.StringWriter @@ -17,7 +19,6 @@ import javax.swing.tree._ import scala.concurrent.Lock import scala.text._ -import scala.language.implicitConversions /** * Tree browsers can show the AST in a graphical and interactive diff --git a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala index 6dda30b5e7..9e1498cf3e 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package ast -import symtab.Flags import scala.language.implicitConversions /** A DSL for generating scala code. The goal is that the diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 332acf4a26..3167f87383 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -242,11 +242,14 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats) - def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = - Block(mkSynchronized( - attrThis, - If(cond, Block(syncBody: _*), EmptyTree)) :: - stats: _*) + def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = { + def blockOrStat(stats: List[Tree]): Tree = stats match { + case head :: Nil => head + case _ => Block(stats : _*) + } + val sync = mkSynchronized(attrThis, If(cond, blockOrStat(syncBody), EmptyTree)) + blockOrStat(sync :: stats) + } /** Creates a tree representing new Object { stats }. * To make sure an anonymous subclass of Object is created, diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 934257092f..2d47e254e5 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -7,13 +7,6 @@ package scala.tools.nsc package ast import scala.reflect.ClassTag -import scala.reflect.internal.Flags.BYNAMEPARAM -import scala.reflect.internal.Flags.DEFAULTPARAM -import scala.reflect.internal.Flags.IMPLICIT -import scala.reflect.internal.Flags.PARAM -import scala.reflect.internal.Flags.PARAMACCESSOR -import scala.reflect.internal.Flags.PRESUPER -import scala.reflect.internal.Flags.TRAIT import scala.compat.Platform.EOL trait Trees extends scala.reflect.internal.Trees { self: Global => diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 52b8a51a79..46d533b037 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -12,7 +12,7 @@ import mutable.{ Buffer, ArrayBuffer, ListBuffer } import scala.util.control.ControlThrowable import scala.tools.nsc.util.CharArrayReader import scala.tools.nsc.ast.parser.xml.{MarkupParserCommon, Utility} -import scala.reflect.internal.Chars.{ SU, LF } +import scala.reflect.internal.Chars.SU // XXX/Note: many/most of the functions in here are almost direct cut and pastes // from another file - scala.xml.parsing.MarkupParser, it looks like. @@ -261,7 +261,7 @@ trait MarkupParsers { def coalesce(): ArrayBuffer[Tree] = { def copy() = { val buf = new ArrayBuffer[Tree] - var acc = new StringBuilder + val acc = new StringBuilder var pos: Position = NoPosition def emit() = if (acc.nonEmpty) { appendText(pos, buf, acc.toString) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index dac3c7a285..1d96b940ae 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -9,10 +9,9 @@ package scala.tools.nsc package ast.parser -import scala.collection.{ mutable, immutable } -import mutable.{ ListBuffer, StringBuilder } +import scala.collection.mutable +import mutable.ListBuffer import scala.reflect.internal.{ Precedence, ModifierFlags => Flags } -import scala.reflect.internal.Chars.{ isScalaLetter } import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator, ListOfNil } import Tokens._ diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 51bb0d3c5b..3d8f5a2dd3 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -10,7 +10,7 @@ import scala.reflect.internal.util._ import scala.reflect.internal.Chars._ import Tokens._ import scala.annotation.{ switch, tailrec } -import scala.collection.{ mutable, immutable } +import scala.collection.mutable import mutable.{ ListBuffer, ArrayBuffer } import scala.tools.nsc.ast.parser.xml.Utility.isNameStart import scala.language.postfixOps diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala index 67241ef639..c3c3ee9d47 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package ast.parser -import scala.collection.{ mutable, immutable } +import scala.collection.mutable import symtab.Flags.MUTABLE import scala.reflect.internal.util.ListOfNil import scala.reflect.internal.util.StringOps.splitWhere diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 6e5a3f6ef7..6b564197a1 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package ast.parser import symtab.Flags._ -import scala.collection.mutable.ListBuffer import scala.reflect.internal.util.{Position, SourceFile, FreshNameCreator} /** Methods for building trees, used in the parser. All the trees 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/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index a921ef3966..32f8c7826f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -8,8 +8,9 @@ package scala.tools.nsc.backend.jvm import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} -import scala.tools.asm.{ClassWriter, Attribute, ClassReader} +import scala.tools.asm.{ClassReader, ClassWriter, Attribute} import scala.collection.convert.decorateAsScala._ +import scala.collection.convert.decorateAsJava._ import scala.tools.nsc.backend.jvm.analysis.InitialProducer import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype @@ -55,11 +56,30 @@ object AsmUtils { node } - def readClass(filename: String): ClassNode = { - val f = new java.io.RandomAccessFile(filename, "r") - val b = new Array[Byte](f.length.toInt) - f.read(b) - readClass(b) + def readClass(filename: String): ClassNode = readClass(classBytes(filename)) + + def classBytes(file: String): Array[Byte] = { + val f = new java.io.RandomAccessFile(file, "r") + val bytes = new Array[Byte](f.length.toInt) + f.read(bytes) + bytes + } + + def textifyClassStably(bytes: Array[Byte]): Unit = { + val node = new ClassNode() + new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) + + node.fields = node.fields.asScala.sortBy(_.name).asJava + node.methods = node.methods.asScala.sortBy(_.name).asJava + node.visibleAnnotations = null + node.attrs = null + node.invisibleAnnotations = null + + println(textify(node)) + } + + def main(args: Array[String]): Unit = { + textifyClassStably(classBytes(args.head)) } /** 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 58c1d0e497..59d584c370 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 d501d4239b..1ec9ba8a95 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -22,8 +22,315 @@ 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 @@ -35,15 +342,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { /* * must-single-thread */ - def getOutFolder(csym: Symbol, cName: String, cunit: CompilationUnit): _root_.scala.tools.nsc.io.AbstractFile = { - try { + def getOutFolder(csym: Symbol, cName: String, cunit: CompilationUnit): _root_.scala.tools.nsc.io.AbstractFile = + _root_.scala.util.Try { outputDirectory(csym) - } catch { + }.recover { case ex: Throwable => reporter.error(cunit.body.pos, s"Couldn't create file for class $cName\n${ex.getMessage}") null - } - } + }.get var pickledBytes = 0 // statistics @@ -335,9 +641,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 + } + + 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 + } - import genASM.{ubytesToCharArray, arrEncode} - import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible} + 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 +760,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. @@ -469,7 +845,21 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { trait BCJGenSigGen { - def getCurrentCUnit(): CompilationUnit + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) /* @return * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). @@ -477,7 +867,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * * must-single-thread */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit()) + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + getGenericSignature(sym, owner, memberTpe) + } + + def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = { + if (!needsGenericSignature(sym)) { return null } + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _: Throwable => false } + } + + if (settings.Xverify) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if(!isValidSignature) { + reporter.warning(sym.pos, + """|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 @@ -510,7 +956,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): 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) + else null + } + } + val moduleName = internalName(module) val methodInfo = module.thisType.memberInfo(m) val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType @@ -527,7 +989,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) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) @@ -582,9 +1044,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") - for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) { + for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) - debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") + debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}") else if (conflictingNames(m.name)) log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}") else if (m.hasAccessBoundary) @@ -650,9 +1112,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { /* builder of mirror classes */ class JMirrorBuilder extends JCommonBuilder { - private var cunit: CompilationUnit = _ - def getCurrentCUnit(): CompilationUnit = cunit; - /* Generate a mirror class for a top-level module. A mirror class is a class * containing only static methods that forward to the corresponding method * on the MODULE instance of the given Scala object. It will only be @@ -664,7 +1123,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { assert(moduleClass.isModuleClass) assert(moduleClass.companionClass == NoSymbol, moduleClass) - this.cunit = cunit val bType = mirrorClassClassBType(moduleClass) val mirrorClass = new asm.tree.ClassNode @@ -866,3 +1324,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..7180c25d25 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, @@ -83,8 +82,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { global synchronized { global.log(msg) } } - override def getCurrentCUnit(): CompilationUnit = { cunit } - /* ---------------- helper utils for generating classes and fields ---------------- */ def genPlainClass(cd: ClassDef) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index f1b9e8ea94..c3f6399901 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 35b84cc4a9..9bfa7dae27 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(!primitiveTypeToBType.contains(sym) || isCompilingPrimitive, sym) } + def implementedInterfaces(classSym: Symbol): List[Symbol] = { + // Additional interface parents based on annotations and other cues + def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match { + case RemoteAttr => Some(RemoteInterfaceClass.tpe) + case _ => None + } + + // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes. + def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag + + val classParents = { + val parents = classSym.info.parents + // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the + // parents of a java annotations. undo this for the backend (where we need classfile-level information). + if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass) + else parents + } + + val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) + + // 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 @@ -580,7 +671,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // Primitives are "abstract final" to prohibit instantiation // without having to provide any implementations, but that is an // illegal combination of modifiers at the bytecode level so - // suppress final if abstract if present. + // suppress final if abstract is present. import asm.Opcodes._ GenBCode.mkFlags( if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 0eaf509133..8bb71a386f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -106,6 +106,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(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]) lazy val srBoxedUnitRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxedUnit]) @@ -350,6 +353,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 def srBoxedUnitRef : ClassBType = _coreBTypes.srBoxedUnitRef 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 4424a3796b..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ /dev/null @@ -1,3355 +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(tp) <- excs.distinct) - yield { - val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) - javaName(erased.typeSymbol) - } - - 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) { - 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(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/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/classpath/FlatClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala index 7f67381d4d..d8ca325885 100644 --- a/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala +++ b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala @@ -5,7 +5,6 @@ package scala.tools.nsc.classpath import scala.tools.nsc.Settings import scala.tools.nsc.io.AbstractFile -import scala.tools.nsc.util.ClassPath import FileUtils.AbstractFileOps /** diff --git a/src/compiler/scala/tools/nsc/io/Jar.scala b/src/compiler/scala/tools/nsc/io/Jar.scala index efb026cdff..779f546f69 100644 --- a/src/compiler/scala/tools/nsc/io/Jar.scala +++ b/src/compiler/scala/tools/nsc/io/Jar.scala @@ -6,11 +6,12 @@ package scala.tools.nsc package io -import java.io.{ InputStream, OutputStream, IOException, FileNotFoundException, FileInputStream, DataOutputStream } +import scala.language.postfixOps + +import java.io.{ InputStream, OutputStream, DataOutputStream } import java.util.jar._ import scala.collection.JavaConverters._ import Attributes.Name -import scala.language.{ implicitConversions, postfixOps } // Attributes.Name instances: // diff --git a/src/compiler/scala/tools/nsc/io/Socket.scala b/src/compiler/scala/tools/nsc/io/Socket.scala index a803e4121a..a17517da2e 100644 --- a/src/compiler/scala/tools/nsc/io/Socket.scala +++ b/src/compiler/scala/tools/nsc/io/Socket.scala @@ -8,7 +8,7 @@ package io import java.io.{ IOException, InputStreamReader, BufferedReader, PrintWriter, Closeable } import java.io.{ BufferedOutputStream, BufferedReader } -import java.net.{ ServerSocket, SocketException, SocketTimeoutException, InetAddress, Socket => JSocket } +import java.net.{ InetAddress, Socket => JSocket } import scala.io.Codec /** A skeletal only-as-much-as-I-need Socket wrapper. diff --git a/src/compiler/scala/tools/nsc/io/SourceReader.scala b/src/compiler/scala/tools/nsc/io/SourceReader.scala index 3220c2e2b2..b84c509a32 100644 --- a/src/compiler/scala/tools/nsc/io/SourceReader.scala +++ b/src/compiler/scala/tools/nsc/io/SourceReader.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package io -import java.io.{ FileInputStream, InputStream, IOException } +import java.io.{ FileInputStream, IOException } import java.nio.{ByteBuffer, CharBuffer} import java.nio.channels.{ ReadableByteChannel, Channels } import java.nio.charset.{CharsetDecoder, CoderResult} diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index eb25eb6e06..7224523a41 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -800,16 +800,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { val superclazz = AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType)) val finalFlag = if (enumIsFinal) Flags.FINAL else 0l - val abstractFlag = { - // javac adds `ACC_ABSTRACT` to enum classes with deferred members - val hasAbstractMember = body exists { - case d: DefDef => d.mods.isDeferred - case _ => false - } - if (hasAbstractMember) Flags.ABSTRACT else 0l - } addCompanionObject(consts ::: statics ::: predefs, atPos(pos) { - ClassDef(mods | Flags.JAVA_ENUM | finalFlag | abstractFlag, name, List(), + // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also ClassfileParser. + // This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags. + ClassDef(mods | Flags.JAVA_ENUM | Flags.SEALED | Flags.ABSTRACT | finalFlag, name, List(), makeTemplate(superclazz :: interfaces, body)) }) } diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index dd17750cd4..5caf7e41bf 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -6,14 +6,12 @@ package scala.tools.nsc package plugins -import scala.tools.nsc.io.{ Jar } +import scala.tools.nsc.io.Jar import scala.reflect.internal.util.ScalaClassLoader import scala.reflect.io.{ Directory, File, Path } import java.io.InputStream -import java.util.zip.ZipException import scala.collection.mutable -import mutable.ListBuffer import scala.util.{ Try, Success, Failure } /** Information about a plugin loaded from a jar file. diff --git a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala index 4bf92fd1fb..9b9d94bb0f 100644 --- a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala @@ -7,7 +7,7 @@ package scala package tools.nsc package reporters -import java.io.{ BufferedReader, IOException, PrintWriter } +import java.io.{ BufferedReader, PrintWriter } import scala.reflect.internal.util._ import StringOps._ diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 1bb1943c0c..d6ca0125ea 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 guarding no instructions, redundant metadata (debug information, line numbers).") val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.") @@ -369,7 +366,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/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 59cc13c64e..f570037760 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -49,7 +49,7 @@ trait Warnings { val NullaryOverride = LintWarning("nullary-override", "Warn when non-nullary `def f()' overrides nullary `def f'.", true) val InferAny = LintWarning("infer-any", "Warn when a type argument is inferred to be `Any`.", true) val MissingInterpolator = LintWarning("missing-interpolator", "A string literal appears to be missing an interpolator id.") - val DocDetached = LintWarning("doc-detached", "A ScalaDoc comment appears to be detached from its element.") + val DocDetached = LintWarning("doc-detached", "A Scaladoc comment appears to be detached from its element.") val PrivateShadow = LintWarning("private-shadow", "A private field (or class parameter) shadows a superclass field.") val TypeParameterShadow = LintWarning("type-parameter-shadow", "A local type parameter shadows a type already in scope.") val PolyImplicitOverload = LintWarning("poly-implicit-overload", "Parameterized overloaded implicit methods are not visible as view bounds.") diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 99e61d2482..406411f21f 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -542,7 +542,7 @@ abstract class ClassfileParser { devWarning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") case linked => if (!linked.isSealed) - // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. + // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also JavaParsers. // This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags. linked setFlag (SEALED | ABSTRACT) linked addChild sym 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/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala index 82e7c76409..085a814c6b 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -242,20 +242,18 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => } } - private def createMemberDef(tree: Tree, isForInterface: Boolean)(create: Tree => Tree) = { - val isInterfaceTree = tree.isDef && isInterfaceMember(tree.symbol) - if (isInterfaceTree && needsImplMethod(tree.symbol)) - create(tree) - else if (isInterfaceTree == isForInterface) - tree - else - EmptyTree - } - private def implMemberDef(tree: Tree): Tree = createMemberDef(tree, false)(implMethodDef) - private def ifaceMemberDef(tree: Tree): Tree = createMemberDef(tree, true)(t => DefDef(t.symbol, EmptyTree)) + private def isInterfaceTree(tree: Tree) = tree.isDef && isInterfaceMember(tree.symbol) + + private def deriveMemberForImplClass(tree: Tree): Tree = + if (isInterfaceTree(tree)) if (needsImplMethod(tree.symbol)) implMethodDef(tree) else EmptyTree + else tree + + private def deriveMemberForInterface(tree: Tree): Tree = + if (isInterfaceTree(tree)) if (needsImplMethod(tree.symbol)) DefDef(tree.symbol, EmptyTree) else tree + else EmptyTree private def ifaceTemplate(templ: Template): Template = - treeCopy.Template(templ, templ.parents, noSelfType, templ.body map ifaceMemberDef) + treeCopy.Template(templ, templ.parents, noSelfType, templ.body map deriveMemberForInterface) /** Transforms the member tree containing the implementation * into a member of the impl class. @@ -286,7 +284,7 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => private def implTemplate(clazz: Symbol, templ: Template): Template = atPos(templ.pos) { val templ1 = ( - Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map implMemberDef)) + Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map deriveMemberForImplClass)) setSymbol clazz.newLocalDummy(templ.pos) ) templ1.changeOwner(templ.symbol.owner -> clazz, templ.symbol -> templ1.symbol) diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index c29826551b..1d98b5da31 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -49,7 +49,9 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { clearStatics() val newBody = transformTrees(body) val templ = deriveTemplate(tree)(_ => transformTrees(newStaticMembers.toList) ::: newBody) - try addStaticInits(templ, newStaticInits, localTyper) // postprocess to include static ctors + try + if (newStaticInits.isEmpty) templ + else deriveTemplate(templ)(body => staticConstructor(body, localTyper, templ.pos)(newStaticInits.toList) :: body) finally clearStatics() } private def mkTerm(prefix: String): TermName = unit.freshTermName(prefix) diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 48eb878e2a..aef2817db7 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -6,8 +6,7 @@ package scala.tools.nsc package transform -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.ListBuffer +import scala.collection.mutable import scala.reflect.internal.util.ListOfNil import symtab.Flags._ @@ -28,7 +27,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { private val ctorParams: mutable.Map[Symbol, List[Symbol]] = perRunCaches.newMap[Symbol, List[Symbol]]() class ConstructorTransformer(unit: CompilationUnit) extends Transformer { - /* * Inspect for obvious out-of-order initialization; concrete, eager vals or vars, declared in this class, * for which a reference to the member precedes its definition. @@ -121,15 +119,15 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * What trees can be visited at this point? * To recap, by the time the constructors phase runs, local definitions have been hoisted out of their original owner. * Moreover, by the time elision is about to happen, the `intoConstructors` rewriting - * of template-level statements has taken place (the resulting trees can be found in `constrStatBuf`). + * of template-level statements has taken place (the resulting trees can be found in `constructorStats`). * * That means: * - * - nested classes are to be found in `defBuf` + * - nested classes are to be found in `defs` * - * - value and method definitions are also in `defBuf` and none of them contains local methods or classes. + * - value and method definitions are also in `defs` and none of them contains local methods or classes. * - * - auxiliary constructors are to be found in `auxConstructorBuf` + * - auxiliary constructors are to be found in `auxConstructors` * * Coming back to the question which trees may contain accesses: * @@ -148,70 +146,56 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * (the primary constructor) into a dedicated synthetic method that an anon-closure may invoke, as required by DelayedInit. * */ - private trait OmittablesHelper { self: TemplateTransformer => - - /* - * Initially populated with all elision candidates. - * Trees are traversed, and those candidates are removed which are actually needed. - * After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls. - */ - val omittables = mutable.Set.empty[Symbol] - - def populateOmittables() { - - omittables.clear() - - if(isDelayedInitSubclass) { - return - } + private trait OmittablesHelper { + def computeOmittableAccessors(clazz: Symbol, defs: List[Tree], auxConstructors: List[Tree]): Set[Symbol] = { + val decls = clazz.info.decls.toSet + val isEffectivelyFinal = clazz.isEffectivelyFinal + // Initially populated with all elision candidates. + // Trees are traversed, and those candidates are removed which are actually needed. + // After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls. + // // Note: elision of outer reference is based on a class-wise analysis, if a class might have subclasses, // it doesn't work. For example, `LocalParent` retains the outer reference in: // // class Outer { def test = {class LocalParent; class LocalChild extends LocalParent } } // // See run/t9408.scala for related test cases. - val isEffectivelyFinal = clazz.isEffectivelyFinal - def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal) - def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && isEffectivelyFinal && !sym.isOverridingSymbol) - - val decls = clazz.info.decls.toSet - val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (decls filter isParamCandidateForElision) - val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (decls filter isOuterCandidateForElision) - - omittables ++= paramCandidatesForElision - omittables ++= outerCandidatesForElision - - val bodyOfOuterAccessor: Map[Symbol, DefDef] = - defBuf.collect { case dd: DefDef if outerCandidatesForElision(dd.symbol) => dd.symbol -> dd }.toMap + def omittableParamAcc(sym: Symbol) = sym.isParamAccessor && sym.isPrivateLocal + def omittableOuterAcc(sym: Symbol) = isEffectivelyFinal && sym.isOuterAccessor && !sym.isOverridingSymbol + val omittables = mutable.Set.empty[Symbol] ++ (decls filter (sym => omittableParamAcc(sym) || omittableOuterAcc(sym))) // the closure only captures isEffectivelyFinal // no point traversing further once omittables is empty, all candidates ruled out already. object detectUsages extends Traverser { - private def markUsage(sym: Symbol) { - omittables -= debuglogResult("omittables -= ")(sym) - // recursive call to mark as needed the field supporting the outer-accessor-method. - bodyOfOuterAccessor get sym foreach (this traverse _.rhs) - } - override def traverse(tree: Tree): Unit = if (omittables.nonEmpty) { - def sym = tree.symbol - tree match { - // don't mark as "needed" the field supporting this outer-accessor, ie not just yet. - case _: DefDef if outerCandidatesForElision(sym) => () - case _: Select if omittables(sym) => markUsage(sym) ; super.traverse(tree) - case _ => super.traverse(tree) + lazy val bodyOfOuterAccessor = defs.collect{ case dd: DefDef if omittableOuterAcc(dd.symbol) => dd.symbol -> dd.rhs }.toMap + + override def traverse(tree: Tree): Unit = + if (omittables.nonEmpty) { + def sym = tree.symbol + tree match { + case _: DefDef if (sym.owner eq clazz) && omittableOuterAcc(sym) => // don't mark as "needed" the field supporting this outer-accessor (not just yet) + case _: Select if omittables(sym) => omittables -= sym // mark usage + bodyOfOuterAccessor get sym foreach traverse // recurse to mark as needed the field supporting the outer-accessor-method + super.traverse(tree) + case _ => super.traverse(tree) + } } - } - def walk(xs: Seq[Tree]) = xs.iterator foreach traverse } - if (omittables.nonEmpty) { - detectUsages walk defBuf - detectUsages walk auxConstructorBuf - } - } - def mustBeKept(sym: Symbol) = !omittables(sym) + if (omittables.nonEmpty) + (defs.iterator ++ auxConstructors.iterator) foreach detectUsages.traverse + + omittables.toSet + } } // OmittablesHelper + trait ConstructorTransformerBase { + def unit: CompilationUnit + def impl: Template + def clazz: Symbol + def localTyper: analyzer.Typer + } + /* * TemplateTransformer rewrites DelayedInit subclasses. * The list of statements that will end up in the primary constructor can be split into: @@ -256,10 +240,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * @return the DefDef for (c) above * * */ - private trait DelayedInitHelper { self: TemplateTransformer => - + private trait DelayedInitHelper extends ConstructorTransformerBase { private def delayedEndpointDef(stats: List[Tree]): DefDef = { - val methodName = currentUnit.freshTermName("delayedEndpoint$" + clazz.fullNameAsName('$').toString + "$") val methodSym = clazz.newMethod(methodName, impl.pos, SYNTHETIC | FINAL) methodSym setInfoAndEnter MethodType(Nil, UnitTpe) @@ -318,36 +300,30 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { satelliteClass.asInstanceOf[ClassDef] } - private def delayedInitCall(closure: Tree) = localTyper.typedPos(impl.pos) { - gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(closure.symbol.tpe, This(clazz)))) - } + /** For a DelayedInit subclass, wrap remainingConstrStats into a DelayedInit closure. */ + def delayedInitDefsAndConstrStats(defs: List[Tree], remainingConstrStats: List[Tree]): (List[Tree], List[Tree]) = { + val delayedHook = delayedEndpointDef(remainingConstrStats) + val delayedHookSym = delayedHook.symbol.asInstanceOf[MethodSymbol] - def rewriteDelayedInit() { - /* XXX This is not correct: remainingConstrStats.nonEmpty excludes too much, - * but excluding it includes too much. The constructor sequence being mimicked - * needs to be reproduced with total fidelity. - * - * See test case files/run/bug4680.scala, the output of which is wrong in many - * particulars. - */ - val needsDelayedInit = (isDelayedInitSubclass && remainingConstrStats.nonEmpty) - - if (needsDelayedInit) { - val delayedHook: DefDef = delayedEndpointDef(remainingConstrStats) - defBuf += delayedHook - val hookCallerClass = { - // transform to make the closure-class' default constructor assign the the outer instance to its param-accessor field. - val drillDown = new ConstructorTransformer(unit) - drillDown transform delayedInitClosure(delayedHook.symbol.asInstanceOf[MethodSymbol]) - } - defBuf += hookCallerClass - remainingConstrStats = delayedInitCall(hookCallerClass) :: Nil + // transform to make the closure-class' default constructor assign the the outer instance to its param-accessor field. + val hookCallerClass = (new ConstructorTransformer(unit)) transform delayedInitClosure(delayedHookSym) + val delayedInitCall = localTyper.typedPos(impl.pos) { + gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(hookCallerClass.symbol.tpe, This(clazz)))) } + + (List(delayedHook, hookCallerClass), List(delayedInitCall)) } } // DelayedInitHelper - private trait GuardianOfCtorStmts { self: TemplateTransformer => + private trait GuardianOfCtorStmts extends ConstructorTransformerBase { + def primaryConstrParams: List[Symbol] + def usesSpecializedField: Boolean + + lazy val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE) + // The constructor of a non-specialized class that has specialized subclasses + // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields. + lazy val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED) /* Return a single list of statements, merging the generic class constructor with the * specialized stats. The original statements are retyped in the current class, and @@ -355,7 +331,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * `specializedStats` are replaced by the specialized assignment. */ private def mergeConstructors(genericClazz: Symbol, originalStats: List[Tree], specializedStats: List[Tree]): List[Tree] = { - val specBuf = new ListBuffer[Tree] + val specBuf = new mutable.ListBuffer[Tree] specBuf ++= specializedStats def specializedAssignFor(sym: Symbol): Option[Tree] = @@ -383,7 +359,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } log("merging: " + originalStats.mkString("\n") + "\nwith\n" + specializedStats.mkString("\n")) - val res = for (s <- originalStats; stat = s.duplicate) yield { + for (s <- originalStats; stat = s.duplicate) yield { log("merge: looking at " + stat) val stat1 = stat match { case Assign(sel @ Select(This(_), field), _) => @@ -413,9 +389,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } else stat1 } - if (specBuf.nonEmpty) - println("residual specialized constructor statements: " + specBuf) - res +// if (specBuf.nonEmpty) +// println("residual specialized constructor statements: " + specBuf) } /* Add an 'if' around the statements coming after the super constructor. This @@ -435,7 +410,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { // postfix = postfix.tail // } - if (guardSpecializedFieldInit && intoConstructor.usesSpecializedField && stats.nonEmpty) { + if (guardSpecializedFieldInit && usesSpecializedField && stats.nonEmpty) { // save them for duplication in the specialized subclass guardedCtorStats(clazz) = stats ctorParams(clazz) = primaryConstrParams @@ -472,24 +447,23 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { with OmittablesHelper with GuardianOfCtorStmts { - val clazz = impl.symbol.owner // the transformed class - val stats = impl.body // the transformed template body - val localTyper = typer.atOwner(impl, clazz) - - val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE) - // The constructor of a non-specialized class that has specialized subclasses - // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields. - val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED) + val clazz = impl.symbol.owner // the transformed class + val localTyper = typer.atOwner(impl, clazz) val isDelayedInitSubclass = clazz isSubClass DelayedInitClass + private val stats = impl.body // the transformed template body + // find and dissect primary constructor - val (primaryConstr, primaryConstrParams, primaryConstrBody) = stats collectFirst { + private val (primaryConstr, _primaryConstrParams, primaryConstrBody) = stats collectFirst { case dd@DefDef(_, _, _, vps :: Nil, _, rhs: Block) if dd.symbol.isPrimaryConstructor => (dd, vps map (_.symbol), rhs) - } getOrElse abort("no constructor in template: impl = " + impl) + } getOrElse { + abort("no constructor in template: impl = " + impl) + } - // The parameter accessor fields which are members of the class - val paramAccessors = clazz.constrParamAccessors + + def primaryConstrParams = _primaryConstrParams + def usesSpecializedField = intoConstructor.usesSpecializedField // The constructor parameter corresponding to an accessor def parameter(acc: Symbol): Symbol = parameterNamed(acc.unexpandedName.getterName) @@ -564,15 +538,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { super.transform(tree) } - // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol - def apply(oldowner: Symbol, tree: Tree) = + // Move tree into constructor, take care of changing owner from `oldOwner` to `newOwner` (the primary constructor symbol) + def apply(oldOwner: Symbol, newOwner: Symbol)(tree: Tree) = if (tree eq EmptyTree) tree - else transform(tree.changeOwner(oldowner -> primaryConstr.symbol)) + else transform(tree.changeOwner(oldOwner -> newOwner)) } // Create an assignment to class field `to` with rhs `from` def mkAssign(to: Symbol, from: Tree): Tree = - localTyper.typedPos(to.pos) { Assign(Select(This(clazz), to), from) } + localTyper.typedPos(to.pos) { + Assign(Select(This(clazz), to), from) + } // Create code to copy parameter to parameter accessor field. // If parameter is $outer, check that it is not null so that we NPE @@ -582,73 +558,58 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { val result = mkAssign(to, Ident(from)) if (from.name != nme.OUTER || - from.tpe.typeSymbol.isPrimitiveValueClass) result + from.tpe.typeSymbol.isPrimitiveValueClass) result else localTyper.typedPos(to.pos) { // `throw null` has the same effect as `throw new NullPointerException`, see JVM spec on instruction `athrow` - IF (from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result + IF(from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result } } - // The list of definitions that go into class - val defBuf = new ListBuffer[Tree] - - // The auxiliary constructors, separate from the defBuf since they should - // follow the primary constructor - val auxConstructorBuf = new ListBuffer[Tree] - - // The list of statements that go into the constructor after and including the superclass constructor call - val constrStatBuf = new ListBuffer[Tree] - - // The list of early initializer statements that go into constructor before the superclass constructor call - val constrPrefixBuf = new ListBuffer[Tree] + // Constant typed vals are not memoized. + def memoizeValue(sym: Symbol) = !sym.info.isInstanceOf[ConstantType] - // The early initialized field definitions of the class (these are the class members) - val presupers = treeInfo.preSuperFields(stats) + /** Triage definitions and statements in this template into the following categories. + * The primary constructor is treated separately, as it is assembled in part from these pieces. + * + * - `defs`: definitions that go into class + * - `auxConstrs`: auxiliary constructors, separate from the defs as they should follow the primary constructor + * - `constrPrefix`: early initializer statements that go into constructor before the superclass constructor call + * - `constrStats`: statements that go into the constructor after and including the superclass constructor call + * - `classInitStats`: statements that go into the class initializer + */ + def triageStats = { + val defBuf, auxConstructorBuf, constrPrefixBuf, constrStatBuf, classInitStatBuf = new mutable.ListBuffer[Tree] - // The list of statements that go into the class initializer - val classInitStatBuf = new ListBuffer[Tree] + // The early initialized field definitions of the class (these are the class members) + val presupers = treeInfo.preSuperFields(stats) - // generate code to copy pre-initialized fields - for (stat <- primaryConstrBody.stats) { - constrStatBuf += stat - stat match { - case ValDef(mods, name, _, _) if mods hasFlag PRESUPER => - // stat is the constructor-local definition of the field value - val fields = presupers filter (_.getterName == name) - assert(fields.length == 1) - val to = fields.head.symbol - if (!to.tpe.isInstanceOf[ConstantType]) - constrStatBuf += mkAssign(to, Ident(stat.symbol)) - case _ => + // generate code to copy pre-initialized fields + for (stat <- primaryConstrBody.stats) { + constrStatBuf += stat + stat match { + case ValDef(mods, name, _, _) if mods hasFlag PRESUPER => + // stat is the constructor-local definition of the field value + val fields = presupers filter (_.getterName == name) + assert(fields.length == 1, s"expected exactly one field by name $name in $presupers of $clazz's early initializers") + val to = fields.head.symbol + + if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol)) + case _ => + } } - } - - - for (stat <- stats) { - val statSym = stat.symbol - - stat match { - // recurse on class definition, store in defBuf - case _: ClassDef => defBuf += new ConstructorTransformer(unit).transform(stat) - - // methods (except primary constructor) go into template - // (non-primary ctors --> auxConstructorBuf / regular defs --> defBuf) - case _: DefDef if statSym.isPrimaryConstructor => () - case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat - case _: DefDef => defBuf += stat - // val defs with constant right-hand sides are eliminated. - case _: ValDef if statSym.info.isInstanceOf[ConstantType] => () - - // For all other val defs, an empty valdef goes into the template. - // Additionally, non-lazy vals are initialized by an assignment in: - // - the class initializer (static), - // - the constructor, before the super call (early initialized or a parameter accessor), - // - the constructor, after the super call (regular val). - case ValDef(mods, _, _, rhs) => + for (stat <- stats) { + val statSym = stat.symbol + + // Move the RHS of a ValDef to the appropriate part of the ctor. + // If the val is an early initialized or a parameter accessor, + // it goes before the superclass constructor call, otherwise it goes after. + // A lazy val's effect is not moved to the constructor, as it is delayed. + // Returns `true` when a `ValDef` is needed. + def moveEffectToCtor(mods: Modifiers, rhs: Tree, assignSym: Symbol): Unit = { val initializingRhs = - if (statSym.isLazy) EmptyTree - else if (!mods.hasStaticFlag) intoConstructor(statSym, rhs) + if ((assignSym eq NoSymbol) || statSym.isLazy) EmptyTree // not memoized, or effect delayed (for lazy val) + else if (!mods.hasStaticFlag) intoConstructor(statSym, primaryConstr.symbol)(rhs) else rhs if (initializingRhs ne EmptyTree) { @@ -657,68 +618,120 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf else constrStatBuf - initPhase += mkAssign(statSym, initializingRhs) + initPhase += mkAssign(assignSym, initializingRhs) } + } - defBuf += deriveValDef(stat)(_ => EmptyTree) - - // all other statements go into the constructor - case _ => constrStatBuf += intoConstructor(impl.symbol, stat) + stat match { + // recurse on class definition, store in defBuf + case _: ClassDef => defBuf += new ConstructorTransformer(unit).transform(stat) + + // Triage methods -- they all end up in the template -- + // regular ones go to `defBuf`, secondary contructors go to `auxConstructorBuf`. + // The primary constructor is dealt with separately (we're massaging it here). + case _: DefDef if statSym.isPrimaryConstructor => () + case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat + case _: DefDef => defBuf += stat + + // If a val needs a field, an empty valdef goes into the template. + // Except for lazy and ConstantTyped vals, the field is initialized by an assignment in: + // - the class initializer (static), + // - the constructor, before the super call (early initialized or a parameter accessor), + // - the constructor, after the super call (regular val). + case ValDef(mods, _, _, rhs) => + if (rhs ne EmptyTree) { + val emitField = memoizeValue(statSym) + moveEffectToCtor(mods, rhs, if (emitField) statSym else NoSymbol) + if (emitField) defBuf += deriveValDef(stat)(_ => EmptyTree) + } else defBuf += stat + + // all other statements go into the constructor + case _ => constrStatBuf += intoConstructor(impl.symbol, primaryConstr.symbol)(stat) + } } - } - - populateOmittables() - - // Initialize all parameters fields that must be kept. - val paramInits = paramAccessors filter mustBeKept map { acc => - // Check for conflicting symbol amongst parents: see bug #1960. - // It would be better to mangle the constructor parameter name since - // it can only be used internally, but I think we need more robust name - // mangling before we introduce more of it. - val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait) - if (conflict ne NoSymbol) - reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString)) - copyParam(acc, parameter(acc)) + (defBuf.toList, auxConstructorBuf.toList, constrPrefixBuf.toList, constrStatBuf.toList, classInitStatBuf.toList) } - /* Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest) */ - def splitAtSuper(stats: List[Tree]) = { - def isConstr(tree: Tree): Boolean = tree match { - case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks - case _ => (tree.symbol ne null) && tree.symbol.isConstructor + def transformed = { + val (defs, auxConstructors, constructorPrefix, constructorStats, classInitStats) = triageStats + + // omit unused outers + val omittableAccessor: Set[Symbol] = + if (isDelayedInitSubclass) Set.empty + else computeOmittableAccessors(clazz, defs, auxConstructors) + + // TODO: this should omit fields for non-memoized (constant-typed, unit-typed vals need no storage -- + // all the action is in the getter) + def omittableSym(sym: Symbol) = omittableAccessor(sym) + def omittableStat(stat: Tree) = omittableSym(stat.symbol) + + // The parameter accessor fields which are members of the class + val paramAccessors = clazz.constrParamAccessors + + // Initialize all parameters fields that must be kept. + val paramInits = paramAccessors filterNot omittableSym map { acc => + // Check for conflicting symbol amongst parents: see bug #1960. + // It would be better to mangle the constructor parameter name since + // it can only be used internally, but I think we need more robust name + // mangling before we introduce more of it. + val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait) + if (conflict ne NoSymbol) + reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString)) + + copyParam(acc, parameter(acc)) } - val (pre, rest0) = stats span (!isConstr(_)) - val (supercalls, rest) = rest0 span (isConstr(_)) - (pre ::: supercalls, rest) - } - - val (uptoSuperStats, remainingConstrStats0) = splitAtSuper(constrStatBuf.toList) - var remainingConstrStats = remainingConstrStats0 - - rewriteDelayedInit() - - // Assemble final constructor - defBuf += deriveDefDef(primaryConstr)(_ => - treeCopy.Block( - primaryConstrBody, - paramInits ::: constrPrefixBuf.toList ::: uptoSuperStats ::: - guardSpecializedInitializer(remainingConstrStats), - primaryConstrBody.expr)) - // Followed by any auxiliary constructors - defBuf ++= auxConstructorBuf - - // Unlink all fields that can be dropped from class scope - for (sym <- clazz.info.decls ; if !mustBeKept(sym)) - clazz.info.decls unlink sym - - // Eliminate all field definitions that can be dropped from template - val templateWithoutOmittables: Template = deriveTemplate(impl)(_ => defBuf.toList filter (stat => mustBeKept(stat.symbol))) + // Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest) + def splitAtSuper(stats: List[Tree]) = { + def isConstr(tree: Tree): Boolean = tree match { + case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks + case _ => (tree.symbol ne null) && tree.symbol.isConstructor + } + val (pre, rest0) = stats span (!isConstr(_)) + val (supercalls, rest) = rest0 span (isConstr(_)) + (pre ::: supercalls, rest) + } - // Add the static initializers - val transformed: Template = addStaticInits(templateWithoutOmittables, classInitStatBuf, localTyper) + val (uptoSuperStats, remainingConstrStats) = splitAtSuper(constructorStats) + /* TODO: XXX This condition (`isDelayedInitSubclass && remainingConstrStats.nonEmpty`) is not correct: + * remainingConstrStats.nonEmpty excludes too much, + * but excluding it includes too much. The constructor sequence being mimicked + * needs to be reproduced with total fidelity. + * + * See test case files/run/bug4680.scala, the output of which is wrong in many + * particulars. + */ + val (delayedHookDefs, remainingConstrStatsDelayedInit) = + if (isDelayedInitSubclass && remainingConstrStats.nonEmpty) delayedInitDefsAndConstrStats(defs, remainingConstrStats) + else (Nil, remainingConstrStats) + + // Assemble final constructor + val primaryConstructor = deriveDefDef(primaryConstr)(_ => { + treeCopy.Block( + primaryConstrBody, + paramInits ::: constructorPrefix ::: uptoSuperStats ::: guardSpecializedInitializer(remainingConstrStatsDelayedInit), + primaryConstrBody.expr) + }) + + val constructors = primaryConstructor :: auxConstructors + + // Unlink all fields that can be dropped from class scope + // Iterating on toList is cheaper (decls.filter does a toList anyway) + val decls = clazz.info.decls + decls.toList.filter(omittableSym).foreach(decls.unlink) + + // Eliminate all field/accessor definitions that can be dropped from template + // We never eliminate delayed hooks or the constructors, so, only filter `defs`. + val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors + + // Add the static initializers + if (classInitStats.isEmpty) deriveTemplate(impl)(_ => prunedStats) + else { + val staticCtor = staticConstructor(prunedStats, localTyper, impl.pos)(classInitStats) + deriveTemplate(impl)(_ => staticCtor :: prunedStats) + } + } } // TemplateTransformer - } diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 33fd5d0461..f15d05f7df 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -4,8 +4,6 @@ package transform import symtab._ import Flags._ import scala.collection._ -import scala.language.postfixOps -import scala.reflect.internal.Symbols import scala.collection.mutable.LinkedHashMap /** @@ -294,6 +292,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon")) val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation + lambdaClass.associatedFile = unit.source.file // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes) currentRun.symSource(lambdaClass) = funOwner.sourceFile lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass) diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 747d20a441..266a422c53 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -253,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 diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index f12f6c4e18..1a0b4cdec0 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -9,9 +9,7 @@ package transform import symtab._ import Flags.{ CASE => _, _ } -import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.tools.nsc.settings.ScalaVersion /** This class ... * @@ -159,19 +157,24 @@ abstract class ExplicitOuter extends InfoTransform * elides outer pointers. */ def transformInfo(sym: Symbol, tp: Type): Type = tp match { - case MethodType(params, restpe1) => - val restpe = transformInfo(sym, restpe1) - if (sym.owner.isTrait && ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule)) { // 5 - sym.makeNotPrivate(sym.owner) + case MethodType(params, resTp) => + val resTpTransformed = transformInfo(sym, resTp) + + // juggle flags (and mangle names) after transforming info + if (sym.owner.isTrait) { + // TODO: I don't believe any private accessors remain after the fields phase + if ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule) sym.makeNotPrivate(sym.owner) // 5 + if (sym.isProtected) sym setFlag notPROTECTED // 6 } - if (sym.owner.isTrait && sym.isProtected) sym setFlag notPROTECTED // 6 - if (sym.isClassConstructor && isInner(sym.owner)) { // 1 - val p = sym.newValueParameter(innerClassConstructorParamName, sym.pos) - .setInfo(sym.owner.outerClass.thisType) - MethodType(p :: params, restpe) - } else if (restpe ne restpe1) - MethodType(params, restpe) + + val paramsWithOuter = + if (sym.isClassConstructor && isInner(sym.owner)) // 1 + sym.newValueParameter(innerClassConstructorParamName, sym.pos).setInfo(sym.owner.outerClass.thisType) :: params + else params + + if ((resTpTransformed ne resTp) || (paramsWithOuter ne params)) MethodType(paramsWithOuter, resTpTransformed) else tp + case ClassInfoType(parents, decls, clazz) => var decls1 = decls if (isInner(clazz) && !clazz.isInterface) { diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index 6349fc3fb9..d83cf68b38 100644 --- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala +++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala @@ -7,7 +7,7 @@ package transform import symtab._ import Flags._ -import scala.collection.{ mutable, immutable } +import scala.collection.mutable /** * Perform Step 1 in the inline classes SIP: Creates extension methods for all diff --git a/src/compiler/scala/tools/nsc/transform/InlineErasure.scala b/src/compiler/scala/tools/nsc/transform/InlineErasure.scala deleted file mode 100644 index 1bbe1b8410..0000000000 --- a/src/compiler/scala/tools/nsc/transform/InlineErasure.scala +++ /dev/null @@ -1,11 +0,0 @@ -package scala.tools.nsc -package transform - -trait InlineErasure { - self: Erasure => - -/* - import global._ - import definitions._ - */ -} diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 43f698d7ba..f6e2dd68f0 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -8,7 +8,7 @@ package transform import symtab._ import Flags._ -import scala.collection.{ mutable, immutable } +import scala.collection.mutable import scala.collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } abstract class LambdaLift extends InfoTransform { @@ -143,7 +143,7 @@ abstract class LambdaLift extends InfoTransform { * } */ private def markFree(sym: Symbol, enclosure: Symbol): Boolean = { - debuglog("mark free: " + sym.fullLocationString + " marked free in " + enclosure) +// println(s"mark free: ${sym.fullLocationString} marked free in $enclosure") (enclosure == sym.owner.logicallyEnclosingMember) || { debuglog("%s != %s".format(enclosure, sym.owner.logicallyEnclosingMember)) if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false @@ -153,7 +153,7 @@ abstract class LambdaLift extends InfoTransform { ss += sym renamable += sym changedFreeVars = true - debuglog("" + sym + " is free in " + enclosure) + debuglog(s"$sym is free in $enclosure") if (sym.isVariable) sym setFlag CAPTURED } !enclosure.isClass @@ -162,7 +162,7 @@ abstract class LambdaLift extends InfoTransform { } private def markCalled(sym: Symbol, owner: Symbol) { - debuglog("mark called: " + sym + " of " + sym.owner + " is called by " + owner) +// println(s"mark called: $sym of ${sym.owner} is called by $owner") symSet(called, owner) += sym if (sym.enclClass != owner.enclClass) calledFromInner += sym } @@ -170,7 +170,7 @@ abstract class LambdaLift extends InfoTransform { /** The traverse function */ private val freeVarTraverser = new Traverser { override def traverse(tree: Tree) { - try { //debug +// try { //debug val sym = tree.symbol tree match { case ClassDef(_, _, _, _) => @@ -217,11 +217,11 @@ abstract class LambdaLift extends InfoTransform { case _ => } super.traverse(tree) - } catch {//debug - case ex: Throwable => - Console.println(s"$ex while traversing $tree") - throw ex - } +// } catch {//debug +// case ex: Throwable => +// Console.println(s"$ex while traversing $tree") +// throw ex +// } } } @@ -235,7 +235,7 @@ abstract class LambdaLift extends InfoTransform { do { changedFreeVars = false - for (caller <- called.keys ; callee <- called(caller) ; fvs <- free get callee ; fv <- fvs) + for ((caller, callees) <- called ; callee <- callees ; fvs <- free get callee ; fv <- fvs) markFree(fv, caller) } while (changedFreeVars) @@ -303,11 +303,14 @@ abstract class LambdaLift extends InfoTransform { afterOwnPhase { for ((owner, freeValues) <- free.toList) { - val newFlags = SYNTHETIC | ( if (owner.isClass) PARAMACCESSOR | PrivateLocal else PARAM ) - debuglog("free var proxy: %s, %s".format(owner.fullLocationString, freeValues.toList.mkString(", "))) + val newFlags = SYNTHETIC | ( + if (owner.isClass) PARAMACCESSOR | PrivateLocal + else PARAM) + proxies(owner) = for (fv <- freeValues.toList) yield { val proxyName = proxyNames.getOrElse(fv, fv.name) + debuglog(s"new proxy ${proxyName} in ${owner.fullLocationString}") val proxy = owner.newValue(proxyName.toTermName, owner.pos, newFlags.toLong) setInfo fv.info if (owner.isClass) owner.info.decls enter proxy proxy @@ -337,31 +340,31 @@ abstract class LambdaLift extends InfoTransform { private def memberRef(sym: Symbol): Tree = { val clazz = sym.owner.enclClass - //Console.println("memberRef from "+currentClass+" to "+sym+" in "+clazz) - def prematureSelfReference() { + // println(s"memberRef from $currentClass to $sym in $clazz (currentClass=$currentClass)") + def prematureSelfReference(): Tree = { val what = if (clazz.isStaticOwner) clazz.fullLocationString else s"the unconstructed `this` of ${clazz.fullLocationString}" val msg = s"Implementation restriction: access of ${sym.fullLocationString} from ${currentClass.fullLocationString}, would require illegal premature access to $what" reporter.error(curTree.pos, msg) + EmptyTree } - val qual = + def qual = if (clazz == currentClass) gen.mkAttributedThis(clazz) else { sym resetFlag (LOCAL | PRIVATE) - if (isUnderConstruction(clazz)) { - prematureSelfReference() - EmptyTree - } + if (isUnderConstruction(clazz)) prematureSelfReference() else if (clazz.isStaticOwner) gen.mkAttributedQualifier(clazz.thisType) - else { - outerValue match { - case EmptyTree => prematureSelfReference(); return EmptyTree - case o => outerPath(o, currentClass.outerClass, clazz) - } + else outerValue match { + case EmptyTree => prematureSelfReference() + case o => outerPath(o, currentClass.outerClass, clazz) } } - Select(qual, sym) setType sym.tpe + + qual match { + case EmptyTree => EmptyTree + case qual => Select(qual, sym) setType sym.tpe + } } private def proxyRef(sym: Symbol) = { @@ -369,41 +372,45 @@ abstract class LambdaLift extends InfoTransform { if (psym.isLocalToBlock) gen.mkAttributedIdent(psym) else memberRef(psym) } - private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = { - free get sym match { - case Some(fvs) => addFree(sym, free = fvs.toList map (fv => atPos(pos)(proxyRef(fv))), original = args) - case _ => args + def freeArgsOrNil(sym: Symbol) = free.getOrElse(sym, Nil).toList + + private def freeArgs(sym: Symbol): List[Symbol] = + freeArgsOrNil(sym) + + private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = + freeArgs(sym) match { + case Nil => args + case fvs => addFree(sym, free = fvs map (fv => atPos(pos)(proxyRef(fv))), original = args) } - } - private def addFreeParams(tree: Tree, sym: Symbol): Tree = proxies.get(sym) match { - case Some(ps) => - val freeParams = ps map (p => ValDef(p) setPos tree.pos setType NoType) - tree match { - case DefDef(_, _, _, vparams :: _, _, _) => - val addParams = cloneSymbols(ps).map(_.setFlag(PARAM)) - sym.updateInfo( - lifted(MethodType(addFree(sym, free = addParams, original = sym.info.params), sym.info.resultType))) + def proxiesOrNil(sym: Symbol) = proxies.getOrElse(sym, Nil) + + private def freeParams(sym: Symbol): List[Symbol] = + proxiesOrNil(sym) + + private def addFreeParams(tree: Tree, sym: Symbol): Tree = + tree match { + case DefDef(_, _, _, vparams :: _, _, _) => + val ps = freeParams(sym) + + if (ps.isEmpty) tree + else { + val paramSyms = cloneSymbols(ps).map(_.setFlag(PARAM)) + val paramDefs = ps map (p => ValDef(p) setPos tree.pos setType NoType) + + sym.updateInfo(lifted(MethodType(addFree(sym, free = paramSyms, original = sym.info.params), sym.info.resultType))) + copyDefDef(tree)(vparamss = List(addFree(sym, free = paramDefs, original = vparams))) + } + + case ClassDef(_, _, _, _) => + val freeParamDefs = freeParams(sym) map (p => ValDef(p) setPos tree.pos setType NoType) + + if (freeParamDefs.isEmpty) tree + else deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParamDefs)) + + case _ => tree + } - copyDefDef(tree)(vparamss = List(addFree(sym, free = freeParams, original = vparams))) - case ClassDef(_, _, _, _) => - // SI-6231 - // Disabled attempt to to add getters to freeParams - // this does not work yet. Problem is that local symbols need local names - // and references to local symbols need to be transformed into - // method calls to setters. - // def paramGetter(param: Symbol): Tree = { - // val getter = param.newGetter setFlag TRANS_FLAG resetFlag PARAMACCESSOR // mark because we have to add them to interface - // sym.info.decls.enter(getter) - // val rhs = Select(gen.mkAttributedThis(sym), param) setType param.tpe - // DefDef(getter, rhs) setPos tree.pos setType NoType - // } - // val newDefs = if (sym.isTrait) freeParams ::: (ps map paramGetter) else freeParams - deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParams)) - } - case None => - tree - } /* SI-6231: Something like this will be necessary to eliminate the implementation * restriction from paramGetter above: @@ -446,7 +453,7 @@ abstract class LambdaLift extends InfoTransform { // See neg/t1909-object.scala def msg = s"SI-1909 Unable to STATICally lift $sym, which is defined in the self- or super-constructor call of ${sym.owner.owner}. A VerifyError is likely." devWarning(tree.pos, msg) - } else sym setFlag STATIC + } else sym setFlag STATIC } sym.owner = sym.owner.enclClass @@ -463,12 +470,11 @@ abstract class LambdaLift extends InfoTransform { private def postTransform(tree: Tree, isBoxedRef: Boolean = false): Tree = { val sym = tree.symbol tree match { - case ClassDef(_, _, _, _) => - val tree1 = addFreeParams(tree, sym) - if (sym.isLocalToBlock) liftDef(tree1) else tree1 - case DefDef(_, _, _, _, _, _) => - val tree1 = addFreeParams(tree, sym) - if (sym.isLocalToBlock) liftDef(tree1) else tree1 + case _: ClassDef | _: DefDef => + val withFreeParams = addFreeParams(tree, sym) + if (sym.isLocalToBlock) liftDef(withFreeParams) + else withFreeParams + case ValDef(mods, name, tpt, rhs) => if (sym.isCapturedVariable) { val tpt1 = TypeTree(sym.tpe) setPos tpt.pos diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index b6695efb0b..8a0086dbdd 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -1,14 +1,14 @@ package scala.tools.nsc package transform -import scala.collection.{ mutable, immutable } +import scala.collection.mutable abstract class LazyVals extends Transform with TypingTransformers with ast.TreeDSL { // inherits abstract value `global` and class `Phase` from Transform import global._ // the global environment import definitions._ // standard classes and methods - import typer.{typed, atOwner} // methods to type trees + import typer.typed // methods to type trees import CODE._ val phaseName: String = "lazyvals" @@ -38,6 +38,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) => + // Avoid adding bitmaps when they are fully overshadowed by those that are added inside loops case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) => case _ => @@ -54,9 +55,28 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0 import symtab.Flags._ + private def flattenThickets(stats: List[Tree]): List[Tree] = stats.flatMap(_ match { + case b @ Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if b.tpe == null && n1.endsWith(nme.LAZY_SLOW_SUFFIX) => + List(d1, d2) + case stat => + List(stat) + }) /** Perform the following transformations: * - for a lazy accessor inside a method, make it check the initialization bitmap + * - implement double checked locking of member modules for non-trait owners (trait just have the abstract accessor) + * ``` + * // typer + * class C { object x } + * // refchecks + * class C { var x$module; def x() = { x$module = new x; x$module } + * // lazyvals + * class C { + * var x$module // module var + * def x() = { if (x$module == null) x$lzycompute() else x$module // fast path + * def x$lzycompute() = { synchronized { if (x$module == null) x$module = new x }; x$module } // slow path + * } + * ``` * - for all methods, add enough int vars to allow one flag per lazy local value * - blocks in template bodies behave almost like methods. A single bitmaps section is * added in the first block, for all lazy values defined in such blocks. @@ -72,20 +92,14 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD case Block(_, _) => val block1 = super.transform(tree) val Block(stats, expr) = block1 - val stats1 = stats.flatMap(_ match { - case Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if (nme.newLazyValSlowComputeName(n2) == n1) => - List(d1, d2) - case stat => - List(stat) - }) - treeCopy.Block(block1, stats1, expr) + treeCopy.Block(block1, flattenThickets(stats), expr) case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) { val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) { val enclosingClassOrDummyOrMethod = { val enclMethod = sym.enclMethod - if (enclMethod != NoSymbol ) { + if (enclMethod != NoSymbol) { val enclClass = sym.enclClass if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod) enclClass @@ -100,30 +114,39 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym) sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR) (rhs1, sDef) - } else + } else if (sym.hasAllFlags(MODULE | METHOD) && !sym.owner.isTrait) { + rhs match { + case b @ Block((assign @ Assign(moduleRef, _)) :: Nil, expr) => + def cond = Apply(Select(moduleRef, Object_eq), List(Literal(Constant(null)))) + val (fastPath, slowPath) = mkFastPathBody(sym.owner.enclClass, moduleRef.symbol, cond, transform(assign) :: Nil, Nil, transform(expr)) + (localTyper.typedPos(tree.pos)(fastPath), localTyper.typedPos(tree.pos)(slowPath)) + case rhs => + global.reporter.error(tree.pos, "Unexpected tree on the RHS of a module accessor: " + rhs) + (rhs, EmptyTree) + } + } else { (transform(rhs), EmptyTree) + } val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res) - if (slowPathDef != EmptyTree) Block(slowPathDef, ddef1) else ddef1 + if (slowPathDef != EmptyTree) { + // The contents of this block are flattened into the enclosing statement sequence, see flattenThickets + // This is a poor man's version of dotty's Thicket: https://github.com/lampepfl/dotty/blob/d5280358d1/src/dotty/tools/dotc/ast/Trees.scala#L707 + Block(slowPathDef, ddef1) + } else ddef1 } case Template(_, _, body) => atOwner(currentOwner) { - val body1 = super.transformTrees(body) + // TODO: shady business... can this logic be encapsulated in LocalLazyValFinder? var added = false - val stats = - for (stat <- body1) yield stat match { - case Block(_, _) | Apply(_, _) | If(_, _, _) | Try(_, _, _) if !added => - // Avoid adding bitmaps when they are fully overshadowed by those - // that are added inside loops - if (LocalLazyValFinder.find(stat)) { - added = true - typed(addBitmapDefs(sym, stat)) - } else stat - case ValDef(_, _, _, _) => - typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) - case _ => - stat + val stats = super.transformTrees(body) mapConserve { + case stat: ValDef => typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) + case stat: TermTree if !added && (LocalLazyValFinder find stat) => + added = true + typed(addBitmapDefs(sym, stat)) + case stat => stat } + val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) { // add bitmap to inner class if necessary val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO))) @@ -135,7 +158,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD }) toAdd0 } else List() - deriveTemplate(tree)(_ => innerClassBitmaps ++ stats) + deriveTemplate(tree)(_ => innerClassBitmaps ++ flattenThickets(stats)) } case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass => @@ -206,7 +229,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD } - def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): (Tree, Tree) = { val slowPathDef: Tree = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal) (If(cond, Apply(Ident(slowPathDef.symbol), Nil), retVal), slowPathDef) @@ -261,7 +284,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD (mkBlock(rhs), UNIT) } - val cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) + def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 })) } diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 00a994fe87..d13e11e9ec 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -8,7 +8,7 @@ package transform import symtab._ import Flags._ -import scala.collection.{ mutable, immutable } +import scala.collection.mutable abstract class Mixin extends InfoTransform with ast.TreeDSL { import global._ @@ -784,12 +784,12 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { defSym } - def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): Tree = { mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List()) } - def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = { val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args) If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal) @@ -861,16 +861,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal)) } - def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree = - rhs match { - case Block(List(assign), returnTree) => - val Assign(moduleVarRef, _) = assign - val cond = Apply(Select(moduleVarRef, Object_eq), List(NULL)) - mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args) - case _ => - abort(s"Invalid getter $rhs for module in $clazz") - } - def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { val sym = fieldSym.getterIn(fieldSym.owner) val bitmapSym = bitmapFor(clazz, offset, sym) @@ -926,18 +916,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) else stat } - else if (sym.isModule && (!clazz.isTrait || clazz.isImplClass) && !sym.isBridge) { - deriveDefDef(stat)(rhs => - typedPos(stat.pos)( - mkInnerClassAccessorDoubleChecked( - // Martin to Hubert: I think this can be replaced by selfRef(tree.pos) - // @PP: It does not seem so, it crashes for me trying to bootstrap. - if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz), - rhs, sym, stat.vparamss.head - ) - ) - ) - } else stat } stats map { @@ -1082,18 +1060,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { }) } else if (sym.isModule && !(sym hasFlag LIFTED | BRIDGE)) { - // add modules - val vsym = sym.owner.newModuleVarSymbol(sym) - addDef(position(sym), ValDef(vsym)) - - // !!! TODO - unravel the enormous duplication between this code and - // eliminateModuleDefs in RefChecks. - val rhs = gen.newModule(sym, vsym.tpe) - val assignAndRet = gen.mkAssignAndReturn(vsym, rhs) - val attrThis = gen.mkAttributedThis(clazz) - val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List()) - - addDefDef(sym, rhs1) + // Moved to Refchecks } else if (!sym.isMethod) { // add fields diff --git a/src/compiler/scala/tools/nsc/transform/Statics.scala b/src/compiler/scala/tools/nsc/transform/Statics.scala index 4673be6de7..9ab00f1a83 100644 --- a/src/compiler/scala/tools/nsc/transform/Statics.scala +++ b/src/compiler/scala/tools/nsc/transform/Statics.scala @@ -1,49 +1,32 @@ package scala.tools.nsc package transform -import collection.mutable.Buffer - abstract class Statics extends Transform with ast.TreeDSL { import global._ class StaticsTransformer extends Transformer { - - /** finds the static ctor DefDef tree within the template if it exists. */ - def findStaticCtor(template: Template): Option[Tree] = - template.body find { - case defdef @ DefDef(_, nme.CONSTRUCTOR, _, _, _, _) => defdef.symbol.hasStaticFlag - case _ => false - } - - /** changes the template for the class so that it contains a static constructor with symbol fields inits, - * augments an existing static ctor if one already existed. + /** generate a static constructor with symbol fields inits, or an augmented existing static ctor */ - def addStaticInits(template: Template, newStaticInits: Buffer[Tree], localTyper: analyzer.Typer): Template = { - if (newStaticInits.isEmpty) - template - else { - val newCtor = findStaticCtor(template) match { - // in case there already were static ctors - augment existing ones - // currently, however, static ctors aren't being generated anywhere else - case Some(ctor @ DefDef(_,_,_,_,_,_)) => - // modify existing static ctor - deriveDefDef(ctor) { - case block @ Block(stats, expr) => - // need to add inits to existing block - treeCopy.Block(block, newStaticInits.toList ::: stats, expr) - case term: TermTree => - // need to create a new block with inits and the old term - treeCopy.Block(term, newStaticInits.toList, term) - } - case _ => - // create new static ctor - val staticCtorSym = currentClass.newStaticConstructor(template.pos) - val rhs = Block(newStaticInits.toList, Literal(Constant(()))) + def staticConstructor(body: List[Tree], localTyper: analyzer.Typer, pos: Position)(newStaticInits: List[Tree]): Tree = + body.collectFirst { + // If there already was a static ctor - augment existing one + // currently, however, static ctors aren't being generated anywhere else (!!!) + case ctor@DefDef(_, nme.CONSTRUCTOR, _, _, _, _) if ctor.symbol.hasStaticFlag => + // modify existing static ctor + deriveDefDef(ctor) { + case block@Block(stats, expr) => + // need to add inits to existing block + treeCopy.Block(block, newStaticInits ::: stats, expr) + case term: TermTree => + // need to create a new block with inits and the old term + treeCopy.Block(term, newStaticInits, term) + } + } getOrElse { + // create new static ctor + val staticCtorSym = currentClass.newStaticConstructor(pos) + val rhs = Block(newStaticInits, Literal(Constant(()))) - localTyper.typedPos(template.pos)(DefDef(staticCtorSym, rhs)) - } - deriveTemplate(template)(newCtor :: _) + localTyper.typedPos(pos)(DefDef(staticCtorSym, rhs)) } - } } } diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala index 3b23306386..1ed728247b 100644 --- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -2,7 +2,6 @@ package scala.tools.nsc package transform import scala.tools.nsc.ast.TreeDSL -import scala.tools.nsc.Global /** * A trait usable by transforms that need to adapt trees of one type to another type diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala index dc3313e2e4..d5adfe12e9 100644 --- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala +++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala @@ -26,7 +26,7 @@ trait TypingTransformers { def atOwner[A](tree: Tree, owner: Symbol)(trans: => A): A = { val savedLocalTyper = localTyper - localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner) + localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner) val result = super.atOwner(owner)(trans) localTyper = savedLocalTyper result diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 9d61dbbcae..f7a1f462b7 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -7,9 +7,10 @@ package scala package tools.nsc package transform -import symtab.Flags._ -import scala.collection.{ mutable, immutable } import scala.language.postfixOps + +import symtab.Flags._ +import scala.collection.mutable import scala.reflect.internal.util.ListOfNil /*<export> */ @@ -696,7 +697,7 @@ abstract class UnCurry extends InfoTransform // declared type and assign this to a synthetic val. Later, we'll patch // the method body to refer to this, rather than the parameter. val tempVal: ValDef = { - // SI-9442: using the "uncurry-erased" type (the one after the uncurry phase) can lead to incorrect + // SI-9442: using the "uncurry-erased" type (the one after the uncurry phase) can lead to incorrect // tree transformations. For example, compiling: // ``` // def foo(c: Ctx)(l: c.Tree): Unit = { @@ -725,7 +726,7 @@ abstract class UnCurry extends InfoTransform // to redo this desugaring manually here // 2. the type needs to be normalized, since `gen.mkCast` checks this (no HK here, just aliases have // to be expanded before handing the type to `gen.mkAttributedCast`, which calls `gen.mkCast`) - val info0 = + val info0 = enteringUncurry(p.symbol.info) match { case DesugaredParameterType(desugaredTpe) => desugaredTpe diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index 62d9c497ba..1a4df9973e 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala @@ -8,9 +8,9 @@ package scala package tools.nsc.transform.patmat import scala.language.postfixOps + import scala.collection.mutable import scala.reflect.internal.util.{NoPosition, Position, Statistics, HashSet} -import scala.tools.nsc.Global trait Logic extends Debugging { import PatternMatchingStats._ @@ -646,7 +646,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { } - import global.{ConstantType, Constant, EmptyScope, SingletonType, Literal, Ident, refinedType, singleType, TypeBounds, NoSymbol} + import global.{ConstantType, SingletonType, Literal, Ident, singleType, TypeBounds, NoSymbol} import global.definitions._ diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index f259283e48..affbcf9ec8 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -6,9 +6,8 @@ package scala.tools.nsc.transform.patmat -import scala.annotation.tailrec -import scala.collection.immutable.{IndexedSeq, Iterable} import scala.language.postfixOps + import scala.collection.mutable import scala.reflect.internal.util.Statistics @@ -707,9 +706,8 @@ trait MatchAnalysis extends MatchApproximation { val (equal, notEqual) = varAssignment.getOrElse(variable, Nil -> Nil) - def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) = { - Map(variable ->(equal ++ equalTo, notEqual ++ notEqualTo)) - } + def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) = + Map(variable ->((equal ++ equalTo, notEqual ++ notEqualTo))) // this assignment is needed in case that // there exists already an assign diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 1642613b9b..03d0a28fb1 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -6,9 +6,9 @@ package scala.tools.nsc.transform.patmat -import scala.tools.nsc.symtab.Flags.SYNTHETIC import scala.language.postfixOps -import scala.reflect.internal.util.Statistics + +import scala.tools.nsc.symtab.Flags.SYNTHETIC import scala.reflect.internal.util.Position /** Factory methods used by TreeMakers to make the actual trees. @@ -55,7 +55,15 @@ trait MatchCodeGen extends Interface { def flatMap(prev: Tree, b: Symbol, next: Tree): Tree def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree def flatMapGuard(cond: Tree, next: Tree): Tree - def ifThenElseZero(c: Tree, thenp: Tree): Tree = IF (c) THEN thenp ELSE zero + def ifThenElseZero(c: Tree, thenp: Tree): Tree = { + val z = zero + thenp match { + case If(c1, thenp1, elsep1) if z equalsStructure elsep1 => + If(c AND c1, thenp1, elsep1) // cleaner, leaner trees + case _ => + If(c, thenp, zero) + } + } protected def zero: Tree } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala index cca8d2dbb8..6fdaa79c10 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala @@ -6,10 +6,10 @@ package scala.tools.nsc.transform.patmat -import scala.tools.nsc.symtab.Flags.MUTABLE import scala.language.postfixOps + +import scala.tools.nsc.symtab.Flags.MUTABLE import scala.collection.mutable -import scala.reflect.internal.util.Statistics import scala.reflect.internal.util.Position /** Optimize and analyze matches based on their TreeMaker-representation. diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 451b72d498..96a20ea631 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -7,7 +7,7 @@ package scala.tools.nsc.transform.patmat import scala.language.postfixOps -import scala.collection.mutable + import scala.reflect.internal.util.Statistics /** Translate typed Trees that represent pattern matches into the patternmatching IR, defined by TreeMakers. @@ -18,8 +18,7 @@ trait MatchTranslation { import PatternMatchingStats._ import global._ import definitions._ - import global.analyzer.{ErrorUtils, formalTypes} - import treeInfo.{ WildcardStarArg, Unapplied, isStar, unbind } + import treeInfo.{ Unapplied, unbind } import CODE._ // Always map repeated params to sequences diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index e0fcc05de2..18a63a6e53 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -6,10 +6,10 @@ package scala.tools.nsc.transform.patmat -import scala.tools.nsc.symtab.Flags.{SYNTHETIC, ARTIFACT} import scala.language.postfixOps + +import scala.tools.nsc.symtab.Flags.{SYNTHETIC, ARTIFACT} import scala.collection.mutable -import scala.reflect.internal.util.Statistics import scala.reflect.internal.util.Position /** Translate our IR (TreeMakers) into actual Scala Trees using the factory methods in MatchCodeGen. diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala index 8beb1837ad..27d674762f 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala @@ -7,8 +7,6 @@ package scala.tools.nsc.transform.patmat import scala.language.postfixOps -import scala.collection.mutable -import scala.reflect.internal.util.Statistics trait MatchWarnings { self: PatternMatching => @@ -83,4 +81,4 @@ trait MatchWarnings { } } } -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index b7c72f5373..ccdff5c9a1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package typechecker import scala.reflect.internal.util.StringOps.{ countElementsAsString, countAsString } -import symtab.Flags.IS_ERROR import scala.compat.Platform.EOL import scala.reflect.runtime.ReflectionUtils import scala.reflect.macros.runtime.AbortMacroException @@ -1190,7 +1189,7 @@ trait ContextErrors { def IllegalDependentMethTpeError(sym: Symbol)(context: Context) = { val errorAddendum = - ": parameter appears in the type of another parameter in the same section or an earlier one" + ": parameter may only be referenced in a subsequent parameter section" issueSymbolTypeError(sym, "illegal dependent method type" + errorAddendum)(context) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 69ae6ec0c8..0c10242950 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package typechecker import scala.tools.nsc.symtab.Flags -import scala.collection.{ mutable, immutable } +import scala.collection.mutable /** Duplicate trees and re-type check them, taking care to replace * and create fresh symbols for new local definitions. diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 383baad2ad..a34e97b6cb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -13,7 +13,7 @@ package tools.nsc package typechecker import scala.annotation.tailrec -import scala.collection.{ mutable, immutable } +import scala.collection.mutable import mutable.{ LinkedHashMap, ListBuffer } import scala.util.matching.Regex import symtab.Flags._ @@ -588,10 +588,10 @@ trait Implicits { if (Statistics.canEnable) Statistics.incCounter(matchingImplicits) // workaround for deficient context provided by ModelFactoryImplicitSupport#makeImplicitConstraints - val isScalaDoc = context.tree == EmptyTree + val isScaladoc = context.tree == EmptyTree val itree0 = atPos(pos.focus) { - if (isLocalToCallsite && !isScalaDoc) { + if (isLocalToCallsite && !isScaladoc) { // SI-4270 SI-5376 Always use an unattributed Ident for implicits in the local scope, // rather than an attributed Select, to detect shadowing. Ident(info.name) @@ -628,7 +628,7 @@ trait Implicits { // for instance, if we have `class C[T]` and `implicit def conv[T: Numeric](c: C[T]) = ???` // then Scaladoc will give us something of type `C[T]`, and it would like to know // that `conv` is potentially available under such and such conditions - case tree if isImplicitMethodType(tree.tpe) && !isScalaDoc => + case tree if isImplicitMethodType(tree.tpe) && !isScaladoc => applyImplicitArgs(tree) case tree => tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 3ed128cbc5..bcf9e018e2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -5,14 +5,11 @@ import java.lang.Math.min import symtab.Flags._ import scala.reflect.internal.util.ScalaClassLoader import scala.reflect.runtime.ReflectionUtils -import scala.collection.mutable.ListBuffer -import scala.reflect.ClassTag import scala.reflect.internal.util.Statistics import scala.reflect.macros.util._ import scala.util.control.ControlThrowable import scala.reflect.internal.util.ListOfNil import scala.reflect.macros.runtime.{AbortMacroException, MacroRuntimes} -import scala.reflect.runtime.{universe => ru} import scala.reflect.macros.compiler.DefaultMacroCompiler import scala.tools.reflect.FastTrack import scala.runtime.ScalaRunTime diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index f3632b144d..abc3e91ed2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -5,12 +5,11 @@ package scala.tools.nsc package typechecker +import scala.language.higherKinds + import symtab.Flags._ -import scala.reflect.internal.util.StringOps.{ ojoin } -import scala.reflect.ClassTag +import scala.reflect.internal.util.StringOps.ojoin import scala.reflect.internal.util.ListOfNil -import scala.reflect.runtime.{ universe => ru } -import scala.language.higherKinds /** Logic related to method synthesis which involves cooperation between * Namer and Typer. @@ -22,17 +21,6 @@ trait MethodSynthesis { import definitions._ import CODE._ - /** The annotations amongst those found on the original symbol which - * should be propagated to this kind of accessor. - */ - def deriveAnnotations(initial: List[AnnotationInfo], category: Symbol, keepClean: Boolean): List[AnnotationInfo] = { - def annotationFilter(ann: AnnotationInfo) = ann.metaAnnotations match { - case Nil if ann.defaultTargets.isEmpty => keepClean // no meta-annotations or default targets - case Nil => ann.defaultTargets contains category // default targets exist for ann - case metas => metas exists (_ matches category) // meta-annotations attached to ann - } - initial filter annotationFilter - } class ClassMethodSynthesis(val clazz: Symbol, localTyper: Typer) { def mkThis = This(clazz) setPos clazz.pos.focus @@ -160,13 +148,15 @@ trait MethodSynthesis { enterBeans(tree) } + import AnnotationInfo.{mkFilter => annotationFilter} + /** This is called for those ValDefs which addDerivedTrees ignores, but * which might have a warnable annotation situation. */ private def warnForDroppedAnnotations(tree: Tree) { val annotations = tree.symbol.initialize.annotations val targetClass = defaultAnnotationTarget(tree) - val retained = deriveAnnotations(annotations, targetClass, keepClean = true) + val retained = annotations filter annotationFilter(targetClass, defaultRetention = true) annotations filterNot (retained contains _) foreach (ann => issueAnnotationWarning(tree, ann, targetClass)) } @@ -208,8 +198,8 @@ trait MethodSynthesis { context.unit.synthetics get meth match { case Some(mdef) => context.unit.synthetics -= meth - meth setAnnotations deriveAnnotations(annotations, MethodTargetClass, keepClean = false) - cd.symbol setAnnotations deriveAnnotations(annotations, ClassTargetClass, keepClean = true) + meth setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + cd.symbol setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) List(cd, mdef) case _ => // Shouldn't happen, but let's give ourselves a reasonable error when it does @@ -293,10 +283,6 @@ trait MethodSynthesis { def tree: ValDef final def enclClass = basisSym.enclClass - /** Which meta-annotation is associated with this kind of entity. - * Presently one of: field, getter, setter, beanGetter, beanSetter, param. - */ - def category: Symbol /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ final def completer(sym: Symbol) = namerOf(sym).accessorTypeCompleter(tree, isSetter) @@ -307,7 +293,6 @@ trait MethodSynthesis { def isSetter = false def isDeferred = mods.isDeferred - def keepClean = false // whether annotations whose definitions are not meta-annotated should be kept. def validate() { } def createAndEnterSymbol(): MethodSymbol = { val sym = owner.newMethod(name, tree.pos.focus, derivedMods.flags) @@ -323,7 +308,29 @@ trait MethodSynthesis { } final def derive(initial: List[AnnotationInfo]): Tree = { validate() - derivedSym setAnnotations deriveAnnotations(initial, category, keepClean) + + // see scala.annotation.meta's package class for more info + // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. + // The defaults are: + // - (`val`-, `var`- or plain) constructor parameter annotations end up on the parameter, not on any other entity. + // - val/var member annotations solely end up on the underlying field. + // + // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit? + // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter) + val annotFilter: AnnotationInfo => Boolean = this match { + case _: Param => annotationFilter(ParamTargetClass, defaultRetention = true) + // By default annotations go to the field, except if the field is generated for a class parameter (PARAMACCESSOR). + case _: Field => annotationFilter(FieldTargetClass, defaultRetention = !mods.isParamAccessor) + case _: BaseGetter => annotationFilter(GetterTargetClass, defaultRetention = false) + case _: Setter => annotationFilter(SetterTargetClass, defaultRetention = false) + case _: BeanSetter => annotationFilter(BeanSetterTargetClass, defaultRetention = false) + case _: AnyBeanGetter => annotationFilter(BeanGetterTargetClass, defaultRetention = false) + } + + // The annotations amongst those found on the original symbol which + // should be propagated to this kind of accessor. + derivedSym setAnnotations (initial filter annotFilter) + logDerived(derivedTree) } } @@ -370,7 +377,6 @@ trait MethodSynthesis { sealed abstract class BaseGetter(tree: ValDef) extends DerivedGetter { def name = tree.name - def category = GetterTargetClass def flagsMask = GetterFlags def flagsExtra = ACCESSOR.toLong | ( if (tree.mods.isMutable) 0 else STABLE ) @@ -395,12 +401,13 @@ trait MethodSynthesis { // starts compiling (instead of failing like it's supposed to) because the typer // expects to be able to identify escaping locals in typedDefDef, and fails to // spot that brand of them. In other words it's an artifact of the implementation. - val tpt = derivedSym.tpe_*.finalResultType.widen match { + val getterTp = derivedSym.tpe_*.finalResultType + val tpt = getterTp.widen match { // Range position errors ensue if we don't duplicate this in some // circumstances (at least: concrete vals with existential types.) - case ExistentialType(_, _) => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) - case _ if isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field - case tp => TypeTree(tp) + case _: ExistentialType => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) + case _ if isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field + case _ => TypeTree(getterTp) } tpt setPos tree.tpt.pos.focus } @@ -449,7 +456,6 @@ trait MethodSynthesis { } case class Setter(tree: ValDef) extends DerivedSetter { def name = tree.setterName - def category = SetterTargetClass def flagsMask = SetterFlags def flagsExtra = ACCESSOR @@ -469,18 +475,14 @@ trait MethodSynthesis { // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`), // as the symbol info is in the process of being created then. // TODO: harmonize tree & symbol creation - // TODO: the `def field` call-site does not tollerate including `|| vd.symbol.owner.isTrait` --> tests break - def noFieldFor(vd: ValDef) = vd.mods.isDeferred || (vd.mods.isLazy && isUnitType(vd.symbol.info)) // || vd.symbol.owner.isTrait)) + // TODO: the `def field` call-site breaks when you add `|| vd.symbol.owner.isTrait` (detected in test suite) + def noFieldFor(vd: ValDef) = vd.mods.isDeferred || (vd.mods.isLazy && isUnitType(vd.symbol.info)) } case class Field(tree: ValDef) extends DerivedFromValDef { def name = tree.localName - def category = FieldTargetClass def flagsMask = FieldFlags def flagsExtra = PrivateLocal - // By default annotations go to the field, except if the field is - // generated for a class parameter (PARAMACCESSOR). - override def keepClean = !mods.isParamAccessor // handle lazy val first for now (we emit a Field even though we probably shouldn't...) override def derivedTree = @@ -491,10 +493,8 @@ trait MethodSynthesis { } case class Param(tree: ValDef) extends DerivedFromValDef { def name = tree.name - def category = ParamTargetClass def flagsMask = -1L def flagsExtra = 0L - override def keepClean = true override def derivedTree = EmptyTree } def validateParam(tree: ValDef) { @@ -508,7 +508,6 @@ trait MethodSynthesis { override def derivedSym = enclClass.info decl name } sealed trait AnyBeanGetter extends BeanAccessor with DerivedGetter { - def category = BeanGetterTargetClass override def validate() { if (derivedSym == NoSymbol) { // the namer decides whether to generate these symbols or not. at that point, we don't @@ -531,9 +530,7 @@ trait MethodSynthesis { } case class BooleanBeanGetter(tree: ValDef) extends BeanAccessor("is") with AnyBeanGetter { } case class BeanGetter(tree: ValDef) extends BeanAccessor("get") with AnyBeanGetter { } - case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter { - def category = BeanSetterTargetClass - } + case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter // No Symbols available. private def beanAccessorsFromNames(tree: ValDef) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index ba095c808e..8943ec810d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package typechecker import scala.collection.mutable -import scala.annotation.tailrec import symtab.Flags._ import scala.language.postfixOps import scala.reflect.internal.util.ListOfNil @@ -654,8 +653,10 @@ trait Namers extends MethodSynthesis { if (isScala && deriveAccessors(tree)) enterGetterSetter(tree) else assignAndEnterFinishedSymbol(tree) - if (isEnumConstant(tree)) + if (isEnumConstant(tree)) { tree.symbol setInfo ConstantType(Constant(tree.symbol)) + tree.symbol.owner.linkedClassOfClass addChild tree.symbol + } } def enterLazyVal(tree: ValDef, lazyAccessor: Symbol): TermSymbol = { @@ -1593,11 +1594,7 @@ trait Namers extends MethodSynthesis { import SymValidateErrors._ def fail(kind: SymValidateErrors.Value) = SymbolValidationError(sym, kind) - def checkWithDeferred(flag: Int) { - if (sym hasFlag flag) - AbstractMemberWithModiferError(sym, flag) - } - def checkNoConflict(flag1: Int, flag2: Int) { + def checkNoConflict(flag1: Int, flag2: Int) = { if (sym hasAllFlags flag1.toLong | flag2) IllegalModifierCombination(sym, flag1, flag2) } @@ -1636,6 +1633,10 @@ trait Namers extends MethodSynthesis { checkNoConflict(ABSTRACT, FINAL) if (sym.isDeferred) { + def checkWithDeferred(flag: Int) = { + if (sym hasFlag flag) + AbstractMemberWithModiferError(sym, flag) + } // Is this symbol type always allowed the deferred flag? def symbolAllowsDeferred = ( sym.isValueParameter @@ -1651,14 +1652,16 @@ trait Namers extends MethodSynthesis { ) if (sym hasAnnotation NativeAttr) sym resetFlag DEFERRED - else if (!symbolAllowsDeferred && ownerRequiresConcrete) - fail(AbstractVar) + else { + if (!symbolAllowsDeferred && ownerRequiresConcrete) fail(AbstractVar) - checkWithDeferred(PRIVATE) - checkWithDeferred(FINAL) + checkWithDeferred(PRIVATE) + checkWithDeferred(FINAL) + } } - checkNoConflict(FINAL, SEALED) + if (!sym.isJavaEnum) + checkNoConflict(FINAL, SEALED) checkNoConflict(PRIVATE, PROTECTED) // checkNoConflict(PRIVATE, OVERRIDE) // this one leads to bad error messages like #4174, so catch in refchecks // checkNoConflict(PRIVATE, FINAL) // can't do this because FINAL also means compile-time constant diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 7252cae0b5..3860cc252d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -6,15 +6,17 @@ package scala.tools.nsc package typechecker -import symtab.Flags._ -import scala.collection.{ mutable, immutable } -import transform.InfoTransform -import scala.collection.mutable.ListBuffer import scala.language.postfixOps + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer import scala.tools.nsc.settings.ScalaVersion -import scala.tools.nsc.settings.AnyScalaVersion import scala.tools.nsc.settings.NoScalaVersion +import symtab.Flags._ +import transform.InfoTransform + + /** <p> * Post-attribution checking and transformation. * </p> @@ -48,7 +50,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans import global._ import definitions._ - import typer.{typed, typedOperator, atOwner} + import typer.typed /** the following two members override abstract members in Transform */ val phaseName: String = "refchecks" @@ -1188,14 +1190,6 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // set NoType so it will be ignored. val cdef = ClassDef(module.moduleClass, impl) setType NoType - def newInnerObject() = { - val moduleVar = site newModuleVarSymbol module - val rhs = gen.newModule(module, moduleVar.tpe) - val body = if (site.isTrait) rhs else gen.mkAssignAndReturn(moduleVar, rhs) - val accessor = DefDef(module, body.changeOwner(moduleVar -> module)) - - ValDef(moduleVar) :: accessor :: Nil - } def matchingInnerObject() = { val newFlags = (module.flags | STABLE) & ~MODULE val newInfo = NullaryMethodType(module.moduleClass.tpe) @@ -1208,10 +1202,36 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O. if (module.isOverridingSymbol) matchingInnerObject() else Nil else - newInnerObject() + newInnerObject(site, module) ) transformTrees(newTrees map localTyper.typedPos(moduleDef.pos)) } + def newInnerObject(site: Symbol, module: Symbol): List[Tree] = { + if (site.isTrait) + DefDef(module, EmptyTree) :: Nil + else { + val moduleVar = site newModuleVarSymbol module + // used for the mixin case: need a new symbol owned by the subclass for the accessor, rather than repurposing the module symbol + def mkAccessorSymbol = + site.newMethod(module.name.toTermName, site.pos, STABLE | MODULE | MIXEDIN) + .setInfo(moduleVar.tpe) + .andAlso(self => if (module.isPrivate) self.expandName(module.owner)) + + val accessor = if (module.owner == site) module else mkAccessorSymbol + val accessorDef = DefDef(accessor, gen.mkAssignAndReturn(moduleVar, gen.newModule(module, moduleVar.tpe)).changeOwner(moduleVar -> accessor)) + + ValDef(moduleVar) :: accessorDef :: Nil + } + } + + def mixinModuleDefs(clazz: Symbol): List[Tree] = { + val res = for { + mixinClass <- clazz.mixinClasses.iterator + module <- mixinClass.info.decls.iterator.filter(_.isModule) + newMember <- newInnerObject(clazz, module) + } yield transform(localTyper.typedPos(clazz.pos)(newMember)) + res.toList + } def transformStat(tree: Tree, index: Int): List[Tree] = tree match { case t if treeInfo.isSelfConstrCall(t) => @@ -1643,11 +1663,12 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans // SI-7870 default getters for constructors live in the companion module checkOverloadedRestrictions(currentOwner, currentOwner.companionModule) val bridges = addVarargBridges(currentOwner) + val moduleDesugared = if (currentOwner.isTrait) Nil else mixinModuleDefs(currentOwner) checkAllOverrides(currentOwner) checkAnyValSubclass(currentOwner) if (currentOwner.isDerivedValueClass) currentOwner.primaryConstructor makeNotPrivate NoSymbol // SI-6601, must be done *after* pickler! - if (bridges.nonEmpty) deriveTemplate(tree)(_ ::: bridges) else tree + if (bridges.nonEmpty || moduleDesugared.nonEmpty) deriveTemplate(tree)(_ ::: bridges ::: moduleDesugared) else tree case dc@TypeTreeWithDeferredRefCheck() => abort("adapt should have turned dc: TypeTreeWithDeferredRefCheck into tpt: TypeTree, with tpt.original == dc") case tpt@TypeTree() => diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index e0d96df062..a1bec13999 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -287,7 +287,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT val result = (localTyper.typedPos(tree.pos) { Select(Super(qual, tpnme.EMPTY) setPos qual.pos, sym.alias) }).asInstanceOf[Select] - debuglog("alias replacement: " + tree + " ==> " + result); //debug + debuglog(s"alias replacement: $sym --> ${sym.alias} / $tree ==> $result"); //debug localTyper.typed(gen.maybeMkAsInstanceOf(transformSuperSelect(result), sym.tpe, sym.alias.tpe, beforeRefChecks = true)) } else { /* @@ -387,7 +387,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT val savedValid = validCurrentOwner if (owner.isClass) validCurrentOwner = true val savedLocalTyper = localTyper - localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner) + localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner) typers = typers updated (owner, localTyper) val result = super.atOwner(tree, owner)(trans) localTyper = savedLocalTyper diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index 4ccc183334..243a706745 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -6,11 +6,13 @@ package scala.tools.nsc package typechecker -import scala.collection.{ mutable, immutable } -import symtab.Flags._ -import scala.collection.mutable.ListBuffer import scala.language.postfixOps +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import symtab.Flags._ + /** Synthetic method implementations for case classes and case objects. * * Added to all case classes/objects: diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index e8db8309f1..f6ee0c47a5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -7,8 +7,6 @@ package scala.tools.nsc package typechecker import scala.collection.mutable -import mutable.ListBuffer -import util.returning import scala.reflect.internal.util.shortClassOfInstance import scala.reflect.internal.util.StringOps._ @@ -94,7 +92,7 @@ abstract class TreeCheckers extends Analyzer { def latest = maps.head._2 val defSyms = symbolTreeMap[DefTree]() val newSyms = mutable.HashSet[Symbol]() - val movedMsgs = new ListBuffer[String] + val movedMsgs = mutable.ListBuffer[String]() def sortedNewSyms = newSyms.toList.distinct sortBy (_.name.toString) def record(tree: Tree) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 510c83eb88..84af9233e1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -14,7 +14,7 @@ package tools.nsc package typechecker import scala.collection.{mutable, immutable} -import scala.reflect.internal.util.{ BatchSourceFile, Statistics, shortClassOfInstance, ListOfNil } +import scala.reflect.internal.util.{ Statistics, ListOfNil } import mutable.ListBuffer import symtab.Flags._ import Mode._ @@ -110,7 +110,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with PatternTyper with TyperContextErrors { import context0.unit - import typeDebug.{ ptTree, ptBlock, ptLine, inGreen, inRed } + import typeDebug.ptTree import TyperErrorGen._ val runDefinitions = currentRun.runDefinitions import runDefinitions._ @@ -862,7 +862,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case Block(_, tree1) => tree1.symbol case _ => tree.symbol } - if (!meth.isConstructor && (isFunctionType(pt) || samOf(pt).exists)) { // (4.2) + def shouldEtaExpandToSam: Boolean = { + // SI-9536 don't adapt parameterless method types to a to SAM's, fall through to empty application + // instead for backwards compatiblity with 2.11. See comments of that ticket and SI-7187 + // for analogous trouble with non-SAM eta expansion. Suggestions there are: a) deprecate eta expansion to Function0, + // or b) switch the order of eta-expansion and empty application in this adaptation. + !mt.params.isEmpty && samOf(pt).exists + } + if (!meth.isConstructor && (isFunctionType(pt) || shouldEtaExpandToSam)) { // (4.2) debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt") checkParamsConvertible(tree, tree.tpe) val tree0 = etaExpand(context.unit, tree, this) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala b/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala index 37fbb73b85..f2911fb98b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypersTracking.scala @@ -6,9 +6,6 @@ package scala.tools.nsc package typechecker -import scala.collection.mutable -import scala.reflect.internal.util.{ BatchSourceFile, Statistics } -import mutable.ListBuffer import Mode._ trait TypersTracking { diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 8fed53c89f..dc26c93066 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -4,7 +4,6 @@ package reflect import scala.reflect.reify.Taggers import scala.tools.nsc.typechecker.{ Analyzer, Macros } import scala.reflect.runtime.Macros.currentMirror -import scala.reflect.api.Universe import scala.reflect.quasiquotes.{ Quasiquotes => QuasiquoteImpls } /** Optimizes system macro expansions by hardwiring them directly to their implementations diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index b445f1e2bb..cbdb01a10a 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -6,7 +6,7 @@ import scala.reflect.internal.util.Position import scala.PartialFunction.cond import scala.util.matching.Regex.Match -import java.util.{ Formatter, Formattable, IllegalFormatException } +import java.util.Formattable abstract class FormatInterpolator { val c: Context @@ -262,7 +262,7 @@ abstract class FormatInterpolator { def goodFlags = { val badFlags = flags map (_ filterNot (okFlags contains _)) for (bf <- badFlags; f <- bf) badFlag(f, s"Illegal flag '$f'") - badFlags.getOrElse("").isEmpty + badFlags.getOrElse("").isEmpty } def goodIndex = { if (index.nonEmpty && hasFlag('<')) @@ -281,7 +281,7 @@ abstract class FormatInterpolator { ) orElse Some(variants(0)) } object Conversion { - import SpecifierGroups.{ Spec, CC, Width } + import SpecifierGroups.{ Spec, CC } def apply(m: Match, p: Position, n: Int): Option[Conversion] = { def badCC(msg: String) = { val dk = new ErrorXn(m, p) diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index 47c88f2c00..ae6a9e22b6 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -3,13 +3,12 @@ package tools package reflect import scala.tools.cmd.CommandLineParser -import scala.tools.nsc.Global import scala.tools.nsc.reporters._ import scala.tools.nsc.CompilerCommand import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} import scala.reflect.internal.util.AbstractFileClassLoader import scala.reflect.internal.Flags._ -import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, NoFile} +import scala.reflect.internal.util.NoSourceFile import java.lang.{Class => jClass} import scala.compat.Platform.EOL import scala.reflect.NameTransformer diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala index 09c6c9d744..42b939f18b 100644 --- a/src/compiler/scala/tools/util/PathResolver.scala +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -7,15 +7,16 @@ package scala package tools package util +import scala.language.postfixOps + import java.net.URL import scala.tools.reflect.WrappedProperties.AccessControl import scala.tools.nsc.Settings import scala.tools.nsc.util.{ ClassFileLookup, ClassPath, JavaClassPath } import scala.reflect.io.{ File, Directory, Path, AbstractFile } import scala.reflect.runtime.ReflectionUtils -import ClassPath.{ JavaContext, DefaultJavaContext, join, split } +import ClassPath.{ JavaContext, DefaultJavaContext, split } import PartialFunction.condOpt -import scala.language.postfixOps import scala.tools.nsc.classpath.{ AggregateFlatClassPath, ClassPathFactory, FlatClassPath, FlatClassPathFactory } import scala.tools.nsc.settings.ClassPathRepresentationType |