diff options
Diffstat (limited to 'src/compiler/scala/tools')
175 files changed, 9584 insertions, 22298 deletions
diff --git a/src/compiler/scala/tools/ant/FastScalac.scala b/src/compiler/scala/tools/ant/FastScalac.scala index 6f0a30aa9d..3b62c493d3 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 @@ -109,7 +109,7 @@ class FastScalac extends Scalac { List( /*scalac*/ s.debuginfo, s.target - ) filter (x => x.value != x.default) map (x => "%s:%s".format(x.name, x.value)) + ) filter (x => x.value != x.default) map (x => s"${x.name}:${x.value}") val booleanSettings = List( @@ -129,7 +129,7 @@ class FastScalac extends Scalac { val phaseSetting = { val s = settings.log if (s.value.isEmpty) Nil - else List("%s:%s".format(s.name, s.value.mkString(","))) + else List(s"${s.name}:${s.value.mkString(",")}") } val fscOptions = @@ -147,8 +147,7 @@ class FastScalac extends Scalac { case cl: AntClassLoader => path add new Path(getProject, cl.getClasspath) case _ => - buildError("Compilation failed because of an internal compiler error;"+ - " see the error output for details.") + buildError("Compilation failed because of an internal compiler error; see the error output for details.") } path } @@ -160,8 +159,7 @@ class FastScalac extends Scalac { File(url.getFile).jfile.getParentFile.getParentFile.getAbsolutePath } catch { case _: Throwable => - buildError("Compilation failed because of an internal compiler error;"+ - " couldn't determine value for -Dscala.home=<value>") + buildError("Compilation failed because of an internal compiler error; couldn't determine value for -Dscala.home=<value>") } java.createJvmarg() setValue "-Dscala.usejavacp=true" java.createJvmarg() setValue ("-Dscala.home="+scalaHome) @@ -186,7 +184,6 @@ class FastScalac extends Scalac { val res = execWithArgFiles(java, paths) if (failonerror && res != 0) - buildError("Compilation failed because of an internal compiler error;"+ - " see the error output for details.") + buildError("Compilation failed because of an internal compiler error; see the error output for details.") } } 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..930163af36 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. */ @@ -553,7 +551,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { val str = if (javaFiles.isEmpty) "%d source file%s".format(list.length, plural(list)) else "%d scala and %d java source files".format(scalaFiles.length, javaFiles.length) - log("Compiling %s to %s".format(str, getDestination.toString)) + log(s"Compiling $str to $getDestination") } else log("No files selected for compilation", Project.MSG_VERBOSE) 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/CommandLine.scala b/src/compiler/scala/tools/cmd/CommandLine.scala index 781cc564cb..3a36a7d345 100644 --- a/src/compiler/scala/tools/cmd/CommandLine.scala +++ b/src/compiler/scala/tools/cmd/CommandLine.scala @@ -51,7 +51,7 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) extends C /* Assumes known options have all been ruled out already. */ def isUnknown(opt: String) = onlyKnownOptions && (opt startsWith "-") && { - errorFn("Option '%s' not recognized.".format(opt)) + errorFn(s"Option '$opt' not recognized.") true } @@ -61,7 +61,7 @@ class CommandLine(val spec: Reference, val originalArgs: List[String]) extends C case x :: Nil => expand(x) foreach (exp => return loop(exp)) if (isBinaryOption(x) && enforceArity) - errorFn("Option '%s' requires argument, found EOF instead.".format(x)) + errorFn(s"Option '$x' requires argument, found EOF instead.") if (isUnaryOption(x)) mapForUnary(x) else if (isUnknown(x)) Map() 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/Opt.scala b/src/compiler/scala/tools/cmd/Opt.scala index df3d0c4462..70756c5bb2 100644 --- a/src/compiler/scala/tools/cmd/Opt.scala +++ b/src/compiler/scala/tools/cmd/Opt.scala @@ -20,7 +20,7 @@ object Opt { self: Implicit => protected def fail(msg: String) = runAndExit(println(programInfo.runner + ": " + msg)) - protected def failOption(arg: String, why: String) = fail("%s: '%s' is %s".format(opt, arg, why)) + protected def failOption(arg: String, why: String) = fail(s"$opt: '$arg' is $why") } trait Implicit { diff --git a/src/compiler/scala/tools/cmd/Property.scala b/src/compiler/scala/tools/cmd/Property.scala index b1d951a5c4..e6262a7e40 100644 --- a/src/compiler/scala/tools/cmd/Property.scala +++ b/src/compiler/scala/tools/cmd/Property.scala @@ -9,6 +9,7 @@ package cmd import nsc.io._ import java.util.Properties import java.io.FileInputStream +import scala.sys.SystemProperties /** Contains logic for translating a property key/value pair into * equivalent command line arguments. The default settings will @@ -58,7 +59,7 @@ trait Property extends Reference { returning(new Properties)(_ load new FileInputStream(file.path)) def systemPropertiesToOptions: List[String] = - propertiesToOptions(System.getProperties) + propertiesToOptions(new SystemProperties().toList) def propertiesToOptions(file: File): List[String] = propertiesToOptions(loadProperties(file)) diff --git a/src/compiler/scala/tools/cmd/Reference.scala b/src/compiler/scala/tools/cmd/Reference.scala index 62b6c893cf..25a16b1e3e 100644 --- a/src/compiler/scala/tools/cmd/Reference.scala +++ b/src/compiler/scala/tools/cmd/Reference.scala @@ -70,18 +70,18 @@ object Reference { def addHelpAlias(f: () => String) = mapHelp { s => val str = "alias for '%s'" format f() def noHelp = (helpFormatStr.format("", "")).length == s.length - val str2 = if (noHelp) str else " (" + str + ")" + val str2 = if (noHelp) str else s" ($str)" s + str2 } def addHelpDefault(f: () => String): Unit = mapHelp { s => val str = "(default: %s)" format f() - if (s.length + str.length < MaxLine) s + " " + str + if (s.length + str.length < MaxLine) s"$s $str" else defaultFormatStr.format(s, str) } def addHelpEnvDefault(name: String): Unit = mapHelp { s => - val line1 = "%s (default: %s)".format(s, name) + val line1 = s"$s (default: $name)" val envNow = envOrNone(name) map ("'" + _ + "'") getOrElse "unset" val line2 = defaultFormatStr.format("Currently " + envNow) 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/cmd/gen/AnyVals.scala b/src/compiler/scala/tools/cmd/gen/AnyVals.scala deleted file mode 100644 index e78589908c..0000000000 --- a/src/compiler/scala/tools/cmd/gen/AnyVals.scala +++ /dev/null @@ -1,484 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.cmd -package gen - -/** Code generation of the AnyVal types and their companions. */ -trait AnyValReps { - self: AnyVals => - - sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String) - extends AnyValRep(name,repr,javaEquiv) { - - case class Op(op : String, doc : String) - - private def companionCoercions(tos: AnyValRep*) = { - tos.toList map (to => - s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}" - ) - } - def coercionComment = -"""/** Language mandated coercions from @name@ to "wider" types. */ -import scala.language.implicitConversions""" - - def implicitCoercions: List[String] = { - val coercions = this match { - case B => companionCoercions(S, I, L, F, D) - case S | C => companionCoercions(I, L, F, D) - case I => companionCoercions(L, F, D) - case L => companionCoercions(F, D) - case F => companionCoercions(D) - case _ => Nil - } - if (coercions.isEmpty) Nil - else coercionComment.lines.toList ++ coercions - } - - def isCardinal: Boolean = isIntegerType(this) - def unaryOps = { - val ops = List( - Op("+", "/** Returns this value, unmodified. */"), - Op("-", "/** Returns the negation of this value. */")) - - if(isCardinal) - Op("~", "/**\n" + - " * Returns the bitwise negation of this value.\n" + - " * @example {{{\n" + - " * ~5 == -6\n" + - " * // in binary: ~00000101 ==\n" + - " * // 11111010\n" + - " * }}}\n" + - " */") :: ops - else ops - } - - def bitwiseOps = - if (isCardinal) - List( - Op("|", "/**\n" + - " * Returns the bitwise OR of this value and `x`.\n" + - " * @example {{{\n" + - " * (0xf0 | 0xaa) == 0xfa\n" + - " * // in binary: 11110000\n" + - " * // | 10101010\n" + - " * // --------\n" + - " * // 11111010\n" + - " * }}}\n" + - " */"), - Op("&", "/**\n" + - " * Returns the bitwise AND of this value and `x`.\n" + - " * @example {{{\n" + - " * (0xf0 & 0xaa) == 0xa0\n" + - " * // in binary: 11110000\n" + - " * // & 10101010\n" + - " * // --------\n" + - " * // 10100000\n" + - " * }}}\n" + - " */"), - Op("^", "/**\n" + - " * Returns the bitwise XOR of this value and `x`.\n" + - " * @example {{{\n" + - " * (0xf0 ^ 0xaa) == 0x5a\n" + - " * // in binary: 11110000\n" + - " * // ^ 10101010\n" + - " * // --------\n" + - " * // 01011010\n" + - " * }}}\n" + - " */")) - else Nil - - def shiftOps = - if (isCardinal) - List( - Op("<<", "/**\n" + - " * Returns this value bit-shifted left by the specified number of bits,\n" + - " * filling in the new right bits with zeroes.\n" + - " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" + - " */"), - - Op(">>>", "/**\n" + - " * Returns this value bit-shifted right by the specified number of bits,\n" + - " * filling the new left bits with zeroes.\n" + - " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" + - " * @example {{{\n" + - " * -21 >>> 3 == 536870909\n" + - " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" + - " * // 00011111 11111111 11111111 11111101\n" + - " * }}}\n" + - " */"), - - Op(">>", "/**\n" + - " * Returns this value bit-shifted right by the specified number of bits,\n" + - " * filling in the left bits with the same value as the left-most bit of this.\n" + - " * The effect of this is to retain the sign of the value.\n" + - " * @example {{{\n" + - " * -21 >> 3 == -3\n" + - " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" + - " * // 11111111 11111111 11111111 11111101\n" + - " * }}}\n" + - " */")) - else Nil - - def comparisonOps = List( - Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"), - Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"), - Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"), - Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"), - Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"), - Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */")) - - def otherOps = List( - Op("+", "/** Returns the sum of this value and `x`. */"), - Op("-", "/** Returns the difference of this value and `x`. */"), - Op("*", "/** Returns the product of this value and `x`. */"), - Op("/", "/** Returns the quotient of this value and `x`. */"), - Op("%", "/** Returns the remainder of the division of this value by `x`. */")) - - // Given two numeric value types S and T , the operation type of S and T is defined as follows: - // If both S and T are subrange types then the operation type of S and T is Int. - // Otherwise the operation type of S and T is the larger of the two types wrt ranking. - // Given two numeric values v and w the operation type of v and w is the operation type - // of their run-time types. - def opType(that: AnyValNum): AnyValNum = { - val rank = IndexedSeq(I, L, F, D) - (rank indexOf this, rank indexOf that) match { - case (-1, -1) => I - case (r1, r2) => rank apply (r1 max r2) - } - } - - def mkCoercions = numeric map (x => "def to%s: %s".format(x, x)) - def mkUnaryOps = unaryOps map (x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) - def mkStringOps = List("def +(x: String): String") - def mkShiftOps = ( - for (op <- shiftOps ; arg <- List(I, L)) yield - "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, this opType I) - ) - - def clumps: List[List[String]] = { - val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "") - val xs2 = List( - mkBinOpsGroup(comparisonOps, numeric, _ => Z), - mkBinOpsGroup(bitwiseOps, cardinal, this opType _), - mkBinOpsGroup(otherOps, numeric, this opType _) - ) - xs1 ++ xs2 - } - def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) { - case (res, Nil) => res - case (res, lines) => - val xs = lines map { - case "" => "" - case s => interpolate(s) - } - res ++ xs - } - def objectLines = { - val comp = if (isCardinal) cardinalCompanion else floatingCompanion - interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.lines.toList ++ (implicitCoercions map interpolate) - } - - /** Makes a set of binary operations based on the given set of ops, args, and resultFn. - * - * @param ops list of function names e.g. List(">>", "%") - * @param args list of types which should appear as arguments - * @param resultFn function which calculates return type based on arg type - * @return list of function definitions - */ - def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = ( - ops flatMap (op => - args.map(arg => - "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ "" - ) - ).toList - } - - sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) { - def classLines: List[String] - def objectLines: List[String] - def commonClassLines = List( - "override def getClass(): Class[@name@] = null" - ) - - def lcname = name.toLowerCase - def boxedSimpleName = this match { - case C => "Character" - case I => "Integer" - case _ => name - } - def boxedName = this match { - case U => "scala.runtime.BoxedUnit" - case _ => "java.lang." + boxedSimpleName - } - def zeroRep = this match { - case L => "0L" - case F => "0.0f" - case D => "0.0d" - case _ => "0" - } - - def representation = repr.map(", a " + _).getOrElse("") - - def indent(s: String) = if (s == "") "" else " " + s - def indentN(s: String) = s.lines map indent mkString "\n" - - def boxUnboxImpls = Map( - "@boxRunTimeDoc@" -> """ - * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. - *""".format(boxedSimpleName), - "@boxImpl@" -> "%s.valueOf(x)".format(boxedName), - "@unboxRunTimeDoc@" -> """ - * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. - *""".format(name), - "@unboxImpl@" -> "x.asInstanceOf[%s].%sValue()".format(boxedName, lcname), - "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname) - ) - def interpolations = Map( - "@name@" -> name, - "@representation@" -> representation, - "@javaequiv@" -> javaEquiv, - "@boxed@" -> boxedName, - "@lcname@" -> lcname, - "@zero@" -> zeroRep - ) ++ boxUnboxImpls - - def interpolate(s: String): String = interpolations.foldLeft(s) { - case (str, (key, value)) => str.replaceAll(key, value) - } - def classDoc = interpolate(classDocTemplate) - def objectDoc = "" - def mkImports = "" - - def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) - def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) - def make() = List[String]( - headerTemplate, - mkImports, - classDoc, - mkClass, - objectDoc, - mkObject - ) mkString "" - - def assemble(decl: String, lines: List[String]): String = { - val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n") - - decl + body + "\n" - } - override def toString = name - } -} - -trait AnyValTemplates { - def headerTemplate = """/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -// DO NOT EDIT, CHANGES WILL BE LOST -// This auto-generated code can be modified in scala.tools.cmd.gen. -// Afterwards, running tools/codegen-anyvals regenerates this source file. - -package scala - -""" - - def classDocTemplate = (""" -/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a - * subtype of [[scala.AnyVal]]. Instances of `@name@` are not - * represented by an object in the underlying runtime system. - * - * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]] - * which provides useful non-primitive operations. - */ -""".trim + "\n") - - def allCompanions = """ -/** Transform a value type into a boxed reference type. - *@boxRunTimeDoc@ - * @param x the @name@ to be boxed - * @return a @boxed@ offering `x` as its underlying value. - */ -def box(x: @name@): @boxed@ = @boxImpl@ - -/** Transform a boxed type into a value type. Note that this - * method is not typesafe: it accepts any Object, but will throw - * an exception if the argument is not a @boxed@. - *@unboxRunTimeDoc@ - * @param x the @boxed@ to be unboxed. - * @throws ClassCastException if the argument is not a @boxed@ - * @return @unboxDoc@ - */ -def unbox(x: java.lang.Object): @name@ = @unboxImpl@ - -/** The String representation of the scala.@name@ companion object. */ -override def toString = "object scala.@name@" -""" - - def nonUnitCompanions = "" // todo - - def cardinalCompanion = """ -/** The smallest value representable as a @name@. */ -final val MinValue = @boxed@.MIN_VALUE - -/** The largest value representable as a @name@. */ -final val MaxValue = @boxed@.MAX_VALUE -""" - - def floatingCompanion = """ -/** The smallest positive value greater than @zero@ which is - * representable as a @name@. - */ -final val MinPositiveValue = @boxed@.MIN_VALUE -final val NaN = @boxed@.NaN -final val PositiveInfinity = @boxed@.POSITIVE_INFINITY -final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY - -/** The negative number with the greatest (finite) absolute value which is representable - * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which - * is the smallest positive value representable by a @name@. In Scala that number - * is called @name@.MinPositiveValue. - */ -final val MinValue = -@boxed@.MAX_VALUE - -/** The largest finite positive number representable as a @name@. */ -final val MaxValue = @boxed@.MAX_VALUE -""" -} - -class AnyVals extends AnyValReps with AnyValTemplates { - object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte") - object S extends AnyValNum("Short", Some("16-bit signed integer"), "short") - object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char") - object I extends AnyValNum("Int", Some("32-bit signed integer"), "int") - object L extends AnyValNum("Long", Some("64-bit signed integer"), "long") - object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float") - object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double") - object Z extends AnyValRep("Boolean", None, "boolean") { - def classLines = """ -/** Negates a Boolean expression. - * - * - `!a` results in `false` if and only if `a` evaluates to `true` and - * - `!a` results in `true` if and only if `a` evaluates to `false`. - * - * @return the negated expression - */ -def unary_! : Boolean - -/** Compares two Boolean expressions and returns `true` if they evaluate to the same value. - * - * `a == b` returns `true` if and only if - * - `a` and `b` are `true` or - * - `a` and `b` are `false`. - */ -def ==(x: Boolean): Boolean - -/** - * Compares two Boolean expressions and returns `true` if they evaluate to a different value. - * - * `a != b` returns `true` if and only if - * - `a` is `true` and `b` is `false` or - * - `a` is `false` and `b` is `true`. - */ -def !=(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. - * - * `a || b` returns `true` if and only if - * - `a` is `true` or - * - `b` is `true` or - * - `a` and `b` are `true`. - * - * @note This method uses 'short-circuit' evaluation and - * behaves as if it was declared as `def ||(x: => Boolean): Boolean`. - * If `a` evaluates to `true`, `true` is returned without evaluating `b`. - */ -def ||(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. - * - * `a && b` returns `true` if and only if - * - `a` and `b` are `true`. - * - * @note This method uses 'short-circuit' evaluation and - * behaves as if it was declared as `def &&(x: => Boolean): Boolean`. - * If `a` evaluates to `false`, `false` is returned without evaluating `b`. - */ -def &&(x: Boolean): Boolean - -// Compiler won't build with these seemingly more accurate signatures -// def ||(x: => Boolean): Boolean -// def &&(x: => Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. - * - * `a | b` returns `true` if and only if - * - `a` is `true` or - * - `b` is `true` or - * - `a` and `b` are `true`. - * - * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. - */ -def |(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. - * - * `a & b` returns `true` if and only if - * - `a` and `b` are `true`. - * - * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. - */ -def &(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if they evaluate to a different value. - * - * `a ^ b` returns `true` if and only if - * - `a` is `true` and `b` is `false` or - * - `a` is `false` and `b` is `true`. - */ -def ^(x: Boolean): Boolean - -override def getClass(): Class[Boolean] = null - """.trim.lines.toList - - def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).lines.toList - } - object U extends AnyValRep("Unit", None, "void") { - override def classDoc = """ -/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type - * `Unit`, `()`, and it is not represented by any object in the underlying - * runtime system. A method with return type `Unit` is analogous to a Java - * method which is declared `void`. - */ -""" - def classLines = List( - """override def getClass(): Class[Unit] = null""" - ) - def objectLines = interpolate(allCompanions).lines.toList - - override def boxUnboxImpls = Map( - "@boxRunTimeDoc@" -> "", - "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT", - "@unboxRunTimeDoc@" -> "", - "@unboxImpl@" -> "()", - "@unboxDoc@" -> "the Unit value ()" - ) - } - - def isSubrangeType = Set(B, S, C) - def isIntegerType = Set(B, S, C, I, L) - def isFloatingType = Set(F, D) - def isWideType = Set(L, D) - - def cardinal = numeric filter isIntegerType - def numeric = List(B, S, C, I, L, F, D) - def values = List(U, Z) ++ numeric - - def make() = values map (x => (x.name, x.make())) -} diff --git a/src/compiler/scala/tools/cmd/gen/Codegen.scala b/src/compiler/scala/tools/cmd/gen/Codegen.scala deleted file mode 100644 index c3aa527ef2..0000000000 --- a/src/compiler/scala/tools/cmd/gen/Codegen.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.cmd -package gen - -class Codegen(args: List[String]) extends { - val parsed = CodegenSpec(args: _*) -} with CodegenSpec with Instance - -object Codegen { - def echo(msg: String) = Console println msg - - def main(args0: Array[String]): Unit = { - val runner = new Codegen(args0.toList) - import runner._ - - if (args0.isEmpty) - return println (CodegenSpec.helpMsg) - - val out = outDir getOrElse { return println("--out is required.") } - val all = genall || !anyvals - - echo("Generating sources into " + out) - - if (anyvals || all) { - val av = new AnyVals { } - - av.make() foreach { case (name, code ) => - val file = (out / (name + ".scala")).toFile - echo("Writing: " + file) - file writeAll code - } - } - } -} - diff --git a/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala b/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala deleted file mode 100644 index 4b4a1e482d..0000000000 --- a/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.cmd -package gen - -import FromString.ExistingDir - -trait CodegenSpec extends Spec with Meta.StdOpts with Interpolation { - def referenceSpec = CodegenSpec - def programInfo = Spec.Info("codegen", "", "scala.tools.cmd.gen.Codegen") - - help("Usage: codegen [<options>]") - - val outDir = "out" / "directory for generated files" --^ ExistingDir - val anyvals = "anyvals" / "generate sources for AnyVal types" --? - val genall = "all" / "generate sources for everything" --? -} - -object CodegenSpec extends CodegenSpec with Reference { - type ThisCommandLine = CommandLine - def creator(args: List[String]): ThisCommandLine = new CommandLine(CodegenSpec, args) -} 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/Driver.scala b/src/compiler/scala/tools/nsc/Driver.scala index 6befa76b3f..b30744c4df 100644 --- a/src/compiler/scala/tools/nsc/Driver.scala +++ b/src/compiler/scala/tools/nsc/Driver.scala @@ -1,7 +1,7 @@ package scala package tools.nsc -import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.nsc.reporters.{ ConsoleReporter, Reporter } import Properties.{ versionMsg, residentPromptString } import scala.reflect.internal.util.FakePos @@ -9,39 +9,43 @@ abstract class Driver { val prompt = residentPromptString - var reporter: ConsoleReporter = _ + var reporter: Reporter = _ protected var command: CompilerCommand = _ protected var settings: Settings = _ + /** Forward errors to the (current) reporter. */ protected def scalacError(msg: String): Unit = { reporter.error(FakePos("scalac"), msg + "\n scalac -help gives more information") } + /** True to continue compilation. */ protected def processSettingsHook(): Boolean = { - if (settings.version) { reporter echo versionMsg ; false } else true + if (settings.version) { reporter echo versionMsg ; false } + else !reporter.hasErrors } protected def newCompiler(): Global - protected def doCompile(compiler: Global) { + protected def doCompile(compiler: Global): Unit = { if (command.files.isEmpty) { reporter.echo(command.usageMsg) reporter.echo(compiler.pluginOptionsHelp) } else { val run = new compiler.Run() run compile command.files - reporter.printSummary() + reporter.finish() } } - def process(args: Array[String]) { + def process(args: Array[String]): Boolean = { val ss = new Settings(scalacError) - reporter = new ConsoleReporter(ss) + reporter = new ConsoleReporter(ss) // for reporting early config errors, before compiler is constructed command = new CompilerCommand(args.toList, ss) settings = command.settings if (processSettingsHook()) { val compiler = newCompiler() + reporter = compiler.reporter // adopt the configured reporter try { if (reporter.hasErrors) reporter.flush() @@ -57,11 +61,9 @@ abstract class Driver { case _ => throw ex // unexpected error, tell the outside world. } } - } + } else if (reporter.hasErrors) reporter.flush() + !reporter.hasErrors } - def main(args: Array[String]) { - process(args) - sys.exit(if (reporter.hasErrors) 1 else 0) - } + def main(args: Array[String]): Unit = sys.exit(if (process(args)) 0 else 1) } diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala index 1289d55c37..e99cce9186 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala @@ -9,7 +9,7 @@ import java.net.URL import scala.tools.util.PathResolverFactory class GenericRunnerSettings(error: String => Unit) extends Settings(error) { - def classpathURLs: Seq[URL] = PathResolverFactory.create(this).resultAsURLs + lazy val classpathURLs: Seq[URL] = PathResolverFactory.create(this).resultAsURLs val howtorun = ChoiceSetting( diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index a618b080c8..847c4cb2d1 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -7,17 +7,17 @@ package scala package tools package nsc -import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException } +import java.io.{File, FileNotFoundException, IOException} 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 util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning } +import java.nio.charset.{Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException} +import scala.collection.{immutable, mutable} +import io.{AbstractFile, Path, SourceReader} +import reporters.Reporter +import util.{ClassFileLookup, ClassPath, StatisticsInfo, returning} import scala.reflect.ClassTag -import scala.reflect.internal.util.{ SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile } +import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile} import scala.reflect.internal.pickling.PickleBuffer -import symtab.{ Flags, SymbolTable, SymbolTrackers } +import symtab.{Flags, SymbolTable, SymbolTrackers} import symtab.classfile.Pickler import plugins.Plugins import ast._ @@ -25,15 +25,11 @@ import ast.parser._ import typechecker._ import transform.patmat.PatternMatching import transform._ -import backend.icode.{ ICodes, GenICode, ICodeCheckers } -import backend.{ ScalaPrimitives, JavaPlatform } +import backend.{JavaPlatform, ScalaPrimitives} 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 +import scala.tools.nsc.classpath._ import scala.tools.nsc.settings.ClassPathRepresentationType class Global(var currentSettings: Settings, var reporter: Reporter) @@ -90,7 +86,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) this(new Settings(err => reporter.error(null, err)), reporter) def this(settings: Settings) = - this(settings, new ConsoleReporter(settings)) + this(settings, Global.reporter(settings)) def picklerPhase: Phase = if (currentRun.isDefined) currentRun.picklerPhase else NoPhase @@ -106,9 +102,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) type ThisPlatform = JavaPlatform { val global: Global.this.type } lazy val platform: ThisPlatform = new GlobalPlatform - type PlatformClassPath = ClassPath[AbstractFile] - type OptClassPath = Option[PlatformClassPath] - def classPath: ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match { case ClassPathRepresentationType.Flat => flatClassPath case ClassPathRepresentationType.Recursive => recursiveClassPath @@ -140,12 +133,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 +150,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 */ @@ -302,7 +283,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // Over 200 closure objects are eliminated by inlining this. @inline final def log(msg: => AnyRef) { if (shouldLogAtThisPhase) - inform("[log %s%s] %s".format(globalPhase, atPhaseStackMessage, msg)) + inform(s"[log $globalPhase$atPhaseStackMessage] $msg") } @inline final override def debuglog(msg: => String) { @@ -324,10 +305,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) try Some(Charset.forName(name)) catch { case _: IllegalCharsetNameException => - globalError("illegal charset name '" + name + "'") + globalError(s"illegal charset name '$name'") None case _: UnsupportedCharsetException => - globalError("unsupported charset '" + name + "'") + globalError(s"unsupported charset '$name'") None } @@ -400,15 +381,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 +563,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 +597,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 +629,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" ) @@ -858,13 +768,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** Extend classpath of `platform` and rescan updated packages. */ def extendCompilerClassPath(urls: URL*): Unit = { - if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) - throw new UnsupportedOperationException("Flat classpath doesn't support extending the compiler classpath") - - val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*) - platform.currentClassPath = Some(newClassPath) - // Reload all specified jars into this compiler instance - invalidateClassPathEntries(urls.map(_.getPath): _*) + if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) { + val urlClasspaths = urls.map(u => FlatClassPathFactory.newClassPath(AbstractFile.getURL(u), settings)) + val newClassPath = AggregateFlatClassPath.createAggregate(platform.flatClassPath +: urlClasspaths : _*) + platform.currentFlatClassPath = Some(newClassPath) + invalidateClassPathEntries(urls.map(_.getPath): _*) + } else { + val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*) + platform.currentClassPath = Some(newClassPath) + // Reload all specified jars into this compiler instance + invalidateClassPathEntries(urls.map(_.getPath): _*) + } } // ------------ Invalidations --------------------------------- @@ -896,43 +810,60 @@ class Global(var currentSettings: Settings, var reporter: Reporter) * entries on the classpath. */ def invalidateClassPathEntries(paths: String*): Unit = { - if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) - throw new UnsupportedOperationException("Flat classpath doesn't support the classpath invalidation") - - implicit object ClassPathOrdering extends Ordering[PlatformClassPath] { - def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClassPathString compare b.asClassPathString + implicit object ClassPathOrdering extends Ordering[ClassFileLookup[AbstractFile]] { + def compare(a:ClassFileLookup[AbstractFile], b:ClassFileLookup[AbstractFile]) = a.asClassPathString compare b.asClassPathString } val invalidated, failed = new mutable.ListBuffer[ClassSymbol] - classPath match { - case cp: MergedClassPath[_] => - def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = { - val dir = AbstractFile.getDirectory(path) - val canonical = dir.canonicalPath - def matchesCanonical(e: ClassPath[_]) = e.origin match { - case Some(opath) => - AbstractFile.getDirectory(opath).canonicalPath == canonical - case None => - false - } - cp.entries find matchesCanonical match { - case Some(oldEntry) => - List(oldEntry -> cp.context.newClassPath(dir)) - case None => - error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath") - List() - } - } - val subst = immutable.TreeMap(paths flatMap assoc: _*) - if (subst.nonEmpty) { - platform updateClassPath subst - informProgress(s"classpath updated on entries [${subst.keys mkString ","}]") - def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath = - if (elems.size == 1) elems.head - else new MergedClassPath(elems, recursiveClassPath.context) - val oldEntries = mkClassPath(subst.keys) - val newEntries = mkClassPath(subst.values) - mergeNewEntries(newEntries, RootClass, Some(recursiveClassPath), Some(oldEntries), invalidated, failed) - } + + def assoc(path: String): Option[(ClassFileLookup[AbstractFile], ClassFileLookup[AbstractFile])] = { + def origin(lookup: ClassFileLookup[AbstractFile]): Option[String] = lookup match { + case cp: ClassPath[_] => cp.origin + case cp: JFileDirectoryLookup[_] => Some(cp.dir.getPath) + case cp: ZipArchiveFileLookup[_] => Some(cp.zipFile.getPath) + case _ => None + } + + def entries(lookup: ClassFileLookup[AbstractFile]): Seq[ClassFileLookup[AbstractFile]] = lookup match { + case cp: ClassPath[_] => cp.entries + case cp: AggregateFlatClassPath => cp.aggregates + case cp: FlatClassPath => Seq(cp) + } + + val dir = AbstractFile.getDirectory(path) // if path is a `jar`, this is a FileZipArchive (isDirectory is true) + val canonical = dir.canonicalPath // this is the canonical path of the .jar + def matchesCanonical(e: ClassFileLookup[AbstractFile]) = origin(e) match { + case Some(opath) => + AbstractFile.getDirectory(opath).canonicalPath == canonical + case None => + false + } + entries(classPath) find matchesCanonical match { + case Some(oldEntry) => + Some(oldEntry -> ClassFileLookup.createForFile(dir, classPath, settings)) + case None => + error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath") + None + } + } + val subst = immutable.TreeMap(paths flatMap assoc: _*) + if (subst.nonEmpty) { + platform updateClassPath subst + informProgress(s"classpath updated on entries [${subst.keys mkString ","}]") + def mkClassPath(elems: Iterable[ClassFileLookup[AbstractFile]]): ClassFileLookup[AbstractFile] = + if (elems.size == 1) elems.head + else ClassFileLookup.createAggregate(elems, classPath) + val oldEntries = mkClassPath(subst.keys) + val newEntries = mkClassPath(subst.values) + classPath match { + case rcp: ClassPath[_] => mergeNewEntriesRecursive( + newEntries.asInstanceOf[ClassPath[AbstractFile]], RootClass, Some(rcp), Some(oldEntries.asInstanceOf[ClassPath[AbstractFile]]), + invalidated, failed) + + case fcp: FlatClassPath => mergeNewEntriesFlat( + RootClass, "", + oldEntries.asInstanceOf[FlatClassPath], newEntries.asInstanceOf[FlatClassPath], fcp, + invalidated, failed) + } } def show(msg: String, syms: scala.collection.Traversable[Symbol]) = if (syms.nonEmpty) @@ -963,13 +894,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter) * * Here, old means classpath, and sym means symboltable. + is presence of an entry in its column, - is absence. */ - private def mergeNewEntries(newEntries: PlatformClassPath, root: ClassSymbol, - allEntries: OptClassPath, oldEntries: OptClassPath, + private def mergeNewEntriesRecursive(newEntries: ClassPath[AbstractFile], root: ClassSymbol, + allEntries: Option[ClassPath[AbstractFile]], oldEntries: Option[ClassPath[AbstractFile]], invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) { ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries")) - val getName: ClassPath[AbstractFile] => String = (_.name) - def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty + val getPackageName: ClassPath[AbstractFile] => String = _.name + def hasClasses(cp: Option[ClassPath[AbstractFile]]) = cp.isDefined && cp.get.classes.nonEmpty def invalidateOrRemove(root: ClassSymbol) = { allEntries match { case Some(cp) => root setInfo new loaders.PackageLoader(cp) @@ -977,8 +908,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } invalidated += root } - def subPackage(cp: PlatformClassPath, name: String): OptClassPath = - cp.packages find (cp1 => getName(cp1) == name) + def subPackage(cp: ClassPath[AbstractFile], name: String): Option[ClassPath[AbstractFile]] = + cp.packages find (cp1 => getPackageName(cp1) == name) val classesFound = hasClasses(oldEntries) || newEntries.classes.nonEmpty if (classesFound && !isSystemPackageClass(root)) { @@ -988,22 +919,81 @@ class Global(var currentSettings: Settings, var reporter: Reporter) if (root.isRoot) invalidateOrRemove(EmptyPackageClass) else failed += root } - if (!oldEntries.isDefined) invalidateOrRemove(root) + if (oldEntries.isEmpty) invalidateOrRemove(root) else - for (pstr <- newEntries.packages.map(getName)) { + for (pstr <- newEntries.packages.map(getPackageName)) { val pname = newTermName(pstr) val pkg = (root.info decl pname) orElse { // package does not exist in symbol table, create symbol to track it - assert(!subPackage(oldEntries.get, pstr).isDefined) + assert(subPackage(oldEntries.get, pstr).isEmpty) loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get)) } - mergeNewEntries(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass, + mergeNewEntriesRecursive(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass, subPackage(allEntries.get, pstr), subPackage(oldEntries.get, pstr), invalidated, failed) } } } + /** + * Merges new classpath entries into the symbol table + * + * @param packageClass The ClassSymbol for the package being updated + * @param fullPackageName The full name of the package being updated + * @param oldEntries The classpath that was removed, it is no longer part of fullClasspath + * @param newEntries The classpath that was added, it is already part of fullClasspath + * @param fullClasspath The full classpath, equivalent to global.classPath + * @param invalidated A ListBuffer collecting the invalidated package classes + * @param failed A ListBuffer collecting system package classes which could not be invalidated + * + * If either oldEntries or newEntries contains classes in the current package, the package symbol + * is re-initialized to a fresh package loader, provided that a corresponding package exists in + * fullClasspath. Otherwise it is removed. + * + * Otherwise, sub-packages in newEntries are looked up in the symbol table (created if + * non-existent) and the merge function is called recursively. + */ + private def mergeNewEntriesFlat( + packageClass: ClassSymbol, fullPackageName: String, + oldEntries: FlatClassPath, newEntries: FlatClassPath, fullClasspath: FlatClassPath, + invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]): Unit = { + ifDebug(informProgress(s"syncing $packageClass, $oldEntries -> $newEntries")) + + def packageExists(cp: FlatClassPath): Boolean = { + val (parent, _) = PackageNameUtils.separatePkgAndClassNames(fullPackageName) + cp.packages(parent).exists(_.name == fullPackageName) + } + + def invalidateOrRemove(pkg: ClassSymbol) = { + if (packageExists(fullClasspath)) + pkg setInfo new loaders.PackageLoaderUsingFlatClassPath(fullPackageName, fullClasspath) + else + pkg.owner.info.decls unlink pkg.sourceModule + invalidated += pkg + } + + val classesFound = oldEntries.classes(fullPackageName).nonEmpty || newEntries.classes(fullPackageName).nonEmpty + if (classesFound) { + // if the package contains classes either in oldEntries or newEntries, the package is invalidated (or removed if there are no more classes in it) + if (!isSystemPackageClass(packageClass)) invalidateOrRemove(packageClass) + else if (packageClass.isRoot) invalidateOrRemove(EmptyPackageClass) + else failed += packageClass + } else { + // no new or removed classes in the current package + for (p <- newEntries.packages(fullPackageName)) { + val (_, subPackageName) = PackageNameUtils.separatePkgAndClassNames(p.name) + val subPackage = packageClass.info.decl(newTermName(subPackageName)) orElse { + // package does not exist in symbol table, create a new symbol + loaders.enterPackage(packageClass, subPackageName, new loaders.PackageLoaderUsingFlatClassPath(p.name, fullClasspath)) + } + mergeNewEntriesFlat( + subPackage.moduleClass.asClass, p.name, + oldEntries, newEntries, fullClasspath, + invalidated, failed) + } + } + } + // ----------- Runs --------------------------------------- private var curRun: Run = null @@ -1057,9 +1047,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 +1323,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 +1336,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val mixinPhase = phaseNamed("mixin") val delambdafyPhase = phaseNamed("delambdafy") val cleanupPhase = phaseNamed("cleanup") - val icodePhase = phaseNamed("icode") - val inlinerPhase = phaseNamed("inliner") - val inlineExceptionHandlersPhase = phaseNamed("inlinehandlers") - val closelimPhase = phaseNamed("closelim") - val dcePhase = phaseNamed("dce") - // val jvmPhase = phaseNamed("jvm") + val jvmPhase = phaseNamed("jvm") def runIsAt(ph: Phase) = globalPhase.id == ph.id - def runIsAtOptimiz = { - runIsAt(inlinerPhase) || // listing phases in full for robustness when -Ystop-after has been given. - runIsAt(inlineExceptionHandlersPhase) || - runIsAt(closelimPhase) || - runIsAt(dcePhase) - } + def runIsAtOptimiz = runIsAt(jvmPhase) isDefined = true @@ -1373,13 +1351,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter) unitbuf += unit compiledFiles += unit.source.file.path } - private def checkDeprecatedSettings(unit: CompilationUnit) { + private def warnDeprecatedAndConflictingSettings(unit: CompilationUnit) { // issue warnings for any usage of deprecated settings settings.userSetSettings filter (_.isDeprecated) foreach { s => currentRun.reporting.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get) } - if (settings.target.value.contains("jvm-1.5")) - currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated: use target for Java 1.6 or above.") + val supportedTarget = "jvm-1.8" + if (settings.target.value != supportedTarget) { + currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated and has no effect, setting to " + supportedTarget) + settings.target.value = supportedTarget + } + settings.conflictWarning.foreach(reporter.warning(NoPosition, _)) } /* An iterator returning all the units being compiled in this run */ @@ -1420,8 +1402,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() } } @@ -1470,7 +1452,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) def compileSources(sources: List[SourceFile]) = if (!reporter.hasErrors) { def checkDeprecations() = { - checkDeprecatedSettings(newCompilationUnit("")) + warnDeprecatedAndConflictingSettings(newCompilationUnit("")) reporting.summarizeErrors() } @@ -1492,7 +1474,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val startTime = currentTime reporter.reset() - checkDeprecatedSettings(unitbuf.head) + warnDeprecatedAndConflictingSettings(unitbuf.head) globalPhase = fromPhase while (globalPhase.hasNext && !reporter.hasErrors) { @@ -1502,14 +1484,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() @@ -1530,7 +1505,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() @@ -1674,33 +1649,17 @@ 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 } object Global { def apply(settings: Settings, reporter: Reporter): Global = new Global(settings, reporter) + + def apply(settings: Settings): Global = new Global(settings, reporter(settings)) + + private def reporter(settings: Settings): Reporter = { + //val loader = ScalaClassLoader(getClass.getClassLoader) // apply does not make delegate + val loader = new ClassLoader(getClass.getClassLoader) with ScalaClassLoader + loader.create[Reporter](settings.reporter.value, settings.errorFn)(settings) + } } diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala index a66ee572a9..e2cf49907b 100644 --- a/src/compiler/scala/tools/nsc/Main.scala +++ b/src/compiler/scala/tools/nsc/Main.scala @@ -17,7 +17,8 @@ class MainClass extends Driver with EvalLoop { new compiler.Run() compile command.files } - override def newCompiler(): Global = Global(settings, reporter) + override def newCompiler(): Global = Global(settings) + override def doCompile(compiler: Global) { if (settings.resident) resident(compiler) else super.doCompile(compiler) diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index e01c536ad1..5bdbf4bb6a 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. @@ -26,27 +26,30 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w protected def PerRunReporting = new PerRunReporting class PerRunReporting extends PerRunReportingBase { /** Collects for certain classes of warnings during this run. */ - private class ConditionalWarning(what: String, option: Settings#BooleanSetting)(reRunFlag: String = option.name) { + private class ConditionalWarning(what: String, doReport: () => Boolean, setting: Settings#Setting) { + def this(what: String, booleanSetting: Settings#BooleanSetting) { + this(what, () => booleanSetting, booleanSetting) + } val warnings = mutable.LinkedHashMap[Position, String]() def warn(pos: Position, msg: String) = - if (option) reporter.warning(pos, msg) + if (doReport()) reporter.warning(pos, msg) else if (!(warnings contains pos)) warnings += ((pos, msg)) def summarize() = - if (warnings.nonEmpty && (option.isDefault || option)) { + if (warnings.nonEmpty && (setting.isDefault || doReport())) { val numWarnings = warnings.size val warningVerb = if (numWarnings == 1) "was" else "were" val warningCount = countElementsAsString(numWarnings, s"$what warning") - reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with $reRunFlag for details") + reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with ${setting.name} for details") } } // This change broke sbt; I gave it the thrilling name of uncheckedWarnings0 so // as to recover uncheckedWarnings for its ever-fragile compiler interface. - private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)() - private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)() - private val _featureWarnings = new ConditionalWarning("feature", settings.feature)() - private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeActive) settings.YoptWarnings.name else settings.YinlinerWarnings.name) + private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation) + private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked) + private val _featureWarnings = new ConditionalWarning("feature", settings.feature) + private val _inlinerWarnings = new ConditionalWarning("inliner", () => !settings.YoptWarningsSummaryOnly, settings.YoptWarnings) private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings) // TODO: remove in favor of the overload that takes a Symbol, give that argument a default (NoSymbol) diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index 6442ef2d54..c70690e697 100644 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -129,25 +129,6 @@ trait DocComments { self: Global => getDocComment(sym) map getUseCases getOrElse List() } - private val wikiReplacements = List( - ("""(\n\s*\*?)(\s*\n)""" .r, """$1 <p>$2"""), - ("""<([^\w/])""" .r, """<$1"""), - ("""([^\w/])>""" .r, """$1>"""), - ("""\{\{\{(.*(?:\n.*)*)\}\}\}""".r, """<pre>$1</pre>"""), - ("""`([^`]*)`""" .r, """<code>$1</code>"""), - ("""__([^_]*)__""" .r, """<u>$1</u>"""), - ("""''([^']*)''""" .r, """<i>$1</i>"""), - ("""'''([^']*)'''""" .r, """<b>$1</b>"""), - ("""\^([^^]*)\^""" .r, """<sup>$1</sup>"""), - (""",,([^,]*),,""" .r, """<sub>$1</sub>""")) - - /** Returns just the wiki expansion (this would correspond to - * a comment in the input format of the JavaDoc tool, modulo differences - * in tags.) - */ - def expandWiki(str: String): String = - (str /: wikiReplacements) { (str1, regexRepl) => regexRepl._1 replaceAllIn(str1, regexRepl._2) } - private def getDocComment(sym: Symbol): Option[DocComment] = mapFind(sym :: allInheritedOverriddenSymbols(sym))(docComments get _) diff --git a/src/compiler/scala/tools/nsc/ast/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..0786ceb7c2 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -145,6 +145,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { override def mkCast(tree: Tree, pt: Type): Tree = { debuglog("casting " + tree + ":" + tree.tpe + " to " + pt + " at phase: " + phase) assert(!tree.tpe.isInstanceOf[MethodType], tree) + assert(!pt.isInstanceOf[MethodType], tree) assert(pt eq pt.normalize, tree +" : "+ debugString(pt) +" ~>"+ debugString(pt.normalize)) atPos(tree.pos) { mkAsInstanceOf(tree, pt, any = !phase.next.erasedTypes, wrapInApply = isAtPhaseAfter(currentRun.uncurryPhase)) @@ -242,11 +243,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, @@ -257,43 +261,77 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition) } - /** - * Create a method based on a Function - * - * Used both to under `-Ydelambdafy:method` create a lifted function and - * under `-Ydelambdafy:inline` to create the apply method on the anonymous - * class. - * - * It creates a method definition with value params cloned from the - * original lambda. Then it calls a supplied function to create - * the body and types the result. Finally - * everything is wrapped up in a DefDef - * - * @param owner The owner for the new method - * @param name name for the new method - * @param additionalFlags flags to be put on the method in addition to FINAL - */ - def mkMethodFromFunction(localTyper: analyzer.Typer) - (fun: Function, owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags) = { - val funParams = fun.vparams map (_.symbol) - val formals :+ restpe = fun.tpe.typeArgs + // Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type) + def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = { + // TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)` + val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol + if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)() + else { + val samMethType = fun.tpe memberInfo sam + mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType) + } + } + + // used to create the lifted method that holds a function's body + def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = + mkMethodForFunctionBody(localTyper)(owner, fun, nme.ANON_FUN_NAME)(additionalFlags = ARTIFACT) + + + /** + * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R] + * + * It creates a method definition with value params derived from the original lambda + * or `methParamProtos` (used to create the correct override for sam methods). + * + * Replace the `fun.vparams` symbols by the newly created method params, + * changes owner of `fun.body` from `fun.symbol` to resulting method's symbol. + * + * @param owner The owner for the new method + * @param fun the function to take the body from + * @param name name for the new method + * @param additionalFlags flags to be put on the method in addition to FINAL + */ + private def mkMethodForFunctionBody(localTyper: analyzer.Typer) + (owner: Symbol, fun: Function, name: TermName) + (methParamProtos: List[Symbol] = fun.vparams.map(_.symbol), + resTp: Type = functionResultType(fun.tpe), + additionalFlags: FlagSet = NoFlags): DefDef = { val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags) + // for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parmeter types) + val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) } + methSym setInfo MethodType(methParamSyms, resTp) - val paramSyms = map2(formals, fun.vparams) { - (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) - } + // we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms + val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms) + // we're now owned by the method that holds the body, and not the function + val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym) + + newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp)) + } - methSym setInfo MethodType(paramSyms, restpe.deconst) + // TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface) + def functionClassType(fun: Function): Type = + if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst) + else fun.tpe - fun.body.substituteSymbols(funParams, paramSyms) - fun.body changeOwner (fun.symbol -> methSym) + def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { + val parents = addSerializable(functionClassType(fun)) + val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation - val methDef = DefDef(methSym, fun.body) + // The original owner is used in the backend for the EnclosingMethod attribute. If fun is + // nested in a value-class method, its owner was already changed to the extension method. + // Saving the original owner allows getting the source structure from the class symbol. + defineOriginalOwner(anonClass, fun.symbol.originalOwner) + anonClass setInfo ClassInfoType(parents, newScope, anonClass) - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - methDef.tpt setType localTyper.packedType(fun.body, methSym).deconst - methDef + val samDef = mkMethodFromFunction(localTyper)(anonClass, fun) + anonClass.info.decls enter samDef.symbol + + localTyper.typedPos(fun.pos) { + Block( + ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos), + Typed(New(anonClass.tpe), TypeTree(fun.tpe))) + } } } 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 c04d305f9e..9c0174d89b 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._ @@ -665,6 +664,15 @@ self => } def isLiteral = isLiteralToken(in.token) + def isSimpleExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match { + case IDENTIFIER | BACKQUOTED_IDENT | + THIS | SUPER | NEW | USCORE | + LPAREN | LBRACE | XMLSTART => true + case _ => false + }) + + def isSimpleExprIntro: Boolean = isExprIntroToken(in.token) + def isExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match { case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER | IF | FOR | NEW | USCORE | TRY | WHILE | @@ -1616,11 +1624,14 @@ self => def prefixExpr(): Tree = { if (isUnaryOp) { atPos(in.offset) { - val name = nme.toUnaryName(rawIdent().toTermName) - if (name == nme.UNARY_- && isNumericLit) - simpleExprRest(literal(isNegated = true), canApply = true) - else - Select(stripParens(simpleExpr()), name) + if (lookingAhead(isSimpleExprIntro)) { + val uname = nme.toUnaryName(rawIdent().toTermName) + if (uname == nme.UNARY_- && isNumericLit) + simpleExprRest(literal(isNegated = true), canApply = true) + else + Select(stripParens(simpleExpr()), uname) + } + else simpleExpr() } } else simpleExpr() @@ -1702,9 +1713,7 @@ self => } simpleExprRest(app, canApply = true) case USCORE => - atPos(t.pos.start, in.skipToken()) { - Typed(stripParens(t), Function(Nil, EmptyTree)) - } + atPos(t.pos.start, in.skipToken()) { makeMethodValue(stripParens(t)) } case _ => t } @@ -2800,11 +2809,6 @@ self => if (mods.isTrait) (Modifiers(Flags.TRAIT), List()) else (accessModifierOpt(), paramClauses(name, classContextBounds, ofCaseClass = mods.isCase)) var mods1 = mods - if (mods.isTrait) { - if (settings.YvirtClasses && in.token == SUBTYPE) mods1 |= Flags.DEFERRED - } else if (in.token == SUBTYPE) { - syntaxError("classes are not allowed to be virtual", skipIt = false) - } val template = templateOpt(mods1, name, constrMods withAnnotations constrAnnots, vparamss, tstart) val result = gen.mkClassDef(mods1, name, tparams, template) // Context bounds generate implicit parameters (part of the template) with types diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index cd41c75298..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 @@ -515,7 +515,7 @@ trait Scanners extends ScannersCommon { charLitOr(getIdentRest) else if (isOperatorPart(ch) && (ch != '\\')) charLitOr(getOperatorRest) - else { + else if (!isAtEnd && (ch != SU && ch != CR && ch != LF || isUnicodeEscape)) { getLitChar() if (ch == '\'') { nextChar() @@ -525,6 +525,8 @@ trait Scanners extends ScannersCommon { syntaxError("unclosed character literal") } } + else + syntaxError("unclosed character literal") } fetchSingleQuote() case '.' => @@ -690,7 +692,7 @@ trait Scanners extends ScannersCommon { private def unclosedStringLit(): Unit = syntaxError("unclosed string literal") - private def getRawStringLit(): Unit = { + @tailrec private def getRawStringLit(): Unit = { if (ch == '\"') { nextRawChar() if (isTripleQuote()) { @@ -707,7 +709,7 @@ trait Scanners extends ScannersCommon { } } - @scala.annotation.tailrec private def getStringPart(multiLine: Boolean): Unit = { + @tailrec private def getStringPart(multiLine: Boolean): Unit = { def finishStringPart() = { setStrVal() token = STRINGPART diff --git a/src/compiler/scala/tools/nsc/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 cc9e39f430..1e9a1762eb 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 @@ -36,6 +35,9 @@ abstract class TreeBuilder { def repeatedApplication(tpe: Tree): Tree = AppliedTypeTree(rootScalaDot(tpnme.REPEATED_PARAM_CLASS_NAME), List(tpe)) + // represents `expr _`, as specified in Method Values of spec/06-expressions.md + def makeMethodValue(expr: Tree): Tree = Typed(expr, Function(Nil, EmptyTree)) + def makeImportSelector(name: Name, nameOffset: Int): ImportSelector = ImportSelector(name, nameOffset, name, nameOffset) diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 6bd123c51f..0e2f059a36 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -7,9 +7,9 @@ package scala.tools.nsc package backend import io.AbstractFile -import scala.tools.nsc.classpath.FlatClassPath +import scala.tools.nsc.classpath.{AggregateFlatClassPath, FlatClassPath} import scala.tools.nsc.settings.ClassPathRepresentationType -import scala.tools.nsc.util.{ ClassPath, DeltaClassPath, MergedClassPath } +import scala.tools.nsc.util.{ClassFileLookup, ClassPath, MergedClassPath} import scala.tools.util.FlatClassPathResolver import scala.tools.util.PathResolver @@ -29,24 +29,33 @@ trait JavaPlatform extends Platform { currentClassPath.get } - private[nsc] lazy val flatClassPath: FlatClassPath = { + private[nsc] var currentFlatClassPath: Option[FlatClassPath] = None + + private[nsc] def flatClassPath: FlatClassPath = { assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat, "To use flat classpath representation you must enable it with -YclasspathImpl:flat compiler option.") - new FlatClassPathResolver(settings).result + if (currentFlatClassPath.isEmpty) currentFlatClassPath = Some(new FlatClassPathResolver(settings).result) + currentFlatClassPath.get } /** Update classpath with a substituted subentry */ - def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) = - currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst)) + def updateClassPath(subst: Map[ClassFileLookup[AbstractFile], ClassFileLookup[AbstractFile]]) = global.classPath match { + case cp: ClassPath[AbstractFile] => + val s = subst.asInstanceOf[Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]] + currentClassPath = Some(new MergedClassPath(cp.entries map (e => s.getOrElse(e, e)), cp.context)) + + case AggregateFlatClassPath(entries) => + val s = subst.asInstanceOf[Map[FlatClassPath, FlatClassPath]] + currentFlatClassPath = Some(AggregateFlatClassPath(entries map (e => s.getOrElse(e, e)))) - private def classEmitPhase = - if (settings.isBCodeActive) genBCode - else genASM + case cp: FlatClassPath => + currentFlatClassPath = Some(subst.getOrElse(cp, cp).asInstanceOf[FlatClassPath]) + } def platformPhases = List( - flatten, // get rid of inner classes - classEmitPhase // generate .class files + flatten, // get rid of inner classes + genBCode // generate .class files ) lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_) diff --git a/src/compiler/scala/tools/nsc/backend/Platform.scala b/src/compiler/scala/tools/nsc/backend/Platform.scala index c3bc213be1..369bcc44ed 100644 --- a/src/compiler/scala/tools/nsc/backend/Platform.scala +++ b/src/compiler/scala/tools/nsc/backend/Platform.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package backend -import util.ClassPath +import util.{ClassFileLookup, ClassPath} import io.AbstractFile import scala.tools.nsc.classpath.FlatClassPath @@ -23,7 +23,7 @@ trait Platform { private[nsc] def flatClassPath: FlatClassPath /** Update classpath with a substitution that maps entries to entries */ - def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) + def updateClassPath(subst: Map[ClassFileLookup[AbstractFile], ClassFileLookup[AbstractFile]]) /** Any platform-specific phases. */ def platformPhases: List[SubComponent] diff --git a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala index b8ddb65de9..00771b6b8c 100644 --- a/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala +++ b/src/compiler/scala/tools/nsc/backend/ScalaPrimitives.scala @@ -7,7 +7,7 @@ package scala package tools.nsc package backend -import scala.collection.{ mutable, immutable } +import scala.collection.mutable /** Scala primitive operations are represented as methods in `Any` and * `AnyVal` subclasses. Here we demultiplex them by providing a mapping @@ -31,7 +31,6 @@ abstract class ScalaPrimitives { import global._ import definitions._ - import global.icodes._ // Arithmetic unary operations final val POS = 1 // +x @@ -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 b6f9bcc9ab..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ /dev/null @@ -1,2239 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - - -package scala -package tools.nsc -package backend -package icode - -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.{ ListBuffer, Buffer } -import scala.tools.nsc.symtab._ -import scala.annotation.switch - -/** - * @author Iulian Dragos - * @version 1.0 - */ -abstract class GenICode extends SubComponent { - import global._ - import icodes._ - import icodes.opcodes._ - import definitions._ - import scalaPrimitives.{ - isArrayOp, isComparisonOp, isLogicalOp, - isUniversalEqualityOp, isReferenceEqualityOp - } - import platform.isMaybeBoxed - - private val bCodeICodeCommon: jvm.BCodeICodeCommon[global.type] = new jvm.BCodeICodeCommon(global) - import bCodeICodeCommon._ - - val phaseName = "icode" - - override def newPhase(prev: Phase) = new ICodePhase(prev) - - @inline private def debugassert(cond: => Boolean, msg: => Any) { - if (settings.debug) - assert(cond, msg) - } - - class ICodePhase(prev: Phase) extends StdPhase(prev) { - - override def description = "Generate ICode from the AST" - - var unit: CompilationUnit = NoCompilationUnit - - override def run() { - if (!settings.isBCodeActive) { - scalaPrimitives.init() - classes.clear() - } - super.run() - } - - override def apply(unit: CompilationUnit): Unit = { - if (settings.isBCodeActive) { return } - this.unit = unit - unit.icode.clear() - informProgress("Generating icode for " + unit) - gen(unit.body) - this.unit = NoCompilationUnit - } - - def gen(tree: Tree): Context = gen(tree, new Context()) - - def gen(trees: List[Tree], ctx: Context): Context = { - var ctx1 = ctx - for (t <- trees) ctx1 = gen(t, ctx1) - ctx1 - } - - /** If the selector type has a member with the right name, - * it is the host class; otherwise the symbol's owner. - */ - def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match { - case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner - case _ => selector.typeSymbol - } - - /////////////////// Code generation /////////////////////// - - def gen(tree: Tree, ctx: Context): Context = tree match { - case EmptyTree => ctx - - case PackageDef(pid, stats) => - gen(stats, ctx setPackage pid.name) - - case ClassDef(mods, name, _, impl) => - debuglog("Generating class: " + tree.symbol.fullName) - val outerClass = ctx.clazz - ctx setClass (new IClass(tree.symbol) setCompilationUnit unit) - addClassFields(ctx, tree.symbol) - classes += (tree.symbol -> ctx.clazz) - unit.icode += ctx.clazz - gen(impl, ctx) - ctx.clazz.methods = ctx.clazz.methods.reverse // preserve textual order - ctx.clazz.fields = ctx.clazz.fields.reverse // preserve textual order - ctx setClass outerClass - - // !! modules should be eliminated by refcheck... or not? - case ModuleDef(mods, name, impl) => - abort("Modules should not reach backend! " + tree) - - case ValDef(mods, name, tpt, rhs) => - ctx // we use the symbol to add fields - - case DefDef(mods, name, tparams, vparamss, tpt, rhs) => - debuglog("Entering method " + name) - val m = new IMethod(tree.symbol) - m.sourceFile = unit.source - m.returnType = if (tree.symbol.isConstructor) UNIT - else toTypeKind(tree.symbol.info.resultType) - ctx.clazz.addMethod(m) - - var ctx1 = ctx.enterMethod(m, tree.asInstanceOf[DefDef]) - addMethodParams(ctx1, vparamss) - m.native = m.symbol.hasAnnotation(definitions.NativeAttr) - - if (!m.isAbstractMethod && !m.native) { - ctx1 = genLoad(rhs, ctx1, m.returnType) - - // reverse the order of the local variables, to match the source-order - m.locals = m.locals.reverse - - rhs match { - case Block(_, Return(_)) => () - case Return(_) => () - case EmptyTree => - globalError("Concrete method has no definition: " + tree + ( - if (settings.debug) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")" - else "") - ) - case _ => if (ctx1.bb.isEmpty) - ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos) - else - ctx1.bb.closeWith(RETURN(m.returnType)) - } - if (!ctx1.bb.closed) ctx1.bb.close() - prune(ctx1.method) - } else - ctx1.method.setCode(NoCode) - ctx1 - - case Template(_, _, body) => - gen(body, ctx) - - case _ => - abort("Illegal tree in gen: " + tree) - } - - private def genStat(trees: List[Tree], ctx: Context): Context = - trees.foldLeft(ctx)((currentCtx, t) => genStat(t, currentCtx)) - - /** - * Generate code for the given tree. The trees should contain statements - * and not produce any value. Use genLoad for expressions which leave - * a value on top of the stack. - * - * @return a new context. This is necessary for control flow instructions - * which may change the current basic block. - */ - private def genStat(tree: Tree, ctx: Context): Context = tree match { - case Assign(lhs @ Select(_, _), rhs) => - val isStatic = lhs.symbol.isStaticMember - var ctx1 = if (isStatic) ctx else genLoadQualifier(lhs, ctx) - - ctx1 = genLoad(rhs, ctx1, toTypeKind(lhs.symbol.info)) - ctx1.bb.emit(STORE_FIELD(lhs.symbol, isStatic), tree.pos) - ctx1 - - case Assign(lhs, rhs) => - val ctx1 = genLoad(rhs, ctx, toTypeKind(lhs.symbol.info)) - val Some(l) = ctx.method.lookupLocal(lhs.symbol) - ctx1.bb.emit(STORE_LOCAL(l), tree.pos) - ctx1 - - case _ => - genLoad(tree, ctx, UNIT) - } - - private def genThrow(expr: Tree, ctx: Context): (Context, TypeKind) = { - require(expr.tpe <:< ThrowableTpe, expr.tpe) - - val thrownKind = toTypeKind(expr.tpe) - val ctx1 = genLoad(expr, ctx, thrownKind) - ctx1.bb.emit(THROW(expr.tpe.typeSymbol), expr.pos) - ctx1.bb.enterIgnoreMode() - - (ctx1, NothingReference) - } - - /** - * Generate code for primitive arithmetic operations. - * Returns (Context, Generated Type) - */ - private def genArithmeticOp(tree: Tree, ctx: Context, code: Int): (Context, TypeKind) = { - val Apply(fun @ Select(larg, _), args) = tree - var ctx1 = ctx - var resKind = toTypeKind(larg.tpe) - - debugassert(args.length <= 1, - "Too many arguments for primitive function: " + fun.symbol) - debugassert(resKind.isNumericType | resKind == BOOL, - resKind.toString() + " is not a numeric or boolean type " + - "[operation: " + fun.symbol + "]") - - args match { - // unary operation - case Nil => - ctx1 = genLoad(larg, ctx1, resKind) - code match { - case scalaPrimitives.POS => - () // nothing - case scalaPrimitives.NEG => - ctx1.bb.emit(CALL_PRIMITIVE(Negation(resKind)), larg.pos) - case scalaPrimitives.NOT => - ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(NOT, resKind)), larg.pos) - case _ => - abort("Unknown unary operation: " + fun.symbol.fullName + - " code: " + code) - } - - // binary operation - case rarg :: Nil => - resKind = getMaxType(larg.tpe :: rarg.tpe :: Nil) - if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) - assert(resKind.isIntegralType | resKind == BOOL, - resKind.toString() + " incompatible with arithmetic modulo operation: " + ctx1) - - ctx1 = genLoad(larg, ctx1, resKind) - ctx1 = genLoad(rarg, - ctx1, // check .NET size of shift arguments! - if (scalaPrimitives.isShiftOp(code)) INT else resKind) - - val primitiveOp = code match { - case scalaPrimitives.ADD => Arithmetic(ADD, resKind) - case scalaPrimitives.SUB => Arithmetic(SUB, resKind) - case scalaPrimitives.MUL => Arithmetic(MUL, resKind) - case scalaPrimitives.DIV => Arithmetic(DIV, resKind) - case scalaPrimitives.MOD => Arithmetic(REM, resKind) - case scalaPrimitives.OR => Logical(OR, resKind) - case scalaPrimitives.XOR => Logical(XOR, resKind) - case scalaPrimitives.AND => Logical(AND, resKind) - case scalaPrimitives.LSL => Shift(LSL, resKind) - case scalaPrimitives.LSR => Shift(LSR, resKind) - case scalaPrimitives.ASR => Shift(ASR, resKind) - case _ => abort("Unknown primitive: " + fun.symbol + "[" + code + "]") - } - ctx1.bb.emit(CALL_PRIMITIVE(primitiveOp), tree.pos) - - case _ => - abort("Too many arguments for primitive function: " + tree) - } - (ctx1, resKind) - } - - /** Generate primitive array operations. - */ - private def genArrayOp(tree: Tree, ctx: Context, code: Int, expectedType: TypeKind): (Context, TypeKind) = { - import scalaPrimitives._ - val Apply(Select(arrayObj, _), args) = tree - val k = toTypeKind(arrayObj.tpe) - val ARRAY(elem) = k - var ctx1 = genLoad(arrayObj, ctx, k) - val elementType = typeOfArrayOp.getOrElse(code, abort("Unknown operation on arrays: " + tree + " code: " + code)) - - var generatedType = expectedType - - if (scalaPrimitives.isArrayGet(code)) { - // load argument on stack - debugassert(args.length == 1, - "Too many arguments for array get operation: " + tree) - ctx1 = genLoad(args.head, ctx1, INT) - generatedType = elem - ctx1.bb.emit(LOAD_ARRAY_ITEM(elementType), tree.pos) - // it's tempting to just drop array loads of type Null instead - // of adapting them but array accesses can cause - // ArrayIndexOutOfBounds so we can't. Besides, Array[Null] - // probably isn't common enough to figure out an optimization - adaptNullRef(generatedType, expectedType, ctx1, tree.pos) - } - else if (scalaPrimitives.isArraySet(code)) { - debugassert(args.length == 2, - "Too many arguments for array set operation: " + tree) - ctx1 = genLoad(args.head, ctx1, INT) - ctx1 = genLoad(args.tail.head, ctx1, toTypeKind(args.tail.head.tpe)) - // the following line should really be here, but because of bugs in erasure - // we pretend we generate whatever type is expected from us. - //generatedType = UNIT - - ctx1.bb.emit(STORE_ARRAY_ITEM(elementType), tree.pos) - } - else { - generatedType = INT - ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(elementType)), tree.pos) - } - - (ctx1, generatedType) - } - private def genSynchronized(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = { - val Apply(fun, args) = tree - val monitor = ctx.makeLocal(tree.pos, ObjectTpe, "monitor") - var monitorResult: Local = null - val argTpe = args.head.tpe - val hasResult = expectedType != UNIT - if (hasResult) - monitorResult = ctx.makeLocal(tree.pos, argTpe, "monitorResult") - - var ctx1 = genLoadQualifier(fun, ctx) - ctx1.bb.emit(Seq( - DUP(ObjectReference), - STORE_LOCAL(monitor), - MONITOR_ENTER() setPos tree.pos - )) - ctx1.enterSynchronized(monitor) - debuglog("synchronized block start") - - ctx1 = ctx1.Try( - bodyCtx => { - val ctx2 = genLoad(args.head, bodyCtx, expectedType /* toTypeKind(tree.tpe.resultType) */) - if (hasResult) - ctx2.bb.emit(STORE_LOCAL(monitorResult)) - ctx2.bb.emit(Seq( - LOAD_LOCAL(monitor), - MONITOR_EXIT() setPos tree.pos - )) - ctx2 - }, List( - // tree.tpe / fun.tpe is object, which is no longer true after this transformation - (ThrowableClass, expectedType, exhCtx => { - exhCtx.bb.emit(Seq( - LOAD_LOCAL(monitor), - MONITOR_EXIT() setPos tree.pos, - THROW(ThrowableClass) - )) - exhCtx.bb.enterIgnoreMode() - exhCtx - })), EmptyTree, tree) - - debuglog("synchronized block end with block %s closed=%s".format(ctx1.bb, ctx1.bb.closed)) - ctx1.exitSynchronized(monitor) - if (hasResult) - ctx1.bb.emit(LOAD_LOCAL(monitorResult)) - (ctx1, expectedType) - } - - private def genLoadIf(tree: If, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = { - val If(cond, thenp, elsep) = tree - - var thenCtx = ctx.newBlock() - var elseCtx = ctx.newBlock() - val contCtx = ctx.newBlock() - - genCond(cond, ctx, thenCtx, elseCtx) - - val ifKind = toTypeKind(tree.tpe) - val thenKind = toTypeKind(thenp.tpe) - val elseKind = if (elsep == EmptyTree) UNIT else toTypeKind(elsep.tpe) - - // we need to drop unneeded results, if one branch gives - // unit and the other gives something on the stack, because - // the type of 'if' is scala.Any, and its erasure would be Object. - // But unboxed units are not Objects... - def hasUnitBranch = thenKind == UNIT || elseKind == UNIT - val resKind = if (hasUnitBranch) UNIT else ifKind - - if (hasUnitBranch) - debuglog("Will drop result from an if branch") - - thenCtx = genLoad(thenp, thenCtx, resKind) - elseCtx = genLoad(elsep, elseCtx, resKind) - - debugassert(!hasUnitBranch || expectedType == UNIT, - "I produce UNIT in a context where " + expectedType + " is expected!") - - // alternatives may be already closed by a tail-recursive jump - val contReachable = !(thenCtx.bb.ignore && elseCtx.bb.ignore) - thenCtx.bb.closeWith(JUMP(contCtx.bb)) - elseCtx.bb.closeWith( - if (elsep == EmptyTree) JUMP(contCtx.bb) - else JUMP(contCtx.bb) setPos tree.pos - ) - - contCtx.bb killUnless contReachable - (contCtx, resKind) - } - private def genLoadTry(tree: Try, ctx: Context, setGeneratedType: TypeKind => Unit): Context = { - val Try(block, catches, finalizer) = tree - val kind = toTypeKind(tree.tpe) - - val caseHandlers = - for (CaseDef(pat, _, body) <- catches.reverse) yield { - def genWildcardHandler(sym: Symbol): (Symbol, TypeKind, Context => Context) = - (sym, kind, ctx => { - ctx.bb.emit(DROP(REFERENCE(sym))) // drop the loaded exception - genLoad(body, ctx, kind) - }) - - pat match { - case Typed(Ident(nme.WILDCARD), tpt) => genWildcardHandler(tpt.tpe.typeSymbol) - case Ident(nme.WILDCARD) => genWildcardHandler(ThrowableClass) - case Bind(_, _) => - val exception = ctx.method addLocal new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false) // the exception will be loaded and stored into this local - - (pat.symbol.tpe.typeSymbol, kind, { - ctx: Context => - ctx.bb.emit(STORE_LOCAL(exception), pat.pos) - genLoad(body, ctx, kind) - }) - } - } - - ctx.Try( - bodyCtx => { - setGeneratedType(kind) - genLoad(block, bodyCtx, kind) - }, - caseHandlers, - finalizer, - tree) - } - - private def genPrimitiveOp(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = { - val sym = tree.symbol - val Apply(fun @ Select(receiver, _), _) = tree - val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) - - if (scalaPrimitives.isArithmeticOp(code)) - genArithmeticOp(tree, ctx, code) - else if (code == scalaPrimitives.CONCAT) - (genStringConcat(tree, ctx), StringReference) - else if (code == scalaPrimitives.HASH) - (genScalaHash(receiver, ctx), INT) - else if (isArrayOp(code)) - genArrayOp(tree, ctx, code, expectedType) - else if (isLogicalOp(code) || isComparisonOp(code)) { - val trueCtx, falseCtx, afterCtx = ctx.newBlock() - - genCond(tree, ctx, trueCtx, falseCtx) - trueCtx.bb.emitOnly( - CONSTANT(Constant(true)) setPos tree.pos, - JUMP(afterCtx.bb) - ) - falseCtx.bb.emitOnly( - CONSTANT(Constant(false)) setPos tree.pos, - JUMP(afterCtx.bb) - ) - (afterCtx, BOOL) - } - else if (code == scalaPrimitives.SYNCHRONIZED) - genSynchronized(tree, ctx, expectedType) - else if (scalaPrimitives.isCoercion(code)) { - val ctx1 = genLoad(receiver, ctx, toTypeKind(receiver.tpe)) - genCoercion(tree, ctx1, code) - (ctx1, scalaPrimitives.generatedKind(code)) - } - else abort( - "Primitive operation not handled yet: " + sym.fullName + "(" + - fun.symbol.simpleName + ") " + " at: " + (tree.pos) - ) - } - - /** - * Generate code for trees that produce values on the stack - * - * @param tree The tree to be translated - * @param ctx The current context - * @param expectedType The type of the value to be generated on top of the - * stack. - * @return The new context. The only thing that may change is the current - * basic block (as the labels map is mutable). - */ - private def genLoad(tree: Tree, ctx: Context, expectedType: TypeKind): Context = { - var generatedType = expectedType - debuglog("at line: " + (if (tree.pos.isDefined) tree.pos.line else tree.pos)) - - val resCtx: Context = tree match { - case LabelDef(name, params, rhs) => - def genLoadLabelDef = { - val ctx1 = ctx.newBlock() // note: we cannot kill ctx1 if ctx is in ignore mode because - // label defs can be the target of jumps from other locations. - // that means label defs can lead to unreachable code without - // proper reachability analysis - - if (nme.isLoopHeaderLabel(name)) - ctx1.bb.loopHeader = true - - ctx1.labels.get(tree.symbol) match { - case Some(label) => - debuglog("Found existing label for " + tree.symbol.fullLocationString) - label.anchor(ctx1.bb) - label.patch(ctx.method.code) - - case None => - val pair = (tree.symbol -> (new Label(tree.symbol) anchor ctx1.bb setParams (params map (_.symbol)))) - debuglog("Adding label " + tree.symbol.fullLocationString + " in genLoad.") - ctx1.labels += pair - ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))) - } - - ctx.bb.closeWith(JUMP(ctx1.bb), tree.pos) - genLoad(rhs, ctx1, expectedType /*toTypeKind(tree.symbol.info.resultType)*/) - } - genLoadLabelDef - - case ValDef(_, name, _, rhs) => - def genLoadValDef = - if (name == nme.THIS) { - debuglog("skipping trivial assign to _$this: " + tree) - ctx - } else { - val sym = tree.symbol - val local = ctx.method.addLocal(new Local(sym, toTypeKind(sym.info), false)) - - if (rhs == EmptyTree) { - debuglog("Uninitialized variable " + tree + " at: " + (tree.pos)) - ctx.bb.emit(getZeroOf(local.kind)) - } - - var ctx1 = ctx - if (rhs != EmptyTree) - ctx1 = genLoad(rhs, ctx, local.kind) - - ctx1.bb.emit(STORE_LOCAL(local), tree.pos) - ctx1.scope.add(local) - ctx1.bb.emit(SCOPE_ENTER(local)) - generatedType = UNIT - ctx1 - } - genLoadValDef - - case t @ If(cond, thenp, elsep) => - val (newCtx, resKind) = genLoadIf(t, ctx, expectedType) - generatedType = resKind - newCtx - - case Return(expr) => - def genLoadReturn = { - val returnedKind = toTypeKind(expr.tpe) - debuglog("Return(" + expr + ") with returnedKind = " + returnedKind) - - var ctx1 = genLoad(expr, ctx, returnedKind) - lazy val tmp = ctx1.makeLocal(tree.pos, expr.tpe, "tmp") - val saved = savingCleanups(ctx1) { - var savedFinalizer = false - ctx1.cleanups foreach { - case MonitorRelease(m) => - debuglog("removing " + m + " from cleanups: " + ctx1.cleanups) - ctx1.bb.emit(Seq(LOAD_LOCAL(m), MONITOR_EXIT())) - ctx1.exitSynchronized(m) - - case Finalizer(f, finalizerCtx) => - debuglog("removing " + f + " from cleanups: " + ctx1.cleanups) - if (returnedKind != UNIT && mayCleanStack(f)) { - log("Emitting STORE_LOCAL for " + tmp + " to save finalizer.") - ctx1.bb.emit(STORE_LOCAL(tmp)) - savedFinalizer = true - } - - // duplicate finalizer (takes care of anchored labels) - val f1 = duplicateFinalizer(Set.empty ++ ctx1.labels.keySet, ctx1, f) - - // we have to run this without the same finalizer in - // the list, otherwise infinite recursion happens for - // finalizers that contain 'return' - val fctx = finalizerCtx.newBlock() - fctx.bb killIf ctx1.bb.ignore - ctx1.bb.closeWith(JUMP(fctx.bb)) - ctx1 = genLoad(f1, fctx, UNIT) - } - savedFinalizer - } - - if (saved) { - log("Emitting LOAD_LOCAL for " + tmp + " after saving finalizer.") - ctx1.bb.emit(LOAD_LOCAL(tmp)) - } - adapt(returnedKind, ctx1.method.returnType, ctx1, tree.pos) - ctx1.bb.emit(RETURN(ctx.method.returnType), tree.pos) - ctx1.bb.enterIgnoreMode() - generatedType = expectedType - ctx1 - } - genLoadReturn - - case t @ Try(_, _, _) => - genLoadTry(t, ctx, generatedType = _) - - case Throw(expr) => - val (ctx1, expectedType) = genThrow(expr, ctx) - generatedType = expectedType - ctx1 - - case New(tpt) => - abort("Unexpected New(" + tpt.summaryString + "/" + tpt + ") received in icode.\n" + - " Call was genLoad" + ((tree, ctx, expectedType))) - - case Apply(TypeApply(fun, targs), _) => - def genLoadApply1 = { - val sym = fun.symbol - val cast = sym match { - case Object_isInstanceOf => false - case Object_asInstanceOf => true - case _ => abort("Unexpected type application " + fun + "[sym: " + sym.fullName + "]" + " in: " + tree) - } - - val Select(obj, _) = fun - val l = toTypeKind(obj.tpe) - val r = toTypeKind(targs.head.tpe) - val ctx1 = genLoadQualifier(fun, ctx) - - if (l.isValueType && r.isValueType) - genConversion(l, r, ctx1, cast) - else if (l.isValueType) { - ctx1.bb.emit(DROP(l), fun.pos) - if (cast) { - ctx1.bb.emit(Seq( - NEW(REFERENCE(definitions.ClassCastExceptionClass)), - DUP(ObjectReference), - THROW(definitions.ClassCastExceptionClass) - )) - } else - ctx1.bb.emit(CONSTANT(Constant(false))) - } else if (r.isValueType && cast) { - /* Erasure should have added an unboxing operation to prevent that. */ - abort("should have been unboxed by erasure: " + tree) - } else if (r.isValueType) { - ctx.bb.emit(IS_INSTANCE(REFERENCE(definitions.boxedClass(r.toType.typeSymbol)))) - } else { - genCast(l, r, ctx1, cast) - } - generatedType = if (cast) r else BOOL - ctx1 - } - genLoadApply1 - - // 'super' call: Note: since constructors are supposed to - // return an instance of what they construct, we have to take - // special care. On JVM they are 'void', and Scala forbids (syntactically) - // to call super constructors explicitly and/or use their 'returned' value. - // therefore, we can ignore this fact, and generate code that leaves nothing - // on the stack (contrary to what the type in the AST says). - case Apply(fun @ Select(Super(_, mix), _), args) => - def genLoadApply2 = { - debuglog("Call to super: " + tree) - val invokeStyle = SuperCall(mix) - // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); - - ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos) - val ctx1 = genLoadArguments(args, fun.symbol.info.paramTypes, ctx) - - ctx1.bb.emit(CALL_METHOD(fun.symbol, invokeStyle), tree.pos) - generatedType = - if (fun.symbol.isConstructor) UNIT - else toTypeKind(fun.symbol.info.resultType) - ctx1 - } - genLoadApply2 - - // 'new' constructor call: Note: since constructors are - // thought to return an instance of what they construct, - // we have to 'simulate' it by DUPlicating the freshly created - // instance (on JVM, <init> methods return VOID). - case Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) => - def genLoadApply3 = { - val ctor = fun.symbol - debugassert(ctor.isClassConstructor, - "'new' call to non-constructor: " + ctor.name) - - generatedType = toTypeKind(tpt.tpe) - debugassert(generatedType.isReferenceType || generatedType.isArrayType, - "Non reference type cannot be instantiated: " + generatedType) - - generatedType match { - case arr @ ARRAY(elem) => - val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) - val dims = arr.dimensions - var elemKind = arr.elementKind - if (args.length > dims) - reporter.error(tree.pos, "too many arguments for array constructor: found " + args.length + - " but array has only " + dims + " dimension(s)") - if (args.length != dims) - for (i <- args.length until dims) elemKind = ARRAY(elemKind) - ctx1.bb.emit(CREATE_ARRAY(elemKind, args.length), tree.pos) - ctx1 - - case rt @ REFERENCE(cls) => - debugassert(ctor.owner == cls, - "Symbol " + ctor.owner.fullName + " is different than " + tpt) - - val nw = NEW(rt) - ctx.bb.emit(nw, tree.pos) - ctx.bb.emit(DUP(generatedType)) - val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) - - val init = CALL_METHOD(ctor, Static(onInstance = true)) - nw.init = init - ctx1.bb.emit(init, tree.pos) - ctx1 - case _ => - abort("Cannot instantiate " + tpt + " of kind: " + generatedType) - } - } - genLoadApply3 - - case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => - def genLoadApply4 = { - debuglog("BOX : " + fun.symbol.fullName) - val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe)) - val nativeKind = toTypeKind(expr.tpe) - if (settings.Xdce) { - // we store this boxed value to a local, even if not really needed. - // boxing optimization might use it, and dead code elimination will - // take care of unnecessary stores - val loc1 = ctx.makeLocal(tree.pos, expr.tpe, "boxed") - ctx1.bb.emit(STORE_LOCAL(loc1)) - ctx1.bb.emit(LOAD_LOCAL(loc1)) - } - ctx1.bb.emit(BOX(nativeKind), expr.pos) - generatedType = toTypeKind(fun.symbol.tpe.resultType) - ctx1 - } - genLoadApply4 - - case Apply(fun @ _, List(expr)) if (currentRun.runDefinitions.isUnbox(fun.symbol)) => - debuglog("UNBOX : " + fun.symbol.fullName) - val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe)) - val boxType = toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) - generatedType = boxType - ctx1.bb.emit(UNBOX(boxType), expr.pos) - ctx1 - - case app @ Apply(fun, args) => - def genLoadApply6 = { - val sym = fun.symbol - - if (sym.isLabel) { // jump to a label - val label = ctx.labels.getOrElse(sym, { - // it is a forward jump, scan for labels - resolveForwardLabel(ctx.defdef, ctx, sym) - ctx.labels.get(sym) match { - case Some(l) => - debuglog("Forward jump for " + sym.fullLocationString + ": scan found label " + l) - l - case _ => - abort("Unknown label target: " + sym + " at: " + (fun.pos) + ": ctx: " + ctx) - } - }) - // note: when one of the args to genLoadLabelArguments is a jump to a label, - // it will call back into genLoad and arrive at this case, which will then set ctx1.bb.ignore to true, - // this is okay, since we're jumping unconditionally, so the loads and jumps emitted by the outer - // call to genLoad (by calling genLoadLabelArguments and emitOnly) can safely be ignored, - // however, as emitOnly will close the block, which reverses its instructions (when it's still open), - // we better not reverse when the block has already been closed but is in ignore mode - // (if it's not in ignore mode, double-closing is an error) - val ctx1 = genLoadLabelArguments(args, label, ctx) - ctx1.bb.emitOnly(if (label.anchored) JUMP(label.block) else PJUMP(label)) - ctx1.bb.enterIgnoreMode() - ctx1 - } else if (isPrimitive(sym)) { // primitive method call - val (newCtx, resKind) = genPrimitiveOp(app, ctx, expectedType) - generatedType = resKind - newCtx - } else { // normal method call - debuglog("Gen CALL_METHOD with sym: " + sym + " isStaticSymbol: " + sym.isStaticMember) - val invokeStyle = - if (sym.isStaticMember) - Static(onInstance = false) - else if (sym.isPrivate || sym.isClassConstructor) - Static(onInstance = true) - else - Dynamic - - var ctx1 = if (invokeStyle.hasInstance) genLoadQualifier(fun, ctx) else ctx - ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx1) - val cm = CALL_METHOD(sym, invokeStyle) - - /* In a couple cases, squirrel away a little extra information in the - * CALL_METHOD for use by GenASM. - */ - fun match { - case Select(qual, _) => - val qualSym = findHostClass(qual.tpe, sym) - if (qualSym == ArrayClass) { - val kind = toTypeKind(qual.tpe) - cm setTargetTypeKind kind - log(s"Stored target type kind for {$sym.fullName} as $kind") - } - else { - cm setHostClass qualSym - if (qual.tpe.typeSymbol != qualSym) - log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}") - } - case _ => - } - ctx1.bb.emit(cm, tree.pos) - ctx1.method.updateRecursive(sym) - generatedType = - if (sym.isClassConstructor) UNIT - else toTypeKind(sym.info.resultType) - // deal with methods that return Null - adaptNullRef(generatedType, expectedType, ctx1, tree.pos) - ctx1 - } - } - genLoadApply6 - - case ApplyDynamic(qual, args) => - // TODO - this is where we'd catch dynamic applies for invokedynamic. - sys.error("No invokedynamic support yet.") - // val ctx1 = genLoad(qual, ctx, ObjectReference) - // genLoadArguments(args, tree.symbol.info.paramTypes, ctx1) - // ctx1.bb.emit(CALL_METHOD(tree.symbol, InvokeDynamic), tree.pos) - // ctx1 - - case This(qual) => - def genLoadThis = { - assert(tree.symbol == ctx.clazz.symbol || tree.symbol.isModuleClass, - "Trying to access the this of another class: " + - "tree.symbol = " + tree.symbol + ", ctx.clazz.symbol = " + ctx.clazz.symbol + " compilation unit:"+unit) - if (tree.symbol.isModuleClass && tree.symbol != ctx.clazz.symbol) { - genLoadModule(ctx, tree) - generatedType = REFERENCE(tree.symbol) - } else { - ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos) - generatedType = REFERENCE( - if (tree.symbol == ArrayClass) ObjectClass else ctx.clazz.symbol - ) - } - ctx - } - genLoadThis - - case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => - debugassert(tree.symbol.isModule, - "Selection of non-module from empty package: " + tree + - " sym: " + tree.symbol + " at: " + (tree.pos) - ) - genLoadModule(ctx, tree) - - case Select(qualifier, selector) => - def genLoadSelect = { - val sym = tree.symbol - generatedType = toTypeKind(sym.info) - val hostClass = findHostClass(qualifier.tpe, sym) - debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass") - val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier - - def genLoadQualUnlessElidable: Context = - if (qualSafeToElide) ctx else genLoadQualifier(tree, ctx) - - if (sym.isModule) { - genLoadModule(genLoadQualUnlessElidable, tree) - } else { - val isStatic = sym.isStaticMember - val ctx1 = if (isStatic) genLoadQualUnlessElidable - else genLoadQualifier(tree, ctx) - ctx1.bb.emit(LOAD_FIELD(sym, isStatic) setHostClass hostClass, tree.pos) - // it's tempting to drop field accesses of type Null instead of adapting them, - // but field access can cause static class init so we can't. Besides, fields - // of type Null probably aren't common enough to figure out an optimization - adaptNullRef(generatedType, expectedType, ctx1, tree.pos) - ctx1 - } - } - genLoadSelect - - case Ident(name) => - def genLoadIdent = { - val sym = tree.symbol - if (!sym.hasPackageFlag) { - if (sym.isModule) { - genLoadModule(ctx, tree) - generatedType = toTypeKind(sym.info) - } else { - ctx.method.lookupLocal(sym) match { - case Some(l) => - ctx.bb.emit(LOAD_LOCAL(l), tree.pos) - generatedType = l.kind - case None => - val saved = settings.uniqid - settings.uniqid.value = true - try { - val methodCode = unit.body.collect { case dd: DefDef - if dd.symbol == ctx.method.symbol => showCode(dd); - }.headOption.getOrElse("<unknown>") - abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}. \nMethod code: $methodCode") - } - finally settings.uniqid.value = saved - } - } - } - ctx - } - genLoadIdent - - case Literal(value) => - def genLoadLiteral = { - if (value.tag != UnitTag) (value.tag, expectedType) match { - case (IntTag, LONG) => - ctx.bb.emit(CONSTANT(Constant(value.longValue)), tree.pos) - generatedType = LONG - case (FloatTag, DOUBLE) => - ctx.bb.emit(CONSTANT(Constant(value.doubleValue)), tree.pos) - generatedType = DOUBLE - case (NullTag, _) => - ctx.bb.emit(CONSTANT(value), tree.pos) - generatedType = NullReference - case _ => - ctx.bb.emit(CONSTANT(value), tree.pos) - generatedType = toTypeKind(tree.tpe) - } - ctx - } - genLoadLiteral - - case Block(stats, expr) => - ctx.enterScope() - var ctx1 = genStat(stats, ctx) - ctx1 = genLoad(expr, ctx1, expectedType) - ctx1.exitScope() - ctx1 - - case Typed(Super(_, _), _) => - genLoad(This(ctx.clazz.symbol), ctx, expectedType) - - case Typed(expr, _) => - genLoad(expr, ctx, expectedType) - - case Assign(_, _) => - generatedType = UNIT - genStat(tree, ctx) - - case ArrayValue(tpt @ TypeTree(), _elems) => - def genLoadArrayValue = { - var ctx1 = ctx - val elmKind = toTypeKind(tpt.tpe) - generatedType = ARRAY(elmKind) - val elems = _elems.toIndexedSeq - - ctx1.bb.emit(CONSTANT(new Constant(elems.length)), tree.pos) - ctx1.bb.emit(CREATE_ARRAY(elmKind, 1)) - // inline array literals - var i = 0 - while (i < elems.length) { - ctx1.bb.emit(DUP(generatedType), tree.pos) - ctx1.bb.emit(CONSTANT(new Constant(i))) - ctx1 = genLoad(elems(i), ctx1, elmKind) - ctx1.bb.emit(STORE_ARRAY_ITEM(elmKind)) - i = i + 1 - } - ctx1 - } - genLoadArrayValue - - case Match(selector, cases) => - def genLoadMatch = { - debuglog("Generating SWITCH statement.") - val ctx1 = genLoad(selector, ctx, INT) // TODO: Java 7 allows strings in switches (so, don't assume INT and don't convert the literals using intValue) - val afterCtx = ctx1.newBlock() - afterCtx.bb killIf ctx1.bb.ignore - var afterCtxReachable = false - var caseCtx: Context = null - generatedType = toTypeKind(tree.tpe) - - var targets: List[BasicBlock] = Nil - var tags: List[Int] = Nil - var default: BasicBlock = afterCtx.bb - - for (caze @ CaseDef(pat, guard, body) <- cases) { - assert(guard == EmptyTree, guard) - val tmpCtx = ctx1.newBlock() - tmpCtx.bb killIf ctx1.bb.ignore - pat match { - case Literal(value) => - tags = value.intValue :: tags - targets = tmpCtx.bb :: targets - case Ident(nme.WILDCARD) => - default = tmpCtx.bb - case Alternative(alts) => - alts foreach { - case Literal(value) => - tags = value.intValue :: tags - targets = tmpCtx.bb :: targets - case _ => - abort("Invalid case in alternative in switch-like pattern match: " + - tree + " at: " + tree.pos) - } - case _ => - abort("Invalid case statement in switch-like pattern match: " + - tree + " at: " + (tree.pos)) - } - - caseCtx = genLoad(body, tmpCtx, generatedType) - afterCtxReachable ||= !caseCtx.bb.ignore - // close the block unless it's already been closed by the body, which closes the block if it ends in a jump (which is emitted to have alternatives share their body) - caseCtx.bb.closeWith(JUMP(afterCtx.bb) setPos caze.pos) - } - afterCtxReachable ||= (default == afterCtx) - ctx1.bb.emitOnly( - SWITCH(tags.reverse map (x => List(x)), (default :: targets).reverse) setPos tree.pos - ) - afterCtx.bb killUnless afterCtxReachable - afterCtx - } - genLoadMatch - - case EmptyTree => - if (expectedType != UNIT) - ctx.bb.emit(getZeroOf(expectedType)) - ctx - - case _ => - abort("Unexpected tree in genLoad: " + tree + "/" + tree.getClass + " at: " + tree.pos) - } - - // emit conversion - if (generatedType != expectedType) { - tree match { - case Literal(Constant(null)) if generatedType == NullReference && expectedType != UNIT => - // literal null on the stack (as opposed to a boxed null, see SI-8233), - // we can bypass `adapt` which would otherwise emit a redundant [DROP, CONSTANT(null)] - // except one case: when expected type is UNIT (unboxed) where we need to emit just a DROP - case _ => - adapt(generatedType, expectedType, resCtx, tree.pos) - } - } - - resCtx - } - - /** - * If we have a method call, field load, or array element load of type Null then - * we need to convince the JVM that we have a null value because in Scala - * land Null is a subtype of all ref types, but in JVM land scala.runtime.Null$ - * is not. Note we don't have to adapt loads of locals because the JVM type - * system for locals does have a null type which it tracks internally. As - * long as we adapt these other things, the JVM will know that a Scala local of - * type Null is holding a null. - */ - private def adaptNullRef(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) { - debuglog(s"GenICode#adaptNullRef($from, $to, $ctx, $pos)") - - // Don't need to adapt null to unit because we'll just drop it anyway. Don't - // need to adapt to Object or AnyRef because the JVM is happy with - // upcasting Null to them. - // We do have to adapt from NullReference to NullReference because we could be storing - // this value into a local of type Null and we want the JVM to see that it's - // a null value so we don't have to also adapt local loads. - if (from == NullReference && to != UNIT && to != ObjectReference && to != AnyRefReference) { - assert(to.isRefOrArrayType, s"Attempt to adapt a null to a non reference type $to.") - // adapt by dropping what we've got and pushing a null which - // will convince the JVM we really do have null - ctx.bb.emit(DROP(from), pos) - ctx.bb.emit(CONSTANT(Constant(null)), pos) - } - } - - private def adapt(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) { - // An awful lot of bugs explode here - let's leave ourselves more clues. - // A typical example is an overloaded type assigned after typer. - debuglog(s"GenICode#adapt($from, $to, $ctx, $pos)") - - def coerce(from: TypeKind, to: TypeKind) = ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)), pos) - - (from, to) match { - // The JVM doesn't have a Nothing equivalent, so it doesn't know that a method of type Nothing can't actually return. So for instance, with - // def f: String = ??? - // we need - // 0: getstatic #25; //Field scala/Predef$.MODULE$:Lscala/Predef$; - // 3: invokevirtual #29; //Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$; - // 6: athrow - // So this case tacks on the ahtrow which makes the JVM happy because class Nothing is declared as a subclass of Throwable - case (NothingReference, _) => - ctx.bb.emit(THROW(ThrowableClass)) - ctx.bb.enterIgnoreMode() - case (NullReference, REFERENCE(_)) => - // SI-8223 we can't assume that the stack contains a `null`, it might contain a Null$ - ctx.bb.emit(Seq(DROP(from), CONSTANT(Constant(null)))) - case _ if from isAssignabledTo to => - () - case (_, UNIT) => - ctx.bb.emit(DROP(from), pos) - // otherwise we'd better be doing a primitive -> primitive coercion or there's a problem - case _ if !from.isRefOrArrayType && !to.isRefOrArrayType => - coerce(from, to) - case _ => - assert(false, s"Can't convert from $from to $to in unit ${unit.source} at $pos") - } - } - - /** Load the qualifier of `tree` on top of the stack. */ - private def genLoadQualifier(tree: Tree, ctx: Context): Context = - tree match { - case Select(qualifier, _) => - genLoad(qualifier, ctx, toTypeKind(qualifier.tpe)) - case _ => - abort("Unknown qualifier " + tree) - } - - /** - * Generate code that loads args into label parameters. - */ - private def genLoadLabelArguments(args: List[Tree], label: Label, ctx: Context): Context = { - debugassert( - args.length == label.params.length, - "Wrong number of arguments in call to label " + label.symbol - ) - var ctx1 = ctx - - def isTrivial(kv: (Tree, Symbol)) = kv match { - case (This(_), p) if p.name == nme.THIS => true - case (arg @ Ident(_), p) if arg.symbol == p => true - case _ => false - } - - val stores = args zip label.params filterNot isTrivial map { - case (arg, param) => - val local = ctx.method.lookupLocal(param).get - ctx1 = genLoad(arg, ctx1, local.kind) - - val store = - if (param.name == nme.THIS) STORE_THIS(toTypeKind(ctx1.clazz.symbol.tpe)) - else STORE_LOCAL(local) - - store setPos arg.pos - } - - // store arguments in reverse order on the stack - ctx1.bb.emit(stores.reverse) - ctx1 - } - - private def genLoadArguments(args: List[Tree], tpes: List[Type], ctx: Context): Context = - (args zip tpes).foldLeft(ctx) { - case (res, (arg, tpe)) => - genLoad(arg, res, toTypeKind(tpe)) - } - - private def genLoadModule(ctx: Context, tree: Tree): Context = { - // Working around SI-5604. Rather than failing the compile when we see - // a package here, check if there's a package object. - val sym = ( - if (!tree.symbol.isPackageClass) tree.symbol - else tree.symbol.info.member(nme.PACKAGE) match { - case NoSymbol => abort("Cannot use package as value: " + tree) - case s => - devWarning(s"Found ${tree.symbol} where a package object is required. Converting to ${s.moduleClass}") - s.moduleClass - } - ) - debuglog("LOAD_MODULE from %s: %s".format(tree.shortClass, sym)) - ctx.bb.emit(LOAD_MODULE(sym), tree.pos) - ctx - } - - def genConversion(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) = { - if (cast) - ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to))) - else { - ctx.bb.emit(DROP(from)) - ctx.bb.emit(CONSTANT(Constant(from == to))) - } - } - - def genCast(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) = - ctx.bb.emit(if (cast) CHECK_CAST(to) else IS_INSTANCE(to)) - - def getZeroOf(k: TypeKind): Instruction = k match { - case UNIT => CONSTANT(Constant(())) - case BOOL => CONSTANT(Constant(false)) - case BYTE => CONSTANT(Constant(0: Byte)) - case SHORT => CONSTANT(Constant(0: Short)) - case CHAR => CONSTANT(Constant(0: Char)) - case INT => CONSTANT(Constant(0: Int)) - case LONG => CONSTANT(Constant(0: Long)) - case FLOAT => CONSTANT(Constant(0.0f)) - case DOUBLE => CONSTANT(Constant(0.0d)) - case REFERENCE(cls) => CONSTANT(Constant(null: Any)) - case ARRAY(elem) => CONSTANT(Constant(null: Any)) - case BOXED(_) => CONSTANT(Constant(null: Any)) - case ConcatClass => abort("no zero of ConcatClass") - } - - - /** Is the given symbol a primitive operation? */ - def isPrimitive(fun: Symbol): Boolean = scalaPrimitives.isPrimitive(fun) - - /** Generate coercion denoted by "code" - */ - def genCoercion(tree: Tree, ctx: Context, code: Int) = { - import scalaPrimitives._ - (code: @switch) match { - case B2B => () - case B2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, CHAR)), tree.pos) - case B2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, SHORT)), tree.pos) - case B2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, INT)), tree.pos) - case B2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, LONG)), tree.pos) - case B2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, FLOAT)), tree.pos) - case B2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, DOUBLE)), tree.pos) - - case S2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, BYTE)), tree.pos) - case S2S => () - case S2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, CHAR)), tree.pos) - case S2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, INT)), tree.pos) - case S2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, LONG)), tree.pos) - case S2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, FLOAT)), tree.pos) - case S2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, DOUBLE)), tree.pos) - - case C2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, BYTE)), tree.pos) - case C2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, SHORT)), tree.pos) - case C2C => () - case C2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, INT)), tree.pos) - case C2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, LONG)), tree.pos) - case C2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, FLOAT)), tree.pos) - case C2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, DOUBLE)), tree.pos) - - case I2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, BYTE)), tree.pos) - case I2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, SHORT)), tree.pos) - case I2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, CHAR)), tree.pos) - case I2I => () - case I2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, LONG)), tree.pos) - case I2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT)), tree.pos) - case I2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE)), tree.pos) - - case L2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, BYTE)), tree.pos) - case L2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, SHORT)), tree.pos) - case L2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, CHAR)), tree.pos) - case L2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, INT)), tree.pos) - case L2L => () - case L2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT)), tree.pos) - case L2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE)), tree.pos) - - case F2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, BYTE)), tree.pos) - case F2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, SHORT)), tree.pos) - case F2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, CHAR)), tree.pos) - case F2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT)), tree.pos) - case F2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG)), tree.pos) - case F2F => () - case F2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE)), tree.pos) - - case D2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, BYTE)), tree.pos) - case D2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, SHORT)), tree.pos) - case D2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, CHAR)), tree.pos) - case D2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT)), tree.pos) - case D2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG)), tree.pos) - case D2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT)), tree.pos) - case D2D => () - - case _ => abort("Unknown coercion primitive: " + code) - } - } - - /** The Object => String overload. - */ - private lazy val String_valueOf: Symbol = getMember(StringModule, nme.valueOf) filter (sym => - sym.info.paramTypes match { - case List(pt) => pt.typeSymbol == ObjectClass - case _ => false - } - ) - - // I wrote it this way before I realized all the primitive types are - // boxed at this point, so I'd have to unbox them. Keeping it around in - // case we want to get more precise. - // - // private def valueOfForType(tp: Type): Symbol = { - // val xs = getMember(StringModule, nme.valueOf) filter (sym => - // // We always exclude the Array[Char] overload because java throws an NPE if - // // you pass it a null. It will instead find the Object one, which doesn't. - // sym.info.paramTypes match { - // case List(pt) => pt.typeSymbol != ArrayClass && (tp <:< pt) - // case _ => false - // } - // ) - // xs.alternatives match { - // case List(sym) => sym - // case _ => NoSymbol - // } - // } - - /** Generate string concatenation. - */ - def genStringConcat(tree: Tree, ctx: Context): Context = { - liftStringConcat(tree) match { - // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. - case List(Literal(Constant("")), arg) => - debuglog("Rewriting \"\" + x as String.valueOf(x) for: " + arg) - val ctx1 = genLoad(arg, ctx, ObjectReference) - ctx1.bb.emit(CALL_METHOD(String_valueOf, Static(onInstance = false)), arg.pos) - ctx1 - case concatenations => - debuglog("Lifted string concatenations for " + tree + "\n to: " + concatenations) - var ctx1 = ctx - ctx1.bb.emit(CALL_PRIMITIVE(StartConcat), tree.pos) - for (elem <- concatenations) { - val kind = toTypeKind(elem.tpe) - ctx1 = genLoad(elem, ctx1, kind) - ctx1.bb.emit(CALL_PRIMITIVE(StringConcat(kind)), elem.pos) - } - ctx1.bb.emit(CALL_PRIMITIVE(EndConcat), tree.pos) - ctx1 - } - } - - /** Generate the scala ## method. - */ - def genScalaHash(tree: Tree, ctx: Context): Context = { - val hashMethod = { - ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule)) - getMember(ScalaRunTimeModule, nme.hash_) - } - - val ctx1 = genLoad(tree, ctx, ObjectReference) - ctx1.bb.emit(CALL_METHOD(hashMethod, Static(onInstance = false))) - ctx1 - } - - /** - * Returns a list of trees that each should be concatenated, from - * left to right. It turns a chained call like "a".+("b").+("c") into - * a list of arguments. - */ - def liftStringConcat(tree: Tree): List[Tree] = tree match { - case Apply(fun @ Select(larg, method), rarg) => - if (isPrimitive(fun.symbol) && - scalaPrimitives.getPrimitive(fun.symbol) == scalaPrimitives.CONCAT) - liftStringConcat(larg) ::: rarg - else - List(tree) - case _ => - List(tree) - } - - /** - * Find the label denoted by `lsym` and enter it in context `ctx`. - * - * We only enter one symbol at a time, even though we might traverse the same - * tree more than once per method. That's because we cannot enter labels that - * might be duplicated (for instance, inside finally blocks). - * - * TODO: restrict the scanning to smaller subtrees than the whole method. - * It is sufficient to scan the trees of the innermost enclosing block. - */ - private def resolveForwardLabel(tree: Tree, ctx: Context, lsym: Symbol): Unit = tree foreachPartial { - case t @ LabelDef(_, params, rhs) if t.symbol == lsym => - ctx.labels.getOrElseUpdate(t.symbol, { - val locals = params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)) - ctx.method addLocals locals - - new Label(t.symbol) setParams (params map (_.symbol)) - }) - rhs - } - - /** - * Generate code for conditional expressions. The two basic blocks - * represent the continuation in case of success/failure of the - * test. - */ - private def genCond(tree: Tree, - ctx: Context, - thenCtx: Context, - elseCtx: Context): Boolean = - { - /** - * Generate the de-sugared comparison mechanism that will underly an '==' - * - * @param l left-hand side of the '==' - * @param r right-hand side of the '==' - * @param code the comparison operator to use - * @return true if either branch can continue normally to a follow on block, false otherwise - */ - def genComparisonOp(l: Tree, r: Tree, code: Int): Boolean = { - val op: TestOp = code match { - case scalaPrimitives.LT => LT - case scalaPrimitives.LE => LE - case scalaPrimitives.GT => GT - case scalaPrimitives.GE => GE - case scalaPrimitives.ID | scalaPrimitives.EQ => EQ - case scalaPrimitives.NI | scalaPrimitives.NE => NE - - case _ => abort("Unknown comparison primitive: " + code) - } - - // special-case reference (in)equality test for null (null eq x, x eq null) - lazy val nonNullSide = ifOneIsNull(l, r) - if (isReferenceEqualityOp(code) && nonNullSide != null) { - val ctx1 = genLoad(nonNullSide, ctx, ObjectReference) - val branchesReachable = !ctx1.bb.ignore - ctx1.bb.emitOnly( - CZJUMP(thenCtx.bb, elseCtx.bb, op, ObjectReference) - ) - branchesReachable - } - else { - val kind = getMaxType(l.tpe :: r.tpe :: Nil) - var ctx1 = genLoad(l, ctx, kind) - ctx1 = genLoad(r, ctx1, kind) - val branchesReachable = !ctx1.bb.ignore - - ctx1.bb.emitOnly( - CJUMP(thenCtx.bb, elseCtx.bb, op, kind) setPos r.pos - ) - branchesReachable - } - } - - debuglog("Entering genCond with tree: " + tree) - - // the default emission - def default(): Boolean = { - val ctx1 = genLoad(tree, ctx, BOOL) - val branchesReachable = !ctx1.bb.ignore - ctx1.bb.closeWith(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) setPos tree.pos) - branchesReachable - } - - tree match { - // The comparison symbol is in ScalaPrimitives's "primitives" map - case Apply(fun, args) if isPrimitive(fun.symbol) => - import scalaPrimitives.{ ZNOT, ZAND, ZOR, EQ, getPrimitive } - - // lhs and rhs of test - lazy val Select(lhs, _) = fun - lazy val rhs = args.head - - def genZandOrZor(and: Boolean): Boolean = { - val ctxInterm = ctx.newBlock() - - val lhsBranchesReachable = if (and) genCond(lhs, ctx, ctxInterm, elseCtx) - else genCond(lhs, ctx, thenCtx, ctxInterm) - // If lhs is known to throw, we can kill the just created ctxInterm. - ctxInterm.bb killUnless lhsBranchesReachable - - val rhsBranchesReachable = genCond(rhs, ctxInterm, thenCtx, elseCtx) - - // Reachable means "it does not always throw", i.e. "it might not throw". - // In an expression (a && b) or (a || b), the b branch might not be evaluated. - // Such an expression is therefore known to throw only if both expressions throw. Or, - // successors are reachable if either of the two is reachable (SI-8625). - lhsBranchesReachable || rhsBranchesReachable - } - def genRefEq(isEq: Boolean) = { - val f = genEqEqPrimitive(lhs, rhs, ctx) _ - if (isEq) f(thenCtx, elseCtx) - else f(elseCtx, thenCtx) - } - - getPrimitive(fun.symbol) match { - case ZNOT => genCond(lhs, ctx, elseCtx, thenCtx) - case ZAND => genZandOrZor(and = true) - case ZOR => genZandOrZor(and = false) - case code => - // x == y where LHS is reference type - if (isUniversalEqualityOp(code) && toTypeKind(lhs.tpe).isReferenceType) { - if (code == EQ) genRefEq(isEq = true) - else genRefEq(isEq = false) - } - else if (isComparisonOp(code)) - genComparisonOp(lhs, rhs, code) - else - default() - } - - case _ => default() - } - } - - /** - * Generate the "==" code for object references. It is equivalent of - * if (l eq null) r eq null else l.equals(r); - * - * @param l left-hand side of the '==' - * @param r right-hand side of the '==' - * @param ctx current context - * @param thenCtx target context if the comparison yields true - * @param elseCtx target context if the comparison yields false - * @return true if either branch can continue normally to a follow on block, false otherwise - */ - def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context)(thenCtx: Context, elseCtx: Context): Boolean = { - def getTempLocal = ctx.method.lookupLocal(nme.EQEQ_LOCAL_VAR) getOrElse { - ctx.makeLocal(l.pos, AnyRefTpe, nme.EQEQ_LOCAL_VAR.toString) - } - - /* True if the equality comparison is between values that require the use of the rich equality - * comparator (scala.runtime.Comparator.equals). This is the case when either side of the - * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character. - * When it is statically known that both sides are equal and subtypes of Number of Character, - * not using the rich equality is possible (their own equals method will do ok.)*/ - def mustUseAnyComparator: Boolean = { - def areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) - !areSameFinals && isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol) - } - - if (mustUseAnyComparator) { - // when -optimise is on we call the @inline-version of equals, found in ScalaRunTime - val equalsMethod: Symbol = { - if (!settings.optimise) { - if (l.tpe <:< BoxedNumberClass.tpe) { - if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum - else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030 - else platform.externalEqualsNumObject - } else platform.externalEquals - } else { - ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule)) - getMember(ScalaRunTimeModule, nme.inlinedEquals) - } - } - - val ctx1 = genLoad(l, ctx, ObjectReference) - val ctx2 = genLoad(r, ctx1, ObjectReference) - val branchesReachable = !ctx2.bb.ignore - ctx2.bb.emitOnly( - CALL_METHOD(equalsMethod, if (settings.optimise) Dynamic else Static(onInstance = false)), - CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) - ) - branchesReachable - } - else { - if (isNull(l)) { - // null == expr -> expr eq null - val ctx1 = genLoad(r, ctx, ObjectReference) - val branchesReachable = !ctx1.bb.ignore - ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference) - branchesReachable - } else if (isNull(r)) { - // expr == null -> expr eq null - val ctx1 = genLoad(l, ctx, ObjectReference) - val branchesReachable = !ctx1.bb.ignore - ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference) - branchesReachable - } else if (isNonNullExpr(l)) { - // Avoid null check if L is statically non-null. - // - // "" == expr -> "".equals(expr) - // Nil == expr -> Nil.equals(expr) - // - // Common enough (through pattern matching) to treat this specially here rather than - // hoping that -Yconst-opt is enabled. The impossible branches for null checks lead - // to spurious "branch not covered" warnings in Jacoco code coverage. - var ctx1 = genLoad(l, ctx, ObjectReference) - val branchesReachable = !ctx1.bb.ignore - ctx1 = genLoad(r, ctx1, ObjectReference) - ctx1.bb emitOnly( - CALL_METHOD(Object_equals, Dynamic), - CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) - ) - branchesReachable - } else { - val eqEqTempLocal = getTempLocal - var ctx1 = genLoad(l, ctx, ObjectReference) - val branchesReachable = !ctx1.bb.ignore - lazy val nonNullCtx = { - val block = ctx1.newBlock() - block.bb killUnless branchesReachable - block - } - - // l == r -> if (l eq null) r eq null else l.equals(r) - ctx1 = genLoad(r, ctx1, ObjectReference) - val nullCtx = ctx1.newBlock() - nullCtx.bb killUnless branchesReachable - - ctx1.bb.emitOnly( - STORE_LOCAL(eqEqTempLocal) setPos l.pos, - DUP(ObjectReference), - CZJUMP(nullCtx.bb, nonNullCtx.bb, EQ, ObjectReference) - ) - nullCtx.bb.emitOnly( - DROP(ObjectReference) setPos l.pos, // type of AnyRef - LOAD_LOCAL(eqEqTempLocal), - CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference) - ) - nonNullCtx.bb.emitOnly( - LOAD_LOCAL(eqEqTempLocal) setPos l.pos, - CALL_METHOD(Object_equals, Dynamic), - CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) - ) - branchesReachable - } - } - } - - /** - * Add all fields of the given class symbol to the current ICode - * class. - */ - private def addClassFields(ctx: Context, cls: Symbol) { - debugassert(ctx.clazz.symbol eq cls, - "Classes are not the same: " + ctx.clazz.symbol + ", " + cls) - - /* Non-method term members are fields, except for module members. Module - * members can only happen on .NET (no flatten) for inner traits. There, - * a module symbol is generated (transformInfo in mixin) which is used - * as owner for the members of the implementation class (so that the - * backend emits them as static). - * No code is needed for this module symbol. - */ - for (f <- cls.info.decls ; if !f.isMethod && f.isTerm && !f.isModule) - ctx.clazz addField new IField(f) - } - - /** - * Add parameters to the current ICode method. It is assumed the methods - * have been uncurried, so the list of lists contains just one list. - */ - private def addMethodParams(ctx: Context, vparamss: List[List[ValDef]]) { - vparamss match { - case Nil => () - - case vparams :: Nil => - for (p <- vparams) { - val lv = new Local(p.symbol, toTypeKind(p.symbol.info), true) - ctx.method.addParam(lv) - ctx.scope.add(lv) - ctx.bb.varsInScope += lv - } - ctx.method.params = ctx.method.params.reverse - - case _ => - abort("Malformed parameter list: " + vparamss) - } - } - - /** Does this tree have a try-catch block? */ - def mayCleanStack(tree: Tree): Boolean = tree exists { - case Try(_, _, _) => true - case _ => false - } - - /** - * If the block consists of a single unconditional jump, prune - * it by replacing the instructions in the predecessor to jump - * directly to the JUMP target of the block. - */ - def prune(method: IMethod) = { - var changed = false - var n = 0 - - def prune0(block: BasicBlock): Unit = { - val optCont = block.lastInstruction match { - case JUMP(b) if (b != block) => Some(b) - case _ => None - } - if (block.size == 1 && optCont.isDefined) { - val Some(cont) = optCont - val pred = block.predecessors - debuglog("Preds: " + pred + " of " + block + " (" + optCont + ")") - pred foreach { p => - changed = true - p.lastInstruction match { - case CJUMP(succ, fail, cond, kind) if (succ == block || fail == block) => - debuglog("Pruning empty if branch.") - p.replaceInstruction(p.lastInstruction, - if (block == succ) - if (block == fail) - CJUMP(cont, cont, cond, kind) - else - CJUMP(cont, fail, cond, kind) - else if (block == fail) - CJUMP(succ, cont, cond, kind) - else - abort("Could not find block in preds: " + method + " " + block + " " + pred + " " + p)) - - case CZJUMP(succ, fail, cond, kind) if (succ == block || fail == block) => - debuglog("Pruning empty ifz branch.") - p.replaceInstruction(p.lastInstruction, - if (block == succ) - if (block == fail) - CZJUMP(cont, cont, cond, kind) - else - CZJUMP(cont, fail, cond, kind) - else if (block == fail) - CZJUMP(succ, cont, cond, kind) - else - abort("Could not find block in preds")) - - case JUMP(b) if (b == block) => - debuglog("Pruning empty JMP branch.") - val replaced = p.replaceInstruction(p.lastInstruction, JUMP(cont)) - debugassert(replaced, "Didn't find p.lastInstruction") - - case SWITCH(tags, labels) if (labels contains block) => - debuglog("Pruning empty SWITCH branch.") - p.replaceInstruction(p.lastInstruction, - SWITCH(tags, labels map (l => if (l == block) cont else l))) - - // the last instr of the predecessor `p` is not a jump to the block `block`. - // this happens when `block` is part of an exception handler covering `b`. - case _ => () - } - } - if (changed) { - debuglog("Removing block: " + block) - method.code.removeBlock(block) - for (e <- method.exh) { - e.covered = e.covered filter (_ != block) - e.blocks = e.blocks filter (_ != block) - if (e.startBlock eq block) - e setStartBlock cont - } - } - } - } - - do { - changed = false - n += 1 - method.blocks foreach prune0 - } while (changed) - - debuglog("Prune fixpoint reached in " + n + " iterations.") - } - - def getMaxType(ts: List[Type]): TypeKind = - ts map toTypeKind reduceLeft (_ maxType _) - - /** Tree transformer that duplicates code and at the same time creates - * fresh symbols for existing labels. Since labels may be used before - * they are defined (forward jumps), all labels found are mapped to fresh - * symbols. References to the same label (use or definition) will remain - * consistent after this transformation (both the use and the definition of - * some label l will be mapped to the same label l'). - * - * Note: If the tree fragment passed to the duplicator contains unbound - * label names, the bind to the outer labeldef will be lost! That's because - * a use of an unbound label l will be transformed to l', and the corresponding - * label def, being outside the scope of this transformation, will not be updated. - * - * All LabelDefs are entered into the context label map, since it makes no sense - * to delay it any more: they will be used at some point. - */ - class DuplicateLabels(boundLabels: Set[Symbol]) extends Transformer { - val labels = perRunCaches.newMap[Symbol, Symbol]() - var method: Symbol = _ - var ctx: Context = _ - - def apply(ctx: Context, t: Tree) = { - this.method = ctx.method.symbol - this.ctx = ctx - transform(t) - } - - override def transform(t: Tree): Tree = { - val sym = t.symbol - def getLabel(pos: Position, name: Name) = - labels.getOrElseUpdate(sym, - method.newLabel(unit.freshTermName(name.toString), sym.pos) setInfo sym.tpe - ) - - t match { - case t @ Apply(_, args) if sym.isLabel && !boundLabels(sym) => - val newSym = getLabel(sym.pos, sym.name) - Apply(global.gen.mkAttributedRef(newSym), transformTrees(args)) setPos t.pos setType t.tpe - - case t @ LabelDef(name, params, rhs) => - val newSym = getLabel(t.pos, name) - val tree = treeCopy.LabelDef(t, newSym.name, params, transform(rhs)) - tree.symbol = newSym - - val pair = (newSym -> (new Label(newSym) setParams (params map (_.symbol)))) - log("Added " + pair + " to labels.") - ctx.labels += pair - ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))) - - tree - - case _ => super.transform(t) - } - } - } - - /////////////////////// Context //////////////////////////////// - - sealed abstract class Cleanup(val value: AnyRef) { - def contains(x: AnyRef) = value == x - } - case class MonitorRelease(m: Local) extends Cleanup(m) { } - case class Finalizer(f: Tree, ctx: Context) extends Cleanup (f) { } - - def duplicateFinalizer(boundLabels: Set[Symbol], targetCtx: Context, finalizer: Tree) = { - (new DuplicateLabels(boundLabels))(targetCtx, finalizer) - } - - def savingCleanups[T](ctx: Context)(body: => T): T = { - val saved = ctx.cleanups - try body - finally ctx.cleanups = saved - } - - /** - * The Context class keeps information relative to the current state - * in code generation - */ - class Context { - /** The current package. */ - var packg: Name = _ - - /** The current class. */ - var clazz: IClass = _ - - /** The current method. */ - var method: IMethod = _ - - /** The current basic block. */ - var bb: BasicBlock = _ - - /** Map from label symbols to label objects. */ - var labels = perRunCaches.newMap[Symbol, Label]() - - /** Current method definition. */ - var defdef: DefDef = _ - - /** current exception handlers */ - var handlers: List[ExceptionHandler] = Nil - - /** The current monitors or finalizers, to be cleaned up upon `return`. */ - var cleanups: List[Cleanup] = Nil - - /** The exception handlers we are currently generating code for */ - var currentExceptionHandlers: List[ExceptionHandler] = Nil - - /** The current local variable scope. */ - var scope: Scope = EmptyScope - - var handlerCount = 0 - - override def toString = - s"package $packg { class $clazz { def $method { bb=$bb } } }" - - def loadException(ctx: Context, exh: ExceptionHandler, pos: Position) = { - debuglog("Emitting LOAD_EXCEPTION for class: " + exh.loadExceptionClass) - ctx.bb.emit(LOAD_EXCEPTION(exh.loadExceptionClass) setPos pos, pos) - } - - def this(other: Context) = { - this() - this.packg = other.packg - this.clazz = other.clazz - this.method = other.method - this.bb = other.bb - this.labels = other.labels - this.defdef = other.defdef - this.handlers = other.handlers - this.handlerCount = other.handlerCount - this.cleanups = other.cleanups - this.currentExceptionHandlers = other.currentExceptionHandlers - this.scope = other.scope - } - - def setPackage(p: Name): this.type = { - this.packg = p - this - } - - def setClass(c: IClass): this.type = { - this.clazz = c - this - } - - def setMethod(m: IMethod): this.type = { - this.method = m - this - } - - def setBasicBlock(b: BasicBlock): this.type = { - this.bb = b - this - } - - def enterSynchronized(monitor: Local): this.type = { - cleanups = MonitorRelease(monitor) :: cleanups - this - } - - def exitSynchronized(monitor: Local): this.type = { - assert(cleanups.head contains monitor, - "Bad nesting of cleanup operations: " + cleanups + " trying to exit from monitor: " + monitor) - cleanups = cleanups.tail - this - } - - def addFinalizer(f: Tree, ctx: Context): this.type = { - cleanups = Finalizer(f, ctx) :: cleanups - this - } - - /** Prepare a new context upon entry into a method. - */ - def enterMethod(m: IMethod, d: DefDef): Context = { - val ctx1 = new Context(this) setMethod(m) - ctx1.labels = mutable.HashMap() - ctx1.method.code = new Code(m) - ctx1.bb = ctx1.method.startBlock - ctx1.defdef = d - ctx1.scope = EmptyScope - ctx1.enterScope() - ctx1 - } - - /** Return a new context for a new basic block. */ - def newBlock(): Context = { - val block = method.code.newBlock() - handlers foreach (_ addCoveredBlock block) - currentExceptionHandlers foreach (_ addBlock block) - block.varsInScope.clear() - block.varsInScope ++= scope.varsInScope - new Context(this) setBasicBlock block - } - - def enterScope() { - scope = new Scope(scope) - } - - def exitScope() { - if (bb.nonEmpty) { - scope.locals foreach { lv => bb.emit(SCOPE_EXIT(lv)) } - } - scope = scope.outer - } - - /** Create a new exception handler and adds it in the list - * of current exception handlers. All new blocks will be - * 'covered' by this exception handler (in addition to the - * previously active handlers). - */ - private def newExceptionHandler(cls: Symbol, pos: Position): ExceptionHandler = { - handlerCount += 1 - val exh = new ExceptionHandler(method, newTermNameCached("" + handlerCount), cls, pos) - method.addHandler(exh) - handlers = exh :: handlers - debuglog("added handler: " + exh) - - exh - } - - /** Add an active exception handler in this context. It will cover all new basic blocks - * created from now on. */ - private def addActiveHandler(exh: ExceptionHandler) { - handlerCount += 1 - handlers = exh :: handlers - debuglog("added handler: " + exh) - } - - /** Return a new context for generating code for the given - * exception handler. - */ - private def enterExceptionHandler(exh: ExceptionHandler): Context = { - currentExceptionHandlers ::= exh - val ctx = newBlock() - exh.setStartBlock(ctx.bb) - ctx - } - - def endHandler() { - currentExceptionHandlers = currentExceptionHandlers.tail - } - - /** Clone the current context */ - def dup: Context = new Context(this) - - /** Make a fresh local variable. It ensures the 'name' is unique. */ - def makeLocal(pos: Position, tpe: Type, name: String): Local = { - val sym = method.symbol.newVariable(unit.freshTermName(name), pos, Flags.SYNTHETIC) setInfo tpe - this.method.addLocal(new Local(sym, toTypeKind(tpe), false)) - } - - - /** - * Generate exception handlers for the body. Body is evaluated - * with a context where all the handlers are active. Handlers are - * evaluated in the 'outer' context. - * - * It returns the resulting context, with the same active handlers as - * before the call. Use it like: - * - * ` ctx.Try( ctx => { - * ctx.bb.emit(...) // protected block - * }, (ThrowableClass, - * ctx => { - * ctx.bb.emit(...); // exception handler - * }), (AnotherExceptionClass, - * ctx => {... - * } ))` - * - * The resulting structure will look something like - * - * outer: - * // this 'useless' jump will be removed later, - * // for now it separates the try body's blocks from previous - * // code since the try body needs its own exception handlers - * JUMP body - * - * body: - * [ try body ] - * JUMP normalExit - * - * catch[i]: - * [ handler[i] body ] - * JUMP normalExit - * - * catchAll: - * STORE exception - * [ finally body ] - * THROW exception - * - * normalExit: - * [ finally body ] - * - * each catch[i] will cover body. catchAll will cover both body and each catch[i] - * Additional finally copies are created on the emission of every RETURN in the try body and exception handlers. - * - * This could result in unreachable code which has to be cleaned up later, e.g. if the try and all the exception - * handlers always end in RETURN then there will be no "normal" flow out of the try/catch/finally. - * Later reachability analysis will remove unreachable code. - */ - def Try(body: Context => Context, - handlers: List[(Symbol, TypeKind, Context => Context)], - finalizer: Tree, - tree: Tree) = { - - val outerCtx = this.dup // context for generating exception handlers, covered by the catch-all finalizer - val finalizerCtx = this.dup // context for generating finalizer handler - val normalExitCtx = outerCtx.newBlock() // context where flow will go on a "normal" (non-return, non-throw) exit from a try or catch handler - var normalExitReachable = false - var tmp: Local = null - val kind = toTypeKind(tree.tpe) - val guardResult = kind != UNIT && mayCleanStack(finalizer) - // we need to save bound labels before any code generation is performed on - // the current context (otherwise, any new labels in the finalizer that need to - // be duplicated would be incorrectly considered bound -- see #2850). - val boundLabels: Set[Symbol] = Set.empty ++ labels.keySet - - if (guardResult) { - tmp = this.makeLocal(tree.pos, tree.tpe, "tmp") - } - - def emitFinalizer(ctx: Context): Context = if (!finalizer.isEmpty) { - val ctx1 = finalizerCtx.dup.newBlock() - ctx1.bb killIf ctx.bb.ignore - ctx.bb.closeWith(JUMP(ctx1.bb)) - - if (guardResult) { - ctx1.bb.emit(STORE_LOCAL(tmp)) - val ctx2 = genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT) - ctx2.bb.emit(LOAD_LOCAL(tmp)) - ctx2 - } else - genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT) - } else ctx - - - // Generate the catch-all exception handler that deals with uncaught exceptions coming - // from the try or exception handlers. It catches the exception, runs the finally code, then rethrows - // the exception - if (settings.YdisableUnreachablePrevention || !outerCtx.bb.ignore) { - if (finalizer != EmptyTree) { - val exh = outerCtx.newExceptionHandler(NoSymbol, finalizer.pos) // finalizer covers exception handlers - this.addActiveHandler(exh) // .. and body as well - val exhStartCtx = finalizerCtx.enterExceptionHandler(exh) - exhStartCtx.bb killIf outerCtx.bb.ignore - val exception = exhStartCtx.makeLocal(finalizer.pos, ThrowableTpe, "exc") - loadException(exhStartCtx, exh, finalizer.pos) - exhStartCtx.bb.emit(STORE_LOCAL(exception)) - val exhEndCtx = genLoad(finalizer, exhStartCtx, UNIT) - exhEndCtx.bb.emit(LOAD_LOCAL(exception)) - exhEndCtx.bb.closeWith(THROW(ThrowableClass)) - exhEndCtx.bb.enterIgnoreMode() - finalizerCtx.endHandler() - } - - // Generate each exception handler - for ((sym, kind, handler) <- handlers) { - val exh = this.newExceptionHandler(sym, tree.pos) - val exhStartCtx = outerCtx.enterExceptionHandler(exh) - exhStartCtx.bb killIf outerCtx.bb.ignore - exhStartCtx.addFinalizer(finalizer, finalizerCtx) - loadException(exhStartCtx, exh, tree.pos) - val exhEndCtx = handler(exhStartCtx) - normalExitReachable ||= !exhEndCtx.bb.ignore - exhEndCtx.bb.closeWith(JUMP(normalExitCtx.bb)) - outerCtx.endHandler() - } - } - - val bodyCtx = this.newBlock() - bodyCtx.bb killIf outerCtx.bb.ignore - if (finalizer != EmptyTree) - bodyCtx.addFinalizer(finalizer, finalizerCtx) - - val bodyEndCtx = body(bodyCtx) - - outerCtx.bb.closeWith(JUMP(bodyCtx.bb)) - - normalExitReachable ||= !bodyEndCtx.bb.ignore - normalExitCtx.bb killUnless normalExitReachable - bodyEndCtx.bb.closeWith(JUMP(normalExitCtx.bb)) - - emitFinalizer(normalExitCtx) - } - } - } - - /** - * Represent a label in the current method code. In order - * to support forward jumps, labels can be created without - * having a designated target block. They can later be attached - * by calling `anchor`. - */ - class Label(val symbol: Symbol) { - var anchored = false - var block: BasicBlock = _ - var params: List[Symbol] = _ - - private var toPatch: List[Instruction] = Nil - - /** Fix this label to the given basic block. */ - def anchor(b: BasicBlock): Label = { - assert(!anchored, "Cannot anchor an already anchored label!") - anchored = true - this.block = b - this - } - - def setParams(p: List[Symbol]): Label = { - assert(params eq null, "Cannot set label parameters twice!") - params = p - this - } - - /** Add an instruction that refers to this label. */ - def addCallingInstruction(i: Instruction) = - toPatch = i :: toPatch - - /** - * Patch the code by replacing pseudo call instructions with - * jumps to the given basic block. - */ - def patch(code: Code) { - val map = mapFrom(toPatch)(patch) - code.blocks foreach (_ subst map) - } - - /** - * Return the patched instruction. If the given instruction - * jumps to this label, replace it with the basic block. Otherwise, - * return the same instruction. Conditional jumps have more than one - * label, so they are replaced only if all labels are anchored. - */ - def patch(instr: Instruction): Instruction = { - assert(anchored, "Cannot patch until this label is anchored: " + this) - - instr match { - case PJUMP(self) - if (self == this) => JUMP(block) - - case PCJUMP(self, failure, cond, kind) - if (self == this && failure.anchored) => - CJUMP(block, failure.block, cond, kind) - - case PCJUMP(success, self, cond, kind) - if (self == this && success.anchored) => - CJUMP(success.block, block, cond, kind) - - case PCZJUMP(self, failure, cond, kind) - if (self == this && failure.anchored) => - CZJUMP(block, failure.block, cond, kind) - - case PCZJUMP(success, self, cond, kind) - if (self == this && success.anchored) => - CZJUMP(success.block, block, cond, kind) - - case _ => instr - } - } - - override def toString() = symbol.toString() - } - - ///////////////// Fake instructions ////////////////////////// - - /** - * Pseudo jump: it takes a Label instead of a basic block. - * It is used temporarily during code generation. It is replaced - * by a real JUMP instruction when all labels are resolved. - */ - abstract class PseudoJUMP(label: Label) extends Instruction { - override def toString = s"PJUMP(${label.symbol})" - override def consumed = 0 - override def produced = 0 - - // register with the given label - if (!label.anchored) - label.addCallingInstruction(this) - } - - case class PJUMP(whereto: Label) extends PseudoJUMP(whereto) - - case class PCJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind) - extends PseudoJUMP(success) { - override def toString(): String = - "PCJUMP (" + kind + ") " + success.symbol.simpleName + - " : " + failure.symbol.simpleName - - if (!failure.anchored) - failure.addCallingInstruction(this) - } - - case class PCZJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind) - extends PseudoJUMP(success) { - override def toString(): String = - "PCZJUMP (" + kind + ") " + success.symbol.simpleName + - " : " + failure.symbol.simpleName - - if (!failure.anchored) - failure.addCallingInstruction(this) - } - - /** Local variable scopes. Keep track of line numbers for debugging info. */ - class Scope(val outer: Scope) { - val locals: ListBuffer[Local] = new ListBuffer - - def add(l: Local) = locals += l - - /** Return all locals that are in scope. */ - def varsInScope: Buffer[Local] = outer.varsInScope.clone() ++= locals - - override def toString() = locals.mkString(outer.toString + "[", ", ", "]") - } - - object EmptyScope extends Scope(null) { - override def toString() = "[]" - override def varsInScope: Buffer[Local] = new ListBuffer - } -} diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala deleted file mode 100644 index 0f17b5d694..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala +++ /dev/null @@ -1,711 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend -package icode - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -abstract class ICodeCheckers { - val global: Global - import global._ - - /** <p> - * This class performs a set of checks similar to what the bytecode - * verifier does. For each basic block, it checks that: - * </p> - * <ul> - * <li> - * for primitive operations: the type and number of operands match - * the type of the operation - * </li> - * <li> - * for method calls: the method exists in the type of the receiver - * and the number and type of arguments match the declared type of - * the method. - * </li> - * <li> - * for object creation: the constructor can be called. - * </li> - * <li> - * for load/stores: the field/local/param exists and the type - * of the value matches that of the target. - * </li> - * </ul> - * <p> - * For a control flow graph it checks that type stacks at entry to - * each basic block 'agree': - * </p> - * <ul> - * <li>they have the same length</li> - * <li>there exists a lub for all types at the same position in stacks.</li> - * </ul> - * - * @author Iulian Dragos - * @version 1.0, 06/09/2005 - * - * @todo Better checks for `MONITOR_ENTER/EXIT` - * Better checks for local var initializations - * - * @todo Iulian says: I think there's some outdated logic in the checker. - * The issue with exception handlers being special for least upper - * bounds pointed out some refactoring in the lattice class. Maybe - * a worthwhile refactoring would be to make the checker use the - * DataFlowAnalysis class, and use the lattice trait. In the - * implementation of LUB, there's a flag telling if one of the - * successors is 'exceptional'. The inliner is using this mechanism. - */ - class ICodeChecker { - import icodes._ - import opcodes._ - - var clasz: IClass = _ - var method: IMethod = _ - var code: Code = _ - - val in: mutable.Map[BasicBlock, TypeStack] = perRunCaches.newMap() - val out: mutable.Map[BasicBlock, TypeStack] = perRunCaches.newMap() - val emptyStack = new TypeStack() { - override def toString = "<empty>" - } - - /** The presence of emptyStack means that path has not yet been checked - * (and may not be empty). - */ - def notChecked(ts: TypeStack) = ts eq emptyStack - def initMaps(bs: Seq[BasicBlock]): Unit = { - in.clear() - out.clear() - bs foreach { b => - in(b) = emptyStack - out(b) = emptyStack - } - } - - /** A wrapper to route log messages to debug output also. - */ - def logChecker(msg: String) = { - log(msg) - checkerDebug(msg) - } - - def checkICodes(): Unit = { - if (settings.verbose) - println("[[consistency check at the beginning of phase " + globalPhase.name + "]]") - classes.values foreach check - } - - private def posStr(p: Position) = - if (p.isDefined) p.line.toString else "<??>" - - private def indent(s: String, prefix: String): String = { - val lines = s split "\\n" - lines map (prefix + _) mkString "\n" - } - - /** Only called when m1 < m2, so already known that (m1 ne m2). - */ - private def isConflict(m1: IMember, m2: IMember, canOverload: Boolean) = ( - (m1.symbol.name == m2.symbol.name) && - (!canOverload || (m1.symbol.tpe =:= m2.symbol.tpe)) - ) - - def check(cls: IClass) { - logChecker("\n<<-- Checking class " + cls + " -->>") - clasz = cls - - for (f1 <- cls.fields ; f2 <- cls.fields ; if f1 < f2) - if (isConflict(f1, f2, canOverload = false)) - icodeError("Repetitive field name: " + f1.symbol.fullName) - - for (m1 <- cls.methods ; m2 <- cls.methods ; if m1 < m2) - if (isConflict(m1, m2, canOverload = true)) - icodeError("Repetitive method: " + m1.symbol.fullName) - - clasz.methods foreach check - } - - def check(m: IMethod) { - logChecker("\n<< Checking method " + m.symbol.name + " >>") - method = m - if (!m.isAbstractMethod) - check(m.code) - } - - def check(c: Code) { - val worklist = new ListBuffer[BasicBlock] - def append(elems: List[BasicBlock]) = - worklist ++= (elems filterNot (worklist contains _)) - - code = c - worklist += c.startBlock - initMaps(c.blocks) - - while (worklist.nonEmpty) { - val block = worklist remove 0 - val output = check(block, in(block)) - if (output != out(block) || notChecked(out(block))) { - if (block.successors.nonEmpty) - logChecker("** Output change for %s: %s -> %s".format(block, out(block), output)) - - out(block) = output - append(block.successors) - block.successors foreach meet - } - } - } - - /** - * Apply the meet operator of the stack lattice on bl's predecessors. - * :-). Compute the input to bl by checking that all stacks have the - * same length, and taking the lub of types at the same positions. - */ - def meet(bl: BasicBlock) { - val preds = bl.predecessors - - def hasNothingType(s: TypeStack) = s.nonEmpty && (s.head == NothingReference) - - /* XXX workaround #1: one stack empty, the other has BoxedUnit. - * One example where this arises is: - * - * def f(b: Boolean): Unit = synchronized { if (b) () } - */ - def allUnits(s: TypeStack) = s.types forall (_ == BoxedUnitReference) - - def ifAthenB[T](f: T => Boolean): PartialFunction[(T, T), T] = { - case (x1, x2) if f(x1) => x2 - case (x1, x2) if f(x2) => x1 - } - - /* XXX workaround #2: different stacks heading into an exception - * handler which will clear them anyway. Examples where it arises: - * - * var bippy: Int = synchronized { if (b) 5 else 10 } - */ - def isHandlerBlock() = bl.exceptionHandlerStart - - def meet2(s1: TypeStack, s2: TypeStack): TypeStack = { - def workaround(msg: String) = { - checkerDebug(msg + ": " + method + " at block " + bl) - checkerDebug(" s1: " + s1) - checkerDebug(" s2: " + s2) - new TypeStack() - } - def incompatibleString = ( - "Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to block " + bl.label + ":\n" + - indent(bl.predContents, "// ") + - indent(bl.succContents, "// ") + - indent(bl.blockContents, "// ") - ) - - val f: ((TypeStack, TypeStack)) => TypeStack = { - ifAthenB(notChecked) orElse ifAthenB(hasNothingType) orElse { - case (s1: TypeStack, s2: TypeStack) => - if (s1.length != s2.length) { - if (allUnits(s1) && allUnits(s2)) - workaround("Ignoring mismatched boxed units") - else if (isHandlerBlock()) - workaround("Ignoring mismatched stacks entering exception handler") - else - throw new CheckerException(incompatibleString) - } - else { - val newStack: TypeStack = try { - new TypeStack((s1.types, s2.types).zipped map lub) - } catch { - case t: Exception => - checkerDebug(t.toString + ": " + s1.types.toString + " vs " + s2.types.toString) - new TypeStack(s1.types) - } - if (newStack.isEmpty || s1.types == s2.types) () // not interesting to report - else checkerDebug("Checker created new stack:\n (%s, %s) => %s".format(s1, s2, newStack)) - - newStack - } - } - } - - f((s1, s2)) - } - - if (preds.nonEmpty) { - in(bl) = (preds map out.apply) reduceLeft meet2 - log("Input changed for block: " + bl +" to: " + in(bl)) - } - } - - private var instruction: Instruction = null - private var basicBlock: BasicBlock = null - private var stringConcatDepth = 0 - private def stringConcatIndent() = " " * stringConcatDepth - private def currentInstrString: String = { - val (indent, str) = this.instruction match { - case CALL_PRIMITIVE(StartConcat) => - val x = stringConcatIndent() - stringConcatDepth += 1 - (x, "concat(") - case CALL_PRIMITIVE(EndConcat) => - if (stringConcatDepth > 0) { - stringConcatDepth -= 1 - (stringConcatIndent(), ") // end concat") - } - else ("", "") - case _ => - (stringConcatIndent(), this.instruction match { - case CALL_PRIMITIVE(StringConcat(el)) => "..." - case null => "null" - case cm @ CALL_METHOD(_, _) => if (clasz.symbol == cm.hostClass) cm.toShortString else cm.toString - case x => x.toString - }) - } - indent + str - } - /** A couple closure creators to reduce noise in the output: when multiple - * items are pushed or popped, this lets us print something short and sensible - * for those beyond the first. - */ - def mkInstrPrinter(f: Int => String): () => String = { - var counter = -1 - val indent = stringConcatIndent() - () => { - counter += 1 - if (counter == 0) currentInstrString - else indent + f(counter) - } - } - def defaultInstrPrinter: () => String = mkInstrPrinter(_ => "\"\"\"") - - /** - * Check the basic block to be type correct and return the - * produced type stack. - */ - def check(b: BasicBlock, initial: TypeStack): TypeStack = { - this.basicBlock = b - - logChecker({ - val prefix = "** Checking " + b.fullString - - if (initial.isEmpty) prefix - else prefix + " with initial stack " + initial.types.mkString("[", ", ", "]") - }) - - val stack = new TypeStack(initial) - def checkStack(len: Int) { - if (stack.length < len) - ICodeChecker.this.icodeError("Expected at least " + len + " elements on the stack", stack) - } - - def sizeString(push: Boolean) = { - val arrow = if (push) "-> " else "<- " - val sp = " " * stack.length - - sp + stack.length + arrow - } - def printStackString(isPush: Boolean, value: TypeKind, instrString: String) = { - val pushString = if (isPush) "+" else "-" - val posString = posStr(this.instruction.pos) - - checkerDebug("%-70s %-4s %s %s".format(sizeString(isPush) + value, posString, pushString, instrString)) - } - def _popStack: TypeKind = { - if (stack.isEmpty) { - icodeError("Popped empty stack in " + b.fullString + ", throwing a Unit") - return UNIT - } - stack.pop - } - def popStackN(num: Int, instrFn: () => String = defaultInstrPrinter) = { - List.range(0, num) map { _ => - val res = _popStack - printStackString(isPush = false, res, instrFn()) - res - } - } - def pushStackN(xs: Seq[TypeKind], instrFn: () => String) = { - xs foreach { x => - stack push x - printStackString(isPush = true, x, instrFn()) - } - } - - def popStack = { checkStack(1) ; (popStackN(1): @unchecked) match { case List(x) => x } } - def popStack2 = { checkStack(2) ; (popStackN(2): @unchecked) match { case List(x, y) => (x, y) } } - def popStack3 = { checkStack(3) ; (popStackN(3): @unchecked) match { case List(x, y, z) => (x, y, z) } } - - /* Called by faux instruction LOAD_EXCEPTION to wipe out the stack. */ - def clearStack() = { - if (stack.nonEmpty) - logChecker("Wiping out the " + stack.length + " element stack for exception handler: " + stack) - - 1 to stack.length foreach (_ => popStack) - } - - def pushStack(xs: TypeKind*): Unit = { - pushStackN(xs filterNot (_ == UNIT), defaultInstrPrinter) - } - - def typeError(k1: TypeKind, k2: TypeKind) { - icodeError("\n expected: " + k1 + "\n found: " + k2) - } - def isSubtype(k1: TypeKind, k2: TypeKind) = (k1 isAssignabledTo k2) || { - import platform.isMaybeBoxed - - (k1, k2) match { - case (REFERENCE(_), REFERENCE(_)) if k1.isInterfaceType || k2.isInterfaceType => - logChecker("Considering %s <:< %s because at least one is an interface".format(k1, k2)) - true - case (REFERENCE(cls1), REFERENCE(cls2)) if isMaybeBoxed(cls1) || isMaybeBoxed(cls2) => - logChecker("Considering %s <:< %s because at least one might be a boxed primitive".format(cls1, cls2)) - true - case _ => - false - } - } - - def subtypeTest(k1: TypeKind, k2: TypeKind): Unit = - if (isSubtype(k1, k2)) () - else typeError(k2, k1) - - for (instr <- b) { - this.instruction = instr - - def checkLocal(local: Local) { - if ((method lookupLocal local.sym.name).isEmpty) - icodeError(s" $local is not defined in method $method") - } - def checkField(obj: TypeKind, field: Symbol): Unit = obj match { - case REFERENCE(sym) => - if (sym.info.member(field.name) == NoSymbol) - icodeError(" " + field + " is not defined in class " + clasz) - case _ => - icodeError(" expected reference type, but " + obj + " found") - } - - /* Checks that tpe is a subtype of one of the allowed types */ - def checkType(tpe: TypeKind, allowed: TypeKind*) = ( - if (allowed exists (k => isSubtype(tpe, k))) () - else icodeError(tpe + " is not one of: " + allowed.mkString("{ ", ", ", " }")) - ) - def checkNumeric(tpe: TypeKind) = - checkType(tpe, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) - - /* Checks that the 2 topmost elements on stack are of the kind TypeKind. */ - def checkBinop(kind: TypeKind) { - val (a, b) = popStack2 - checkType(a, kind) - checkType(b, kind) - } - - /* Check that arguments on the stack match method params. */ - def checkMethodArgs(method: Symbol) { - val params = method.info.paramTypes - checkStack(params.length) - ( - popStackN(params.length, mkInstrPrinter(num => "<arg" + num + ">")), - params.reverse map toTypeKind).zipped foreach ((x, y) => checkType(x, y) - ) - } - - /* Checks that the object passed as receiver has a method - * `method` and that it is callable from the current method. - */ - def checkMethod(receiver: TypeKind, method: Symbol) = - receiver match { - case REFERENCE(sym) => - checkBool(sym.info.member(method.name) != NoSymbol, - "Method " + method + " does not exist in " + sym.fullName) - if (method.isPrivate) - checkBool(method.owner == clasz.symbol, - "Cannot call private method of " + method.owner.fullName - + " from " + clasz.symbol.fullName) - else if (method.isProtected) { - val isProtectedOK = ( - (clasz.symbol isSubClass method.owner) || - (clasz.symbol.typeOfThis.typeSymbol isSubClass method.owner) // see pos/bug780.scala - ) - - checkBool(isProtectedOK, - "Cannot call protected method of " + method.owner.fullName - + " from " + clasz.symbol.fullName) - } - - case ARRAY(_) => - checkBool(receiver.toType.member(method.name) != NoSymbol, - "Method " + method + " does not exist in " + receiver) - - case t => - icodeError("Not a reference type: " + t) - } - - def checkBool(cond: Boolean, msg: String) = - if (!cond) icodeError(msg) - - if (settings.debug) { - log("PC: " + instr) - log("stack: " + stack) - log("================") - } - instr match { - case THIS(clasz) => - pushStack(toTypeKind(clasz.tpe)) - - case CONSTANT(const) => - pushStack(toTypeKind(const.tpe)) - - case LOAD_ARRAY_ITEM(kind) => - popStack2 match { - case (INT, ARRAY(elem)) => - subtypeTest(elem, kind) - pushStack(elem) - case (a, b) => - icodeError(" expected an INT and an array reference, but " + - a + ", " + b + " found") - } - - case LOAD_LOCAL(local) => - checkLocal(local) - pushStack(local.kind) - - case LOAD_FIELD(field, isStatic) => - // the symbol's owner should contain its field, but - // this is already checked by the type checker, no need - // to redo that here - if (isStatic) () - else checkField(popStack, field) - - pushStack(toTypeKind(field.tpe)) - - case LOAD_MODULE(module) => - checkBool((module.isModule || module.isModuleClass), - "Expected module: " + module + " flags: " + module.flagString) - pushStack(toTypeKind(module.tpe)) - - case STORE_THIS(kind) => - val actualType = popStack - if (actualType.isReferenceType) subtypeTest(actualType, kind) - else icodeError("Expected this reference but found: " + actualType) - - case STORE_ARRAY_ITEM(kind) => - popStack3 match { - case (k, INT, ARRAY(elem)) => - subtypeTest(k, kind) - subtypeTest(k, elem) - case (a, b, c) => - icodeError(" expected and array reference, and int and " + kind + - " but " + a + ", " + b + ", " + c + " found") - } - - case STORE_LOCAL(local) => - checkLocal(local) - val actualType = popStack - if (local.kind != NullReference) - subtypeTest(actualType, local.kind) - - case STORE_FIELD(field, true) => // static - val fieldType = toTypeKind(field.tpe) - val actualType = popStack - subtypeTest(actualType, fieldType) - - case STORE_FIELD(field, false) => // not static - val (value, obj) = popStack2 - checkField(obj, field) - val fieldType = toTypeKind(field.tpe) - if (fieldType == NullReference) () - else subtypeTest(value, fieldType) - - case CALL_PRIMITIVE(primitive) => - checkStack(instr.consumed) - primitive match { - case Negation(kind) => - checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) - checkType(popStack, kind) - pushStack(kind) - - case Test(op, kind, zero) => - if (zero) checkType(popStack, kind) - else checkBinop(kind) - - pushStack(BOOL) - - case Comparison(op, kind) => - checkNumeric(kind) - checkBinop(kind) - pushStack(INT) - - case Arithmetic(op, kind) => - checkNumeric(kind) - if (op == NOT) - checkType(popStack, kind) - else - checkBinop(kind) - pushStack(kind) - - case Logical(op, kind) => - checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG) - checkBinop(kind) - pushStack(kind) - - case Shift(op, kind) => - checkType(kind, BYTE, CHAR, SHORT, INT, LONG) - val (a, b) = popStack2 - checkType(a, INT) - checkType(b, kind) - pushStack(kind) - - case Conversion(src, dst) => - checkNumeric(src) - checkNumeric(dst) - checkType(popStack, src) - pushStack(dst) - - case ArrayLength(kind) => - popStack match { - case ARRAY(elem) => checkType(elem, kind) - case arr => icodeError(" array reference expected, but " + arr + " found") - } - pushStack(INT) - - case StartConcat => - pushStack(ConcatClass) - - case EndConcat => - checkType(popStack, ConcatClass) - pushStack(StringReference) - - case StringConcat(el) => - checkType(popStack, el) - checkType(popStack, ConcatClass) - pushStack(ConcatClass) - } - - case CALL_METHOD(method, style) => - // PP to ID: I moved the if (!method.isConstructor) check to cover all - // the styles to address checker failure. Can you confirm if the change - // was correct? If I remember right it's a matter of whether some brand - // of supercall should leave a value on the stack, and I know there is some - // trickery performed elsewhere regarding this. - val paramCount = method.info.paramTypes.length match { - case x if style.hasInstance => x + 1 - case x => x - } - if (style == Static(onInstance = true)) - checkBool(method.isPrivate || method.isConstructor, "Static call to non-private method.") - - checkStack(paramCount) - checkMethodArgs(method) - if (style.hasInstance) - checkMethod(popStack, method) - if (!method.isConstructor) - pushStack(toTypeKind(method.info.resultType)) - - case NEW(kind) => - pushStack(kind) - - case CREATE_ARRAY(elem, dims) => - checkStack(dims) - stack.pop(dims) foreach (checkType(_, INT)) - pushStack(ARRAY(elem)) - - case IS_INSTANCE(tpe) => - val ref = popStack - checkBool(!ref.isValueType, "IS_INSTANCE on primitive type: " + ref) - checkBool(!tpe.isValueType, "IS_INSTANCE on primitive type: " + tpe) - pushStack(BOOL) - - case CHECK_CAST(tpe) => - val ref = popStack - checkBool(!ref.isValueType, "CHECK_CAST to primitive type: " + ref) - checkBool(!tpe.isValueType, "CHECK_CAST to primitive type: " + tpe) - pushStack(tpe) - - case SWITCH(tags, labels) => - checkType(popStack, INT) - checkBool(tags.length == labels.length - 1, - "The number of tags and labels does not coincide.") - checkBool(labels forall (b => code.blocks contains b), - "Switch target cannot be found in code.") - - case JUMP(whereto) => - checkBool(code.blocks contains whereto, - "Jump to non-existant block " + whereto) - - case CJUMP(success, failure, cond, kind) => - checkBool(code.blocks contains success, - "Jump to non-existant block " + success) - checkBool(code.blocks contains failure, - "Jump to non-existant block " + failure) - checkBinop(kind) - - case CZJUMP(success, failure, cond, kind) => - checkBool(code.blocks contains success, - "Jump to non-existant block " + success) - checkBool(code.blocks contains failure, - "Jump to non-existant block " + failure) - checkType(popStack, kind) - - case RETURN(UNIT) => () - case RETURN(kind) => - val top = popStack - if (kind.isValueType) checkType(top, kind) - else checkBool(!top.isValueType, "" + kind + " is a reference type, but " + top + " is not") - - case THROW(clasz) => - checkType(popStack, toTypeKind(clasz.tpe)) - pushStack(NothingReference) - - case DROP(kind) => - checkType(popStack, kind) - - case DUP(kind) => - val top = popStack - checkType(top, kind) - pushStack(top) - pushStack(top) - - case MONITOR_ENTER() => - checkBool(popStack.isReferenceType, "MONITOR_ENTER on non-reference type") - - case MONITOR_EXIT() => - checkBool(popStack.isReferenceType, "MONITOR_EXIT on non-reference type") - - case BOX(kind) => - checkType(popStack, kind) - pushStack(REFERENCE(definitions.boxedClass(kind.toType.typeSymbol))) - - case UNBOX(kind) => - popStack - pushStack(kind) - - case LOAD_EXCEPTION(clasz) => - clearStack() - pushStack(REFERENCE(clasz)) - - case SCOPE_ENTER(_) | SCOPE_EXIT(_) => - () - - case _ => - abort("Unknown instruction: " + instr) - } - } - stack - } - - //////////////// Error reporting ///////////////////////// - - def icodeError(msg: String) { - ICodeCheckers.this.global.warning( - "!! ICode checker fatality in " + method + - "\n at: " + basicBlock.fullString + - "\n error message: " + msg - ) - } - - def icodeError(msg: String, stack: TypeStack) { - icodeError(msg + "\n type stack: " + stack) - } - } -} diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala deleted file mode 100644 index 10f0c6ee00..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* NSC -- new scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend -package icode - -import java.io.PrintWriter -import analysis.{ Liveness, ReachingDefinitions } -import scala.tools.nsc.symtab.classfile.ICodeReader -import scala.reflect.io.AbstractFile - -/** Glue together ICode parts. - * - * @author Iulian Dragos - */ -abstract class ICodes extends AnyRef - with Members - with BasicBlocks - with Opcodes - with TypeStacks - with TypeKinds - with ExceptionHandlers - with Primitives - with Linearizers - with Printers - with Repository -{ - val global: Global - import global.{ log, definitions, settings, perRunCaches, devWarning } - - /** The ICode representation of classes */ - val classes = perRunCaches.newMap[global.Symbol, IClass]() - - /** Debugging flag */ - def shouldCheckIcode = settings.check contains global.genicode.phaseName - def checkerDebug(msg: String) = if (shouldCheckIcode && global.settings.debug) println(msg) - - /** The ICode linearizer. */ - val linearizer: Linearizer = settings.Xlinearizer.value match { - case "rpo" => new ReversePostOrderLinearizer() - case "dfs" => new DepthFirstLinerizer() - case "normal" => new NormalLinearizer() - case "dump" => new DumpLinearizer() - case x => global.abort("Unknown linearizer: " + x) - } - - def newTextPrinter() = - new TextPrinter(new PrintWriter(Console.out, true), new DumpLinearizer) - - /** Have to be careful because dump calls around, possibly - * re-entering methods which initiated the dump (like foreach - * in BasicBlocks) which leads to the icode output olympics. - */ - private var alreadyDumping = false - - /** Print all classes and basic blocks. Used for debugging. */ - - def dumpClassesAndAbort(msg: String): Nothing = { - if (alreadyDumping) global.abort(msg) - else alreadyDumping = true - - Console.println(msg) - val printer = newTextPrinter() - classes.values foreach printer.printClass - global.abort(msg) - } - - def dumpMethodAndAbort(m: IMethod, msg: String): Nothing = { - Console.println("Fatal bug in inlinerwhile traversing " + m + ": " + msg) - m.dump() - global.abort("" + m) - } - def dumpMethodAndAbort(m: IMethod, b: BasicBlock): Nothing = - dumpMethodAndAbort(m, "found open block " + b + " " + b.flagsString) - - def checkValid(m: IMethod) { - // always slightly dicey to iterate over mutable structures - m foreachBlock { b => - if (!b.closed) { - // Something is leaving open/empty blocks around (see SI-4840) so - // let's not kill the deal unless it's nonempty. - if (b.isEmpty) { - devWarning(s"Found open but empty block while inlining $m: removing from block list.") - m.code removeBlock b - } - else dumpMethodAndAbort(m, b) - } - } - } - - object liveness extends Liveness { - val global: ICodes.this.global.type = ICodes.this.global - } - - object reachingDefinitions extends ReachingDefinitions { - val global: ICodes.this.global.type = ICodes.this.global - } - - lazy val AnyRefReference: TypeKind = REFERENCE(definitions.AnyRefClass) - lazy val BoxedUnitReference: TypeKind = REFERENCE(definitions.BoxedUnitClass) - lazy val NothingReference: TypeKind = REFERENCE(definitions.NothingClass) - lazy val NullReference: TypeKind = REFERENCE(definitions.NullClass) - lazy val ObjectReference: TypeKind = REFERENCE(definitions.ObjectClass) - lazy val StringReference: TypeKind = REFERENCE(definitions.StringClass) - - object icodeReader extends ICodeReader { - lazy val global: ICodes.this.global.type = ICodes.this.global - import global._ - def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = - global.loaders.lookupMemberAtTyperPhaseIfPossible(sym, name) - lazy val symbolTable: global.type = global - lazy val loaders: global.loaders.type = global.loaders - - def classFileLookup: util.ClassFileLookup[AbstractFile] = global.classPath - } - - /** A phase which works on icode. */ - abstract class ICodePhase(prev: Phase) extends global.GlobalPhase(prev) { - override def erasedTypes = true - override def apply(unit: global.CompilationUnit): Unit = - unit.icode foreach apply - - def apply(cls: global.icodes.IClass): Unit - } -} - diff --git a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala b/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala deleted file mode 100644 index 54be9d18f1..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/Linearizers.scala +++ /dev/null @@ -1,201 +0,0 @@ -/* NSC -- new scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - - -package scala -package tools.nsc -package backend -package icode - -import scala.collection.{ mutable, immutable } -import mutable.ListBuffer - -trait Linearizers { - self: ICodes => - - import global.debuglog - import opcodes._ - - abstract class Linearizer { - def linearize(c: IMethod): List[BasicBlock] - def linearizeAt(c: IMethod, start: BasicBlock): List[BasicBlock] - } - - /** - * A simple linearizer which predicts all branches to - * take the 'success' branch and tries to schedule those - * blocks immediately after the test. This is in sync with - * how 'while' statements are translated (if the test is - * 'true', the loop continues). - */ - class NormalLinearizer extends Linearizer with WorklistAlgorithm { - type Elem = BasicBlock - val worklist: WList = new mutable.Stack() - var blocks: List[BasicBlock] = Nil - - def linearize(m: IMethod): List[BasicBlock] = { - val b = m.startBlock - blocks = Nil - - run { - worklist pushAll (m.exh map (_.startBlock)) - worklist.push(b) - } - - blocks.reverse - } - - def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = { - blocks = Nil - worklist.clear() - linearize(start) - } - - /** Linearize another subtree and append it to the existing blocks. */ - def linearize(startBlock: BasicBlock): List[BasicBlock] = { - //blocks = startBlock :: Nil; - run( { worklist.push(startBlock); } ) - blocks.reverse - } - - def processElement(b: BasicBlock) = - if (b.nonEmpty) { - add(b) - b.lastInstruction match { - case JUMP(whereto) => - add(whereto) - case CJUMP(success, failure, _, _) => - add(success) - add(failure) - case CZJUMP(success, failure, _, _) => - add(success) - add(failure) - case SWITCH(_, labels) => - add(labels) - case RETURN(_) => () - case THROW(clasz) => () - } - } - - def dequeue: Elem = worklist.pop() - - /** - * Prepend b to the list, if not already scheduled. - * TODO: use better test than linear search - */ - def add(b: BasicBlock) { - if (blocks.contains(b)) - () - else { - blocks = b :: blocks - worklist push b - } - } - - def add(bs: List[BasicBlock]): Unit = bs foreach add - } - - /** - * Linearize code using a depth first traversal. - */ - class DepthFirstLinerizer extends Linearizer { - var blocks: List[BasicBlock] = Nil - - def linearize(m: IMethod): List[BasicBlock] = { - blocks = Nil - - dfs(m.startBlock) - m.exh foreach (b => dfs(b.startBlock)) - - blocks.reverse - } - - def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = { - blocks = Nil - dfs(start) - blocks.reverse - } - - def dfs(b: BasicBlock): Unit = - if (b.nonEmpty && add(b)) - b.successors foreach dfs - - /** - * Prepend b to the list, if not already scheduled. - * TODO: use better test than linear search - * @return Returns true if the block was added. - */ - def add(b: BasicBlock): Boolean = - !(blocks contains b) && { - blocks = b :: blocks - true - } - } - - /** - * Linearize code in reverse post order. In fact, it does - * a post order traversal, prepending visited nodes to the list. - * This way, it is constructed already in reverse post order. - */ - class ReversePostOrderLinearizer extends Linearizer { - var blocks: List[BasicBlock] = Nil - val visited = new mutable.HashSet[BasicBlock] - val added = new mutable.BitSet - - def linearize(m: IMethod): List[BasicBlock] = { - blocks = Nil - visited.clear() - added.clear() - - m.exh foreach (b => rpo(b.startBlock)) - rpo(m.startBlock) - - // if the start block has predecessors, it won't be the first one - // in the linearization, so we need to enforce it here - if (m.startBlock.predecessors eq Nil) - blocks - else - m.startBlock :: (blocks.filterNot(_ == m.startBlock)) - } - - def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = { - blocks = Nil - visited.clear() - added.clear() - - rpo(start) - blocks - } - - def rpo(b: BasicBlock): Unit = - if (b.nonEmpty && !visited(b)) { - visited += b - b.successors foreach rpo - add(b) - } - - /** - * Prepend b to the list, if not already scheduled. - * @return Returns true if the block was added. - */ - def add(b: BasicBlock) = { - debuglog("Linearizer adding block " + b.label) - - if (!added(b.label)) { - added += b.label - blocks = b :: blocks - } - } - } - - /** A 'dump' of the blocks in this method, which does not - * require any well-formedness of the basic blocks (like - * the last instruction being a jump). - */ - class DumpLinearizer extends Linearizer { - def linearize(m: IMethod): List[BasicBlock] = m.blocks - def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = sys.error("not implemented") - } -} diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala deleted file mode 100644 index 64146585e5..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ /dev/null @@ -1,296 +0,0 @@ -/* NSC -- new scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala -package tools.nsc -package backend -package icode - -import scala.collection.{ mutable, immutable } -import scala.reflect.internal.util.{ SourceFile, NoSourceFile } - -trait ReferenceEquality { - override def hashCode = System.identityHashCode(this) - override def equals(that: Any) = this eq that.asInstanceOf[AnyRef] -} - -trait Members { - self: ICodes => - - import global._ - - object NoCode extends Code(null, TermName("NoCode")) { - override def blocksList: List[BasicBlock] = Nil - } - - /** - * This class represents the intermediate code of a method or - * other multi-block piece of code, like exception handlers. - */ - class Code(method: IMethod, name: Name) { - def this(method: IMethod) = this(method, method.symbol.name) - /** The set of all blocks */ - val blocks = mutable.ListBuffer[BasicBlock]() - - /** The start block of the method */ - var startBlock: BasicBlock = NoBasicBlock - - private var currentLabel: Int = 0 - private var _touched = false - - def blocksList: List[BasicBlock] = blocks.toList - def instructions = blocksList flatMap (_.iterator) - def blockCount = blocks.size - def instructionCount = (blocks map (_.length)).sum - - def touched = _touched - def touched_=(b: Boolean): Unit = { - @annotation.tailrec def loop(xs: List[BasicBlock]) { - xs match { - case Nil => - case x :: xs => x.touched = true ; loop(xs) - } - } - if (b) loop(blocks.toList) - - _touched = b - } - - // Constructor code - startBlock = newBlock() - - def removeBlock(b: BasicBlock) { - if (settings.debug) { - // only do this sanity check when debug is turned on because it's moderately expensive - val referers = blocks filter (_.successors contains b) - assert(referers.isEmpty, s"Trying to removing block $b (with preds ${b.predecessors.mkString}) but it is still refered to from block(s) ${referers.mkString}") - } - - if (b == startBlock) { - assert(b.successors.length == 1, - s"Removing start block ${b} with ${b.successors.length} successors (${b.successors.mkString})." - ) - startBlock = b.successors.head - } - - blocks -= b - assert(!blocks.contains(b)) - method.exh filter (_ covers b) foreach (_.covered -= b) - touched = true - } - - /** This methods returns a string representation of the ICode */ - override def toString = "ICode '" + name.decoded + "'" - - /* Compute a unique new label */ - def nextLabel: Int = { - currentLabel += 1 - currentLabel - } - - /* Create a new block and append it to the list - */ - def newBlock(): BasicBlock = { - touched = true - val block = new BasicBlock(nextLabel, method) - blocks += block - block - } - } - - /** Common interface for IClass/IField/IMethod. */ - trait IMember extends Ordered[IMember] { - def symbol: Symbol - - def compare(other: IMember) = - if (symbol eq other.symbol) 0 - else if (symbol isLess other.symbol) -1 - else 1 - - override def equals(other: Any): Boolean = - other match { - case other: IMember => (this compare other) == 0 - case _ => false - } - - override def hashCode = symbol.## - } - - /** Represent a class in ICode */ - class IClass(val symbol: Symbol) extends IMember { - var fields: List[IField] = Nil - var methods: List[IMethod] = Nil - var cunit: CompilationUnit = _ - - def addField(f: IField): this.type = { - fields = f :: fields - this - } - - def addMethod(m: IMethod): this.type = { - methods = m :: methods - this - } - - def setCompilationUnit(unit: CompilationUnit): this.type = { - this.cunit = unit - this - } - - override def toString() = symbol.fullName - - def lookupMethod(s: Symbol) = methods find (_.symbol == s) - - /* returns this methods static ctor if it has one. */ - def lookupStaticCtor: Option[IMethod] = methods find (_.symbol.isStaticConstructor) - } - - /** Represent a field in ICode */ - class IField(val symbol: Symbol) extends IMember { } - - object NoIMethod extends IMethod(NoSymbol) { } - - /** - * Represents a method in ICode. Local variables contain - * both locals and parameters, similar to the way the JVM - * 'sees' them. - * - * Locals and parameters are added in reverse order, as they - * are kept in cons-lists. The 'builder' is responsible for - * reversing them and putting them back, when the generation is - * finished (GenICode does that). - */ - class IMethod(val symbol: Symbol) extends IMember { - var code: Code = NoCode - - def newBlock() = code.newBlock() - def startBlock = code.startBlock - def lastBlock = { assert(blocks.nonEmpty, symbol); blocks.last } - def blocks = code.blocksList - def linearizedBlocks(lin: Linearizer = self.linearizer): List[BasicBlock] = lin linearize this - - def foreachBlock[U](f: BasicBlock => U): Unit = blocks foreach f - - var native = false - - /** The list of exception handlers, ordered from innermost to outermost. */ - var exh: List[ExceptionHandler] = Nil - var sourceFile: SourceFile = NoSourceFile - var returnType: TypeKind = _ - var recursive: Boolean = false - var bytecodeHasEHs = false // set by ICodeReader only, used by Inliner to prevent inlining (SI-6188) - var bytecodeHasInvokeDynamic = false // set by ICodeReader only, used by Inliner to prevent inlining until we have proper invoke dynamic support - - /** local variables and method parameters */ - var locals: List[Local] = Nil - - /** method parameters */ - var params: List[Local] = Nil - - def hasCode = code ne NoCode - def setCode(code: Code): IMethod = { - this.code = code - this - } - - final def updateRecursive(called: Symbol): Unit = { - recursive ||= (called == symbol) - } - - def addLocal(l: Local): Local = findOrElse(locals)(_ == l) { locals ::= l ; l } - - def addParam(p: Local): Unit = - if (params contains p) () - else { - params ::= p - locals ::= p - } - - def addLocals(ls: List[Local]) = ls foreach addLocal - - def lookupLocal(n: Name): Option[Local] = locals find (_.sym.name == n) - def lookupLocal(sym: Symbol): Option[Local] = locals find (_.sym == sym) - - def addHandler(e: ExceptionHandler) = exh ::= e - - /** Is this method deferred ('abstract' in Java sense)? - */ - def isAbstractMethod = symbol.isDeferred || symbol.owner.isInterface || native - - def isStatic: Boolean = symbol.isStaticMember - - override def toString() = symbol.fullName - - import opcodes._ - - /** Merge together blocks that have a single successor which has a - * single predecessor. Exception handlers are taken into account (they - * might force to break a block of straight line code like that). - * - * This method should be most effective after heavy inlining. - */ - def normalize(): Unit = if (this.hasCode) { - val nextBlock: mutable.Map[BasicBlock, BasicBlock] = mutable.HashMap.empty - for (b <- code.blocks.toList - if b.successors.length == 1; - succ = b.successors.head - if succ ne b - if succ.predecessors.length == 1 - if succ.predecessors.head eq b - if !(exh.exists { (e: ExceptionHandler) => - (e.covers(succ) && !e.covers(b)) || (e.covers(b) && !e.covers(succ)) })) { - nextBlock(b) = succ - } - - var bb = code.startBlock - while (!nextBlock.isEmpty) { - if (nextBlock.isDefinedAt(bb)) { - bb.open() - var succ = bb - do { - succ = nextBlock(succ) - val lastInstr = bb.lastInstruction - /* Ticket SI-5672 - * Besides removing the control-flow instruction at the end of `bb` (usually a JUMP), we have to pop any values it pushes. - * Examples: - * `SWITCH` consisting of just the default case, or - * `CJUMP(targetBlock, targetBlock, _, _)` ie where success and failure targets coincide (this one consumes two stack values). - */ - val oldTKs = lastInstr.consumedTypes - assert(lastInstr.consumed == oldTKs.size, "Someone forgot to override consumedTypes() in " + lastInstr) - - bb.removeLastInstruction() - for(tk <- oldTKs.reverse) { bb.emit(DROP(tk), lastInstr.pos) } - succ.toList foreach { i => bb.emit(i, i.pos) } - code.removeBlock(succ) - exh foreach { e => e.covered = e.covered - succ } - - nextBlock -= bb - } while (nextBlock.isDefinedAt(succ)) - bb.close() - } else - bb = nextBlock.keysIterator.next() - } - checkValid(this) - } - - def dump() { - Console.println("dumping IMethod(" + symbol + ")") - newTextPrinter() printMethod this - } - } - - /** Represent local variables and parameters */ - class Local(val sym: Symbol, val kind: TypeKind, val arg: Boolean) { - var index: Int = -1 - - override def equals(other: Any): Boolean = other match { - case x: Local => sym == x.sym - case _ => false - } - override def hashCode = sym.hashCode - override def toString(): String = sym.toString - } -} diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala deleted file mode 100644 index 351a8e33d3..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ /dev/null @@ -1,767 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala -package tools.nsc -package backend -package icode - -import scala.reflect.internal.util.{Position,NoPosition} - -/* - A pattern match - - // locals - case THIS(clasz) => - case STORE_THIS(kind) => - case LOAD_LOCAL(local) => - case STORE_LOCAL(local) => - case SCOPE_ENTER(lv) => - case SCOPE_EXIT(lv) => - // stack - case LOAD_MODULE(module) => - case LOAD_EXCEPTION(clasz) => - case DROP(kind) => - case DUP(kind) => - // constants - case CONSTANT(const) => - // arithlogic - case CALL_PRIMITIVE(primitive) => - // casts - case IS_INSTANCE(tpe) => - case CHECK_CAST(tpe) => - // objs - case NEW(kind) => - case MONITOR_ENTER() => - case MONITOR_EXIT() => - case BOX(boxType) => - case UNBOX(tpe) => - // flds - case LOAD_FIELD(field, isStatic) => - case STORE_FIELD(field, isStatic) => - // mthds - case CALL_METHOD(method, style) => - // arrays - case LOAD_ARRAY_ITEM(kind) => - case STORE_ARRAY_ITEM(kind) => - case CREATE_ARRAY(elem, dims) => - // jumps - case SWITCH(tags, labels) => - case JUMP(whereto) => - case CJUMP(success, failure, cond, kind) => - case CZJUMP(success, failure, cond, kind) => - // ret - case RETURN(kind) => - case THROW(clasz) => -*/ - - -/** - * The ICode intermediate representation. It is a stack-based - * representation, very close to the JVM and .NET. It uses the - * erased types of Scala and references Symbols to refer named entities - * in the source files. - */ -trait Opcodes { self: ICodes => - import global.{Symbol, NoSymbol, Name, Constant} - - // categories of ICode instructions - final val localsCat = 1 - final val stackCat = 2 - final val constCat = 3 - final val arilogCat = 4 - final val castsCat = 5 - final val objsCat = 6 - final val fldsCat = 7 - final val mthdsCat = 8 - final val arraysCat = 9 - final val jumpsCat = 10 - final val retCat = 11 - - private lazy val ObjectReferenceList = ObjectReference :: Nil - - /** This class represents an instruction of the intermediate code. - * Each case subclass will represent a specific operation. - */ - abstract class Instruction extends Cloneable { - // Vlad: I used these for checking the quality of the implementation, and we should regularly run a build with them - // enabled. But for production these should definitely be disabled, unless we enjoy getting angry emails from Greg :) - //if (!this.isInstanceOf[opcodes.LOAD_EXCEPTION]) - // assert(consumed == consumedTypes.length) - //assert(produced == producedTypes.length) - - def category: Int = 0 // undefined - - /** This abstract method returns the number of used elements on the stack */ - def consumed : Int = 0 - - /** This abstract method returns the number of produced elements on the stack */ - def produced : Int = 0 - - /** This instruction consumes these types from the top of the stack, the first - * element in the list is the deepest element on the stack. - */ - def consumedTypes: List[TypeKind] = Nil - - /** This instruction produces these types on top of the stack. */ - // Vlad: I wonder why we keep producedTypes around -- it looks like an useless thing to have - def producedTypes: List[TypeKind] = Nil - - /** The corresponding position in the source file */ - private var _pos: Position = NoPosition - - def pos: Position = _pos - - def setPos(p: Position): this.type = { - _pos = p - this - } - - /** Clone this instruction. */ - override def clone(): Instruction = - super.clone.asInstanceOf[Instruction] - } - - object opcodes { - /** Loads "this" on top of the stack. - * Stack: ... - * ->: ...:ref - */ - case class THIS(clasz: Symbol) extends Instruction { - /** Returns a string representation of this constant */ - override def toString = "THIS(" + clasz.name + ")" - - override def consumed = 0 - override def produced = 1 - - override def producedTypes = - // we're not allowed to have REFERENCE(Array), but what about compiling the Array class? Well, we use object for it. - if (clasz != global.definitions.ArrayClass) - REFERENCE(clasz) :: Nil - else - ObjectReference :: Nil - - override def category = localsCat - } - - /** Loads a constant on the stack. - * Stack: ... - * ->: ...:constant - */ - case class CONSTANT(constant: Constant) extends Instruction { - override def toString = "CONSTANT(" + constant.escapedStringValue + ")" - override def consumed = 0 - override def produced = 1 - - override def producedTypes = toTypeKind(constant.tpe) :: Nil - - override def category = constCat - } - - /** Loads an element of an array. The array and the index should - * be on top of the stack. - * Stack: ...:array[a](Ref):index(Int) - * ->: ...:element(a) - */ - case class LOAD_ARRAY_ITEM(kind: TypeKind) extends Instruction { - override def consumed = 2 - override def produced = 1 - - override def consumedTypes = ARRAY(kind) :: INT :: Nil - override def producedTypes = kind :: Nil - - override def category = arraysCat - } - - /** Load a local variable on the stack. It can be a method argument. - * Stack: ... - * ->: ...:value - */ - case class LOAD_LOCAL(local: Local) extends Instruction { - override def consumed = 0 - override def produced = 1 - - override def producedTypes = local.kind :: Nil - - override def category = localsCat - } - - /** Load a field on the stack. The object to which it refers should be - * on the stack. - * Stack: ...:ref (assuming isStatic = false) - * ->: ...:value - */ - case class LOAD_FIELD(field: Symbol, isStatic: Boolean) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = - "LOAD_FIELD " + (if (isStatic) field.fullName else field.toString()) - - override def consumed = if (isStatic) 0 else 1 - override def produced = 1 - - override def consumedTypes = if (isStatic) Nil else REFERENCE(field.owner) :: Nil - override def producedTypes = toTypeKind(field.tpe) :: Nil - - // more precise information about how to load this field - // see #4283 - var hostClass: Symbol = field.owner - def setHostClass(cls: Symbol): this.type = { hostClass = cls; this } - - override def category = fldsCat - } - - case class LOAD_MODULE(module: Symbol) extends Instruction { - assert(module != NoSymbol, "Invalid module symbol") - /** Returns a string representation of this instruction */ - override def toString(): String = "LOAD_MODULE " + module - - override def consumed = 0 - override def produced = 1 - - override def producedTypes = REFERENCE(module) :: Nil - - override def category = stackCat - } - - /** Store a value into an array at a specified index. - * Stack: ...:array[a](Ref):index(Int):value(a) - * ->: ... - */ - case class STORE_ARRAY_ITEM(kind: TypeKind) extends Instruction { - override def consumed = 3 - override def produced = 0 - - override def consumedTypes = ARRAY(kind) :: INT :: kind :: Nil - - override def category = arraysCat - } - - /** Store a value into a local variable. It can be an argument. - * Stack: ...:value - * ->: ... - */ - case class STORE_LOCAL(local: Local) extends Instruction { - override def consumed = 1 - override def produced = 0 - - override def consumedTypes = local.kind :: Nil - - override def category = localsCat - } - - /** Store a value into a field. - * Stack: ...:ref:value (assuming isStatic=false) - * ->: ... - */ - case class STORE_FIELD(field: Symbol, isStatic: Boolean) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = - "STORE_FIELD "+field + (if (isStatic) " (static)" else " (dynamic)") - - override def consumed = if(isStatic) 1 else 2 - - override def produced = 0 - - override def consumedTypes = - if (isStatic) - toTypeKind(field.tpe) :: Nil - else - REFERENCE(field.owner) :: toTypeKind(field.tpe) :: Nil - - override def category = fldsCat - } - - /** Store a value into the 'this' pointer. - * Stack: ...:ref - * ->: ... - */ - case class STORE_THIS(kind: TypeKind) extends Instruction { - override def consumed = 1 - override def produced = 0 - override def consumedTypes = kind :: Nil - override def category = localsCat - } - - /** Call a primitive function. - * Stack: ...:arg1:arg2:...:argn - * ->: ...:result - */ - case class CALL_PRIMITIVE(primitive: Primitive) extends Instruction { - override def consumed = primitive match { - case Negation(_) => 1 - case Test(_,_, true) => 1 - case Test(_,_, false) => 2 - case Comparison(_,_) => 2 - case Arithmetic(NOT,_) => 1 - case Arithmetic(_,_) => 2 - case Logical(_,_) => 2 - case Shift(_,_) => 2 - case Conversion(_,_) => 1 - case ArrayLength(_) => 1 - case StringConcat(_) => 2 - case StartConcat => 0 - case EndConcat => 1 - } - override def produced = 1 - - override def consumedTypes = primitive match { - case Negation(kind) => kind :: Nil - case Test(_, kind, true) => kind :: Nil - case Test(_, kind, false) => kind :: kind :: Nil - case Comparison(_, kind) => kind :: kind :: Nil - case Arithmetic(NOT, kind) => kind :: Nil - case Arithmetic(_, kind) => kind :: kind :: Nil - case Logical(_, kind) => kind :: kind :: Nil - case Shift(_, kind) => kind :: INT :: Nil - case Conversion(from, _) => from :: Nil - case ArrayLength(kind) => ARRAY(kind) :: Nil - case StringConcat(kind) => ConcatClass :: kind :: Nil - case StartConcat => Nil - case EndConcat => ConcatClass :: Nil - } - - override def producedTypes = primitive match { - case Negation(kind) => kind :: Nil - case Test(_, _, true) => BOOL :: Nil - case Test(_, _, false) => BOOL :: Nil - case Comparison(_, _) => INT :: Nil - case Arithmetic(_, kind) => kind :: Nil - case Logical(_, kind) => kind :: Nil - case Shift(_, kind) => kind :: Nil - case Conversion(_, to) => to :: Nil - case ArrayLength(_) => INT :: Nil - case StringConcat(_) => ConcatClass :: Nil - case StartConcat => ConcatClass :: Nil - case EndConcat => REFERENCE(global.definitions.StringClass) :: Nil - } - - override def category = arilogCat - } - - /** This class represents a CALL_METHOD instruction - * STYLE: dynamic / static(StaticInstance) - * Stack: ...:ref:arg1:arg2:...:argn - * ->: ...:result - * - * STYLE: static(StaticClass) - * Stack: ...:arg1:arg2:...:argn - * ->: ...:result - * - */ - case class CALL_METHOD(method: Symbol, style: InvokeStyle) extends Instruction with ReferenceEquality { - def toShortString = - "CALL_METHOD " + method.name +" ("+style+")" - - /** Returns a string representation of this instruction */ - override def toString(): String = - "CALL_METHOD " + method.fullName +" ("+style+")" - - var hostClass: Symbol = method.owner - def setHostClass(cls: Symbol): this.type = { hostClass = cls; this } - - /** This is specifically for preserving the target native Array type long - * enough that clone() can generate the right call. - */ - var targetTypeKind: TypeKind = UNIT // the default should never be used, so UNIT should fail fast. - def setTargetTypeKind(tk: TypeKind) = targetTypeKind = tk - - private def params = method.info.paramTypes - private def consumesInstance = style match { - case Static(false) => 0 - case _ => 1 - } - - override def consumed = params.length + consumesInstance - override def consumedTypes = { - val args = params map toTypeKind - if (consumesInstance > 0) ObjectReference :: args - else args - } - - private val producedList = toTypeKind(method.info.resultType) match { - case UNIT => Nil - case _ if method.isConstructor => Nil - case kind => kind :: Nil - } - override def produced = producedList.size - override def producedTypes = producedList - - /** object identity is equality for CALL_METHODs. Needed for - * being able to store such instructions into maps, when more - * than one CALL_METHOD to the same method might exist. - */ - - override def category = mthdsCat - } - - /** - * A place holder entry that allows us to parse class files with invoke dynamic - * instructions. Because the compiler doesn't yet really understand the - * behavior of invokeDynamic, this op acts as a poison pill. Any attempt to analyze - * this instruction will cause a failure. The only optimization that - * should ever look at non-Scala generated icode is the inliner, and it - * has been modified to not examine any method with invokeDynamic - * instructions. So if this poison pill ever causes problems then - * there's been a serious misunderstanding - */ - // TODO do the real thing - case class INVOKE_DYNAMIC(poolEntry: Int) extends Instruction { - private def error = sys.error("INVOKE_DYNAMIC is not fully implemented and should not be analyzed") - override def consumed = error - override def produced = error - override def producedTypes = error - override def category = error - } - - case class BOX(boxType: TypeKind) extends Instruction { - assert(boxType.isValueType && (boxType ne UNIT)) // documentation - override def toString(): String = "BOX " + boxType - override def consumed = 1 - override def consumedTypes = boxType :: Nil - override def produced = 1 - override def producedTypes = BOXED(boxType) :: Nil - override def category = objsCat - } - - case class UNBOX(boxType: TypeKind) extends Instruction { - assert(boxType.isValueType && !boxType.isInstanceOf[BOXED] && (boxType ne UNIT)) // documentation - override def toString(): String = "UNBOX " + boxType - override def consumed = 1 - override def consumedTypes = ObjectReferenceList - override def produced = 1 - override def producedTypes = boxType :: Nil - override def category = objsCat - } - - /** Create a new instance of a class through the specified constructor - * Stack: ...:arg1:arg2:...:argn - * ->: ...:ref - */ - case class NEW(kind: REFERENCE) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = "NEW "+ kind - - override def consumed = 0 - - override def produced = 1 - - override def producedTypes = kind :: Nil - - /** The corresponding constructor call. */ - var init: CALL_METHOD = _ - - override def category = objsCat - } - - - /** This class represents a CREATE_ARRAY instruction - * Stack: ...:size_1:size_2:..:size_n - * ->: ...:arrayref - */ - case class CREATE_ARRAY(elem: TypeKind, dims: Int) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="CREATE_ARRAY "+elem + " x " + dims - - override def consumed = dims - - override def consumedTypes = List.fill(dims)(INT) - override def produced = 1 - - override def producedTypes = ARRAY(elem) :: Nil - - override def category = arraysCat - } - - /** This class represents a IS_INSTANCE instruction - * Stack: ...:ref - * ->: ...:result(boolean) - */ - case class IS_INSTANCE(typ: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="IS_INSTANCE "+typ - - override def consumed = 1 - override def produced = 1 - override def consumedTypes = ObjectReferenceList - override def producedTypes = BOOL :: Nil - - override def category = castsCat - } - - /** This class represents a CHECK_CAST instruction - * Stack: ...:ref(oldtype) - * ->: ...:ref(typ <=: oldtype) - */ - case class CHECK_CAST(typ: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="CHECK_CAST "+typ - - override def consumed = 1 - override def produced = 1 - override def consumedTypes = ObjectReferenceList - override def producedTypes = typ :: Nil - - override def category = castsCat - } - - /** This class represents a SWITCH instruction - * Stack: ...:index(int) - * ->: ...: - * - * The tags array contains one entry per label, each entry consisting of - * an array of ints, any of which will trigger the jump to the corresponding label. - * labels should contain an extra label, which is the 'default' jump. - */ - case class SWITCH(tags: List[List[Int]], labels: List[BasicBlock]) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="SWITCH ..." - - override def consumed = 1 - override def produced = 0 - - override def consumedTypes = INT :: Nil - - def flatTagsCount: Int = { var acc = 0; var rest = tags; while(rest.nonEmpty) { acc += rest.head.length; rest = rest.tail }; acc } // a one-liner - - override def category = jumpsCat - } - - /** This class represents a JUMP instruction - * Stack: ... - * ->: ... - */ - case class JUMP(whereto: BasicBlock) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="JUMP "+whereto.label - - override def consumed = 0 - override def produced = 0 - - override def category = jumpsCat - } - - /** This class represents a CJUMP instruction - * It compares the two values on the stack with the 'cond' test operator - * Stack: ...:value1:value2 - * ->: ... - */ - case class CJUMP(successBlock: BasicBlock, - failureBlock: BasicBlock, - cond: TestOp, - kind: TypeKind) extends Instruction - { - - /** Returns a string representation of this instruction */ - override def toString(): String = ( - "CJUMP (" + kind + ")" + - cond + " ? "+successBlock.label+" : "+failureBlock.label - ) - - override def consumed = 2 - override def produced = 0 - - override def consumedTypes = kind :: kind :: Nil - - override def category = jumpsCat - } - - /** This class represents a CZJUMP instruction - * It compares the one value on the stack and zero with the 'cond' test operator - * Stack: ...:value: - * ->: ... - */ - case class CZJUMP(successBlock: BasicBlock, - failureBlock: BasicBlock, - cond: TestOp, - kind: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = ( - "CZJUMP (" + kind + ")" + - cond + " ? "+successBlock.label+" : "+failureBlock.label - ) - - override def consumed = 1 - override def produced = 0 - - override def consumedTypes = kind :: Nil - override def category = jumpsCat - } - - - /** This class represents a RETURN instruction - * Stack: ... - * ->: ... - */ - case class RETURN(kind: TypeKind) extends Instruction { - override def consumed = if (kind == UNIT) 0 else 1 - override def produced = 0 - - override def consumedTypes = if (kind == UNIT) Nil else kind :: Nil - - override def category = retCat - } - - /** This class represents a THROW instruction - * Stack: ...:Throwable(Ref) - * ->: ...: - */ - case class THROW(clasz: Symbol) extends Instruction { - /** PP to ID: We discussed parameterizing LOAD_EXCEPTION but - * not THROW, which came about organically. It seems like the - * right thing, but can you confirm? - */ - override def toString = "THROW(" + clasz.name + ")" - - override def consumed = 1 - override def produced = 0 - - override def consumedTypes = toTypeKind(clasz.tpe) :: Nil - - override def category = retCat - } - - /** This class represents a DROP instruction - * Stack: ...:something - * ->: ... - */ - case class DROP (typ: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="DROP "+typ - - override def consumed = 1 - override def produced = 0 - - override def consumedTypes = typ :: Nil - - override def category = stackCat - } - - /** This class represents a DUP instruction - * Stack: ...:something - * ->: ...:something:something - */ - case class DUP (typ: TypeKind) extends Instruction { - override def consumed = 1 - override def produced = 2 - override def consumedTypes = typ :: Nil - override def producedTypes = typ :: typ :: Nil - override def category = stackCat - } - - /** This class represents a MONITOR_ENTER instruction - * Stack: ...:object(ref) - * ->: ...: - */ - case class MONITOR_ENTER() extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="MONITOR_ENTER" - - override def consumed = 1 - override def produced = 0 - - override def consumedTypes = ObjectReference :: Nil - - override def category = objsCat - } - - /** This class represents a MONITOR_EXIT instruction - * Stack: ...:object(ref) - * ->: ...: - */ - case class MONITOR_EXIT() extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="MONITOR_EXIT" - - override def consumed = 1 - - override def produced = 0 - - override def consumedTypes = ObjectReference :: Nil - - override def category = objsCat - } - - /** A local variable becomes visible at this point in code. - * Used only for generating precise local variable tables as - * debugging information. - */ - case class SCOPE_ENTER(lv: Local) extends Instruction { - override def toString(): String = "SCOPE_ENTER " + lv - override def consumed = 0 - override def produced = 0 - override def category = localsCat - } - - /** A local variable leaves its scope at this point in code. - * Used only for generating precise local variable tables as - * debugging information. - */ - case class SCOPE_EXIT(lv: Local) extends Instruction { - override def toString(): String = "SCOPE_EXIT " + lv - override def consumed = 0 - override def produced = 0 - override def category = localsCat - } - - /** Fake instruction. It designates the VM who pushes an exception - * on top of the /empty/ stack at the beginning of each exception handler. - * Note: Unlike other instructions, it consumes all elements on the stack! - * then pushes one exception instance. - */ - case class LOAD_EXCEPTION(clasz: Symbol) extends Instruction { - override def consumed = sys.error("LOAD_EXCEPTION does clean the whole stack, no idea how many things it consumes!") - override def produced = 1 - override def producedTypes = REFERENCE(clasz) :: Nil - override def category = stackCat - } - - /** This class represents a method invocation style. */ - sealed abstract class InvokeStyle { - /** Is this a dynamic method call? */ - def isDynamic: Boolean = false - - /** Is this a static method call? */ - def isStatic: Boolean = false - - def isSuper: Boolean = false - - /** Is this an instance method call? */ - def hasInstance: Boolean = true - - /** Returns a string representation of this style. */ - override def toString(): String - } - - /** Virtual calls. - * On JVM, translated to either `invokeinterface` or `invokevirtual`. - */ - case object Dynamic extends InvokeStyle { - override def isDynamic = true - override def toString(): String = "dynamic" - } - - /** - * Special invoke: - * Static(true) is used for calls to private members, ie `invokespecial` on JVM. - * Static(false) is used for calls to class-level instance-less static methods, ie `invokestatic` on JVM. - */ - case class Static(onInstance: Boolean) extends InvokeStyle { - override def isStatic = true - override def hasInstance = onInstance - override def toString(): String = { - if(onInstance) "static-instance" - else "static-class" - } - } - - /** Call through super[mix]. - * On JVM, translated to `invokespecial`. - */ - case class SuperCall(mix: Name) extends InvokeStyle { - override def isSuper = true - override def toString(): String = { "super(" + mix + ")" } - } - } -} diff --git a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala deleted file mode 100644 index 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 cd7e0b83e8..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,6 +56,32 @@ object AsmUtils { node } + def readClass(filename: String): ClassNode = readClass(classBytes(filename)) + + def classBytes(file: String): Array[Byte] = { + val f = new java.io.RandomAccessFile(file, "r") + val bytes = new Array[Byte](f.length.toInt) + f.read(bytes) + bytes + } + + def 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)) + } + /** * Returns a human-readable representation of the cnode ClassNode. */ @@ -115,12 +142,12 @@ object AsmUtils { * Run ASM's CheckClassAdapter over a class. Returns None if no problem is found, otherwise * Some(msg) with the verifier's error message. */ - def checkClass(classNode: ClassNode): Option[String] = { + def checkClass(classNode: ClassNode, dumpNonErroneous: Boolean = false): Option[String] = { val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) classNode.accept(cw) val sw = new StringWriter() val pw = new PrintWriter(sw) - CheckClassAdapter.verify(new ClassReader(cw.toByteArray), false, pw) + CheckClassAdapter.verify(new ClassReader(cw.toByteArray), dumpNonErroneous, pw) val res = sw.toString if (res.isEmpty) None else Some(res) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala deleted file mode 100644 index 93f5159f89..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ /dev/null @@ -1,465 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2014 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend.jvm - -import scala.tools.nsc.Global -import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo} -import BackendReporting.ClassSymbolInfoFailureSI9111 -import scala.tools.asm - -/** - * This trait contains code shared between GenBCode and GenASM that depends on types defined in - * the compiler cake (Global). - */ -final class BCodeAsmCommon[G <: Global](val global: G) { - import global._ - import definitions._ - - val ExcludedForwarderFlags = { - import scala.tools.nsc.symtab.Flags._ - // Should include DEFERRED but this breaks findMember. - SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO - } - - /** - * True for classes generated by the Scala compiler that are considered top-level in terms of - * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes. - */ - def considerAsTopLevelImplementationArtifact(classSym: Symbol) = { - classSym.isImplClass || classSym.isSpecialized - } - - /** - * Cache the value of delambdafy == "inline" for each run. We need to query this value many - * times, so caching makes sense. - */ - object delambdafyInline { - private var runId = -1 - private var value = false - - def apply(): Boolean = { - if (runId != global.currentRunId) { - runId = global.currentRunId - value = settings.Ydelambdafy.value == "inline" - } - value - } - } - - /** - * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a - * member class. This method is used to decide if we should emit an EnclosingMethod attribute. - * It is also used to decide whether the "owner" field in the InnerClass attribute should be - * null. - */ - def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { - assert(classSym.isClass, s"not a class: $classSym") - val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass - if (r && settings.Ybackend.value == "GenBCode") { - // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally - // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode - // we prevent this, see `nonAnon` in LambdaLift. - // phase travel necessary: after flatten, the name includes the name of outer classes. - // if some outer name contains $lambda, a non-lambda class is considered lambda. - assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) - } - r - } - - /** - * The next enclosing definition in the source structure. Includes anonymous function classes - * under delambdafy:inline, even though they are only generated during UnCurry. - */ - def nextEnclosing(sym: Symbol): Symbol = { - val origOwner = sym.originalOwner - // phase travel necessary: after flatten, the name includes the name of outer classes. - // if some outer name contains $anon, a non-anon class is considered anon. - if (delambdafyInline() && sym.rawowner.isAnonymousFunction) { - // SI-9105: special handling for anonymous functions under delambdafy:inline. - // - // class C { def t = () => { def f { class Z } } } - // - // class C { def t = byNameMethod { def f { class Z } } } - // - // In both examples, the method f lambda-lifted into the anonfun class. - // - // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun. - // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ... - // - // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!) - // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!). - // - // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if` - // above makes sure we don't jump over the anonymous function in the by-name argument case. - // - // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f - // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym` - // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and - // we need to return it. - // If the rawowners are different, the symbol was not in between. In the first example, the - // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing - // of `f` is its rawowner, the anonFunClassSym. - // - // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C, - // not into the anonymous function class. The originalOwner chain is Z - f - C. - if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner - else sym.rawowner - } else { - origOwner - } - } - - def nextEnclosingClass(sym: Symbol): Symbol = { - if (sym.isClass) sym - else nextEnclosingClass(nextEnclosing(sym)) - } - - def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={ - nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass - } - - /** - * Returns the enclosing method for non-member classes. In the following example - * - * class A { - * def f = { - * class B { - * class C - * } - * } - * } - * - * the method returns Some(f) for B, but None for C, because C is a member class. For non-member - * classes that are not enclosed by a method, it returns None: - * - * class A { - * { class B } - * } - * - * In this case, for B, we return None. - * - * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). - * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. - */ - private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { - assert(classSym.isClass, classSym) - - def doesNotExist(method: Symbol) = { - // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes. - // (2) Value classes. Member methods of value classes exist in the generated box class. However, - // nested methods lifted into a value class are moved to the companion object and don't exist - // in the value class itself. We can identify such nested methods: the initial enclosing class - // is a value class, but the current owner is some other class (the module class). - method.owner.isTrait && method.isImplOnly || { // (1) - val enclCls = nextEnclosingClass(method) - exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2) - } - } - - def enclosingMethod(sym: Symbol): Option[Symbol] = { - if (sym.isClass || sym == NoSymbol) None - else if (sym.isMethod) { - if (doesNotExist(sym)) None else Some(sym) - } - else enclosingMethod(nextEnclosing(sym)) - } - enclosingMethod(nextEnclosing(classSym)) - } - - /** - * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level - * property, this method looks at the originalOwner chain. See doc in BTypes. - */ - private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { - assert(classSym.isClass, classSym) - val r = nextEnclosingClass(nextEnclosing(classSym)) - // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release - if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r") - r - } - - final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) - - /** - * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not - * an anonymous or local class). See doc in BTypes. - * - * The class is parametrized by two functions to obtain a bytecode class descriptor for a class - * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend - * on the implementation of GenASM / GenBCode, so they need to be passed in. - */ - def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { - // trait impl classes are always top-level, see comment in BTypes - if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { - val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) - val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match { - case some @ Some(m) => - if (m.owner != enclosingClass) { - // This should never happen. In case it does, it prevents emitting an invalid - // EnclosingMethod attribute: if the attribute specifies an enclosing method, - // it needs to exist in the specified enclosing class. - devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") - None - } else some - case none => none - } - Some(EnclosingMethodEntry( - classDesc(enclosingClass), - methodOpt.map(_.javaSimpleName.toString).orNull, - methodOpt.map(methodDesc).orNull)) - } else { - None - } - } - - /** - * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. - * - * The problem is that we are interested in a source-level property. Various phases changed the - * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. - * Therefore, `sym.isStatic` is not what we want. For example, in - * object T { def f { object U } } - * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. - */ - def isOriginallyStaticOwner(sym: Symbol): Boolean = { - sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) - } - - /** - * Reconstruct the classfile flags from a Java defined class symbol. - * - * The implementation of this method is slightly different that `javaFlags` in BTypesFromSymbols. - * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags - * that are used in the generated classfiles. For example, all classes emitted by the Scala - * compiler have ACC_PUBLIC. - * - * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have - * to correspond exactly to the flags in the classfile. For example, if the class is package - * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the - * ClassBType. For example, the inliner needs the correct flags for access checks. - * - * Class flags are listed here: - * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1 - */ - def javaClassfileFlags(classSym: Symbol): Int = { - assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}") - import asm.Opcodes._ - def enumFlags = ACC_ENUM | { - // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method. - // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all - // Java enums for exhaustiveness checking. - val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred) - if (hasAbstractMethod) ACC_ABSTRACT else 0 - } - GenBCode.mkFlags( - // SI-9393: the classfile / java source parser make java annotation symbols look like classes. - // here we recover the actual classfile flags. - if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0, - if (classSym.isPublic) ACC_PUBLIC else 0, - if (classSym.isFinal) ACC_FINAL else 0, - // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces. - if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, - // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags) - if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (classSym.isArtifact) ACC_SYNTHETIC else 0, - if (classSym.hasJavaEnumFlag) enumFlags else 0 - ) - } - - /** - * The member classes of a class symbol. Note that the result of this method depends on the - * current phase, for example, after lambdalift, all local classes become member of the enclosing - * class. - * - * Impl classes are always considered top-level, see comment in BTypes. - */ - def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ - case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => - sym - case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin - val r = exitingPickler(sym.moduleClass) - assert(r != NoSymbol, sym.fullLocationString) - r - })(collection.breakOut) - - lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule - lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE")) - lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) - lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) - - /** Whether an annotation should be emitted as a Java annotation - * .initialize: if 'annot' is read from pickle, atp might be uninitialized - */ - def shouldEmitAnnotation(annot: AnnotationInfo) = { - annot.symbol.initialize.isJavaDefined && - annot.matches(ClassfileAnnotationClass) && - retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue && - annot.args.isEmpty - } - - def isRuntimeVisible(annot: AnnotationInfo): Boolean = { - annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match { - case Some(retentionAnnot) => - retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue))) - case _ => - // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the - // annotation is emitted with visibility `RUNTIME` - true - } - } - - private def retentionPolicyOf(annot: AnnotationInfo): Symbol = - annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc => - assoc.collectFirst { - case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value - }).getOrElse(AnnotationRetentionPolicyClassValue) - - def implementedInterfaces(classSym: Symbol): List[Symbol] = { - // Additional interface parents based on annotations and other cues - def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match { - case RemoteAttr => Some(RemoteInterfaceClass.tpe) - case _ => None - } - - // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes. - def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag - - val classParents = { - val parents = classSym.info.parents - // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the - // parents of a java annotations. undo this for the backend (where we need classfile-level information). - if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass) - else parents - } - - val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) - - // We keep the superClass when computing minimizeParents to eliminate more interfaces. - // Example: T can be eliminated from D - // trait T - // class C extends T - // class D extends C with T - val interfaces = erasure.minimizeParents(allParents) match { - case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) => - ifs - case ifs => - // minimizeParents removes the superclass if it's redundant, for example: - // trait A - // class C extends Object with A // minimizeParents removes Object - ifs - } - interfaces.map(_.typeSymbol) - } - - /** - * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We - * cannot change the typer context of the completer at this point and make it silent: the context - * captured when creating the completer in the namer. However, we can temporarily replace - * global.reporter (it's a var) to store errors. - */ - def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = { - if (sym.hasCompleteInfo) false - else { - val originalReporter = global.reporter - val storeReporter = new reporters.StoreReporter() - global.reporter = storeReporter - try { - sym.info - } finally { - global.reporter = originalReporter - } - sym.isErroneous - } - } - - /** - * Build the [[InlineInfo]] for a class symbol. - */ - def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { - val traitSelfType = if (classSym.isTrait && !classSym.isImplClass) { - // The mixin phase uses typeOfThis for the self parameter in implementation class methods. - val selfSym = classSym.typeOfThis.typeSymbol - if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None - } else { - None - } - - val isEffectivelyFinal = classSym.isEffectivelyFinal - - var warning = Option.empty[ClassSymbolInfoFailureSI9111] - - // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some - // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ - case methodSym => - if (completeSilentlyAndCheckErroneous(methodSym)) { - // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. - if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") - warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) - None - } else { - val name = methodSym.javaSimpleName.toString // same as in genDefDef - val signature = name + methodSymToDescriptor(methodSym) - - // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE): - // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin. - // This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final - // but non-overridden methods of sealed traits from being inlined. - // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the - // lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late - // flag is ignored. The members are therefore not isEffectivelyFinal (their owner - // is not a module). Since we know that all impl class members are static, we can - // just take the shortcut. - val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden) - - // Identify trait interface methods that have a static implementation in the implementation - // class. Invocations of these methods can be re-wrired directly to the static implementation - // if they are final or the receiver is known. - // - // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters - // and super accessors. When AddInterfaces creates the impl class, these methods are - // initially added to it. - // - // The mixin phase later on filters out most of these members from the impl class (see - // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the - // impl class after mixin. So the filter in mixin is not exactly what we need here (we - // want to identify concrete trait methods, not any accessors). So we check some symbol - // properties manually. - val traitMethodWithStaticImplementation = { - import symtab.Flags._ - classSym.isTrait && !classSym.isImplClass && - erasure.needsImplMethod(methodSym) && - !methodSym.isModule && - !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR)) - } - - val info = MethodInlineInfo( - effectivelyFinal = effectivelyFinal, - traitMethodWithStaticImplementation = traitMethodWithStaticImplementation, - annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), - annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) - ) - Some((signature, info)) - } - }).toMap - - InlineInfo(traitSelfType, isEffectivelyFinal, methodInlineInfos, warning) - } -} - -object BCodeAsmCommon { - /** - * Valid flags for InnerClass attribute entry. - * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 - */ - val INNER_CLASSES_FLAGS = { - asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | - asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | - asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | - asm.Opcodes.ACC_ENUM - } -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 416628d5ba..22587c68f7 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -15,6 +15,8 @@ import scala.reflect.internal.Flags import scala.tools.asm import GenBCode._ import BackendReporting._ +import scala.tools.asm.tree.MethodInsnNode +import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp} /* * @@ -26,24 +28,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { import global._ import definitions._ import bTypes._ - import bCodeICodeCommon._ import coreBTypes._ /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. */ abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) { - import icodes.TestOp - import icodes.opcodes.InvokeStyle - - /* If the selector type has a member with the right name, - * it is the host class; otherwise the symbol's owner. - */ - def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match { - case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner - case _ => selector.typeSymbol - } - /* ---------------- helper utils for generating methods and code ---------------- */ def emit(opc: Int) { mnode.visitInsn(opc) } @@ -71,12 +61,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genStat(tree: Tree) { lineNumber(tree) tree match { - case Assign(lhs @ Select(_, _), rhs) => + case Assign(lhs @ Select(qual, _), rhs) => val isStatic = lhs.symbol.isStaticMember if (!isStatic) { genLoadQualifier(lhs) } genLoad(rhs, symInfoTK(lhs.symbol)) lineNumber(tree) - fieldStore(lhs.symbol) + // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283 + val receiverClass = qual.tpe.typeSymbol + fieldStore(lhs.symbol, receiverClass) case Assign(lhs, rhs) => val s = lhs.symbol @@ -94,12 +86,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val thrownKind = tpeTK(expr) // `throw null` is valid although scala.Null (as defined in src/library-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/library-aux). - assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get) + assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(jlThrowableRef).get) genLoad(expr, thrownKind) lineNumber(expr) emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. - RT_NOTHING // always returns the same, the invoker should know :) + srNothingRef // always returns the same, the invoker should know :) } /* Generate code for primitive arithmetic operations. */ @@ -119,7 +111,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") } @@ -171,21 +163,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genLoad(args.head, INT) generatedType = k.asArrayBType.componentType bc.aload(elementType) - } - else if (scalaPrimitives.isArraySet(code)) { - args match { - case a1 :: a2 :: Nil => - genLoad(a1, INT) - genLoad(a2) - // the following line should really be here, but because of bugs in erasure - // we pretend we generate whatever type is expected from us. - //generatedType = UNIT - bc.astore(elementType) - case _ => - abort(s"Too many arguments for array set operation: $tree") - } - } - else { + } else if (scalaPrimitives.isArraySet(code)) { + val List(a1, a2) = args + genLoad(a1, INT) + genLoad(a2) + generatedType = UNIT + bc.astore(elementType) + } else { generatedType = INT emit(asm.Opcodes.ARRAYLENGTH) } @@ -203,14 +187,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val hasElse = !elsep.isEmpty val postIf = if (hasElse) new asm.Label else failure - genCond(condp, success, failure) + genCond(condp, success, failure, targetIfNoJump = success) + markProgramPoint(success) val thenKind = tpeTK(thenp) val elseKind = if (!hasElse) UNIT else tpeTK(elsep) def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT) val resKind = if (hasUnitBranch) UNIT else tpeTK(tree) - markProgramPoint(success) genLoad(thenp, resKind) if (hasElse) { bc goTo postIf } markProgramPoint(failure) @@ -235,14 +219,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { else if (isArrayOp(code)) genArrayOp(tree, code, expectedType) else if (isLogicalOp(code) || isComparisonOp(code)) { val success, failure, after = new asm.Label - genCond(tree, success, failure) + genCond(tree, success, failure, targetIfNoJump = success) // success block - markProgramPoint(success) - bc boolconst true - bc goTo after + markProgramPoint(success) + bc boolconst true + bc goTo after // failure block - markProgramPoint(failure) - bc boolconst false + markProgramPoint(failure) + bc boolconst false // after markProgramPoint(after) @@ -311,6 +295,15 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case app : Apply => generatedType = genApply(app, expectedType) + case app @ ApplyDynamic(qual, Literal(Constant(boostrapMethodRef: Symbol)) :: staticAndDynamicArgs) => + val numStaticArgs = boostrapMethodRef.paramss.head.size - 3 /*JVM provided args*/ + val (staticArgs, dynamicArgs) = staticAndDynamicArgs.splitAt(numStaticArgs) + val boostrapDescriptor = staticHandleFromSymbol(boostrapMethodRef) + val bootstrapArgs = staticArgs.map({case t @ Literal(c: Constant) => bootstrapMethodArg(c, t.pos)}) + val descriptor = methodBTypeFromMethodType(qual.symbol.info, false) + genLoadArguments(dynamicArgs, qual.symbol.info.params.map(param => typeToBType(param.info))) + mnode.visitInvokeDynamicInsn(qual.symbol.name.encoded, descriptor.descriptor, boostrapDescriptor, bootstrapArgs : _*) + case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.") case This(qual) => @@ -323,7 +316,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { else { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) generatedType = - if (tree.symbol == ArrayClass) ObjectReference + if (tree.symbol == ArrayClass) ObjectRef else classBTypeFromSymbol(claszSymbol) } @@ -331,26 +324,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { assert(tree.symbol.isModule, s"Selection of non-module from empty package: $tree sym: ${tree.symbol} at: ${tree.pos}") genLoadModule(tree) - case Select(qualifier, selector) => + case Select(qualifier, _) => val sym = tree.symbol generatedType = symInfoTK(sym) - val hostClass = findHostClass(qualifier.tpe, sym) - debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass") val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier - def genLoadQualUnlessElidable() { if (!qualSafeToElide) { genLoadQualifier(tree) } } - + // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283 + def receiverClass = qualifier.tpe.typeSymbol if (sym.isModule) { genLoadQualUnlessElidable() genLoadModule(tree) - } - else if (sym.isStaticMember) { + } else if (sym.isStaticMember) { genLoadQualUnlessElidable() - fieldLoad(sym, hostClass) - } - else { + fieldLoad(sym, receiverClass) + } else { genLoadQualifier(tree) - fieldLoad(sym, hostClass) + fieldLoad(sym, receiverClass) } case Ident(name) => @@ -366,7 +355,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (value.tag != UnitTag) (value.tag, expectedType) match { case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE - case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = srNullRef case _ => genConstant(value); generatedType = tpeTK(tree) } @@ -403,24 +392,18 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { /* * must-single-thread */ - def fieldLoad( field: Symbol, hostClass: Symbol = null) { - fieldOp(field, isLoad = true, hostClass) - } + def fieldLoad(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = true, hostClass) + /* * must-single-thread */ - def fieldStore(field: Symbol, hostClass: Symbol = null) { - fieldOp(field, isLoad = false, hostClass) - } + def fieldStore(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = false, hostClass) /* * must-single-thread */ - private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol) { - // LOAD_FIELD.hostClass , CALL_METHOD.hostClass , and #4283 - val owner = - if (hostClass == null) internalName(field.owner) - else internalName(hostClass) + private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol): Unit = { + val owner = internalName(if (hostClass == null) field.owner else hostClass) val fieldJName = field.javaSimpleName.toString val fieldDescr = symInfoTK(field).descriptor val isStatic = field.isStaticMember @@ -428,7 +411,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD } else { if (isStatic) asm.Opcodes.PUTSTATIC else asm.Opcodes.PUTFIELD } mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr) - } // ---------------- emitting constant values ---------------- @@ -461,19 +443,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case NullTag => emit(asm.Opcodes.ACONST_NULL) case ClazzTag => - val toPush: BType = { - toTypeKind(const.typeValue) match { - case kind: PrimitiveBType => boxedClassOfPrimitive(kind) - case kind => kind - } - } - mnode.visitLdcInsn(toPush.toASMType) + val tp = typeToBType(const.typeValue) + // classOf[Int] is transformed to Integer.TYPE by CleanUp + assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp") + mnode.visitLdcInsn(tp.toASMType) case EnumTag => val sym = const.symbolValue val ownerName = internalName(sym.owner) val fieldName = sym.javaSimpleName.toString - val fieldDesc = toTypeKind(sym.tpe.underlying).descriptor + val fieldDesc = typeToBType(sym.tpe.underlying).descriptor mnode.visitFieldInsn( asm.Opcodes.GETSTATIC, ownerName, @@ -527,6 +506,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { private def genApply(app: Apply, expectedType: BType): BType = { var generatedType = expectedType lineNumber(app) + app match { case Apply(TypeApply(fun, targs), _) => @@ -551,8 +531,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { else if (l.isPrimitive) { bc drop l if (cast) { - mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.internalName) - bc dup ObjectReference + mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName) + bc dup ObjectRef emit(asm.Opcodes.ATHROW) } else { bc boolconst false @@ -574,19 +554,33 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { generatedType = genTypeApply() - // 'super' call: Note: since constructors are supposed to - // return an instance of what they construct, we have to take - // special care. On JVM they are 'void', and Scala forbids (syntactically) - // to call super constructors explicitly and/or use their 'returned' value. - // therefore, we can ignore this fact, and generate code that leaves nothing - // on the stack (contrary to what the type in the AST says). - case Apply(fun @ Select(Super(_, mix), _), args) => - val invokeStyle = icodes.opcodes.SuperCall(mix) - // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); + case Apply(fun @ Select(Super(_, _), _), args) => + def initModule() { + // we initialize the MODULE$ field immediately after the super ctor + if (!isModuleInitialized && + jMethodName == INSTANCE_CONSTRUCTOR_NAME && + fun.symbol.javaSimpleName.toString == INSTANCE_CONSTRUCTOR_NAME && + isStaticModuleClass(claszSymbol)) { + isModuleInitialized = true + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + mnode.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisBType.internalName, + strMODULE_INSTANCE_FIELD, + thisBType.descriptor + ) + } + } + // 'super' call: Note: since constructors are supposed to + // return an instance of what they construct, we have to take + // special care. On JVM they are 'void', and Scala forbids (syntactically) + // to call super constructors explicitly and/or use their 'returned' value. + // therefore, we can ignore this fact, and generate code that leaves nothing + // on the stack (contrary to what the type in the AST says). mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) genLoadArguments(args, paramTKs(app)) - genCallMethod(fun.symbol, invokeStyle, app.pos) - generatedType = asmMethodType(fun.symbol).returnType + generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.pos) + initModule() // 'new' constructor call: Note: since constructors are // thought to return an instance of what they construct, @@ -617,8 +611,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } argsSize match { case 1 => bc newarray elemKind - case _ => - val descr = ('[' * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor + case _ => // this is currently dead code is Scalac, unlike in Dotty + val descr = ("[" * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor mnode.visitMultiANewArrayInsn(descr, argsSize) } @@ -627,7 +621,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc dup generatedType genLoadArguments(args, paramTKs(app)) - genCallMethod(ctor, icodes.opcodes.Static(onInstance = true), app.pos) + genCallMethod(ctor, InvokeStyle.Special, app.pos) case _ => abort(s"Cannot instantiate $tpt of kind: $generatedType") @@ -635,85 +629,93 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get genLoadArguments(args, paramTKs(app)) - genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface) - generatedType = asmMethodType(fun.symbol).returnType + genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface, attachment.sam) + generatedType = methodBTypeFromSymbol(fun.symbol).returnType - case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => + case Apply(fun, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) - val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) - generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) + val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) + generatedType = boxResultType(fun.symbol) - case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => + case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => genLoad(expr) - val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) + val boxType = unboxResultType(fun.symbol) generatedType = boxType - val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) + val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) case app @ Apply(fun, args) => val sym = fun.symbol - if (sym.isLabel) { // jump to a label + if (sym.isLabel) { // jump to a label genLoadLabelArguments(args, labelDef(sym), app.pos) bc goTo programPoint(sym) } else if (isPrimitive(sym)) { // primitive method call generatedType = genPrimitiveOp(app, expectedType) - } else { // normal method call - - def genNormalMethodCall() { - - val invokeStyle = - if (sym.isStaticMember) icodes.opcodes.Static(onInstance = false) - else if (sym.isPrivate || sym.isClassConstructor) icodes.opcodes.Static(onInstance = true) - else icodes.opcodes.Dynamic; - - if (invokeStyle.hasInstance) { - genLoadQualifier(fun) + } else { // normal method call + val invokeStyle = + if (sym.isStaticMember) InvokeStyle.Static + else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special + else InvokeStyle.Virtual + + if (invokeStyle.hasInstance) genLoadQualifier(fun) + genLoadArguments(args, paramTKs(app)) + + val Select(qual, _) = fun // fun is a Select, also checked in genLoadQualifier + if (sym == definitions.Array_clone) { + // Special-case Array.clone, introduced in 36ef60e. The goal is to generate this call + // as "[I.clone" instead of "java/lang/Object.clone". This is consistent with javac. + // Arrays have a public method `clone` (jls 10.7). + // + // The JVMS is not explicit about this, but that receiver type can be an array type + // descriptor (instead of a class internal name): + // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object + // + // Note that using `Object.clone()` would work as well, but only because the JVM + // relaxes protected access specifically if the receiver is an array: + // http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/interpreter/linkResolver.cpp#l439 + // Example: `class C { override def clone(): Object = "hi" }` + // Emitting `def f(c: C) = c.clone()` as `Object.clone()` gives a VerifyError. + val target: String = tpeTK(qual).asRefBType.classOrArrayType + val methodBType = methodBTypeFromSymbol(sym) + bc.invokevirtual(target, sym.javaSimpleName.toString, methodBType.descriptor, app.pos) + generatedType = methodBType.returnType + } else { + val receiverClass = if (!invokeStyle.isVirtual) null else { + // receiverClass is used in the bytecode to as the method receiver. using sym.owner + // may lead to IllegalAccessErrors, see 9954eaf / aladdin bug 455. + val qualSym = qual.tpe.typeSymbol + if (qualSym == ArrayClass) { + // For invocations like `Array(1).hashCode` or `.wait()`, use Object as receiver + // in the bytecode. Using the array descriptor (like we do for clone above) seems + // to work as well, but it seems safer not to change this. Javac also uses Object. + // Note that array apply/update/length are handled by isPrimitive (above). + assert(sym.owner == ObjectClass, s"unexpected array call: ${show(app)}") + ObjectClass + } else qualSym } - genLoadArguments(args, paramTKs(app)) - - // In "a couple cases", squirrel away a extra information (hostClass, targetTypeKind). TODO Document what "in a couple cases" refers to. - var hostClass: Symbol = null - var targetTypeKind: BType = null - fun match { - case Select(qual, _) => - val qualSym = findHostClass(qual.tpe, sym) - if (qualSym == ArrayClass) { - targetTypeKind = tpeTK(qual) - log(s"Stored target type kind for ${sym.fullName} as $targetTypeKind") - } - else { - hostClass = qualSym - if (qual.tpe.typeSymbol != qualSym) { - log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}") - } - } - - case _ => - } - if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) { - // An invokevirtual points to a CONSTANT_Methodref_info which in turn points to a - // CONSTANT_Class_info of the receiver type. - // The JVMS is not explicit about this, but that receiver type may be an array type - // descriptor (instead of a class internal name): - // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object - val target: String = targetTypeKind.asRefBType.classOrArrayType - bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos) - } - else { - genCallMethod(sym, invokeStyle, app.pos, hostClass) + generatedType = genCallMethod(sym, invokeStyle, app.pos, receiverClass) + + // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer + // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment + // is on the Select node (not on the Apply node added by UnCurry). + def recordInlineAnnotated(t: Tree): Unit = { + if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match { + case m: MethodInsnNode => + if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m + else inlineAnnotatedCallsites += m + case _ => + } else t match { + case Apply(fun, _) => recordInlineAnnotated(fun) + case _ => + } } - - } // end of genNormalMethodCall() - - genNormalMethodCall() - - generatedType = asmMethodType(sym).returnType + recordInlineAnnotated(app) + } } - } generatedType @@ -767,7 +769,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { for (caze @ CaseDef(pat, guard, body) <- tree.cases) { assert(guard == EmptyTree, guard) val switchBlockPoint = new asm.Label - switchBlocks ::= (switchBlockPoint, body) + switchBlocks ::= ((switchBlockPoint, body)) pat match { case Literal(value) => flatKeys ::= value.intValue @@ -843,8 +845,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * loading another throwable first). * * New (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1) - * - Requires consistent stack map frames. GenBCode generates stack frames if -target:jvm-1.6 - * or higher. + * - Requires consistent stack map frames. GenBCode always generates stack frames. * - In practice: the ASM library computes stack map frames for us (ClassWriter). Emitting * correct frames after an ATHROW is probably complex, so ASM uses the following strategy: * - Every time when generating an ATHROW, a new basic block is started. @@ -866,10 +867,24 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * emitted instruction was an ATHROW. As explained above, it is OK to emit a second ATHROW, * the verifiers will be happy. */ - emit(asm.Opcodes.ATHROW) + if (lastInsn.getOpcode != asm.Opcodes.ATHROW) + emit(asm.Opcodes.ATHROW) } else if (from.isNullType) { - bc drop from - emit(asm.Opcodes.ACONST_NULL) + /* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL. + * This is required to pass the verifier: in Scala's type system, Null conforms to any + * reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which + * is not a subtype of all reference types. Example: + * + * def nl: Null = null // in bytecode, nl has return type scala.runtime.Null$ + * val a: String = nl // OK for Scala but not for the JVM, scala.runtime.Null$ does not conform to String + * + * In order to fix the above problem, the value returned by nl is dropped and ACONST_NULL is + * inserted instead - after all, an expression of type scala.runtime.Null$ can only be null. + */ + if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) { + bc drop from + emit(asm.Opcodes.ACONST_NULL) + } } else (from, to) match { case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG) @@ -922,7 +937,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genLoadModule(tree: Tree): BType = { val module = ( if (!tree.symbol.isPackageClass) tree.symbol - else tree.symbol.info.member(nme.PACKAGE) match { + else tree.symbol.info.packageObject match { case NoSymbol => abort(s"SI-5604: Cannot use package as value: $tree") case s => abort(s"SI-5604: found package class where package object expected: $tree") } @@ -942,7 +957,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { asm.Opcodes.GETSTATIC, mbt.internalName /* + "$" */ , strMODULE_INSTANCE_FIELD, - mbt.descriptor // for nostalgics: toTypeKind(module.tpe).descriptor + mbt.descriptor // for nostalgics: typeToBType(module.tpe).descriptor ) } } @@ -978,92 +993,104 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genStringConcat(tree: Tree): BType = { lineNumber(tree) liftStringConcat(tree) match { - // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. case List(Literal(Constant("")), arg) => - genLoad(arg, ObjectReference) - genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos) + genLoad(arg, ObjectRef) + genCallMethod(String_valueOf, InvokeStyle.Static, arg.pos) case concatenations => bc.genStartConcat(tree.pos) for (elem <- concatenations) { - val kind = tpeTK(elem) - genLoad(elem, kind) - bc.genStringConcat(kind, elem.pos) + val loadedElem = elem match { + case Apply(boxOp, value :: Nil) if currentRun.runDefinitions.isBox(boxOp.symbol) => + // Eliminate boxing of primitive values. Boxing is introduced by erasure because + // there's only a single synthetic `+` method "added" to the string class. + value + + case _ => elem + } + val elemType = tpeTK(loadedElem) + genLoad(loadedElem, elemType) + bc.genConcat(elemType, loadedElem.pos) } bc.genEndConcat(tree.pos) - } - - StringReference + StringRef } - def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) { - - val siteSymbol = claszSymbol - val hostSymbol = if (hostClass0 == null) method.owner else hostClass0 + /** + * Generate a method invocation. If `specificReceiver != null`, it is used as receiver in the + * invocation instruction, otherwise `method.owner`. A specific receiver class is needed to + * prevent an IllegalAccessError, (aladdin bug 455). + */ + def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, specificReceiver: Symbol = null): BType = { val methodOwner = method.owner - // info calls so that types are up to date; erasure may add lateINTERFACE to traits - hostSymbol.info ; methodOwner.info - - def needsInterfaceCall(sym: Symbol) = ( - sym.isInterface - || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) - ) - - // whether to reference the type of the receiver or - // the type of the method owner - val useMethodOwner = ( - style != icodes.opcodes.Dynamic - || hostSymbol.isBottomClass - || methodOwner == definitions.ObjectClass - ) - val receiver = if (useMethodOwner) methodOwner else hostSymbol - val jowner = internalName(receiver) - val jname = method.javaSimpleName.toString - val bmType = asmMethodType(method) - val mdescr = bmType.descriptor - - def initModule() { - // we initialize the MODULE$ field immediately after the super ctor - if (!isModuleInitialized && - jMethodName == INSTANCE_CONSTRUCTOR_NAME && - jname == INSTANCE_CONSTRUCTOR_NAME && - isStaticModuleClass(siteSymbol)) { - isModuleInitialized = true - mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) - mnode.visitFieldInsn( - asm.Opcodes.PUTSTATIC, - thisName, - strMODULE_INSTANCE_FIELD, - "L" + thisName + ";" - ) + // the class used in the invocation's method descriptor in the classfile + val receiverClass = { + if (specificReceiver != null) + assert(style.isVirtual || specificReceiver == methodOwner, s"specificReceiver can only be specified for virtual calls. $method - $specificReceiver") + + val useSpecificReceiver = specificReceiver != null && !specificReceiver.isBottomClass + val receiver = if (useSpecificReceiver) specificReceiver else methodOwner + + // workaround for a JVM bug: https://bugs.openjdk.java.net/browse/JDK-8154587 + // when an interface method overrides a member of Object (note that all interfaces implicitly + // have superclass Object), the receiver needs to be the interface declaring the override (and + // not a sub-interface that inherits it). example: + // trait T { override def clone(): Object = "" } + // trait U extends T + // class C extends U + // class D { def f(u: U) = u.clone() } + // The invocation `u.clone()` needs `T` as a receiver: + // - using Object is illegal, as Object.clone is protected + // - using U results in a `NoSuchMethodError: U.clone. This is the JVM bug. + // Note that a mixin forwarder is generated, so the correct method is executed in the end: + // class C { override def clone(): Object = super[T].clone() } + val isTraitMethodOverridingObjectMember = { + receiver != methodOwner && // fast path - the boolean is used to pick either of these two, if they are the same it does not matter + style.isVirtual && + receiver.isTraitOrInterface && + ObjectTpe.decl(method.name).exists && // fast path - compute overrideChain on the next line only if necessary + method.overrideChain.last.owner == ObjectClass } + if (isTraitMethodOverridingObjectMember) methodOwner else receiver } - if (style.isStatic) { - if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) } - else { bc.invokestatic (jowner, jname, mdescr, pos) } + receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits + val receiverName = internalName(receiverClass) + + // super calls are only allowed to direct parents + if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) { + thisBType.info.get.inlineInfo.lateInterfaces += receiverName + cnode.interfaces.add(receiverName) } - else if (style.isDynamic) { - if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) } - else { bc.invokevirtual (jowner, jname, mdescr, pos) } + + def needsInterfaceCall(sym: Symbol) = { + sym.isTraitOrInterface || + sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) } - else { - assert(style.isSuper, s"An unknown InvokeStyle: $style") - bc.invokespecial(jowner, jname, mdescr, pos) - initModule() + + val jname = method.javaSimpleName.toString + val bmType = methodBTypeFromSymbol(method) + val mdescr = bmType.descriptor + + import InvokeStyle._ + style match { + case Static => bc.invokestatic (receiverName, jname, mdescr, pos) + case Special => bc.invokespecial (receiverName, jname, mdescr, pos) + case Virtual => + if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos) + else bc.invokevirtual (receiverName, jname, mdescr, pos) + case Super => bc.invokespecial (receiverName, jname, mdescr, pos) } + bmType.returnType } // end of genCallMethod() /* Generate the scala ## method. */ def genScalaHash(tree: Tree, applyPos: Position): BType = { - genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? - genLoad(tree, ObjectReference) - genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos) - - INT + genLoad(tree, ObjectRef) + genCallMethod(hashMethodSym, InvokeStyle.Static, applyPos) } /* @@ -1082,86 +1109,102 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } /* Emit code to compare the two top-most stack values using the 'op' operator. */ - private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { - if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT - bc.emitIF_ICMP(op, success) - } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) - bc.emitIF_ACMP(op, success) - } else { - (tk: @unchecked) match { - case LONG => emit(asm.Opcodes.LCMP) - case FLOAT => - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) - else emit(asm.Opcodes.FCMPL) - case DOUBLE => - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) - else emit(asm.Opcodes.DCMPL) + private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) { + if (targetIfNoJump == success) genCJUMP(failure, success, op.negate, tk, targetIfNoJump) + else { + if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + bc.emitIF_ICMP(op, success) + } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) + bc.emitIF_ACMP(op, success) + } else { + (tk: @unchecked) match { + case LONG => emit(asm.Opcodes.LCMP) + case FLOAT => + if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.FCMPG) + else emit(asm.Opcodes.FCMPL) + case DOUBLE => + if (op == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) + else emit(asm.Opcodes.DCMPL) + } + bc.emitIF(op, success) } - bc.emitIF(op, success) + if (targetIfNoJump != failure) bc goTo failure } - bc goTo failure } /* Emits code to compare (and consume) stack-top and zero using the 'op' operator */ - private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { - if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT - bc.emitIF(op, success) - } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) - // @unchecked because references aren't compared with GT, GE, LT, LE. - (op : @unchecked) match { - case icodes.EQ => bc emitIFNULL success - case icodes.NE => bc emitIFNONNULL success - } - } else { - (tk: @unchecked) match { - case LONG => - emit(asm.Opcodes.LCONST_0) - emit(asm.Opcodes.LCMP) - case FLOAT => - emit(asm.Opcodes.FCONST_0) - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) - else emit(asm.Opcodes.FCMPL) - case DOUBLE => - emit(asm.Opcodes.DCONST_0) - if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) - else emit(asm.Opcodes.DCMPL) + private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType, targetIfNoJump: asm.Label) { + if (targetIfNoJump == success) genCZJUMP(failure, success, op.negate, tk, targetIfNoJump) + else { + if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + bc.emitIF(op, success) + } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) + op match { // references are only compared with EQ and NE + case TestOp.EQ => bc emitIFNULL success + case TestOp.NE => bc emitIFNONNULL success + } + } else { + (tk: @unchecked) match { + case LONG => + emit(asm.Opcodes.LCONST_0) + emit(asm.Opcodes.LCMP) + case FLOAT => + emit(asm.Opcodes.FCONST_0) + 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 == TestOp.LT || op == TestOp.LE) emit(asm.Opcodes.DCMPG) + else emit(asm.Opcodes.DCMPL) + } + bc.emitIF(op, success) } - bc.emitIF(op, success) + if (targetIfNoJump != failure) bc goTo failure } - bc goTo failure } - val testOpForPrimitive: Array[TestOp] = Array( - icodes.EQ, icodes.NE, icodes.EQ, icodes.NE, icodes.LT, icodes.LE, icodes.GE, icodes.GT - ) + def testOpForPrimitive(primitiveCode: Int) = (primitiveCode: @switch) match { + case scalaPrimitives.ID => TestOp.EQ + case scalaPrimitives.NI => TestOp.NE + case scalaPrimitives.EQ => TestOp.EQ + case scalaPrimitives.NE => TestOp.NE + case scalaPrimitives.LT => TestOp.LT + case scalaPrimitives.LE => TestOp.LE + case scalaPrimitives.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. * The jump targets success/failure of the test are `then-target` and `else-target` resp. */ - private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) { + private def genCond(tree: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label) { def genComparisonOp(l: Tree, r: Tree, code: Int) { - val op: TestOp = testOpForPrimitive(code - scalaPrimitives.ID) - // special-case reference (in)equality test for null (null eq x, x eq null) - var nonNullSide: Tree = null - if (scalaPrimitives.isReferenceEqualityOp(code) && - { nonNullSide = ifOneIsNull(l, r); nonNullSide != null } - ) { - genLoad(nonNullSide, ObjectReference) - genCZJUMP(success, failure, op, ObjectReference) - } - else { + val op = testOpForPrimitive(code) + val nonNullSide = if (scalaPrimitives.isReferenceEqualityOp(code)) ifOneIsNull(l, r) else null + if (nonNullSide != null) { + // special-case reference (in)equality test for null (null eq x, x eq null) + genLoad(nonNullSide, ObjectRef) + genCZJUMP(success, failure, op, ObjectRef, targetIfNoJump) + } else { val tk = tpeTK(l).maxType(tpeTK(r)) genLoad(l, tk) genLoad(r, tk) - genCJUMP(success, failure, op, tk) + genCJUMP(success, failure, op, tk, targetIfNoJump) } } - def default() = { + def loadAndTestBoolean() = { genLoad(tree, BOOL) - genCZJUMP(success, failure, icodes.NE, BOOL) + genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) } lineNumber(tree) @@ -1172,37 +1215,35 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // lhs and rhs of test lazy val Select(lhs, _) = fun - val rhs = if (args.isEmpty) EmptyTree else args.head; // args.isEmpty only for ZNOT + val rhs = if (args.isEmpty) EmptyTree else args.head // args.isEmpty only for ZNOT - def genZandOrZor(and: Boolean) { // TODO WRONG + def genZandOrZor(and: Boolean) { // reaching "keepGoing" indicates the rhs should be evaluated too (ie not short-circuited). val keepGoing = new asm.Label - if (and) genCond(lhs, keepGoing, failure) - else genCond(lhs, success, keepGoing) + if (and) genCond(lhs, keepGoing, failure, targetIfNoJump = keepGoing) + else genCond(lhs, success, keepGoing, targetIfNoJump = keepGoing) markProgramPoint(keepGoing) - genCond(rhs, success, failure) + genCond(rhs, success, failure, targetIfNoJump) } getPrimitive(fun.symbol) match { - case ZNOT => genCond(lhs, failure, success) + case ZNOT => genCond(lhs, failure, success, targetIfNoJump) case ZAND => genZandOrZor(and = true) case ZOR => genZandOrZor(and = false) case code => - // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null) if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) { - // `lhs` has reference type - if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos) - else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos) - } - else if (scalaPrimitives.isComparisonOp(code)) + // rewrite `==` to null tests and `equals`. not needed for arrays (`equals` is reference equality). + if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, targetIfNoJump, tree.pos) + else genEqEqPrimitive(lhs, rhs, failure, success, targetIfNoJump, tree.pos) + } else if (scalaPrimitives.isComparisonOp(code)) { genComparisonOp(lhs, rhs, code) - else - default + } else + loadAndTestBoolean() } - case _ => default + case _ => loadAndTestBoolean() } } // end of genCond() @@ -1214,7 +1255,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * @param l left-hand-side of the '==' * @param r right-hand-side of the '==' */ - def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) { + def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label, pos: Position) { /* True if the equality comparison is between values that require the use of the rich equality * comparator (scala.runtime.Comparator.equals). This is the case when either side of the @@ -1224,7 +1265,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { */ val mustUseAnyComparator: Boolean = { val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) - !areSameFinals && platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol) } @@ -1232,51 +1272,50 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val equalsMethod: Symbol = { if (l.tpe <:< BoxedNumberClass.tpe) { if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum - else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030 + else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumChar else platform.externalEqualsNumObject } else platform.externalEquals } - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) - genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos) - genCZJUMP(success, failure, icodes.NE, BOOL) - } - else { + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) + genCallMethod(equalsMethod, InvokeStyle.Static, pos) + genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) + } else { if (isNull(l)) { // null == expr -> expr eq null - genLoad(r, ObjectReference) - genCZJUMP(success, failure, icodes.EQ, ObjectReference) + genLoad(r, ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump) } else if (isNull(r)) { // expr == null -> expr eq null - genLoad(l, ObjectReference) - genCZJUMP(success, failure, icodes.EQ, ObjectReference) + genLoad(l, ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump) } else if (isNonNullExpr(l)) { // SI-7852 Avoid null check if L is statically non-null. - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) - genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) - genCZJUMP(success, failure, icodes.NE, BOOL) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) + genCallMethod(Object_equals, InvokeStyle.Virtual, pos) + genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) } else { // l == r -> if (l eq null) r eq null else l.equals(r) - val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.toString) + val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.toString) val lNull = new asm.Label val lNonNull = new asm.Label - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) locals.store(eqEqTempLocal) - bc dup ObjectReference - genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectReference) + bc dup ObjectRef + genCZJUMP(lNull, lNonNull, TestOp.EQ, ObjectRef, targetIfNoJump = lNull) markProgramPoint(lNull) - bc drop ObjectReference + bc drop ObjectRef locals.load(eqEqTempLocal) - genCZJUMP(success, failure, icodes.EQ, ObjectReference) + genCZJUMP(success, failure, TestOp.EQ, ObjectRef, targetIfNoJump = lNonNull) markProgramPoint(lNonNull) locals.load(eqEqTempLocal) - genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) - genCZJUMP(success, failure, icodes.NE, BOOL) + genCallMethod(Object_equals, InvokeStyle.Virtual, pos) + genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) } } } @@ -1285,26 +1324,25 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genSynchronized(tree: Apply, expectedType: BType): BType def genLoadTry(tree: Try): BType - def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) { + def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol) { val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC) def asmType(sym: Symbol) = classBTypeFromSymbol(sym).toASMType val implMethodHandle = - new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else asm.Opcodes.H_INVOKEVIRTUAL, + new asm.Handle(if (lambdaTarget.hasFlag(Flags.STATIC)) asm.Opcodes.H_INVOKESTATIC else if (lambdaTarget.owner.isTrait) asm.Opcodes.H_INVOKEINTERFACE else asm.Opcodes.H_INVOKEVIRTUAL, classBTypeFromSymbol(lambdaTarget.owner).internalName, lambdaTarget.name.toString, - asmMethodType(lambdaTarget).descriptor) + methodBTypeFromSymbol(lambdaTarget).descriptor) val receiver = if (isStaticMethod) Nil else lambdaTarget.owner :: Nil val (capturedParams, lambdaParams) = lambdaTarget.paramss.head.splitAt(lambdaTarget.paramss.head.length - arity) // Requires https://github.com/scala/scala-java8-compat on the runtime classpath - val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => toTypeKind(sym.info).toASMType): _*) + val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => typeToBType(sym.info).toASMType): _*) - val constrainedType = new MethodBType(lambdaParams.map(p => toTypeKind(p.tpe)), toTypeKind(lambdaTarget.tpe.resultType)).toASMType - val sam = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply)) + val constrainedType = new MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType val samName = sam.name.toString - val samMethodType = asmMethodType(sam).toASMType + val samMethodType = methodBTypeFromSymbol(sam).toASMType - val flags = 3 // TODO 2.12.x Replace with LambdaMetafactory.FLAG_SERIALIZABLE | LambdaMetafactory.FLAG_MARKERS + val flags = java.lang.invoke.LambdaMetafactory.FLAG_SERIALIZABLE | java.lang.invoke.LambdaMetafactory.FLAG_MARKERS val ScalaSerializable = classBTypeFromSymbol(definitions.SerializableClass).toASMType bc.jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryBootstrapHandle, @@ -1316,13 +1354,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { /* markerInterfaces[0] = */ ScalaSerializable, /* bridgeCount = */ 0.asInstanceOf[AnyRef] ) - indyLambdaHosts += this.claszSymbol + indyLambdaHosts += cnode.name } } - - 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;") - } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 1b97681743..a32c21795d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -8,7 +8,6 @@ package tools.nsc package backend.jvm import scala.tools.asm -import scala.collection.mutable import scala.tools.nsc.io.AbstractFile import GenBCode._ import BackendReporting._ @@ -22,8 +21,266 @@ import BackendReporting._ */ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { import global._ + import definitions._ import bTypes._ import coreBTypes._ + import BTypes.{InternalName, InlineInfo, MethodInlineInfo} + + /** + * True for classes generated by the Scala compiler that are considered top-level in terms of + * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes. + */ + def considerAsTopLevelImplementationArtifact(classSym: Symbol) = classSym.isSpecialized + + /** + * Cache the value of delambdafy == "inline" for each run. We need to query this value many + * times, so caching makes sense. + */ + object delambdafyInline { + private var runId = -1 + private var value = false + + def apply(): Boolean = { + if (runId != global.currentRunId) { + runId = global.currentRunId + value = settings.Ydelambdafy.value == "inline" + } + value + } + } + + /** + * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a + * member class. This method is used to decide if we should emit an EnclosingMethod attribute. + * It is also used to decide whether the "owner" field in the InnerClass attribute should be + * null. + */ + def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { + assert(classSym.isClass, s"not a class: $classSym") + val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass + if (r) { + // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true. + // we prevent this, see `nonAnon` in LambdaLift. + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $lambda, a non-lambda class is considered lambda. + assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) + } + r + } + + /** + * The next enclosing definition in the source structure. Includes anonymous function classes + * under delambdafy:inline, even though they are only generated during UnCurry. + */ + def nextEnclosing(sym: Symbol): Symbol = { + val origOwner = sym.originalOwner + // phase travel necessary: after flatten, the name includes the name of outer classes. + // if some outer name contains $anon, a non-anon class is considered anon. + if (delambdafyInline() && sym.rawowner.isAnonymousFunction) { + // SI-9105: special handling for anonymous functions under delambdafy:inline. + // + // class C { def t = () => { def f { class Z } } } + // + // class C { def t = byNameMethod { def f { class Z } } } + // + // In both examples, the method f lambda-lifted into the anonfun class. + // + // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun. + // So nextEnclosing needs to return the following chain: Z - f - anonFunClassSym - ... + // + // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!) + // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!). + // + // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if` + // above makes sure we don't jump over the anonymous function in the by-name argument case. + // + // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f + // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym` + // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and + // we need to return it. + // If the rawowners are different, the symbol was not in between. In the first example, the + // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing + // of `f` is its rawowner, the anonFunClassSym. + // + // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C, + // not into the anonymous function class. The originalOwner chain is Z - f - C. + if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner + else sym.rawowner + } else { + origOwner + } + } + + def nextEnclosingClass(sym: Symbol): Symbol = + if (sym.isClass) sym + else nextEnclosingClass(nextEnclosing(sym)) + + def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) = + nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass + + /** + * Returns the enclosing method for non-member classes. In the following example + * + * class A { + * def f = { + * class B { + * class C + * } + * } + * } + * + * the method returns Some(f) for B, but None for C, because C is a member class. For non-member + * classes that are not enclosed by a method, it returns None: + * + * class A { + * { class B } + * } + * + * In this case, for B, we return None. + * + * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). + * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. + */ + private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { + assert(classSym.isClass, classSym) + + def doesNotExist(method: Symbol) = { + // Value classes. Member methods of value classes exist in the generated box class. However, + // nested methods lifted into a value class are moved to the companion object and don't exist + // in the value class itself. We can identify such nested methods: the initial enclosing class + // is a value class, but the current owner is some other class (the module class). + val enclCls = nextEnclosingClass(method) + exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls + } + + def enclosingMethod(sym: Symbol): Option[Symbol] = { + if (sym.isClass || sym == NoSymbol) None + else if (sym.isMethod) { + if (doesNotExist(sym)) None else Some(sym) + } + else enclosingMethod(nextEnclosing(sym)) + } + enclosingMethod(nextEnclosing(classSym)) + } + + /** + * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level + * property, this method looks at the originalOwner chain. See doc in BTypes. + */ + private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { + assert(classSym.isClass, classSym) + val r = nextEnclosingClass(nextEnclosing(classSym)) + r + } + + final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) + + /** + * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not + * an anonymous or local class). See doc in BTypes. + * + * The class is 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] = { + // specialized classes are always top-level, see comment in BTypes + if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { + val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) + for (m <- methodOpt) assert(m.owner == enclosingClass, s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") + Some(EnclosingMethodEntry( + classDesc(enclosingClass), + methodOpt.map(_.javaSimpleName.toString).orNull, + methodOpt.map(methodDesc).orNull)) + } else { + None + } + } + + /** + * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. + * + * The problem is that we are interested in a source-level property. Various phases changed the + * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. + * Therefore, `sym.isStatic` is not what we want. For example, in + * object T { def f { object U } } + * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. + */ + def isOriginallyStaticOwner(sym: Symbol): Boolean = + sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) + + /** + * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We + * cannot change the typer context of the completer at this point and make it silent: the context + * captured when creating the completer in the namer. However, we can temporarily replace + * global.reporter (it's a var) to store errors. + */ + def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = + if (sym.hasCompleteInfo) false + else { + val originalReporter = global.reporter + val storeReporter = new reporters.StoreReporter() + global.reporter = storeReporter + try { + sym.info + } finally { + global.reporter = originalReporter + } + sym.isErroneous + } + + /** + * Build the [[InlineInfo]] for a class symbol. + */ + def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { + val isEffectivelyFinal = classSym.isEffectivelyFinal + + val sam = { + if (classSym.isEffectivelyFinal) None + else { + // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an + // empty parameter list in later phases and would therefore be picked as SAM. + val samSym = exitingPickler(definitions.samOf(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) + + // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the + // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded + // so they are not marked final in the InlineInfo attribute. + // + // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not + // work, the abstract accessor for O will be marked effectivelyFinal. + val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred + + val info = MethodInlineInfo( + effectivelyFinal = effectivelyFinal, + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) + ) + Some((signature, info)) + } + }).toMap + + InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning) + } /* * must-single-thread @@ -191,20 +448,18 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } /* - * Populates the InnerClasses JVM attribute with `refedInnerClasses`. - * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted) - * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass` - * but otherwise not mentioned in `jclass`. + * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner + * classes in BTypes.scala. * - * `refedInnerClasses` may contain duplicates, - * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency). + * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of + * each inner class it lists (those are looked up and included). * - * This method serializes in the InnerClasses JVM attribute in an appropriate order, - * not necessarily that given by `refedInnerClasses`. + * This method serializes in the InnerClasses JVM attribute in an appropriate order, not + * necessarily that given by `refedInnerClasses`. * * can-multi-thread */ - final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { + final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler @@ -310,85 +565,122 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final val emitLines = debugLevel >= 2 final val emitVars = debugLevel >= 3 - /* - * Contains class-symbols that: - * (a) are known to denote inner classes - * (b) are mentioned somewhere in the class being generated. - * - * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". - */ - final val innerClassBufferASM = mutable.Set.empty[ClassBType] - /** - * The class internal name for a given class symbol. If the symbol describes a nested class, the - * ClassBType is added to the innerClassBufferASM. + * The class internal name for a given class symbol. */ final def internalName(sym: Symbol): String = { // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that we - // register the class (instead of the module class) in innerClassBufferASM. + // If the `sym` is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). // The two symbols have the same name, so the resulting internalName is the same. // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym - getClassBTypeAndRegisterInnerClass(classSym).internalName + classBTypeFromSymbol(classSym).internalName } + } // end of trait BCInnerClassGen + + trait BCAnnotGen extends BCInnerClassGen { + private lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule + private lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE")) + private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) + private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) /** - * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the - * innerClassBufferASM. + * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the + * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding + * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure). * - * TODO: clean up the way we track referenced inner classes. - * doing it during code generation is not correct when the optimizer changes the code. + * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend. + * Java annoations on the other hand are additionally emitted to the classfile in Java's format. + * + * This means that [[Type]] instances within an AnnotaionInfo reach the backend non-erased. Examples: + * - @(javax.annotation.Resource @annotation.meta.getter) val x = 0 + * Here, annotationInfo.atp is an AnnotatedType. + * - @SomeAnnotation[T] val x = 0 + * In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot + * actually happen because Java annotations cannot be generic. + * - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0 + * The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the + * non-erased existential type. */ - final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { - val r = classBTypeFromSymbol(sym) - if (r.isNestedClass.get) innerClassBufferASM += r - r + def erasedType(tp: Type): Type = enteringErasure { + // make sure we don't erase value class references to the type that the value class boxes + // this is basically the same logic as in erasure's preTransform, case Literal(classTag). + tp.dealiasWiden match { + case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr) + case tpe => erasure.erasure(tpe.typeSymbol)(tpe) + } } - /** - * The BType for a type reference. If the result is a ClassBType for a nested class, it is added - * to the innerClassBufferASM. - * TODO: clean up the way we track referenced inner classes. - */ - final def toTypeKind(t: Type): BType = typeToBType(t) match { - case c: ClassBType if c.isNestedClass.get => - innerClassBufferASM += c - c - case r => r + def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor + + /** Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be uninitialized + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = { + annot.symbol.initialize.isJavaDefined && + annot.matches(ClassfileAnnotationClass) && + retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue && + annot.args.isEmpty } - /** - * Class components that are nested classes are added to the innerClassBufferASM. - * TODO: clean up the way we track referenced inner classes. - */ - final def asmMethodType(msym: Symbol): MethodBType = { - val r = methodBTypeFromSymbol(msym) - (r.returnType :: r.argumentTypes) foreach { - case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c + private def isRuntimeVisible(annot: AnnotationInfo): Boolean = { + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match { + case Some(retentionAnnot) => + retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue))) case _ => + // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the + // annotation is emitted with visibility `RUNTIME` + true } - r } - /** - * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to - * the innerClassBufferASM. - */ - final def descriptor(t: Type): String = { toTypeKind(t).descriptor } - - /** - * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added - * to the innerClassBufferASM. - */ - final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor } - - } // end of trait BCInnerClassGen - - trait BCAnnotGen extends BCInnerClassGen { + private def retentionPolicyOf(annot: AnnotationInfo): Symbol = + annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc => + assoc.collectFirst { + case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value + }).getOrElse(AnnotationRetentionPolicyClassValue) + + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.length) + var idx = 0 + while(idx < bytes.length) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + ca + } - import genASM.{ubytesToCharArray, arrEncode} - import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible} + final def arrEncode(sb: ScalaSigBytes): Array[String] = { + var strs: List[String] = Nil + val bSeven: Array[Byte] = sb.sevenBitsMayBeZero + // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) + var prevOffset = 0 + var offset = 0 + var encLength = 0 + while(offset < bSeven.length) { + val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) + val newEncLength = encLength.toLong + deltaEncLength + if(newEncLength >= 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += deltaEncLength + offset += 1 + } + } + if(prevOffset < offset) { + assert(offset == bSeven.length) + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + } + assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? + strs.reverse.toArray + } /* * can-multi-thread @@ -420,9 +712,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case StringTag => assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, toTypeKind(const.typeValue).toASMType) + case ClazzTag => + av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType) case EnumTag => - val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class. val evalue = const.symbolValue.name.toString // value the actual enumeration value. av.visitEnum(name, edesc, evalue) } @@ -435,7 +728,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { av.visit(name, strEncode(sb)) } else { val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- genASM.arrEncode(sb)) { arrAnnotV.visit(name, arg) } + for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } arrAnnotV.visitEnd() } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. @@ -447,7 +740,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case NestedAnnotArg(annInfo) => val AnnotationInfo(typ, args, assocs) = annInfo assert(args.isEmpty, args) - val desc = descriptor(typ) // the class descriptor of the nested annotation class + val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class val nestedVisitor = av.visitAnnotation(name, desc) emitAssocs(nestedVisitor, assocs) } @@ -472,7 +765,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -484,7 +777,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -496,7 +789,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -511,16 +804,40 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { annot <- annots) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } } + /* + * must-single-thread + */ + def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = { + for (param <- params) { + var access = asm.Opcodes.ACC_FINAL + if (param.isArtifact) + access |= asm.Opcodes.ACC_SYNTHETIC + jmethod.visitParameter(param.name.decoded, access) + } + } } // end of trait BCAnnotGen trait BCJGenSigGen { - def getCurrentCUnit(): CompilationUnit + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + ) /* @return * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). @@ -528,7 +845,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * * must-single-thread */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit()) + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + getGenericSignature(sym, owner, memberTpe) + } + + def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = { + if (!needsGenericSignature(sym)) { return null } + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _: Throwable => false } + } + + if (settings.Xverify) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if(!isValidSignature) { + reporter.warning(sym.pos, + sm"""|compiler bug: created invalid generic signature for $sym in ${sym.owner.skipPackageObject.fullName} + |signature: $sig + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + reporter.warning(sym.pos, + sm"""|compiler bug: created generic signature for $sym in ${sym.owner.skipPackageObject.fullName} that does not conform to its erasure + |signature: $sig + |original type: $memberTpe + |normalized type: $normalizedTpe + |erasure type: $bytecodeTpe + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim) + return null + } + } + + sig + } } // end of trait BCJGenSigGen @@ -541,11 +914,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { - val needsAnnotation = ( - ( isRemoteClass || - isRemote(meth) && isJMethodPublic - ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass) - ) + def hasThrowsRemoteException = meth.annotations.exists { + case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass + case _ => false + } + val needsAnnotation = { + (isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !hasThrowsRemoteException + } if (needsAnnotation) { val c = Constant(definitions.RemoteExceptionClass.tpe) val arg = Literal(c) setType c.tpe @@ -557,10 +934,26 @@ 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 toTypeKind + val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) /* Forwarders must not be marked final, @@ -574,12 +967,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit()) + val jgensig = staticForwarderGenericSignature(m, module) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) - val jReturnType = toTypeKind(methodInfo.resultType) + val jReturnType = typeToBType(methodInfo.resultType) val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor val mirrorMethodName = m.javaSimpleName.toString val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( @@ -595,7 +988,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { mirrorMethod.visitCode() - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(module).descriptor) var index = 0 for(jparamType <- paramJavaTypes) { @@ -604,7 +997,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { index += jparamType.size } - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).descriptor, false) + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false) mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN)) mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -629,9 +1022,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") - for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) { + for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) - debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") + debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}") else if (conflictingNames(m.name)) log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}") else if (m.hasAccessBoundary) @@ -654,8 +1047,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def getExceptions(excs: List[AnnotationInfo]): List[String] = { - for (ThrownException(exc) <- excs.distinct) - yield internalName(exc) + for (ThrownException(tp) <- excs.distinct) + yield { + val erased = erasedType(tp) + internalName(erased.typeSymbol) + } } } // end of trait BCForwardersGen @@ -682,60 +1078,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { new java.lang.Long(id) ).visitEnd() } - - /** - * Add: - * private static java.util.Map $deserializeLambdaCache$ = null - * private static Object $deserializeLambda$(SerializedLambda l) { - * var cache = $deserializeLambdaCache$ - * if (cache eq null) { - * cache = new java.util.HashMap() - * $deserializeLambdaCache$ = cache - * } - * return scala.compat.java8.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l); - * } - */ - def addLambdaDeserialize(clazz: Symbol, jclass: asm.ClassVisitor): Unit = { - val cw = jclass - import scala.tools.asm.Opcodes._ - - // Need to force creation of BTypes for these as `getCommonSuperClass` is called on - // automatically computing the max stack size (`visitMaxs`) during method writing. - javaUtilHashMapReference - javaUtilMapReference - - cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC) - - { - val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null) - fv.visitEnd() - } - - { - val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null) - mv.visitCode() - // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol. - mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") - mv.visitVarInsn(ASTORE, 1) - mv.visitVarInsn(ALOAD, 1) - val l0 = new asm.Label() - mv.visitJumpInsn(IFNONNULL, l0) - mv.visitTypeInsn(NEW, "java/util/HashMap") - mv.visitInsn(DUP) - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false) - mv.visitVarInsn(ASTORE, 1) - mv.visitVarInsn(ALOAD, 1) - mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") - mv.visitLabel(l0) - mv.visitFieldInsn(GETSTATIC, "scala/compat/java8/runtime/LambdaDeserializer$", "MODULE$", "Lscala/compat/java8/runtime/LambdaDeserializer$;") - mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false) - mv.visitVarInsn(ALOAD, 1) - mv.visitVarInsn(ALOAD, 0) - mv.visitMethodInsn(INVOKEVIRTUAL, "scala/compat/java8/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) - mv.visitInsn(ARETURN) - mv.visitEnd() - } - } } // end of trait BCClassGen /* functionality for building plain and mirror classes */ @@ -748,9 +1090,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { /* builder of mirror classes */ class JMirrorBuilder extends JCommonBuilder { - private var cunit: CompilationUnit = _ - def getCurrentCUnit(): CompilationUnit = cunit; - /* Generate a mirror class for a top-level module. A mirror class is a class * containing only static methods that forward to the corresponding method * on the MODULE instance of the given Scala object. It will only be @@ -762,8 +1101,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { assert(moduleClass.isModuleClass) assert(moduleClass.companionClass == NoSymbol, moduleClass) - innerClassBufferASM.clear() - this.cunit = cunit val bType = mirrorClassClassBType(moduleClass) val mirrorClass = new asm.tree.ClassNode @@ -772,7 +1109,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { bType.info.get.flags, bType.internalName, null /* no java-generic-signature */, - ObjectReference.internalName, + ObjectRef.internalName, EMPTY_STRING_ARRAY ) @@ -785,9 +1122,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= bType.info.get.nestedClasses - addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) - mirrorClass.visitEnd() ("" + moduleClass.name) // this side-effect is necessary, really. @@ -811,18 +1145,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString } - innerClassBufferASM.clear() + val beanInfoType = beanInfoClassClassBType(cls) - val flags = javaFlags(cls) - - val beanInfoName = (internalName(cls) + "BeanInfo") val beanInfoClass = new asm.tree.ClassNode beanInfoClass.visit( classfileVersion, - flags, - beanInfoName, + beanInfoType.info.get.flags, + beanInfoType.internalName, null, // no java-generic-signature - "scala/beans/ScalaBeanInfo", + sbScalaBeanInfoRef.internalName, EMPTY_STRING_ARRAY ) @@ -859,7 +1190,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { EMPTY_STRING_ARRAY // no throwable exceptions ) - val stringArrayJType: BType = ArrayBType(StringReference) + val stringArrayJType: BType = ArrayBType(StringRef) val conJType: BType = MethodBType( classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil, UNIT @@ -872,7 +1203,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitLdcInsn(new java.lang.Integer(fi)) if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } else { constructor.visitLdcInsn(f) } - constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE)) + constructor.visitInsn(StringRef.typedOpcode(asm.Opcodes.IASTORE)) fi += 1 } } @@ -885,12 +1216,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { // push the string array of field information constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName) push(fieldList) // push the string array of method information constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName) push(methodList) // invoke the superclass constructor, which will do the @@ -901,9 +1232,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses - addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) - beanInfoClass.visitEnd() beanInfoClass @@ -932,8 +1260,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { - // this tracks the inner class in innerClassBufferASM, if needed. - val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass) + val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( @@ -975,3 +1302,62 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } // end of trait JAndroidBuilder } + +object BCodeHelpers { + val ExcludedForwarderFlags = { + import scala.tools.nsc.symtab.Flags._ + // Should include DEFERRED but this breaks findMember. + SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO + } + + /** + * Valid flags for InnerClass attribute entry. + * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 + */ + val INNER_CLASSES_FLAGS = { + asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | + asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | + asm.Opcodes.ACC_ENUM + } + + class TestOp(val op: Int) extends AnyVal { + import TestOp._ + def negate = this match { + case EQ => NE + case NE => EQ + case LT => GE + case GE => LT + case GT => LE + case LE => GT + } + def opcodeIF = asm.Opcodes.IFEQ + op + def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op + } + + object TestOp { + val EQ = new TestOp(0) + val NE = new TestOp(1) + val LT = new TestOp(2) + val GE = new TestOp(3) + val GT = new TestOp(4) + val LE = new TestOp(5) + } + + class InvokeStyle(val style: Int) extends AnyVal { + import InvokeStyle._ + def isVirtual: Boolean = this == Virtual + def isStatic : Boolean = this == Static + def isSpecial: Boolean = this == Special + def isSuper : Boolean = this == Super + + def hasInstance = this != Static + } + + object InvokeStyle { + val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface + val Static = new InvokeStyle(1) // InvokeStatic + val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors) + val Super = new InvokeStyle(3) // InvokeSpecial (super calls) + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala deleted file mode 100644 index 50d20921d5..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeICodeCommon.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2014 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc.backend.jvm - -import scala.tools.nsc.Global -import PartialFunction._ - -/** - * This trait contains code shared between GenBCode and GenICode that depends on types defined in - * the compiler cake (Global). - */ -final class BCodeICodeCommon[G <: Global](val global: G) { - import global._ - - /** Some useful equality helpers. */ - def isNull(t: Tree) = cond(t) { case Literal(Constant(null)) => true } - def isLiteral(t: Tree) = cond(t) { case Literal(_) => true } - def isNonNullExpr(t: Tree) = isLiteral(t) || ((t.symbol ne null) && t.symbol.isModule) - - /** If l or r is constant null, returns the other ; otherwise null */ - def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index eb0da7caef..0a95bc5e39 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -12,6 +12,7 @@ import scala.annotation.switch import scala.collection.mutable import GenBCode._ import scala.tools.asm.tree.MethodInsnNode +import scala.tools.nsc.backend.jvm.BCodeHelpers.TestOp /* * A high-level facade to the ASM API for bytecode generation. @@ -28,9 +29,6 @@ abstract class BCodeIdiomatic extends SubComponent { import coreBTypes._ val classfileVersion: Int = settings.target.value match { - case "jvm-1.5" => asm.Opcodes.V1_5 - case "jvm-1.6" => asm.Opcodes.V1_6 - case "jvm-1.7" => asm.Opcodes.V1_7 case "jvm-1.8" => asm.Opcodes.V1_8 } @@ -42,7 +40,7 @@ abstract class BCodeIdiomatic extends SubComponent { if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 ) - val StringBuilderClassName = "scala/collection/mutable/StringBuilder" + lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName val EMPTY_STRING_ARRAY = Array.empty[String] val EMPTY_INT_ARRAY = Array.empty[Int] @@ -109,41 +107,20 @@ abstract class BCodeIdiomatic extends SubComponent { def jmethod: asm.tree.MethodNode import asm.Opcodes; - import icodes.opcodes.{ Static, Dynamic, SuperCall } final def emit(opc: Int) { jmethod.visitInsn(opc) } - /* - * can-multi-thread - */ - final def genPrimitiveArithmetic(op: icodes.ArithmeticOp, kind: BType) { - - import icodes.{ ADD, SUB, MUL, DIV, REM, NOT } - - op match { - - case ADD => add(kind) - case SUB => sub(kind) - case MUL => mul(kind) - case DIV => div(kind) - case REM => rem(kind) - - case NOT => - if (kind.isIntSizedType) { - emit(Opcodes.ICONST_M1) - emit(Opcodes.IXOR) - } else if (kind == LONG) { - jmethod.visitLdcInsn(new java.lang.Long(-1)) - jmethod.visitInsn(Opcodes.LXOR) - } else { - abort(s"Impossible to negate an $kind") - } - - case _ => - abort(s"Unknown arithmetic primitive $op") + final def genPrimitiveNot(bType: BType): Unit = { + if (bType.isIntSizedType) { + emit(Opcodes.ICONST_M1) + emit(Opcodes.IXOR) + } else if (bType == LONG) { + jmethod.visitLdcInsn(new java.lang.Long(-1)) + jmethod.visitInsn(Opcodes.LXOR) + } else { + abort(s"Impossible to negate a $bType") } - - } // end of method genPrimitiveArithmetic() + } /* * can-multi-thread @@ -207,10 +184,10 @@ abstract class BCodeIdiomatic extends SubComponent { * can-multi-thread */ final def genStartConcat(pos: Position): Unit = { - jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) + jmethod.visitTypeInsn(Opcodes.NEW, JavaStringBuilderClassName) jmethod.visitInsn(Opcodes.DUP) invokespecial( - StringBuilderClassName, + JavaStringBuilderClassName, INSTANCE_CONSTRUCTOR_NAME, "()V", pos @@ -220,22 +197,27 @@ abstract class BCodeIdiomatic extends SubComponent { /* * can-multi-thread */ - final def genStringConcat(el: BType, pos: Position): Unit = { - - val jtype = - if (el.isArray || el.isClass) ObjectReference - else el - - val bt = MethodBType(List(jtype), StringBuilderReference) - - invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos) + def genConcat(elemType: BType, pos: Position): Unit = { + val paramType = elemType match { + case ct: ClassBType if ct.isSubtypeOf(StringRef).get => StringRef + case ct: ClassBType if ct.isSubtypeOf(jlStringBufferRef).get => jlStringBufferRef + case ct: ClassBType if ct.isSubtypeOf(jlCharSequenceRef).get => jlCharSequenceRef + // Don't match for `ArrayBType(CHAR)`, even though StringBuilder has such an overload: + // `"a" + Array('b')` should NOT be "ab", but "a[C@...". + case _: RefBType => ObjectRef + // jlStringBuilder does not have overloads for byte and short, but we can just use the int version + case BYTE | SHORT => INT + case pt: PrimitiveBType => pt + } + val bt = MethodBType(List(paramType), jlStringBuilderRef) + invokevirtual(JavaStringBuilderClassName, "append", bt.descriptor, pos) } /* * can-multi-thread */ final def genEndConcat(pos: Position): Unit = { - invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos) + invokevirtual(JavaStringBuilderClassName, "toString", "()Ljava/lang/String;", pos) } /* @@ -419,13 +401,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 a9b6a312e9..f190c1f2de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -9,7 +9,6 @@ package backend package jvm import scala.collection.{ mutable, immutable } -import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository import scala.tools.nsc.symtab._ import scala.tools.asm @@ -26,7 +25,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { import global._ import bTypes._ import coreBTypes._ - import bCodeAsmCommon._ /* * There's a dedicated PlainClassBuilder for each CompilationUnit, @@ -61,48 +59,39 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // current class var cnode: asm.tree.ClassNode = null - var thisName: String = null // the internal name of the class being emitted + var thisBType: ClassBType = null var claszSymbol: Symbol = null var isCZParcelable = false var isCZStaticModule = false var isCZRemote = false - protected val indyLambdaHosts = collection.mutable.Set[Symbol]() - /* ---------------- idiomatic way to ask questions to typer ---------------- */ def paramTKs(app: Apply): List[BType] = { val Apply(fun, _) = app val funSym = fun.symbol - (funSym.info.paramTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) + funSym.info.paramTypes map typeToBType } - def symInfoTK(sym: Symbol): BType = { - toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM) - } + def symInfoTK(sym: Symbol): BType = typeToBType(sym.info) - def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) } + def tpeTK(tree: Tree): BType = typeToBType(tree.tpe) def log(msg: => AnyRef) { global synchronized { global.log(msg) } } - override def getCurrentCUnit(): CompilationUnit = { cunit } - /* ---------------- helper utils for generating classes and fields ---------------- */ def genPlainClass(cd: ClassDef) { assert(cnode == null, "GenBCode detected nested methods.") - innerClassBufferASM.clear() claszSymbol = cd.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) isCZStaticModule = isStaticModuleClass(claszSymbol) isCZRemote = isRemote(claszSymbol) - thisName = internalName(claszSymbol) - - val classBType = classBTypeFromSymbol(claszSymbol) + thisBType = classBTypeFromSymbol(claszSymbol) cnode = new asm.tree.ClassNode() @@ -121,30 +110,21 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBType.info.get.nestedClasses gen(cd.impl) - val shouldAddLambdaDeserialize = ( settings.target.value == "jvm-1.8" && settings.Ydelambdafy.value == "method" - && indyLambdaHosts.contains(claszSymbol)) + && indyLambdaHosts.contains(cnode.name)) if (shouldAddLambdaDeserialize) - addLambdaDeserialize(claszSymbol, cnode) - - addInnerClassesASM(cnode, innerClassBufferASM.toList) + backendUtils.addLambdaDeserialize(cnode) - cnode.visitAttribute(classBType.inlineInfoAttribute.get) + cnode.visitAttribute(thisBType.inlineInfoAttribute.get) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) - if (settings.YoptAddToBytecodeRepository) { - // The inliner needs to find all classes in the code repo, also those being compiled - byteCodeRepository.add(cnode, ByteCodeRepository.CompilationUnit) - } - assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") } // end of method genPlainClass() @@ -154,31 +134,27 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor) { val bType = classBTypeFromSymbol(claszSymbol) - val superClass = bType.info.get.superClass.getOrElse(ObjectReference).internalName - val interfaceNames = bType.info.get.interfaces map { - case classBType => - if (classBType.isNestedClass.get) { innerClassBufferASM += classBType } - classBType.internalName - } + val superClass = bType.info.get.superClass.getOrElse(ObjectRef).internalName + val interfaceNames = bType.info.get.interfaces.map(_.internalName) val flags = javaFlags(claszSymbol) val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) cnode.visit(classfileVersion, flags, - thisName, thisSignature, + thisBType.internalName, thisSignature, superClass, interfaceNames.toArray) if (emitSource) { cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) } - enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { + enclosingMethodAttribute(claszSymbol, internalName, methodBTypeFromSymbol(_).descriptor) match { case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => cnode.visitOuterClass(className, methodName, methodDescriptor) case _ => () } - val ssa = getAnnotPickle(thisName, claszSymbol) + val ssa = getAnnotPickle(thisBType.internalName, claszSymbol) cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(cnode, claszSymbol.annotations ++ ssa) @@ -195,11 +171,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (lmoc != NoSymbol) { // it must be a top level class (name contains no $s) val isCandidateForForwarders = { - exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } + exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isNestedClass } } if (isCandidateForForwarders) { log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'") - addForwarders(isRemote(claszSymbol), cnode, thisName, lmoc.moduleClass) + addForwarders(isRemote(claszSymbol), cnode, thisBType.internalName, lmoc.moduleClass) } } } @@ -217,7 +193,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val fv = cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED strMODULE_INSTANCE_FIELD, - "L" + thisName + ";", + thisBType.descriptor, null, // no java-generic-signature null // no initial value ) @@ -241,11 +217,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { /* "legacy static initialization" */ if (isCZStaticModule) { - clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) + clinit.visitTypeInsn(asm.Opcodes.NEW, thisBType.internalName) clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, - thisName, INSTANCE_CONSTRUCTOR_NAME, "()V", false) + thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", false) } - if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) } + if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) } clinit.visitInsn(asm.Opcodes.RETURN) clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -253,13 +229,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { } def addClassFields() { - /* Non-method term members are fields, except for module members. Module - * members can only happen on .NET (no flatten) for inner traits. There, - * a module symbol is generated (transformInfo in mixin) which is used - * as owner for the members of the implementation class (so that the - * backend emits them as static). - * No code is needed for this module symbol. - */ for (f <- fieldSymbols(claszSymbol)) { val javagensig = getGenericSignature(f, claszSymbol) val flags = javaFieldFlags(f) @@ -458,9 +427,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { var varsInScope: List[Tuple2[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope) // helpers around program-points. - def lastInsn: asm.tree.AbstractInsnNode = { - mnode.instructions.getLast - } + def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast def currProgramPoint(): asm.Label = { lastInsn match { case labnode: asm.tree.LabelNode => labnode.getLabel @@ -533,7 +500,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { /* * must-single-thread */ - def initJMethod(flags: Int, paramAnnotations: List[List[AnnotationInfo]]) { + def initJMethod(flags: Int, params: List[Symbol]) { val jgensig = getGenericSignature(methSymbol, claszSymbol) addRemoteExceptionAnnot(isCZRemote, hasPublicBitSet(flags), methSymbol) @@ -544,7 +511,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME else jMethodName - val mdesc = asmMethodType(methSymbol).descriptor + val mdesc = methodBTypeFromSymbol(methSymbol).descriptor mnode = cnode.visitMethod( flags, bytecodeName, @@ -553,10 +520,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { mkArray(thrownExceptions) ).asInstanceOf[asm.tree.MethodNode] - // TODO param names: (m.params map (p => javaName(p.sym))) - + emitParamNames(mnode, params) emitAnnotations(mnode, others) - emitParamAnnotations(mnode, paramAnnotations) + emitParamAnnotations(mnode, params.map(_.annotations)) } // end of method initJMethod @@ -568,7 +534,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName.toString - returnType = asmMethodType(dd.symbol).returnType + returnType = methodBTypeFromSymbol(dd.symbol).returnType isMethSymStaticCtor = methSymbol.isStaticConstructor resetMethodBookkeeping(dd) @@ -587,16 +553,15 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { } val isNative = methSymbol.hasAnnotation(definitions.NativeAttr) - val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) + val isAbstractMethod = rhs == EmptyTree val flags = GenBCode.mkFlags( javaFlags(methSymbol), - if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0, + if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0, if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes ) - // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } - initJMethod(flags, params.map(p => p.symbol.annotations)) + initJMethod(flags, params.map(_.symbol)) /* Add method-local vars for LabelDef-params. * @@ -621,13 +586,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { genLoad(rhs, returnType) rhs match { - case Block(_, Return(_)) => () - case Return(_) => () + case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => () case EmptyTree => globalError("Concrete method has no definition: " + dd + ( if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")" - else "") - ) + else "")) case _ => bc emitRETURN returnType } @@ -638,7 +601,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (!hasStaticBitSet) { mnode.visitLocalVariable( "this", - "L" + thisName + ";", + thisBType.descriptor, null, veryFirstProgramPoint, onePastLastProgramPoint, @@ -697,7 +660,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val callee = methSymbol.enclClass.primaryConstructor val jname = callee.javaSimpleName.toString val jowner = internalName(callee.owner) - val jtype = asmMethodType(callee).descriptor + val jtype = methodBTypeFromSymbol(callee).descriptor insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype, false) } @@ -706,7 +669,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor + val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", @@ -718,10 +681,10 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName) val jowner = internalName(callee.owner) val jname = callee.javaSimpleName.toString - val jtype = asmMethodType(callee).descriptor + val jtype = methodBTypeFromSymbol(callee).descriptor insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false) - // PUTSTATIC `thisName`.CREATOR; - insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) + // PUTSTATIC `thisBType.internalName`.CREATOR; + insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisBType.internalName, "CREATOR", andrFieldDescr) } // insert a few instructions for initialization before each return instruction diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index b94208c1a5..3e53419573 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -30,7 +30,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { def genSynchronized(tree: Apply, expectedType: BType): BType = { val Apply(fun, args) = tree - val monitor = locals.makeLocal(ObjectReference, "monitor") + val monitor = locals.makeLocal(ObjectRef, "monitor") val monCleanup = new asm.Label // if the synchronized block returns a result, store it in a local variable. @@ -40,7 +40,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ genLoadQualifier(fun) - bc dup ObjectReference + bc dup ObjectRef locals.store(monitor) emit(asm.Opcodes.MONITORENTER) @@ -75,7 +75,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { * Protected by whatever protects the whole synchronized expression. * ------ */ - protect(startProtected, endProtected, currProgramPoint(), ThrowableReference) + protect(startProtected, endProtected, currProgramPoint(), jlThrowableRef) locals.load(monitor) emit(asm.Opcodes.MONITOREXIT) emit(asm.Opcodes.ATHROW) @@ -184,7 +184,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { for (CaseDef(pat, _, caseBody) <- catches) yield { pat match { case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody) - case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) + case Ident(nme.WILDCARD) => NamelessEH(jlThrowableRef, caseBody) case Bind(_, _) => BoundEH (pat.symbol, caseBody) } } @@ -290,7 +290,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { nopIfNeeded(startTryBody) val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. protect(startTryBody, finalHandler, finalHandler, null) - val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc")) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(jlThrowableRef, "exc")) bc.store(eIdx, eTK) emitFinalizer(finalizer, null, isDuplicate = true) bc.load(eIdx, eTK) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 0c26e01322..49353afef4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -7,15 +7,18 @@ package scala.tools.nsc package backend.jvm import scala.annotation.switch +import scala.collection.{concurrent, mutable} import scala.collection.concurrent.TrieMap import scala.reflect.internal.util.Position import scala.tools.asm import asm.Opcodes -import scala.tools.asm.tree.{MethodNode, MethodInsnNode, InnerClassNode, ClassNode} +import scala.tools.asm.tree._ import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} import scala.tools.nsc.backend.jvm.BackendReporting._ +import scala.tools.nsc.backend.jvm.analysis.BackendUtils import scala.tools.nsc.backend.jvm.opt._ import scala.collection.convert.decorateAsScala._ +import scala.collection.mutable.ListBuffer import scala.tools.nsc.settings.ScalaSettings /** @@ -29,6 +32,8 @@ import scala.tools.nsc.settings.ScalaSettings abstract class BTypes { import BTypes.InternalName + val backendUtils: BackendUtils[this.type] + // Some core BTypes are required here, in class BType, where no Global instance is available. // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. @@ -38,12 +43,14 @@ abstract class BTypes { /** * Tools for parsing classfiles, used by the inliner. */ - val byteCodeRepository: ByteCodeRepository + val byteCodeRepository: ByteCodeRepository[this.type] val localOpt: LocalOpt[this.type] val inliner: Inliner[this.type] + val inlinerHeuristics: InlinerHeuristics[this.type] + val closureOptimizer: ClosureOptimizer[this.type] val callGraph: CallGraph[this.type] @@ -56,7 +63,6 @@ abstract class BTypes { // Allows access to the compiler settings for backend components that don't have a global in scope def compilerSettings: ScalaSettings - /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -68,19 +74,27 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty) + val classBTypeFromInternalName: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty) /** * Store the position of every MethodInsnNode during code generation. This allows each callsite * in the call graph to remember its source position, which is required for inliner warnings. */ - val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) + val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) + + /** + * Stores callsite instructions of invocatinos annotated `f(): @inline/noinline`. + * Instructions are added during code generation (BCodeBodyBuilder). The maps are then queried + * when building the CallGraph, every Callsite object has an annotated(No)Inline field. + */ + val inlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty) + val noInlineAnnotatedCallsites: mutable.Set[MethodInsnNode] = recordPerRunCache(mutable.Set.empty) /** * Contains the internal names of all classes that are defined in Java source files of the current * compilation run (mixed compilation). Used for more detailed error reporting. */ - val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty) + val javaDefinedClasses: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty) /** * Cache, contains methods whose unreachable instructions are eliminated. @@ -92,12 +106,28 @@ abstract class BTypes { * This cache allows running dead code elimination whenever an analyzer is used. If the method * is already optimized, DCE can return early. */ - val unreachableCodeEliminated: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty) + val unreachableCodeEliminated: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty) + + /** + * Cache of methods which have correct `maxLocals` / `maxStack` values assigned. This allows + * invoking `computeMaxLocalsMaxStack` whenever running an analyzer but performing the actual + * computation only when necessary. + */ + val maxLocalsMaxStackComputed: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty) + + /** + * Classes with indyLambda closure instantiations where the SAM type is serializable (e.g. Scala's + * FunctionN) need a `$deserializeLambda$` method. This map contains classes for which such a + * method has been generated. It is used during ordinary code generation, as well as during + * inlining: when inlining an indyLambda instruction into a class, we need to make sure the class + * has the method. + */ + val indyLambdaHosts: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty) /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType * is constructed by parsing the corresponding classfile. - * + * * Some JVM operations use either a full descriptor or only an internal name. Example: * ANEWARRAY java/lang/String // a new array of strings (internal name for the String class) * ANEWARRAY [Ljava/lang/String; // a new array of array of string (full descriptor for the String class) @@ -128,7 +158,7 @@ abstract class BTypes { val res = ClassBType(internalName) byteCodeRepository.classNode(internalName) match { case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res - case Right(c) => setClassInfoFromParsedClassfile(c, res) + case Right(c) => setClassInfoFromClassNode(c, res) } }) } @@ -138,21 +168,19 @@ abstract class BTypes { */ def classBTypeFromClassNode(classNode: ClassNode): ClassBType = { classBTypeFromInternalName.getOrElse(classNode.name, { - setClassInfoFromParsedClassfile(classNode, ClassBType(classNode.name)) + setClassInfoFromClassNode(classNode, ClassBType(classNode.name)) }) } - private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = { + private def setClassInfoFromClassNode(classNode: ClassNode, classBType: ClassBType): ClassBType = { val superClass = classNode.superName match { case null => - assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") + assert(classNode.name == ObjectRef.internalName, s"class with missing super type: ${classNode.name}") None case superName => Some(classBTypeFromParsedClassfile(superName)) } - val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) - val flags = classNode.access /** @@ -197,6 +225,9 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) + val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) + val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName)) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -226,14 +257,13 @@ abstract class BTypes { val methodInfos = classNode.methods.asScala.map(methodNode => { val info = MethodInlineInfo( effectivelyFinal = BytecodeUtils.isFinalMethod(methodNode), - traitMethodWithStaticImplementation = false, annotatedInline = false, annotatedNoInline = false) (methodNode.name + methodNode.desc, info) }).toMap InlineInfo( - traitImplClassSelfType = None, isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode), + sam = inlinerHeuristics.javaSam(classNode.name), methodInfos = methodInfos, warning) } @@ -291,8 +321,8 @@ abstract class BTypes { final def isNonVoidPrimitiveType = isPrimitive && this != UNIT - final def isNullType = this == RT_NULL - final def isNothingType = this == RT_NOTHING + final def isNullType = this == srNullRef + final def isNothingType = this == srNothingRef final def isBoxed = this.isClass && boxedClasses(this.asClassBType) @@ -315,7 +345,7 @@ abstract class BTypes { this match { case ArrayBType(component) => - if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true + if (other == ObjectRef || other == jlCloneableRef || other == jiSerializableRef) true else other match { case ArrayBType(otherComponent) => component.conformsTo(otherComponent).orThrow case _ => false @@ -324,7 +354,7 @@ abstract class BTypes { case classType: ClassBType => if (isBoxed) { if (other.isBoxed) this == other - else if (other == ObjectReference) true + else if (other == ObjectRef) true else other match { case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number case _ => false @@ -367,7 +397,7 @@ abstract class BTypes { assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. - ObjectReference + ObjectRef case _: MethodBType => assertionError(s"unexpected method type when computing maxType: $this") @@ -554,6 +584,8 @@ abstract class BTypes { * Terminology * ----------- * + * Diagram here: https://blogs.oracle.com/darcy/entry/nested_inner_member_and_top + * * - Nested class (JLS 8): class whose declaration occurs within the body of another class * * - Top-level class (JLS 8): non-nested class @@ -760,26 +792,17 @@ abstract class BTypes { * } * * - * Traits Members - * -------------- - * - * Some trait methods don't exist in the generated interface, but only in the implementation class - * (private methods in traits for example). Since EnclosingMethod expresses a source-level property, - * but the source-level enclosing method doesn't exist in the classfile, we the enclosing method - * is null (the enclosing class is still emitted). - * See BCodeAsmCommon.considerAsTopLevelImplementationArtifact - * - * - * Implementation Classes, Specialized Classes, Delambdafy:method closure classes - * ------------------------------------------------------------------------------ + * Specialized Classes, Delambdafy:method closure classes + * ------------------------------------------------------ * - * Trait implementation classes and specialized classes are always considered top-level. Again, - * the InnerClass / EnclosingMethod attributes describe a source-level properties. The impl - * classes are compilation artifacts. + * Specialized classes are always considered top-level, as the InnerClass / EnclosingMethod + * attributes describe a source-level properties. * * The same is true for delambdafy:method closure classes. These classes are generated at * top-level in the delambdafy phase, no special support is required in the backend. * + * See also BCodeHelpers.considerAsTopLevelImplementationArtifact. + * * * Mirror Classes * -------------- @@ -837,7 +860,7 @@ abstract class BTypes { // best-effort verification. also we don't report an error if the info is a Left. def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c) - def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName + def isJLO(t: ClassBType) = t.internalName == ObjectRef.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") @@ -900,7 +923,7 @@ abstract class BTypes { // the static flag in the InnerClass table has a special meaning, see InnerClass comment i.flags & ~Opcodes.ACC_STATIC, if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 - ) & BCodeAsmCommon.INNER_CLASSES_FLAGS + ) & BCodeHelpers.INNER_CLASSES_FLAGS ) }) @@ -917,7 +940,7 @@ abstract class BTypes { def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try { if (this == other) return Right(true) if (isInterface.orThrow) { - if (other == ObjectReference) return Right(true) // interfaces conform to Object + if (other == ObjectRef) return Right(true) // interfaces conform to Object if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. // else: this and other are both interfaces. continue to (*) } else { @@ -950,13 +973,13 @@ abstract class BTypes { // exercised by test/files/run/t4761.scala if (other.isSubtypeOf(this).orThrow) this else if (this.isSubtypeOf(other).orThrow) other - else ObjectReference + else ObjectRef case (true, false) => - if (other.isSubtypeOf(this).orThrow) this else ObjectReference + if (other.isSubtypeOf(this).orThrow) this else ObjectRef case (false, true) => - if (this.isSubtypeOf(other).orThrow) other else ObjectReference + if (this.isSubtypeOf(other).orThrow) other else ObjectRef case _ => // TODO @lry I don't really understand the reasoning here. @@ -1081,7 +1104,7 @@ abstract class BTypes { */ /** - * Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo. + * Just a named pair, used in CoreBTypes.srBoxesRuntimeBoxToMethods/srBoxesRuntimeUnboxToMethods. */ final case class MethodNameAndType(name: String, methodType: MethodBType) @@ -1104,23 +1127,13 @@ object BTypes { * Metadata about a ClassBType, used by the inliner. * * More information may be added in the future to enable more elaborate inlinine heuristics. - * - * @param traitImplClassSelfType `Some(tp)` if this InlineInfo describes a trait, and the `self` - * parameter type of the methods in the implementation class is not - * the trait itself. Example: - * trait T { self: U => def f = 1 } - * Generates something like: - * class T$class { static def f(self: U) = 1 } - * - * In order to inline a trat method call, the INVOKEINTERFACE is - * rewritten to an INVOKESTATIC of the impl class, so we need the - * self type (U) to get the right signature. - * - * `None` if the self type is the interface type, or if this - * InlineInfo does not describe a trait. + * Note that this class should contain information that can only be obtained from the ClassSymbol. + * Information that can be computed from the ClassNode should be added to the call graph instead. * * @param isEffectivelyFinal True if the class cannot have subclasses: final classes, module - * classes, trait impl classes. + * classes. + * + * @param sam If this class is a SAM type, the SAM's "$name$descriptor". * * @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class. * The map is indexed by the string s"$name$descriptor" (to @@ -1130,29 +1143,45 @@ object BTypes { * InlineInfo, for example if some classfile could not be found on * the classpath. This warning can be reported later by the inliner. */ - final case class InlineInfo(traitImplClassSelfType: Option[InternalName], - isEffectivelyFinal: Boolean, + final case class InlineInfo(isEffectivelyFinal: Boolean, + sam: Option[String], methodInfos: Map[String, MethodInlineInfo], - warning: Option[ClassInlineInfoWarning]) + warning: Option[ClassInlineInfoWarning]) { + /** + * A super call (invokespecial) to a default method T.m is only allowed if the interface T is + * a direct parent of the class. Super calls are introduced for example in Mixin when generating + * forwarder methods: + * + * trait T { override def clone(): Object = "hi" } + * trait U extends T + * class C extends U + * + * The class C gets a forwarder that invokes T.clone(). During code generation the interface T + * is added as direct parent to class C. Note that T is not a (direct) parent in the frontend + * type of class C. + * + * All interfaces that are added to a class during code generation are added to this buffer and + * stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a + * specific class is the same no matter if it's constructed from a Symbol or from a classfile. + * This is tested in BTypesFromClassfileTest. + */ + val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty + } - val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None) + val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None) /** * Metadata about a method, used by the inliner. * * @param effectivelyFinal True if the method cannot be overridden (in Scala) - * @param traitMethodWithStaticImplementation True if the method is an interface method method of - * a trait method and has a static counterpart in the - * implementation class. * @param annotatedInline True if the method is annotated `@inline` * @param annotatedNoInline True if the method is annotated `@noinline` */ final case class MethodInlineInfo(effectivelyFinal: Boolean, - traitMethodWithStaticImplementation: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean) // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR val ScalaAttributeName = "Scala" val ScalaSigAttributeName = "ScalaSig" -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 45d9cc3ff3..d10b6c8dba 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,8 +7,9 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm +import scala.tools.nsc.backend.jvm.analysis.BackendUtils import scala.tools.nsc.backend.jvm.opt._ -import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName} +import scala.tools.nsc.backend.jvm.BTypes._ import BackendReporting._ import scala.tools.nsc.settings.ScalaSettings @@ -27,21 +28,22 @@ import scala.tools.nsc.settings.ScalaSettings class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { import global._ import definitions._ + import genBCode._ - val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) - val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) - import bCodeAsmCommon._ + val backendUtils: BackendUtils[this.type] = new BackendUtils(this) // Why the proxy, see documentation of class [[CoreBTypes]]. val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty)) + val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.classPath, this) val localOpt: LocalOpt[this.type] = new LocalOpt(this) val inliner: Inliner[this.type] = new Inliner(this) + val inlinerHeuristics: InlinerHeuristics[this.type] = new InlinerHeuristics(this) + val closureOptimizer: ClosureOptimizer[this.type] = new ClosureOptimizer(this) val callGraph: CallGraph[this.type] = new CallGraph(this) @@ -94,19 +96,14 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes * in the classfile method signature. - * - * Note that the referenced class symbol may be an implementation class. For example when - * compiling a mixed-in method that forwards to the static method in the implementation class, - * the class descriptor of the receiver (the implementation class) is obtained by creating the - * ClassBType. */ final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") assertClassNotArrayNotPrimitive(classSym) - assert(!primitiveTypeMap.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym") - if (classSym == NothingClass) RT_NOTHING - else if (classSym == NullClass) RT_NULL + assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym") + if (classSym == NothingClass) srNothingRef + else if (classSym == NullClass) srNullRef else { val internalName = classSym.javaBinaryName.toString classBTypeFromInternalName.getOrElse(internalName, { @@ -128,17 +125,35 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { */ final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = { assert(methodSymbol.isMethod, s"not a method-symbol: $methodSymbol") + methodBTypeFromMethodType(methodSymbol.info, methodSymbol.isClassConstructor || methodSymbol.isConstructor) + } + + /** + * Builds a [[MethodBType]] for a method type. + */ + final def methodBTypeFromMethodType(tpe: Type, isConstructor: Boolean): MethodBType = { val resultType: BType = - if (methodSymbol.isClassConstructor || methodSymbol.isConstructor) UNIT - else typeToBType(methodSymbol.tpe.resultType) - MethodBType(methodSymbol.tpe.paramTypes map typeToBType, resultType) + if (isConstructor) UNIT + else typeToBType(tpe.resultType) + MethodBType(tpe.paramTypes map typeToBType, resultType) + } + + def bootstrapMethodArg(t: Constant, pos: Position): AnyRef = t match { + case Constant(mt: Type) => methodBTypeFromMethodType(transformedType(mt), isConstructor = false).toASMType + case c @ Constant(sym: Symbol) => staticHandleFromSymbol(sym) + case c @ Constant(value: String) => value + case c @ Constant(value) if c.isNonUnitAnyVal => c.value.asInstanceOf[AnyRef] + case _ => reporter.error(pos, "Unable to convert static argument of ApplyDynamic into a classfile constant: " + t); null + } + + def staticHandleFromSymbol(sym: Symbol): asm.Handle = { + val owner = if (sym.owner.isModuleClass) sym.owner.linkedClassOfClass else sym.owner + val descriptor = methodBTypeFromMethodType(sym.info, isConstructor = false).descriptor + new asm.Handle(asm.Opcodes.H_INVOKESTATIC, classBTypeFromSymbol(owner).internalName, sym.name.encoded, descriptor) } /** * This method returns the BType for a type reference, for example a parameter type. - * - * If `t` references a class, typeToBType ensures that the class is not an implementation class. - * See also comment on classBTypeFromSymbol, which is invoked for implementation classes. */ final def typeToBType(t: Type): BType = { import definitions.ArrayClass @@ -149,17 +164,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { */ def primitiveOrClassToBType(sym: Symbol): BType = { assertClassNotArray(sym) - assert(!sym.isImplClass, sym) - primitiveTypeMap.getOrElse(sym, classBTypeFromSymbol(sym)) + primitiveTypeToBType.getOrElse(sym, classBTypeFromSymbol(sym)) } /** * When compiling Array.scala, the type parameter T is not erased and shows up in method - * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + * signatures, e.g. `def apply(i: Int): T`. A TypeRef for T is replaced by ObjectRef. */ def nonClassTypeRefToBType(sym: Symbol): ClassBType = { assert(sym.isType && isCompilingArray, sym) - ObjectReference + ObjectRef } t.dealiasWiden match { @@ -168,39 +182,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info) - /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for - * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. - * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. - */ - case a @ AnnotatedType(_, t) => - debuglog(s"typeKind of annotated type $a") - typeToBType(t) - - /* ExistentialType should (probably) be eliminated by erasure. We know they get here for - * classOf constants: - * class C[T] - * class T { final val k = classOf[C[_]] } - */ - case e @ ExistentialType(_, t) => - debuglog(s"typeKind of existential type $e") - typeToBType(t) - /* The cases below should probably never occur. They are kept for now to avoid introducing * new compiler crashes, but we added a warning. The compiler / library bootstrap and the * test suite don't produce any warning. */ case tp => - currentUnit.warning(tp.typeSymbol.pos, + warning(tp.typeSymbol.pos, s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + "If possible, please file a bug on issues.scala-lang.org.") tp match { - case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test - case ThisType(sym) => classBTypeFromSymbol(sym) - case SingleType(_, sym) => primitiveOrClassToBType(sym) - case ConstantType(_) => typeToBType(t.underlying) - case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) + case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case ThisType(sym) => classBTypeFromSymbol(sym) + case SingleType(_, sym) => primitiveOrClassToBType(sym) + case ConstantType(_) => typeToBType(t.underlying) + case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) + case AnnotatedType(_, t) => typeToBType(t) + case ExistentialType(_, t) => typeToBType(t) } } } @@ -212,15 +211,108 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = { assertClassNotArray(sym) - assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) + assert(!primitiveTypeToBType.contains(sym) || isCompilingPrimitive, sym) + } + + def implementedInterfaces(classSym: Symbol): List[Symbol] = { + // Additional interface parents based on annotations and other cues + def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match { + case RemoteAttr => Some(RemoteInterfaceClass.tpe) + case _ => None + } + + // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes. + def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag + + val classParents = { + val parents = classSym.info.parents + // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the + // parents of a java annotations. undo this for the backend (where we need classfile-level information). + if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass) + else parents + } + + val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation) + + // 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. + * + * Specialized classes are always considered top-level, see comment in BTypes. + */ + private def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => + sym + case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => + val r = exitingPickler(sym.moduleClass) + assert(r != NoSymbol, sym.fullLocationString) + r + })(collection.breakOut) + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { - // Check for isImplClass: trait implementation classes have NoSymbol as superClass + /** + * Reconstruct the classfile flags from a Java defined class symbol. + * + * The implementation of this method is slightly different from `javaFlags` in BTypesFromSymbols. + * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags + * that are used in the generated classfiles. For example, all classes emitted by the Scala + * compiler have ACC_PUBLIC. + * + * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have + * to correspond exactly to the flags in the classfile. For example, if the class is package + * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the + * ClassBType. For example, the inliner needs the correct flags for access checks. + * + * Class flags are listed here: + * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1 + */ + def javaClassfileFlags(classSym: Symbol): Int = { + assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}") + import asm.Opcodes._ + def enumFlags = ACC_ENUM | { + // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method. + // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all + // Java enums for exhaustiveness checking. + val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred) + if (hasAbstractMethod) ACC_ABSTRACT else 0 + } + GenBCode.mkFlags( + // SI-9393: the classfile / java source parser make java annotation symbols look like classes. + // here we recover the actual classfile flags. + if (classSym.hasJavaAnnotationFlag) ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0, + if (classSym.isPublic) ACC_PUBLIC else 0, + if (classSym.isFinal) ACC_FINAL else 0, + // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces. + if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, + // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags) + if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (classSym.isArtifact) ACC_SYNTHETIC else 0, + if (classSym.hasJavaEnumFlag) enumFlags else 0 + ) + } + // Check for hasAnnotationFlag for SI-9393: the classfile / java source parsers add // scala.annotation.Annotation as superclass to java annotations. In reality, java // annotation classfiles have superclass Object (like any interface classfile). - val superClassSym = if (classSym.isImplClass || classSym.hasJavaAnnotationFlag) ObjectClass else { + val superClassSym = if (classSym.hasJavaAnnotationFlag) ObjectClass else { val sc = classSym.superClass // SI-9393: Java annotation classes don't have the ABSTRACT/INTERFACE flag, so they appear // (wrongly) as superclasses. Fix this for BTypes: the java annotation will appear as interface @@ -235,7 +327,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { superClassSym == ObjectClass else // A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes. - ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeMap.contains(classSym)), + ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeToBType.contains(classSym)), s"Bad superClass for $classSym: $superClassSym" ) val superClass = if (superClassSym == NoSymbol) None @@ -251,13 +343,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { /* The InnerClass table of a class C must contain all nested classes of C, even if they are only * declared but not otherwise referenced in C (from the bytecode or a method / field signature). * We collect them here. - * - * Nested classes that are also referenced in C will be added to the innerClassBufferASM during - * code generation, but those duplicates will be eliminated when emitting the InnerClass - * attribute. - * - * Why do we need to collect classes into innerClassBufferASM at all? To collect references to - * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases @@ -286,8 +371,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } val companionModuleMembers = if (considerAsTopLevelImplementationArtifact(classSym)) Nil else { - // If this is a top-level non-impl (*) class, the member classes of the companion object are - // added as members of the class. For example: + // If this is a top-level class, the member classes of the companion object are added as + // members of the class. For example: // class C { } // object C { // class D @@ -298,11 +383,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // (done by buildNestedInfo). See comment in BTypes. // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks // like D is a member of C, not C$. - // - // (*) We exclude impl classes: if the classfile for the impl class exists on the classpath, - // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching - // members of that weird impl-class-module-class-symbol. that search probably cannot return - // any classes, but it's better to exclude it. val javaCompatMembers = { if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass)) // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member @@ -360,7 +440,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") val isTopLevel = innerClassSym.rawowner.isPackageClass - // impl classes are considered top-level, see comment in BTypes + // specialized classes are considered top-level, see comment in BTypes if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None else if (innerClassSym.rawowner.isTerm) { // This case should never be reached: the lambdalift phase mutates the rawowner field of all @@ -444,13 +524,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { case Right(classNode) => inlineInfoFromClassfile(classNode) case Left(missingClass) => - InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass))) + EmptyInlineInfo.copy(warning = Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass))) } } } /** - * For top-level objects without a companion class, the compilere generates a mirror class with + * For top-level objects without a companion class, the compiler generates a mirror class with * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes). */ @@ -462,12 +542,27 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // class info consistent with BCodeHelpers.genMirrorClass val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol c.info = Right(ClassInfo( - superClass = Some(ObjectReference), + superClass = Some(ObjectRef), interfaces = Nil, flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, nestedClasses = nested, nestedInfo = None, - InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class + inlineInfo = EmptyInlineInfo.copy(isEffectivelyFinal = true))) // no method inline infos needed, scala never invokes methods on the mirror class + c + }) + } + + def beanInfoClassClassBType(mainClass: Symbol): ClassBType = { + val internalName = mainClass.javaBinaryName.toString + "BeanInfo" + classBTypeFromInternalName.getOrElse(internalName, { + val c = ClassBType(internalName) + c.info = Right(ClassInfo( + superClass = Some(sbScalaBeanInfoRef), + interfaces = Nil, + flags = javaFlags(mainClass), + nestedClasses = Nil, + nestedInfo = None, + inlineInfo = EmptyInlineInfo)) c }) } @@ -478,26 +573,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { */ final def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler { // phase travel to pickler required for isNestedClass (looks at owner) - val r = sym.isModuleClass && !sym.isNestedClass - // The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag - // is late, it should not be visible here inside the time travel. We check this. - if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym") - r + sym.isModuleClass && !sym.isNestedClass } /** * True for module classes of modules that are top-level or owned only by objects. Module classes - * for such objects will get a MODULE$ flag and a corresponding static initializer. + * for such objects will get a MODULE$ field and a corresponding static initializer. */ final def isStaticModuleClass(sym: Symbol): Boolean = { - /* (1) Phase travel to to pickler is required to exclude implementation classes; they have the - * lateMODULEs after mixin, so isModuleClass would be true. - * (2) isStaticModuleClass is a source-level property. See comment on isOriginallyStaticOwner. - */ - exitingPickler { // (1) - sym.isModuleClass && - isOriginallyStaticOwner(sym.originalOwner) // (2) - } + sym.isModuleClass && + isOriginallyStaticOwner(sym.originalOwner) // isStaticModuleClass is a source-level property, see comment on isOriginallyStaticOwner } // legacy, to be removed when the @remote annotation gets removed @@ -559,7 +644,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val finalFlag = ( (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym)) - && !sym.enclClass.isInterface + && !sym.enclClass.isTrait && !sym.isClassConstructor && !sym.isMutable // lazy vals and vars both ) @@ -567,17 +652,17 @@ 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, - if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, + if ((sym.isDeferred && !sym.hasFlag(symtab.Flags.JAVA_DEFAULTMETHOD))|| sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isTraitOrInterface) ACC_INTERFACE else 0, if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, if (sym.isStaticMember) ACC_STATIC else 0, if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, if (sym.isArtifact) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.isClass && !sym.isTraitOrInterface) ACC_SUPER else 0, if (sym.hasJavaEnumFlag) ACC_ENUM else 0, if (sym.isVarargsMethod) ACC_VARARGS else 0, if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index b41d0de92f..01206aa6eb 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -1,7 +1,7 @@ package scala.tools.nsc package backend.jvm -import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode} +import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.reflect.internal.util.Position import scala.tools.nsc.settings.ScalaSettings @@ -44,7 +44,7 @@ object BackendReporting { implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal { def map[U](f: B => U) = v.right.map(f) def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f) - def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match { + def withFilter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match { case Left(_) => v case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty } @@ -86,8 +86,8 @@ object BackendReporting { def emitWarning(settings: ScalaSettings): Boolean } - // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here - // in scope allows for-comprehensions that desugar into filter calls (for example when using a + // Method withFilter in RightBiasedEither requires an implicit empty value. Taking the value here + // in scope allows for-comprehensions that desugar into withFilter calls (for example when using a // tuple de-constructor). implicit object emptyOptimizerWarning extends OptimizerWarning { def emitWarning(settings: ScalaSettings): Boolean = false @@ -170,9 +170,6 @@ object BackendReporting { case MethodInlineInfoError(_, _, _, cause) => s"Error while computing the inline information for method $warningMessageSignature:\n" + cause - - case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => - cause.toString } def emitWarning(settings: ScalaSettings): Boolean = this match { @@ -182,15 +179,12 @@ object BackendReporting { case MethodInlineInfoMissing(_, _, _, None) => settings.YoptWarningNoInlineMissingBytecode case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings) - - case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings) } } case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning - case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning sealed trait CannotInlineWarning extends OptimizerWarning { def calleeDeclarationClass: InternalName @@ -228,7 +222,7 @@ object BackendReporting { def emitWarning(settings: ScalaSettings): Boolean = this match { case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge => - settings.YoptWarningEmitAtInlineFailed + settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed) case IllegalAccessCheckFailed(_, _, _, _, _, cause) => cause.emitWarning(settings) @@ -246,9 +240,11 @@ object BackendReporting { case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning + // TODO: this should be a subtype of CannotInlineWarning + // but at the place where it's created (in findIllegalAccess) we don't have the necessary data (calleeName, calleeDescriptor). case object UnknownInvokeDynamicInstruction extends OptimizerWarning { override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)." - def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed + def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed) } /** @@ -260,7 +256,7 @@ object BackendReporting { override def emitWarning(settings: ScalaSettings): Boolean = this match { case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings) - case RewriteClosureIllegalAccess(_, _) => settings.YoptWarningEmitAtInlineFailed + case RewriteClosureIllegalAccess(_, _) => settings.YoptWarnings.contains(settings.YoptWarningsChoices.anyInlineFailed) } override def toString: String = this match { @@ -285,7 +281,7 @@ object BackendReporting { s"Failed to get the type of a method of class symbol $classFullName due to SI-9111." case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) => - s"Failed to build the inline information: $missingClass." + s"Failed to build the inline information: $missingClass" case UnknownScalaInlineInfoVersion(internalName, version) => s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler." diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala index 03306f30aa..8d0547b607 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala @@ -8,6 +8,7 @@ package backend.jvm import scala.reflect.internal.util.Statistics +// Enable with `-Ystatistics:jvm` object BackendStats { import Statistics.{newTimer, newSubTimer} val bcodeTimer = newTimer("time in backend", "jvm") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index 1d29fdee10..84f6d87c5c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package backend.jvm -import java.io.{ DataOutputStream, FileOutputStream, IOException, OutputStream, File => JFile } +import java.io.{ DataOutputStream, FileOutputStream, IOException, File => JFile } import scala.tools.nsc.io._ import java.util.jar.Attributes.Name import scala.language.postfixOps diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 00ca096e59..0b53ea2fb1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -1,7 +1,8 @@ package scala.tools.nsc package backend.jvm -import scala.annotation.switch +import scala.tools.asm +import scala.tools.nsc.backend.jvm.BTypes.InternalName /** * Core BTypes and some other definitions. The initialization of these definitions requires access @@ -9,7 +10,7 @@ import scala.annotation.switch * * The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To * make sure the definitions are consistent with the symbols in the current run, the - * `intializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each + * `initializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each * compiler run. * * The class BTypesFromSymbols does not directly reference CoreBTypes, but CoreBTypesProxy. The @@ -29,14 +30,14 @@ import scala.annotation.switch class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { import bTypes._ import global._ - import rootMirror.{requiredClass, getClassIfDefined} + import rootMirror.{requiredClass, getRequiredClass, getClassIfDefined} import definitions._ /** * Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above * the first use of `classBTypeFromSymbol` because that method looks at the map. */ - lazy val primitiveTypeMap: Map[Symbol, PrimitiveBType] = Map( + lazy val primitiveTypeToBType: Map[Symbol, PrimitiveBType] = Map( UnitClass -> UNIT, BooleanClass -> BOOL, CharClass -> CHAR, @@ -45,34 +46,22 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { IntClass -> INT, LongClass -> LONG, FloatClass -> FLOAT, - DoubleClass -> DOUBLE - ) - - lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) - lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass) - lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass) - lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass) - lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass) - lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass) - lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass) - lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass) - lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass) + DoubleClass -> DOUBLE) /** * Map from primitive types to their boxed class type. Useful when pushing class literals onto the * operand stack (ldc instruction taking a class literal), see genConstant. */ lazy val boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = Map( - UNIT -> BOXED_UNIT, - BOOL -> BOXED_BOOLEAN, - BYTE -> BOXED_BYTE, - SHORT -> BOXED_SHORT, - CHAR -> BOXED_CHAR, - INT -> BOXED_INT, - LONG -> BOXED_LONG, - FLOAT -> BOXED_FLOAT, - DOUBLE -> BOXED_DOUBLE - ) + UNIT -> classBTypeFromSymbol(requiredClass[java.lang.Void]), + BOOL -> classBTypeFromSymbol(BoxedBooleanClass), + BYTE -> classBTypeFromSymbol(BoxedByteClass), + SHORT -> classBTypeFromSymbol(BoxedShortClass), + CHAR -> classBTypeFromSymbol(BoxedCharacterClass), + INT -> classBTypeFromSymbol(BoxedIntClass), + LONG -> classBTypeFromSymbol(BoxedLongClass), + FLOAT -> classBTypeFromSymbol(BoxedFloatClass), + DOUBLE -> classBTypeFromSymbol(BoxedDoubleClass)) lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet @@ -82,7 +71,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { */ lazy val boxResultType: Map[Symbol, ClassBType] = { for ((valueClassSym, boxMethodSym) <- currentRun.runDefinitions.boxMethod) - yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym)) + yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeToBType(valueClassSym)) } /** @@ -90,96 +79,167 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { * For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */ lazy val unboxResultType: Map[Symbol, PrimitiveBType] = { for ((valueClassSym, unboxMethodSym) <- currentRun.runDefinitions.unboxMethod) - yield unboxMethodSym -> primitiveTypeMap(valueClassSym) + yield unboxMethodSym -> primitiveTypeToBType(valueClassSym) } /* * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in - * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass + * (scala.Null) in Scala ASTs. * * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal * names of NothingClass and NullClass can't be emitted as-is. */ - lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$]) - lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$]) - - lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass) - lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) - - lazy val StringReference : ClassBType = classBTypeFromSymbol(StringClass) - lazy val StringBuilderReference : ClassBType = classBTypeFromSymbol(StringBuilderClass) - lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(ThrowableClass) - lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable - lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException - lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable - lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable - lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException - lazy val javaUtilMapReference : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map - lazy val javaUtilHashMapReference : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap - - lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef]) - lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef]) - lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef]) - lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef]) - lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef]) - lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef]) - lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef]) - - lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_) + lazy val srNothingRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$]) + lazy val srNullRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$]) + + lazy val ObjectRef : ClassBType = classBTypeFromSymbol(ObjectClass) + lazy val StringRef : ClassBType = classBTypeFromSymbol(StringClass) + lazy val PredefRef : ClassBType = classBTypeFromSymbol(PredefModule.moduleClass) + lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(JavaStringBuilderClass) + lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(JavaStringBufferClass) + lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(JavaCharSequenceClass) + lazy val jlThrowableRef : ClassBType = classBTypeFromSymbol(ThrowableClass) + lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable + lazy val jiSerializableRef : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable + lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException + lazy val juMapRef : ClassBType = classBTypeFromSymbol(JavaUtilMap) // java/util/Map + lazy val juHashMapRef : ClassBType = classBTypeFromSymbol(JavaUtilHashMap) // java/util/HashMap + lazy val sbScalaBeanInfoRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.beans.ScalaBeanInfo]) + lazy val jliSerializedLambdaRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda]) + lazy val jliMethodHandlesRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles]) + lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(exitingPickler(getRequiredClass("java.lang.invoke.MethodHandles.Lookup"))) // didn't find a reliable non-stringly-typed way that works for inner classes in the backend + lazy val jliMethodTypeRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType]) + lazy val jliCallSiteRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite]) + lazy val jliLambdaMetafactoryRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory]) + lazy val srBoxesRunTimeRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) + lazy val srSymbolLiteral : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.SymbolLiteral]) + lazy val srStructuralCallSite : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.StructuralCallSite]) + lazy val srLambdaDeserialize : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize]) + lazy val srBoxedUnitRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxedUnit]) + + private def methodNameAndType(cls: Symbol, name: Name, static: Boolean = false, filterOverload: Symbol => Boolean = _ => true): MethodNameAndType = { + val holder = if (static) cls.companionModule.moduleClass else cls + val method = holder.info.member(name).suchThat(filterOverload) + assert(!method.isOverloaded, method) + MethodNameAndType(name.toString, methodBTypeFromSymbol(method)) + } - // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 - lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable") - lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator") + private def srBoxesRuntimeMethods(getName: (String, String) => String): Map[BType, MethodNameAndType] = { + ScalaValueClassesNoUnit.map(primitive => { + val bType = primitiveTypeToBType(primitive) + val name = newTermName(getName(primitive.name.toString, boxedClass(primitive).name.toString)) + (bType, methodNameAndType(BoxesRunTimeClass, name)) + })(collection.breakOut) + } - lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo] + // Z -> MethodNameAndType(boxToBoolean,(Z)Ljava/lang/Boolean;) + lazy val srBoxesRuntimeBoxToMethods: Map[BType, MethodNameAndType] = srBoxesRuntimeMethods((primitive, boxed) => "boxTo" + boxed) - /* The Object => String overload. */ - lazy val String_valueOf: Symbol = { - getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match { - case List(pt) => pt.typeSymbol == ObjectClass - case _ => false - }) + // Z -> MethodNameAndType(unboxToBoolean,(Ljava/lang/Object;)Z) + lazy val srBoxesRuntimeUnboxToMethods: Map[BType, MethodNameAndType] = srBoxesRuntimeMethods((primitive, boxed) => "unboxTo" + primitive) + + def singleParamOfClass(cls: Symbol) = (s: Symbol) => s.paramss match { + case List(List(param)) => param.info.typeSymbol == cls + case _ => false } - // scala.FunctionX and scala.runtim.AbstractFunctionX - lazy val FunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut) - lazy val AbstractFunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut) - lazy val AbstractFunctionArityMap : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap + // java/lang/Boolean -> MethodNameAndType(valueOf,(Z)Ljava/lang/Boolean;) + lazy val javaBoxMethods: Map[InternalName, MethodNameAndType] = { + ScalaValueClassesNoUnit.map(primitive => { + val boxed = boxedClass(primitive) + val method = methodNameAndType(boxed, newTermName("valueOf"), static = true, filterOverload = singleParamOfClass(primitive)) + (classBTypeFromSymbol(boxed).internalName, method) + })(collection.breakOut) + } - lazy val PartialFunctionReference : ClassBType = classBTypeFromSymbol(PartialFunctionClass) - lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass) + // java/lang/Boolean -> MethodNameAndType(booleanValue,()Z) + lazy val javaUnboxMethods: Map[InternalName, MethodNameAndType] = { + ScalaValueClassesNoUnit.map(primitive => { + val boxed = boxedClass(primitive) + val name = primitive.name.toString.toLowerCase + "Value" + (classBTypeFromSymbol(boxed).internalName, methodNameAndType(boxed, newTermName(name))) + })(collection.breakOut) + } - lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) + private def predefBoxingMethods(getName: (String, String) => String): Map[String, MethodBType] = { + ScalaValueClassesNoUnit.map(primitive => { + val boxed = boxedClass(primitive) + val name = getName(primitive.name.toString, boxed.name.toString) + (name, methodNameAndType(PredefModule.moduleClass, newTermName(name)).methodType) + })(collection.breakOut) + } - /** - * Methods in scala.runtime.BoxesRuntime - */ - lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map( - BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), BOXED_BOOLEAN)), - BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), BOXED_BYTE)), - CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), BOXED_CHAR)), - SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), BOXED_SHORT)), - INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), BOXED_INT)), - LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), BOXED_LONG)), - FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), BOXED_FLOAT)), - DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), BOXED_DOUBLE)) - ) - - lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( - BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)), - BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)), - CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)), - SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)), - INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)), - LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)), - FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)), - DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE)) - ) + // boolean2Boolean -> (Z)Ljava/lang/Boolean; + lazy val predefAutoBoxMethods: Map[String, MethodBType] = predefBoxingMethods((primitive, boxed) => primitive.toLowerCase + "2" + boxed) + + // Boolean2boolean -> (Ljava/lang/Boolean;)Z + lazy val predefAutoUnboxMethods: Map[String, MethodBType] = predefBoxingMethods((primitive, boxed) => boxed + "2" + primitive.toLowerCase) + + private def staticRefMethods(name: Name): Map[InternalName, MethodNameAndType] = { + allRefClasses.map(refClass => + (classBTypeFromSymbol(refClass).internalName, methodNameAndType(refClass, name, static = true)))(collection.breakOut) + } + + // scala/runtime/BooleanRef -> MethodNameAndType(create,(Z)Lscala/runtime/BooleanRef;) + lazy val srRefCreateMethods: Map[InternalName, MethodNameAndType] = staticRefMethods(nme.create) + + // scala/runtime/BooleanRef -> MethodNameAndType(zero,()Lscala/runtime/BooleanRef;) + lazy val srRefZeroMethods: Map[InternalName, MethodNameAndType] = staticRefMethods(nme.zero) + + // java/lang/Boolean -> MethodNameAndType(<init>,(Z)V) + lazy val primitiveBoxConstructors: Map[InternalName, MethodNameAndType] = { + ScalaValueClassesNoUnit.map(primitive => { + val boxed = boxedClass(primitive) + (classBTypeFromSymbol(boxed).internalName, methodNameAndType(boxed, nme.CONSTRUCTOR, filterOverload = singleParamOfClass(primitive))) + })(collection.breakOut) + } + + private def nonOverloadedConstructors(classes: Iterable[Symbol]): Map[InternalName, MethodNameAndType] = { + classes.map(cls => (classBTypeFromSymbol(cls).internalName, methodNameAndType(cls, nme.CONSTRUCTOR)))(collection.breakOut) + } + + // scala/runtime/BooleanRef -> MethodNameAndType(<init>,(Z)V) + lazy val srRefConstructors: Map[InternalName, MethodNameAndType] = nonOverloadedConstructors(allRefClasses) + + private def specializedSubclasses(cls: Symbol): List[Symbol] = { + exitingSpecialize(cls.info) // the `transformInfo` method of specialization adds specialized subclasses to the `specializedClass` map + specializeTypes.specializedClass.collect({ + case ((`cls`, _), specCls) => specCls + }).toList + } + + // scala/Tuple3 -> MethodNameAndType(<init>,(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V) + // scala/Tuple2$mcZC$sp -> MethodNameAndType(<init>,(ZC)V) + lazy val tupleClassConstructors: Map[InternalName, MethodNameAndType] = { + val tupleClassSymbols = TupleClass.seq ++ specializedSubclasses(TupleClass(1)) ++ specializedSubclasses(TupleClass(2)) + nonOverloadedConstructors(tupleClassSymbols) + } + + // enumeration of specialized classes is temporary, while we still use the java-defined JFunctionN. + // once we switch to ordinary FunctionN, we can use specializedSubclasses just like for tuples. + private def specializedJFunctionSymbols(base: String): Seq[Symbol] = { + def primitives = Seq("B", "S", "I", "J", "C", "F", "D", "Z", "V") + def ijfd = Iterator("I", "J", "F", "D") + def ijfdzv = Iterator("I", "J", "F", "D", "Z", "V") + def ijd = Iterator("I", "J", "D") + val classNames = { + primitives.map(base + "0$mc" + _ + "$sp") // Function0 + } ++ { + // return type specializations appear first in the name string (alphabetical sorting) + for (r <- ijfdzv; a <- ijfd) yield base + "1$mc" + r + a + "$sp" // Function1 + } ++ { + for (r <- ijfdzv; a <- ijd; b <- ijd) yield base + "2$mc" + r + a + b + "$sp" // Function2 + } + classNames map getRequiredClass + } + + lazy val functionRefs: Set[InternalName] = (FunctionClass.seq ++ specializedJFunctionSymbols("scala.runtime.java8.JFunction")).map(classBTypeFromSymbol(_).internalName).toSet lazy val typeOfArrayOp: Map[Int, BType] = { import scalaPrimitives._ Map( - (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ + (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ @@ -187,9 +247,49 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ - (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) : _* ) } + + lazy val hashMethodSym: Symbol = getMember(RuntimeStaticsModule, nme.anyHash) + + // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 + lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable") + lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator") + + lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo] + + /* The Object => String overload. */ + lazy val String_valueOf: Symbol = { + getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match { + case List(pt) => pt.typeSymbol == ObjectClass + case _ => false + }) + } + + lazy val lambdaMetaFactoryBootstrapHandle = + new asm.Handle(asm.Opcodes.H_INVOKESTATIC, + coreBTypes.jliLambdaMetafactoryRef.internalName, sn.AltMetafactory.toString, + MethodBType( + List( + coreBTypes.jliMethodHandlesLookupRef, + coreBTypes.StringRef, + coreBTypes.jliMethodTypeRef, + ArrayBType(ObjectRef)), + coreBTypes.jliCallSiteRef + ).descriptor) + + lazy val lambdaDeserializeBootstrapHandle = + new scala.tools.asm.Handle(scala.tools.asm.Opcodes.H_INVOKESTATIC, + coreBTypes.srLambdaDeserialize.internalName, sn.Bootstrap.toString, + MethodBType( + List( + coreBTypes.jliMethodHandlesLookupRef, + coreBTypes.StringRef, + coreBTypes.jliMethodTypeRef + ), + coreBTypes.jliCallSiteRef + ).descriptor) } /** @@ -205,13 +305,45 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { import bTypes._ def boxedClasses: Set[ClassBType] + def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] + + def srNothingRef : ClassBType + def srNullRef : ClassBType - def RT_NOTHING : ClassBType - def RT_NULL : ClassBType + def ObjectRef : ClassBType + def StringRef : ClassBType + def PredefRef : ClassBType + def jlCloneableRef : ClassBType + def jiSerializableRef : ClassBType + def juHashMapRef : ClassBType + def juMapRef : ClassBType + def jliCallSiteRef : ClassBType + def jliMethodTypeRef : ClassBType + def jliSerializedLambdaRef : ClassBType + def jliMethodHandlesLookupRef : ClassBType + def srBoxesRunTimeRef : ClassBType + def srBoxedUnitRef : ClassBType - def ObjectReference : ClassBType - def jlCloneableReference : ClassBType - def jioSerializableReference : ClassBType + def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType] + def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType] + + def javaBoxMethods : Map[InternalName, MethodNameAndType] + def javaUnboxMethods : Map[InternalName, MethodNameAndType] + + def predefAutoBoxMethods : Map[String, MethodBType] + def predefAutoUnboxMethods : Map[String, MethodBType] + + def srRefCreateMethods : Map[InternalName, MethodNameAndType] + def srRefZeroMethods : Map[InternalName, MethodNameAndType] + + def primitiveBoxConstructors : Map[InternalName, MethodNameAndType] + def srRefConstructors : Map[InternalName, MethodNameAndType] + def tupleClassConstructors : Map[InternalName, MethodNameAndType] + + def functionRefs: Set[InternalName] + + def lambdaMetaFactoryBootstrapHandle : asm.Handle + def lambdaDeserializeBootstrapHandle : asm.Handle } /** @@ -226,50 +358,64 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: _coreBTypes = coreBTypes.asInstanceOf[CoreBTypes[bTypes.type]] } - def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap - - def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT - def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN - def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE - def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT - def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR - def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT - def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG - def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT - def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE + def primitiveTypeToBType: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeToBType def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses - def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType - def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType - def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING - def RT_NULL : ClassBType = _coreBTypes.RT_NULL - - def ObjectReference : ClassBType = _coreBTypes.ObjectReference - def objArrayReference : ArrayBType = _coreBTypes.objArrayReference - - def StringReference : ClassBType = _coreBTypes.StringReference - def StringBuilderReference : ClassBType = _coreBTypes.StringBuilderReference - def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference - def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference - def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference - def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference - def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference - def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference - def javaUtilMapReference : ClassBType = _coreBTypes.javaUtilMapReference - def javaUtilHashMapReference : ClassBType = _coreBTypes.javaUtilHashMapReference - - def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef - def srByteRef : ClassBType = _coreBTypes.srByteRef - def srCharRef : ClassBType = _coreBTypes.srCharRef - def srIntRef : ClassBType = _coreBTypes.srIntRef - def srLongRef : ClassBType = _coreBTypes.srLongRef - def srFloatRef : ClassBType = _coreBTypes.srFloatRef - def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef + def srNothingRef : ClassBType = _coreBTypes.srNothingRef + def srNullRef : ClassBType = _coreBTypes.srNullRef + + def ObjectRef : ClassBType = _coreBTypes.ObjectRef + def StringRef : ClassBType = _coreBTypes.StringRef + def PredefRef : ClassBType = _coreBTypes.PredefRef + def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef + def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef + def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef + def jlThrowableRef : ClassBType = _coreBTypes.jlThrowableRef + def jlCloneableRef : ClassBType = _coreBTypes.jlCloneableRef + def jiSerializableRef : ClassBType = _coreBTypes.jiSerializableRef + def jlClassCastExceptionRef : ClassBType = _coreBTypes.jlClassCastExceptionRef + def juMapRef : ClassBType = _coreBTypes.juMapRef + def juHashMapRef : ClassBType = _coreBTypes.juHashMapRef + def sbScalaBeanInfoRef : ClassBType = _coreBTypes.sbScalaBeanInfoRef + def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef + def jliMethodHandlesRef : ClassBType = _coreBTypes.jliMethodHandlesRef + def jliMethodHandlesLookupRef : ClassBType = _coreBTypes.jliMethodHandlesLookupRef + def jliMethodTypeRef : ClassBType = _coreBTypes.jliMethodTypeRef + def jliCallSiteRef : ClassBType = _coreBTypes.jliCallSiteRef + def jliLambdaMetafactoryRef : ClassBType = _coreBTypes.jliLambdaMetafactoryRef + def srBoxesRunTimeRef : ClassBType = _coreBTypes.srBoxesRunTimeRef + def srBoxedUnitRef : ClassBType = _coreBTypes.srBoxedUnitRef + + def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType] = _coreBTypes.srBoxesRuntimeBoxToMethods + def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType] = _coreBTypes.srBoxesRuntimeUnboxToMethods + + def javaBoxMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.javaBoxMethods + def javaUnboxMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.javaUnboxMethods + + def predefAutoBoxMethods : Map[String, MethodBType] = _coreBTypes.predefAutoBoxMethods + def predefAutoUnboxMethods : Map[String, MethodBType] = _coreBTypes.predefAutoUnboxMethods + + def srRefCreateMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefCreateMethods + def srRefZeroMethods : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefZeroMethods + + def primitiveBoxConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.primitiveBoxConstructors + def srRefConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefConstructors + def tupleClassConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.tupleClassConstructors + + def functionRefs: Set[InternalName] = _coreBTypes.functionRefs + + def srSymbolLiteral : ClassBType = _coreBTypes.srSymbolLiteral + def srStructuralCallSite : ClassBType = _coreBTypes.srStructuralCallSite + def srLambdaDeserialize : ClassBType = _coreBTypes.srLambdaDeserialize + + def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp + + // Some symbols. These references should probably be moved to Definitions. def hashMethodSym: Symbol = _coreBTypes.hashMethodSym @@ -280,17 +426,6 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def String_valueOf: Symbol = _coreBTypes.String_valueOf - def FunctionReference : Vector[ClassBType] = _coreBTypes.FunctionReference - def AbstractFunctionReference : Vector[ClassBType] = _coreBTypes.AbstractFunctionReference - def AbstractFunctionArityMap : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap - - def PartialFunctionReference : ClassBType = _coreBTypes.PartialFunctionReference - def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference - - def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime - - def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo - def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo - - def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp + def lambdaMetaFactoryBootstrapHandle = _coreBTypes.lambdaMetaFactoryBootstrapHandle + def lambdaDeserializeBootstrapHandle = _coreBTypes.lambdaDeserializeBootstrapHandle } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala deleted file mode 100644 index 4768417c67..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ /dev/null @@ -1,3350 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Martin Odersky - */ - -package scala -package tools.nsc -package backend.jvm - -import scala.collection.{ mutable, immutable } -import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } -import scala.tools.nsc.backend.jvm.opt.InlineInfoAttribute -import scala.tools.nsc.symtab._ -import scala.tools.asm -import asm.Label -import scala.annotation.tailrec - -/** - * @author Iulian Dragos (version 1.0, FJBG-based implementation) - * @author Miguel Garcia (version 2.0, ASM-based implementation) - * - * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf - */ -abstract class GenASM extends SubComponent with BytecodeWriters { self => - import global._ - import icodes._ - import icodes.opcodes._ - import definitions._ - - val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) - import bCodeAsmCommon._ - - // Strangely I can't find this in the asm code - // 255, but reserving 1 for "this" - final val MaximumJvmParameters = 254 - - val phaseName = "jvm" - - /** Create a new phase */ - override def newPhase(p: Phase): Phase = new AsmPhase(p) - - /** From the reference documentation of the Android SDK: - * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. - * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, - * which is an object implementing the `Parcelable.Creator` interface. - */ - private val androidFieldName = newTermName("CREATOR") - - private lazy val AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") - private lazy val AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") - - /** JVM code generation phase - */ - class AsmPhase(prev: Phase) extends ICodePhase(prev) { - def name = phaseName - override def erasedTypes = true - def apply(cls: IClass) = sys.error("no implementation") - - // An AsmPhase starts and ends within a Run, thus the caches in question will get populated and cleared within a Run, too), SI-7422 - javaNameCache.clear() - javaNameCache ++= List( - NothingClass -> binarynme.RuntimeNothing, - RuntimeNothingClass -> binarynme.RuntimeNothing, - NullClass -> binarynme.RuntimeNull, - RuntimeNullClass -> binarynme.RuntimeNull - ) - - // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. - reverseJavaName.clear() - reverseJavaName ++= List( - binarynme.RuntimeNothing.toString() -> RuntimeNothingClass, // RuntimeNothingClass is the bytecode-level return type of Scala methods with Nothing return-type. - binarynme.RuntimeNull.toString() -> RuntimeNullClass - ) - - // Lazy val; can't have eager vals in Phase constructors which may - // cause cycles before Global has finished initialization. - lazy val BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") - - private def initBytecodeWriter(entryPoints: List[IClass]): BytecodeWriter = { - settings.outputDirs.getSingleOutput match { - case Some(f) if f hasExtension "jar" => - // If no main class was specified, see if there's only one - // entry point among the classes going into the jar. - if (settings.mainClass.isDefault) { - entryPoints map (_.symbol fullName '.') match { - case Nil => - log("No Main-Class designated or discovered.") - case name :: Nil => - log("Unique entry point: setting Main-Class to " + name) - settings.mainClass.value = name - case names => - log("No Main-Class due to multiple entry points:\n " + names.mkString("\n ")) - } - } - else log("Main-Class was specified: " + settings.mainClass.value) - - new DirectToJarfileWriter(f.file) - - case _ => factoryNonJarBytecodeWriter() - } - } - - private def isJavaEntryPoint(icls: IClass) = { - val sym = icls.symbol - def fail(msg: String, pos: Position = sym.pos) = { - reporter.warning(sym.pos, - sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" + - " Reason: " + msg - // TODO: make this next claim true, if possible - // by generating valid main methods as static in module classes - // not sure what the jvm allows here - // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." - ) - false - } - def failNoForwarder(msg: String) = { - fail(msg + ", which means no static forwarder can be generated.\n") - } - val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil - val hasApproximate = possibles exists { m => - m.info match { - case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass - case _ => false - } - } - // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. - hasApproximate && { - // Before erasure so we can identify generic mains. - enteringErasure { - val companion = sym.linkedClassOfClass - - if (hasJavaMainMethod(companion)) - failNoForwarder("companion contains its own main method") - else if (companion.tpe.member(nme.main) != NoSymbol) - // this is only because forwarders aren't smart enough yet - failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") - else if (companion.isTrait) - failNoForwarder("companion is a trait") - // Now either succeeed, or issue some additional warnings for things which look like - // attempts to be java main methods. - else (possibles exists isJavaMainMethod) || { - possibles exists { m => - m.info match { - case PolyType(_, _) => - fail("main methods cannot be generic.") - case MethodType(params, res) => - if (res.typeSymbol :: params exists (_.isAbstractType)) - fail("main methods cannot refer to type parameters or abstract types.", m.pos) - else - isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) - case tp => - fail("don't know what this is: " + tp, m.pos) - } - } - } - } - } - } - - override def run() { - - if (settings.debug) - inform("[running phase " + name + " on icode]") - - if (settings.Xdce) { - val classes = icodes.classes.keys.toList // copy to avoid mutating the map while iterating - for (sym <- classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) { - log(s"Optimizer eliminated ${sym.fullNameString}") - deadCode.elidedClosures += sym - icodes.classes -= sym - } - } - - // For predictably ordered error messages. - var sortedClasses = classes.values.toList sortBy (_.symbol.fullName) - - // Warn when classes will overwrite one another on case-insensitive systems. - for ((_, v1 :: v2 :: _) <- sortedClasses groupBy (_.symbol.javaClassName.toString.toLowerCase)) { - reporter.warning(v1.symbol.pos, - s"Class ${v1.symbol.javaClassName} differs only in case from ${v2.symbol.javaClassName}. " + - "Such classes will overwrite one another on case-insensitive filesystems.") - } - - debuglog(s"Created new bytecode generator for ${classes.size} classes.") - val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) - val needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] - val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile) - val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile) - val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile) - - def emitFor(c: IClass) { - if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { - if (c.symbol.companionClass == NoSymbol) - mirrorCodeGen genMirrorClass (c.symbol, c.cunit) - else - log(s"No mirror class for module with linked class: ${c.symbol.fullName}") - } - plainCodeGen genClass c - if (c.symbol hasAnnotation BeanInfoAttr) beanInfoCodeGen genBeanInfoClass c - } - - while (!sortedClasses.isEmpty) { - val c = sortedClasses.head - try emitFor(c) - catch { - case e: FileConflictException => - reporter.error(c.symbol.pos, s"error writing ${c.symbol}: ${e.getMessage}") - } - sortedClasses = sortedClasses.tail - classes -= c.symbol // GC opportunity - } - - bytecodeWriter.close() - - /* don't javaNameCache.clear() because that causes the following tests to fail: - * test/files/run/macro-repl-dontexpand.scala - * test/files/jvm/interpreter.scala - * TODO but why? what use could javaNameCache possibly see once GenASM is over? - */ - - /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification: - * - * (1) call the asm.util.CheckAdapter.verify() overload: - * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) - * - * (2) passing a custom ClassLoader to verify inter-dependent classes. - * - * Alternatively, an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). - */ - - } // end of AsmPhase.run() - - } // end of class AsmPhase - - var pickledBytes = 0 // statistics - - val javaNameCache = perRunCaches.newAnyRefMap[Symbol, Name]() - - // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names. - val reverseJavaName = perRunCaches.newAnyRefMap[String, Symbol]() - - private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) - private def hasPublicBitSet(flags: Int) = (flags & asm.Opcodes.ACC_PUBLIC) != 0 - private def isRemote(s: Symbol) = s hasAnnotation RemoteAttr - - /** - * Return the Java modifiers for the given symbol. - * Java modifiers for classes: - * - public, abstract, final, strictfp (not used) - * for interfaces: - * - the same as for classes, without 'final' - * for fields: - * - public, private (*) - * - static, final - * for methods: - * - the same as for fields, plus: - * - abstract, synchronized (not used), strictfp (not used), native (not used) - * - * (*) protected cannot be used, since inner classes 'see' protected members, - * and they would fail verification after lifted. - */ - def javaFlags(sym: Symbol): Int = { - // constructors of module classes should be private - // PP: why are they only being marked private at this stage and not earlier? - val privateFlag = - sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) - - // Final: the only fields which can receive ACC_FINAL are eager vals. - // Neither vars nor lazy vals can, because: - // - // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 - // "Another problem is that the specification allows aggressive - // optimization of final fields. Within a thread, it is permissible to - // reorder reads of a final field with those modifications of a final - // field that do not take place in the constructor." - // - // A var or lazy val which is marked final still has meaning to the - // scala compiler. The word final is heavily overloaded unfortunately; - // for us it means "not overridable". At present you can't override - // vars regardless; this may change. - // - // The logic does not check .isFinal (which checks flags for the FINAL flag, - // and includes symbols marked lateFINAL) instead inspecting rawflags so - // we can exclude lateFINAL. Such symbols are eligible for inlining, but to - // avoid breaking proxy software which depends on subclassing, we do not - // emit ACC_FINAL. - // Nested objects won't receive ACC_FINAL in order to allow for their overriding. - - val finalFlag = ( - (((sym.rawflags & Flags.FINAL) != 0) || isTopLevelModule(sym)) - && !sym.enclClass.isInterface - && !sym.isClassConstructor - && !sym.isMutable // lazy vals and vars both - ) - - // Primitives are "abstract final" to prohibit instantiation - // without having to provide any implementations, but that is an - // illegal combination of modifiers at the bytecode level so - // suppress final if abstract if present. - import asm.Opcodes._ - mkFlags( - if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, - if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, - if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, - if (sym.isStaticMember) ACC_STATIC else 0, - if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, - if (sym.isArtifact) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.hasJavaEnumFlag) ACC_ENUM else 0, - if (sym.isVarargsMethod) ACC_VARARGS else 0, - if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 - ) - } - - def javaFieldFlags(sym: Symbol) = { - javaFlags(sym) | mkFlags( - if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, - if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, - if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL - ) - } - - def isTopLevelModule(sym: Symbol): Boolean = - exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } - - def isStaticModule(sym: Symbol): Boolean = { - sym.isModuleClass && !sym.isImplClass && !sym.isLifted - } - - // ----------------------------------------------------------------------------------------- - // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM) - // Background: - // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf - // http://comments.gmane.org/gmane.comp.java.vm.languages/2293 - // https://issues.scala-lang.org/browse/SI-3872 - // ----------------------------------------------------------------------------------------- - - /** - * Given an internal name (eg "java/lang/Integer") returns the class symbol for it. - * - * Better not to need this method (an example where control flow arrives here is welcome). - * This method is invoked only upon both (1) and (2) below happening: - * (1) providing an asm.ClassWriter with an internal name by other means than javaName() - * (2) forgetting to track the corresponding class-symbol in reverseJavaName. - * - * (The first item is already unlikely because we rely on javaName() - * to do the bookkeeping for entries that should go in innerClassBuffer.) - * - * (We could do completely without this method at the expense of computing stack-map-frames ourselves and - * invoking visitFrame(), but that would require another pass over all instructions.) - * - * Right now I can't think of any invocation of visitSomething() on MethodVisitor - * where we hand an internal name not backed by a reverseJavaName. - * However, I'm leaving this note just in case any such oversight is discovered. - */ - def inameToSymbol(iname: String): Symbol = { - val name = global.newTypeName(iname) - val res0 = - if (nme.isModuleName(name)) rootMirror.getModuleByName(name.dropModule) - else rootMirror.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested). - assert(res0 != NoSymbol) - val res = jsymbol(res0) - res - } - - def jsymbol(sym: Symbol): Symbol = { - if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass - else if(sym.isModule) sym.moduleClass - else sym // we track only module-classes and plain-classes - } - - private def superClasses(s: Symbol): List[Symbol] = { - assert(!s.isInterface) - s.superClass match { - case NoSymbol => List(s) - case sc => s :: superClasses(sc) - } - } - - private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = { - assert(!(as contains NoSymbol)) - assert(!(bs contains NoSymbol)) - var chainA = as - var chainB = bs - var fcs: Symbol = NoSymbol - do { - if (chainB contains chainA.head) fcs = chainA.head - else if (chainA contains chainB.head) fcs = chainB.head - else { - chainA = chainA.tail - chainB = chainB.tail - } - } while(fcs == NoSymbol) - fcs - } - - private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = { - assert(a.isClass) - assert(b.isClass) - - val res = (a.isInterface, b.isInterface) match { - case (true, true) => - global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents - case (true, false) => - if(b isSubClass a) a else ObjectClass - case (false, true) => - if(a isSubClass b) b else ObjectClass - case _ => - firstCommonSuffix(superClasses(a), superClasses(b)) - } - assert(res != NoSymbol) - res - } - - /* The internal name of the least common ancestor of the types given by inameA and inameB. - It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */ - def getCommonSuperClass(inameA: String, inameB: String): String = { - val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA)) - val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB)) - - // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString() - // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType - val lcaSym = jvmWiseLUB(a, b) - val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer. - val oldsym = reverseJavaName.put(lcaName, lcaSym) - assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption") - assert(lcaName != "scala/Any") - - lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching. - } - - class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { - override def getCommonSuperClass(iname1: String, iname2: String): String = { - GenASM.this.getCommonSuperClass(iname1, iname2) - } - } - - // ----------------------------------------------------------------------------------------- - // constants - // ----------------------------------------------------------------------------------------- - - private val classfileVersion: Int = settings.target.value match { - case "jvm-1.5" => asm.Opcodes.V1_5 - case "jvm-1.6" => asm.Opcodes.V1_6 - case "jvm-1.7" => asm.Opcodes.V1_7 - case "jvm-1.8" => asm.Opcodes.V1_8 - } - - private val majorVersion: Int = (classfileVersion & 0xFF) - private val emitStackMapFrame = (majorVersion >= 50) - - private val extraProc: Int = mkFlags( - asm.ClassWriter.COMPUTE_MAXS, - if(emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 - ) - - val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object") - val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String") - - /** - * We call many Java varargs methods from ASM library that expect Arra[asm.Type] as argument so - * we override default (compiler-generated) ClassTag so we can provide specialized newArray implementation. - * - * Examples of methods that should pick our definition are: JBuilder.javaType and JPlainBuilder.genMethod. - */ - private implicit val asmTypeTag: scala.reflect.ClassTag[asm.Type] = new scala.reflect.ClassTag[asm.Type] { - def runtimeClass: java.lang.Class[asm.Type] = classOf[asm.Type] - final override def newArray(len: Int): Array[asm.Type] = new Array[asm.Type](len) - } - - /** basic functionality for class file building */ - abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) { - - val EMPTY_STRING_ARRAY = Array.empty[String] - - val mdesc_arglessvoid = "()V" - - val CLASS_CONSTRUCTOR_NAME = "<clinit>" - val INSTANCE_CONSTRUCTOR_NAME = "<init>" - - // ----------------------------------------------------------------------------------------- - // factory methods - // ----------------------------------------------------------------------------------------- - - /** - * Returns a new ClassWriter for the class given by arguments. - * - * @param access the class's access flags. This parameter also indicates if the class is deprecated. - * - * @param name the internal name of the class. - * - * @param signature the signature of this class. May be <tt>null</tt> if - * the class is not a generic one, and does not extend or implement - * generic classes or interfaces. - * - * @param superName the internal of name of the super class. For interfaces, - * the super class is [[Object]]. May be <tt>null</tt>, but - * only for the [[Object]] class. - * - * @param interfaces the internal names of the class's interfaces (see - * {@link Type#getInternalName() getInternalName}). May be - * <tt>null</tt>. - */ - def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = { - val cw = new CClassWriter(extraProc) - cw.visit(classfileVersion, - access, name, signature, - superName, interfaces) - - cw - } - - def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { - val dest = new Array[Byte](len) - System.arraycopy(b, offset, dest, 0, len) - new asm.CustomAttr(name, dest) - } - - // ----------------------------------------------------------------------------------------- - // utilities useful when emitting plain, mirror, and beaninfo classes. - // ----------------------------------------------------------------------------------------- - - def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { - try { - val arr = jclass.toByteArray() - val outF: scala.tools.nsc.io.AbstractFile = { - if(needsOutfile) getFile(sym, jclassName, ".class") else null - } - bytecodeWriter.writeClass(label, jclassName, arr, outF) - } catch { - case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") => - reporter.error(sym.pos, - s"Could not write class $jclassName because it exceeds JVM code size limits. ${e.getMessage}") - case e: java.io.IOException if e.getMessage != null && (e.getMessage contains "File name too long") => - reporter.error(sym.pos, e.getMessage + "\n" + - "This can happen on some encrypted or legacy file systems. Please see SI-3623 for more details.") - - } - } - - /** Specialized array conversion to prevent calling - * java.lang.reflect.Array.newInstance via TraversableOnce.toArray - */ - def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a } - - // ----------------------------------------------------------------------------------------- - // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances). - // These getters track behind the scenes the inner classes referred to in the class being emitted, - // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()` - // (which also adds as member classes those inner classes that have been declared, - // thus also covering the case of inner classes declared but otherwise not referred). - // ----------------------------------------------------------------------------------------- - - val innerClassBuffer = mutable.LinkedHashSet[Symbol]() - - /** For given symbol return a symbol corresponding to a class that should be declared as inner class. - * - * For example: - * class A { - * class B - * object C - * } - * - * then method will return: - * NoSymbol for A, - * the same symbol for A.B (corresponding to A$B class), and - * A$C$ symbol for A.C. - */ - def innerClassSymbolFor(s: Symbol): Symbol = - if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol - - /** Return the name of this symbol that can be used on the Java platform. It removes spaces from names. - * - * Special handling: - * scala.Nothing erases to scala.runtime.Nothing$ - * scala.Null erases to scala.runtime.Null$ - * - * This is needed because they are not real classes, and they mean - * 'abrupt termination upon evaluation of that expression' or null respectively. - * This handling is done already in GenICode, but here we need to remove - * references from method signatures to these types, because such classes - * cannot exist in the classpath: the type checker will be very confused. - */ - def javaName(sym: Symbol): String = { - - /* - * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer - * - * Note: This method is called recursively thus making sure that we add complete chain - * of inner class all until root class. - */ - def collectInnerClass(s: Symbol): Unit = { - // TODO: some enteringFlatten { ... } which accounts for - // being nested in parameterized classes (if we're going to selectively flatten.) - val x = innerClassSymbolFor(s) - if(x ne NoSymbol) { - assert(x.isClass, "not an inner-class symbol") - // impl classes are considered top-level, see comment in BTypes - val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass - if (isInner) { - innerClassBuffer += x - collectInnerClass(x.rawowner) - } - } - } - - collectInnerClass(sym) - - val hasInternalName = sym.isClass || sym.isModuleNotMethod - val cachedJN = javaNameCache.getOrElseUpdate(sym, { - if (hasInternalName) { sym.javaBinaryName } - else { sym.javaSimpleName } - }) - - if(emitStackMapFrame && hasInternalName) { - val internalName = cachedJN.toString() - val trackedSym = jsymbol(sym) - reverseJavaName.get(internalName) match { - case None => - reverseJavaName.put(internalName, trackedSym) - case Some(oldsym) => - // TODO: `duplicateOk` seems pretty ad-hoc (a more aggressive version caused SI-9356 because it called oldSym.exists, which failed in the unpickler; see also SI-5031) - def duplicateOk = oldsym == NoSymbol || trackedSym == NoSymbol || (syntheticCoreClasses contains oldsym) || (oldsym.isModuleClass && (oldsym.sourceModule == trackedSym.sourceModule)) - if (oldsym != trackedSym && !duplicateOk) - devWarning(s"""|Different class symbols have the same bytecode-level internal name: - | name: $internalName - | oldsym: ${oldsym.fullNameString} - | tracked: ${trackedSym.fullNameString}""".stripMargin) - } - } - - cachedJN.toString - } - - def descriptor(t: Type): String = { javaType(t).getDescriptor } - def descriptor(k: TypeKind): String = { javaType(k).getDescriptor } - def descriptor(s: Symbol): String = { javaType(s).getDescriptor } - - def javaType(tk: TypeKind): asm.Type = { - if(tk.isValueType) { - if(tk.isIntSizedType) { - (tk: @unchecked) match { - case BOOL => asm.Type.BOOLEAN_TYPE - case BYTE => asm.Type.BYTE_TYPE - case SHORT => asm.Type.SHORT_TYPE - case CHAR => asm.Type.CHAR_TYPE - case INT => asm.Type.INT_TYPE - } - } else { - (tk: @unchecked) match { - case UNIT => asm.Type.VOID_TYPE - case LONG => asm.Type.LONG_TYPE - case FLOAT => asm.Type.FLOAT_TYPE - case DOUBLE => asm.Type.DOUBLE_TYPE - } - } - } else { - assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway) - (tk: @unchecked) match { - case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) - case ARRAY(elem) => javaArrayType(javaType(elem)) - } - } - } - - def javaType(t: Type): asm.Type = javaType(toTypeKind(t)) - - def javaType(s: Symbol): asm.Type = { - if (s.isMethod) { - val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType) - asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _*) - } else { javaType(s.tpe) } - } - - def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) } - - def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - - def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) { - /* The outer name for this inner class. Note that it returns null - * when the inner class should not get an index in the constant pool. - * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. - */ - def outerName(innerSym: Symbol): String = { - if (isAnonymousOrLocalClass(innerSym)) - null - else { - val outerName = javaName(innerSym.rawowner) - if (isTopLevelModule(innerSym.rawowner)) "" + TermName(outerName).dropModule - else outerName - } - } - - def innerName(innerSym: Symbol): String = { - // phase travel necessary: after flatten, the name includes the name of outer classes. - // if some outer name contains $anon, a non-anon class is considered anon. - if (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null - else innerSym.rawname + innerSym.moduleSuffix - } - - val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases - - innerClassBuffer ++= { - val members = exitingPickler(memberClassesForInnerClassTable(csym)) - // lambdalift makes all classes (also local, anonymous) members of their enclosing class - val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) - val nested = { - // Classes nested in value classes are nested in the companion at this point. For InnerClass / - // EnclosingMethod, we use the value class as the outer class. So we remove nested classes - // from the companion that were originally nested in the value class. - if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) - else allNested - } - - // for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala). - // for module classes, we filter out those members. - if (isMirror) members - else if (isTopLevelModule(csym)) nested diff members - else nested - } - - if (!considerAsTopLevelImplementationArtifact(csym)) { - // If this is a top-level non-impl class, add members of the companion object. These are the - // classes for which we change the InnerClass entry to allow using them from Java. - // We exclude impl classes: if the classfile for the impl class exists on the classpath, a - // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching - // members of that weird impl-class-module-class-symbol. that search probably cannot return - // any classes, but it's better to exclude it. - if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) { - // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only - // sees member classes, not local classes that were lifted by lambdalift. - innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass)) - } - - // Classes nested in value classes are nested in the companion at this point. For InnerClass / - // EnclosingMethod we use the value class as enclosing class. Here we search nested classes - // in the companion that were originally nested in the value class, and we add them as nested - // in the value class. - if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) { - val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) - innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym)) - } - } - - val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures - - if (allInners.nonEmpty) { - debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.") - - // entries ready to be serialized into the classfile, used to detect duplicates. - val entries = mutable.Map.empty[String, String] - - // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler - for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ?? - val flagsWithFinal: Int = mkFlags( - // See comment in BTypes, when is a class marked static in the InnerClass table. - if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0, - (if (innerSym.isJava) javaClassfileFlags(innerSym) else javaFlags(innerSym)) & ~asm.Opcodes.ACC_STATIC, - if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag - ) & (BCodeAsmCommon.INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) - val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding. - val jname = javaName(innerSym) // never null - val oname = outerName(innerSym) // null when method-enclosed - val iname = innerName(innerSym) // null for anonymous inner class - - // Mimicking javap inner class output - debuglog( - if (oname == null || iname == null) "//class " + jname - else "//%s=class %s of class %s".format(iname, jname, oname) - ) - - assert(jname != null, "javaName is broken.") // documentation - val doAdd = entries.get(jname) match { - // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) - case Some(prevOName) => - // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, - // i.e. for them it must be the case that oname == java/lang/Thread - assert(prevOName == oname, "duplicate") - false - case None => true - } - - if(doAdd) { - entries += (jname -> oname) - jclass.visitInnerClass(jname, oname, iname, flags) - } - - /* - * TODO assert (JVMS 4.7.6 The InnerClasses attribute) - * If a class file has a version number that is greater than or equal to 51.0, and - * has an InnerClasses attribute in its attributes table, then for all entries in the - * classes array of the InnerClasses attribute, the value of the - * outer_class_info_index item must be zero if the value of the - * inner_name_index item is zero. - */ - - } - } - } - - } // end of class JBuilder - - - /** functionality for building plain and mirror classes */ - abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { - - def debugLevel = settings.debuginfo.indexOfChoice - - val emitSource = debugLevel >= 1 - val emitLines = debugLevel >= 2 - val emitVars = debugLevel >= 3 - - // ----------------------------------------------------------------------------------------- - // more constants - // ----------------------------------------------------------------------------------------- - - val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC - val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL - - val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString - - // ----------------------------------------------------------------------------------------- - // Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only - // i.e., the pickle is contained in a custom annotation, see: - // (1) `addAnnotations()`, - // (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10 - // (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5 - // That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9) - // other than both ending up encoded as attributes (JVMS 4.7) - // (with the caveat that the "ScalaSig" attribute is associated to some classes, - // while the "Signature" attribute can be associated to classes, methods, and fields.) - // ----------------------------------------------------------------------------------------- - - val versionPickle = { - val vp = new PickleBuffer(new Array[Byte](16), -1, 0) - assert(vp.writeIndex == 0, vp) - vp writeNat PickleFormat.MajorVersion - vp writeNat PickleFormat.MinorVersion - vp writeNat 0 - vp - } - - def pickleMarkerLocal = { - createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) - } - - def pickleMarkerForeign = { - createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0) - } - - /** Returns a ScalaSignature annotation if it must be added to this class, none otherwise. - * This annotation must be added to the class' annotations list when generating them. - * - * Depending on whether the returned option is defined, it adds to `jclass` one of: - * (a) the ScalaSig marker attribute - * (indicating that a scala-signature-annotation aka pickle is present in this class); or - * (b) the Scala marker attribute - * (indicating that a scala-signature-annotation aka pickle is to be found in another file). - * - * - * @param jclassName The class file that is being readied. - * @param sym The symbol for which the signature has been entered in the symData map. - * This is different than the symbol - * that is being generated in the case of a mirror class. - * @return An option that is: - * - defined and contains an AnnotationInfo of the ScalaSignature type, - * instantiated with the pickle signature for sym. - * - empty if the jclass/sym pair must not contain a pickle. - * - */ - def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = { - currentRun.symData get sym match { - case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) => - val scalaAnnot = { - val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex)) - AnnotationInfo(sigBytes.sigAnnot, Nil, List((nme.bytes, sigBytes))) - } - pickledBytes += pickle.writeIndex - currentRun.symData -= sym - currentRun.symData -= sym.companionSymbol - Some(scalaAnnot) - case _ => - None - } - } - - /** - * Quoting from JVMS 4.7.5 The Exceptions Attribute - * "The Exceptions attribute indicates which checked exceptions a method may throw. - * There may be at most one Exceptions attribute in each method_info structure." - * - * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod() - * This method returns such list of internal names. - */ - def getExceptions(excs: List[AnnotationInfo]): List[String] = - for (ThrownException(exc) <- excs.distinct) - yield javaName(exc) - - def getCurrentCUnit(): CompilationUnit - - def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit()) - - def emitArgument(av: asm.AnnotationVisitor, - name: String, - arg: ClassfileAnnotArg) { - (arg: @unchecked) match { - - case LiteralAnnotArg(const) => - if(const.isNonUnitAnyVal) { av.visit(name, const.value) } - else { - const.tag match { - case StringTag => - assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` - av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, javaType(const.typeValue)) - case EnumTag => - val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. - val evalue = const.symbolValue.name.toString // value the actual enumeration value. - av.visitEnum(name, edesc, evalue) - } - } - - case sb@ScalaSigBytes(bytes) => - // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) - // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. - if (sb.fitsInOneString) - av.visit(name, strEncode(sb)) - else { - val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } - arrAnnotV.visitEnd() - } - // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. - - case ArrayAnnotArg(args) => - val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- args) { emitArgument(arrAnnotV, null, arg) } - arrAnnotV.visitEnd() - - case NestedAnnotArg(annInfo) => - val AnnotationInfo(typ, args, assocs) = annInfo - assert(args.isEmpty, args) - val desc = descriptor(typ) // the class descriptor of the nested annotation class - val nestedVisitor = av.visitAnnotation(name, desc) - emitAssocs(nestedVisitor, assocs) - } - } - - def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) { - for ((name, value) <- assocs) { - emitArgument(av, name.toString(), value) - } - av.visitEnd() - } - - def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) { - for(annot <- annotations; if shouldEmitAnnotation(annot)) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) - emitAssocs(av, assocs) - } - } - - def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) { - for(annot <- annotations; if shouldEmitAnnotation(annot)) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) - emitAssocs(av, assocs) - } - } - - def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) { - for(annot <- annotations; if shouldEmitAnnotation(annot)) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) - emitAssocs(av, assocs) - } - } - - def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { - val annotationss = pannotss map (_ filter shouldEmitAnnotation) - if (annotationss forall (_.isEmpty)) return - for ((annots, idx) <- annotationss.zipWithIndex; - annot <- annots) { - val AnnotationInfo(typ, args, assocs) = annot - assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) - emitAssocs(pannVisitor, assocs) - } - } - - /** Adds a @remote annotation, actual use unknown. - * - * Invoked from genMethod() and addForwarder(). - */ - def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { - val needsAnnotation = ( - ( isRemoteClass || - isRemote(meth) && isJMethodPublic - ) && !(meth.throwsAnnotations contains RemoteExceptionClass) - ) - if (needsAnnotation) { - val c = Constant(RemoteExceptionClass.tpe) - val arg = Literal(c) setType c.tpe - meth.addAnnotation(appliedType(ThrowsClass, c.tpe), arg) - } - } - - // ----------------------------------------------------------------------------------------- - // Static forwarders (related to mirror classes but also present in - // a plain class lacking companion module, for details see `isCandidateForForwarders`). - // ----------------------------------------------------------------------------------------- - - /** Add a forwarder for method m. Used only from addForwarders(). */ - private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { - val moduleName = javaName(module) - val methodInfo = module.thisType.memberInfo(m) - val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType - // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) - - /* Forwarders must not be marked final, - * as the JVM will not allow redefinition of a final static method, - * and we don't know what classes might be subclassing the companion class. See SI-4827. - */ - // TODO: evaluate the other flags we might be dropping on the floor here. - // TODO: ACC_SYNTHETIC ? - val flags = PublicStatic | ( - if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 - ) - - // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = staticForwarderGenericSignature(m, module, getCurrentCUnit()) - addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) - val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) - val thrownExceptions: List[String] = getExceptions(throws) - - val jReturnType = javaType(methodInfo.resultType) - val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*) - val mirrorMethodName = javaName(m) - val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( - flags, - mirrorMethodName, - mdesc, - jgensig, - mkArray(thrownExceptions) - ) - - // typestate: entering mode with valid call sequences: - // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* - - emitAnnotations(mirrorMethod, others) - emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations)) - - // typestate: entering mode with valid call sequences: - // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd - - mirrorMethod.visitCode() - - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) - - var index = 0 - for(jparamType <- paramJavaTypes) { - mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index) - assert(jparamType.getSort() != asm.Type.METHOD, jparamType) - index += jparamType.getSize() - } - - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(m).getDescriptor, false) - mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN)) - - mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - mirrorMethod.visitEnd() - - } - - /** Add forwarders for all methods defined in `module` that don't conflict - * with methods in the companion class of `module`. A conflict arises when - * a method with the same name is defined both in a class and its companion object: - * method signature is not taken into account. - */ - def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) { - assert(moduleClass.isModuleClass, moduleClass) - debuglog("Dumping mirror class for object: " + moduleClass) - - val linkedClass = moduleClass.companionClass - lazy val conflictingNames: Set[Name] = { - (linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet - } - debuglog("Potentially conflicting names for forwarders: " + conflictingNames) - - for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) { - if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor) - debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") - else if (conflictingNames(m.name)) - log(s"No forwarder for $m due to conflict with " + linkedClass.info.member(m.name)) - else if (m.hasAccessBoundary) - log(s"No forwarder for non-public member $m") - else { - debuglog(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'") - addForwarder(isRemoteClass, jclass, moduleClass, m) - } - } - } - - } // end of class JCommonBuilder - - - trait JAndroidBuilder { - self: JPlainBuilder => - - def isAndroidParcelableClass(sym: Symbol) = - (AndroidParcelableInterface != NoSymbol) && - (sym.parentSymbols contains AndroidParcelableInterface) - - /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */ - def addCreatorCode(block: BasicBlock) { - val fieldSymbol = ( - clasz.symbol.newValue(androidFieldName, NoPosition, Flags.STATIC | Flags.FINAL) - setInfo AndroidCreatorClass.tpe - ) - val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName) - clasz addField new IField(fieldSymbol) - block emit CALL_METHOD(methodSymbol, Static(onInstance = false)) - block emit STORE_FIELD(fieldSymbol, isStatic = true) - } - - def legacyAddCreatorCode(clinit: asm.MethodVisitor) { - val creatorType: asm.Type = javaType(AndroidCreatorClass) - val tdesc_creator = creatorType.getDescriptor - - jclass.visitField( - PublicStaticFinal, - androidFieldName.toString, - tdesc_creator, - null, // no java-generic-signature - null // no initial value - ).visitEnd() - - val moduleName = javaName(clasz.symbol)+"$" - - // GETSTATIC `moduleName`.MODULE$ : `moduleName`; - clinit.visitFieldInsn( - asm.Opcodes.GETSTATIC, - moduleName, - strMODULE_INSTANCE_FIELD, - asm.Type.getObjectType(moduleName).getDescriptor - ) - - // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; - clinit.visitMethodInsn( - asm.Opcodes.INVOKEVIRTUAL, - moduleName, - androidFieldName.toString, - asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*), - false - ) - - // PUTSTATIC `thisName`.CREATOR; - clinit.visitFieldInsn( - asm.Opcodes.PUTSTATIC, - thisName, - androidFieldName.toString, - tdesc_creator - ) - } - - } // end of trait JAndroidBuilder - - /** Map from type kinds to the Java reference types. - * It is used to push class literals onto the operand stack. - * @see Predef.classOf - * @see genConstant() - */ - private val classLiteral = immutable.Map[TypeKind, asm.Type]( - UNIT -> asm.Type.getObjectType("java/lang/Void"), - BOOL -> asm.Type.getObjectType("java/lang/Boolean"), - BYTE -> asm.Type.getObjectType("java/lang/Byte"), - SHORT -> asm.Type.getObjectType("java/lang/Short"), - CHAR -> asm.Type.getObjectType("java/lang/Character"), - INT -> asm.Type.getObjectType("java/lang/Integer"), - LONG -> asm.Type.getObjectType("java/lang/Long"), - FLOAT -> asm.Type.getObjectType("java/lang/Float"), - DOUBLE -> asm.Type.getObjectType("java/lang/Double") - ) - - def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT } - - case class MethodNameAndType(mname: String, mdesc: String) - - private val jBoxTo: Map[TypeKind, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , - BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , - CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , - SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , - INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , - LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , - FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , - DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) - ) - } - - private val jUnboxTo: Map[TypeKind, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , - BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , - CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , - SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , - INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , - LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , - FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , - DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") - ) - } - - case class BlockInteval(start: BasicBlock, end: BasicBlock) - - /** builder of plain classes */ - class JPlainBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) - extends JCommonBuilder(bytecodeWriter, needsOutfile) - with JAndroidBuilder { - - val MIN_SWITCH_DENSITY = 0.7 - - val StringBuilderClassName = javaName(definitions.StringBuilderClass) - val BoxesRunTime = "scala/runtime/BoxesRunTime" - - val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName) - val mdesc_toString = "()Ljava/lang/String;" - val mdesc_arrayClone = "()Ljava/lang/Object;" - - val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J" - - def isParcelableClass = isAndroidParcelableClass(clasz.symbol) - - def serialVUID: Option[Long] = genBCode.serialVUID(clasz.symbol) - - var clasz: IClass = _ // this var must be assigned only by genClass() - var jclass: asm.ClassWriter = _ // the classfile being emitted - var thisName: String = _ // the internal name of jclass - - def thisDescr: String = { - assert(thisName != null, "thisDescr invoked too soon.") - asm.Type.getObjectType(thisName).getDescriptor - } - - def getCurrentCUnit(): CompilationUnit = { clasz.cunit } - - def genClass(c: IClass) { - clasz = c - innerClassBuffer.clear() - - thisName = javaName(c.symbol) // the internal name of the class being emitted - - val ps = c.symbol.info.parents - val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol) - - val ifaces: Array[String] = implementedInterfaces(c.symbol).map(javaName)(collection.breakOut) - - val thisSignature = getGenericSignature(c.symbol, c.symbol.owner) - val flags = mkFlags( - javaFlags(c.symbol), - if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - jclass = createJClass(flags, - thisName, thisSignature, - superClass, ifaces) - - // typestate: entering mode with valid call sequences: - // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* - - if(emitSource) { - jclass.visitSource(c.cunit.source.toString, - null /* SourceDebugExtension */) - } - - enclosingMethodAttribute(clasz.symbol, javaName, javaType(_).getDescriptor) match { - case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => - jclass.visitOuterClass(className, methodName, methodDescriptor) - case _ => () - } - - // typestate: entering mode with valid call sequences: - // ( visitAnnotation | visitAttribute )* - - val ssa = getAnnotPickle(thisName, c.symbol) - jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) - emitAnnotations(jclass, c.symbol.annotations ++ ssa) - - if (!settings.YskipInlineInfoAttribute.value) - jclass.visitAttribute(InlineInfoAttribute(buildInlineInfoFromClassSymbol(c.symbol, javaName, javaType(_).getDescriptor))) - - // typestate: entering mode with valid call sequences: - // ( visitInnerClass | visitField | visitMethod )* visitEnd - - if (isStaticModule(c.symbol) || isParcelableClass) { - - if (isStaticModule(c.symbol)) { addModuleInstanceField() } - addStaticInit(c.lookupStaticCtor) - - } else { - - for (constructor <- c.lookupStaticCtor) { - addStaticInit(Some(constructor)) - } - val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders) - if (!skipStaticForwarders) { - val lmoc = c.symbol.companionModule - // add static forwarders if there are no name conflicts; see bugs #363 and #1735 - if (lmoc != NoSymbol) { - // it must be a top level class (name contains no $s) - val isCandidateForForwarders = { - exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } - } - if (isCandidateForForwarders) { - log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc)) - addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass) - } - } - } - - } - - // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` - serialVUID foreach { value => - val fieldName = "serialVersionUID" - jclass.visitField( - PublicStaticFinal, - fieldName, - tdesc_long, - null, // no java-generic-signature - value - ).visitEnd() - } - - clasz.fields foreach genField - clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) } - - addInnerClasses(clasz.symbol, jclass) - jclass.visitEnd() - writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) - } - - def genField(f: IField) { - debuglog("Adding field: " + f.symbol.fullName) - - val javagensig = getGenericSignature(f.symbol, clasz.symbol) - - val flags = mkFlags( - javaFieldFlags(f.symbol), - if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - val jfield: asm.FieldVisitor = jclass.visitField( - flags, - javaName(f.symbol), - javaType(f.symbol.tpe).getDescriptor(), - javagensig, - null // no initial value - ) - - emitAnnotations(jfield, f.symbol.annotations) - jfield.visitEnd() - } - - var method: IMethod = _ - var jmethod: asm.MethodVisitor = _ - var jMethodName: String = _ - - final def emit(opc: Int) { jmethod.visitInsn(opc) } - - def genMethod(m: IMethod, isJInterface: Boolean) { - - def isClosureApply(sym: Symbol): Boolean = { - (sym.name == nme.apply) && - sym.owner.isSynthetic && - sym.owner.tpe.parents.exists { t => - val TypeRef(_, sym, _) = t - FunctionClass.seq contains sym - } - } - - if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return - - if (m.params.size > MaximumJvmParameters) { - reporter.error(m.symbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.") - return - } - - debuglog("Generating method " + m.symbol.fullName) - method = m - computeLocalVarsIndex(m) - - var resTpe: asm.Type = javaType(m.symbol.tpe.resultType) - if (m.symbol.isClassConstructor) - resTpe = asm.Type.VOID_TYPE - - val flags = mkFlags( - javaFlags(m.symbol), - if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0, - if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, - if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes - if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } - val jgensig = getGenericSignature(m.symbol, clasz.symbol) - addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol) - val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass) - val thrownExceptions: List[String] = getExceptions(excs) - - jMethodName = javaName(m.symbol) - val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*) - jmethod = jclass.visitMethod( - flags, - jMethodName, - mdesc, - jgensig, - mkArray(thrownExceptions) - ) - - // TODO param names: (m.params map (p => javaName(p.sym))) - - // typestate: entering mode with valid call sequences: (see ASM Guide, 3.2.1) - // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* - - emitAnnotations(jmethod, others) - emitParamAnnotations(jmethod, m.params.map(_.sym.annotations)) - - // typestate: entering mode with valid call sequences: - // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd - // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code, - // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and - // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited. - - val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0) - val hasCodeAttribute = (!hasAbstractBitSet && !method.native) - if (hasCodeAttribute) { - - jmethod.visitCode() - - if (emitVars && isClosureApply(method.symbol)) { - // add a fake local for debugging purposes - val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL) - if (outerField != NoSymbol) { - log("Adding fake local to represent outer 'this' for closure " + clasz) - val _this = - new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS), - toTypeKind(outerField.tpe), - false) - m.locals = m.locals ::: List(_this) - computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes - jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) - jmethod.visitFieldInsn(asm.Opcodes.GETFIELD, - javaName(clasz.symbol), // field owner - javaName(outerField), // field name - descriptor(outerField) // field descriptor - ) - assert(_this.kind.isReferenceType, _this.kind) - jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this)) - } - } - - assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals ) - - val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0) - genCode(m, emitVars, hasStaticBitSet) - - // visitMaxs needs to be called according to the protocol. The arguments will be ignored - // since maximums (and stack map frames) are computed. See ASM Guide, Section 3.2.1, - // section "ClassWriter options" - jmethod.visitMaxs(0, 0) - } - - jmethod.visitEnd() - - } - - def addModuleInstanceField() { - val fv = - jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED - strMODULE_INSTANCE_FIELD, - thisDescr, - null, // no java-generic-signature - null // no initial value - ) - - // typestate: entering mode with valid call sequences: - // ( visitAnnotation | visitAttribute )* visitEnd. - - fv.visitEnd() - } - - - /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */ - def addStaticInit(mopt: Option[IMethod]) { - - val clinitMethod: asm.MethodVisitor = jclass.visitMethod( - PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED - CLASS_CONSTRUCTOR_NAME, - mdesc_arglessvoid, - null, // no java-generic-signature - null // no throwable exceptions - ) - - mopt match { - - case Some(m) => - - val oldLastBlock = m.lastBlock - val lastBlock = m.newBlock() - oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock)) - - if (isStaticModule(clasz.symbol)) { - // call object's private ctor from static ctor - lastBlock emit NEW(REFERENCE(m.symbol.enclClass)) - lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(onInstance = true)) - } - - if (isParcelableClass) { addCreatorCode(lastBlock) } - - lastBlock emit RETURN(UNIT) - lastBlock.close() - - method = m - jmethod = clinitMethod - jMethodName = CLASS_CONSTRUCTOR_NAME - jmethod.visitCode() - computeLocalVarsIndex(m) - genCode(m, emitVars = false, isStatic = true) - jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - jmethod.visitEnd() - - case None => - clinitMethod.visitCode() - legacyStaticInitializer(clinitMethod) - clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments - clinitMethod.visitEnd() - - } - } - - /* used only from addStaticInit() */ - private def legacyStaticInitializer(clinit: asm.MethodVisitor) { - if (isStaticModule(clasz.symbol)) { - clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) - clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, - thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid, false) - } - - if (isParcelableClass) { legacyAddCreatorCode(clinit) } - - clinit.visitInsn(asm.Opcodes.RETURN) - } - - // ----------------------------------------------------------------------------------------- - // Emitting bytecode instructions. - // ----------------------------------------------------------------------------------------- - - private def genConstant(mv: asm.MethodVisitor, const: Constant) { - const.tag match { - - case BooleanTag => jcode.boolconst(const.booleanValue) - - case ByteTag => jcode.iconst(const.byteValue.toInt) - case ShortTag => jcode.iconst(const.shortValue.toInt) - case CharTag => jcode.iconst(const.charValue) - case IntTag => jcode.iconst(const.intValue) - - case LongTag => jcode.lconst(const.longValue) - case FloatTag => jcode.fconst(const.floatValue) - case DoubleTag => jcode.dconst(const.doubleValue) - - case UnitTag => () - - case StringTag => - assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` - mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag - - case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL) - - case ClazzTag => - val kind = toTypeKind(const.typeValue) - val toPush: asm.Type = - if (kind.isValueType) classLiteral(kind) - else javaType(kind) - mv.visitLdcInsn(toPush) - - case EnumTag => - val sym = const.symbolValue - mv.visitFieldInsn( - asm.Opcodes.GETSTATIC, - javaName(sym.owner), - javaName(sym), - javaType(sym.tpe.underlying).getDescriptor() - ) - - case _ => abort("Unknown constant value: " + const) - } - } - - /** Just a namespace for utilities that encapsulate MethodVisitor idioms. - * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, - * but the methods here allow choosing when to transition from ICode to ASM types - * (including not at all, e.g. for performance). - */ - object jcode { - - import asm.Opcodes - - final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) } - - def iconst(cst: Char) { iconst(cst.toInt) } - def iconst(cst: Int) { - if (cst >= -1 && cst <= 5) { - jmethod.visitInsn(Opcodes.ICONST_0 + cst) - } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) { - jmethod.visitIntInsn(Opcodes.BIPUSH, cst) - } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) { - jmethod.visitIntInsn(Opcodes.SIPUSH, cst) - } else { - jmethod.visitLdcInsn(new Integer(cst)) - } - } - - def lconst(cst: Long) { - if (cst == 0L || cst == 1L) { - jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int]) - } else { - jmethod.visitLdcInsn(new java.lang.Long(cst)) - } - } - - def fconst(cst: Float) { - val bits: Int = java.lang.Float.floatToIntBits(cst) - if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 - jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int]) - } else { - jmethod.visitLdcInsn(new java.lang.Float(cst)) - } - } - - def dconst(cst: Double) { - val bits: Long = java.lang.Double.doubleToLongBits(cst) - if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d - jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int]) - } else { - jmethod.visitLdcInsn(new java.lang.Double(cst)) - } - } - - def newarray(elem: TypeKind) { - if(elem.isRefOrArrayType) { - jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName) - } else { - val rand = { - if(elem.isIntSizedType) { - (elem: @unchecked) match { - case BOOL => Opcodes.T_BOOLEAN - case BYTE => Opcodes.T_BYTE - case SHORT => Opcodes.T_SHORT - case CHAR => Opcodes.T_CHAR - case INT => Opcodes.T_INT - } - } else { - (elem: @unchecked) match { - case LONG => Opcodes.T_LONG - case FLOAT => Opcodes.T_FLOAT - case DOUBLE => Opcodes.T_DOUBLE - } - } - } - jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) - } - } - - - def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) } - def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) } - - def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) } - def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) } - - def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) } - def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) } - def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) } - def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) } - def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) } - def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) } - - def invokespecial(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false) - } - def invokestatic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false) - } - def invokeinterface(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true) - } - def invokevirtual(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false) - } - - def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } - def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF(), label) } - def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP(), label) } - def emitIF_ACMP(cond: TestOp, label: asm.Label) { - assert((cond == EQ) || (cond == NE), cond) - val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) - jmethod.visitJumpInsn(opc, label) - } - def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } - def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } - - def emitRETURN(tk: TypeKind) { - if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) } - else { emitTypeBased(returnOpcodes, tk) } - } - - /** Emits one of tableswitch or lookoupswitch. */ - def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) { - assert(keys.length == branches.length) - - // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only. - // Similar to what javac emits for a switch statement consisting only of a default case. - if (keys.length == 0) { - jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) - return - } - - // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort - var i = 1 - while (i < keys.length) { - var j = 1 - while (j <= keys.length - i) { - if (keys(j) < keys(j - 1)) { - val tmp = keys(j) - keys(j) = keys(j - 1) - keys(j - 1) = tmp - val tmpL = branches(j) - branches(j) = branches(j - 1) - branches(j - 1) = tmpL - } - j += 1 - } - i += 1 - } - - // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011) - i = 1 - while (i < keys.length) { - if(keys(i-1) == keys(i)) { - abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") - } - i += 1 - } - - val keyMin = keys(0) - val keyMax = keys(keys.length - 1) - - val isDenseEnough: Boolean = { - /* Calculate in long to guard against overflow. TODO what overflow??? */ - val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] - val klenD: Double = keys.length.toDouble - val kdensity: Double = (klenD / keyRangeD) - - kdensity >= minDensity - } - - if (isDenseEnough) { - // use a table in which holes are filled with defaultBranch. - val keyRange = (keyMax - keyMin + 1) - val newBranches = new Array[asm.Label](keyRange) - var oldPos = 0 - var i = 0 - while(i < keyRange) { - val key = keyMin + i - if (keys(oldPos) == key) { - newBranches(i) = branches(oldPos) - oldPos += 1 - } else { - newBranches(i) = defaultBranch - } - i += 1 - } - assert(oldPos == keys.length, "emitSWITCH") - jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*) - } else { - jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) - } - } - - // internal helpers -- not part of the public API of `jcode` - // don't make private otherwise inlining will suffer - - def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) { - assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) - jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx) - } - - // ---------------- array load and store ---------------- - - val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) } - val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) } - - val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) } - - def emitTypeBased(opcs: Array[Int], tk: TypeKind) { - assert(tk != UNIT, tk) - val opc = { - if(tk.isRefOrArrayType) { opcs(0) } - else if(tk.isIntSizedType) { - (tk: @unchecked) match { - case BOOL | BYTE => opcs(1) - case SHORT => opcs(2) - case CHAR => opcs(3) - case INT => opcs(4) - } - } else { - (tk: @unchecked) match { - case LONG => opcs(5) - case FLOAT => opcs(6) - case DOUBLE => opcs(7) - } - } - } - jmethod.visitInsn(opc) - } - - // ---------------- primitive operations ---------------- - - val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) } - val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) } - val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) } - val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) } - val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) } - val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) } - - def emitPrimitive(opcs: Array[Int], tk: TypeKind) { - val opc = { - if(tk.isIntSizedType) { opcs(0) } - else { - (tk: @unchecked) match { - case LONG => opcs(1) - case FLOAT => opcs(2) - case DOUBLE => opcs(3) - } - } - } - jmethod.visitInsn(opc) - } - - } - - /** Invoked from genMethod() and addStaticInit() */ - def genCode(m: IMethod, - emitVars: Boolean, // this param name hides the instance-level var - isStatic: Boolean) { - - - newNormal.normalize(m) - - // ------------------------------------------------------------------------------------------------------------ - // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization` - // ------------------------------------------------------------------------------------------------------------ - - val linearization: List[BasicBlock] = linearizer.linearize(m) - if(linearization.isEmpty) { return } - - var isModuleInitialized = false - - val labels: scala.collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*) - - val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted - - // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label. - val linNext: scala.collection.Map[BasicBlock, asm.Label] = { - val result = mutable.HashMap.empty[BasicBlock, asm.Label] - var rest = linearization - var prev = rest.head - rest = rest.tail - while(!rest.isEmpty) { - result += (prev -> labels(rest.head)) - prev = rest.head - rest = rest.tail - } - assert(!result.contains(prev)) - result += (prev -> onePastLast) - - result - } - - // ------------------------------------------------------------------------------------------------------------ - // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock()) - // ------------------------------------------------------------------------------------------------------------ - - /* Generate exception handlers for the current method. - * - * Quoting from the JVMS 4.7.3 The Code Attribute - * The items of the Code_attribute structure are as follows: - * . . . - * exception_table[] - * Each entry in the exception_table array describes one - * exception handler in the code array. The order of the handlers in - * the exception_table array is significant. - * Each exception_table entry contains the following four items: - * start_pc, end_pc: - * ... The value of end_pc either must be a valid index into - * the code array of the opcode of an instruction or must be equal to code_length, - * the length of the code array. - * handler_pc: - * The value of the handler_pc item indicates the start of the exception handler - * catch_type: - * ... If the value of the catch_type item is zero, - * this exception handler is called for all exceptions. - * This is used to implement finally - */ - def genExceptionHandlers() { - - /* Return a list of pairs of intervals where the handler is active. - * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end]. - * Preconditions: - * - e.covered non-empty - * Postconditions for the result: - * - always non-empty - * - intervals are sorted as per `linearization` - * - the argument's `covered` blocks have been grouped into maximally contiguous intervals, - * ie. between any two intervals in the result there is a non-empty gap. - * - each of the `covered` blocks in the argument is contained in some interval in the result - */ - def intervals(e: ExceptionHandler): List[BlockInteval] = { - assert(e.covered.nonEmpty, e) - var result: List[BlockInteval] = Nil - var rest = linearization - - // find intervals - while(!rest.isEmpty) { - // find interval start - var start: BasicBlock = null - while(!rest.isEmpty && (start eq null)) { - if(e.covered(rest.head)) { start = rest.head } - rest = rest.tail - } - if(start ne null) { - // find interval end - var end = start // for the time being - while(!rest.isEmpty && (e.covered(rest.head))) { - end = rest.head - rest = rest.tail - } - result = BlockInteval(start, end) :: result - } - } - - assert(result.nonEmpty, e) - - result - } - - /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains - * blocks not in the linearization (dead-code?). Is that well-formed or not? - * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does). - */ - for (e <- this.method.exh) { - val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } ) - // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)") - if(ignore.nonEmpty) { - e.covered = e.covered filterNot ignore - } - } - - // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table. - // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class? - for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) { - debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method + - " from: " + p.start + " to: " + p.end + " catching: " + e.cls) - val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null - else javaName(e.cls) - jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls) - } - } // end of genCode()'s genExceptionHandlers() - - if (m.exh.nonEmpty) { genExceptionHandlers() } - - // ------------------------------------------------------------------------------------------------------------ - // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute). - // ------------------------------------------------------------------------------------------------------------ - - case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive. - - case class Interval(lstart: asm.Label, lend: asm.Label) { - final def start = lstart.getOffset - final def end = lend.getOffset - - def precedes(that: Interval): Boolean = { this.end < that.start } - - def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) } - - def mergeWith(that: Interval): Interval = { - val newStart = if(this.start <= that.start) this.lstart else that.lstart - val newEnd = if(this.end <= that.end) that.lend else this.lend - Interval(newStart, newEnd) - } - - def repOK: Boolean = { start <= end } - - } - - /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */ - object scoping { - - private val pending = mutable.Map.empty[Local, mutable.Stack[Label]] - private var seen: List[LocVarEntry] = Nil - - private def fuse(ranges: List[Interval], added: Interval): List[Interval] = { - assert(added.repOK, added) - if(ranges.isEmpty) { return List(added) } - // precond: ranges is sorted by increasing start - var fused: List[Interval] = Nil - var done = false - var rest = ranges - while(!done && rest.nonEmpty) { - val current = rest.head - assert(current.repOK, current) - rest = rest.tail - if(added precedes current) { - fused = fused ::: ( added :: current :: rest ) - done = true - } else if(current overlaps added) { - fused = fused ::: ( added.mergeWith(current) :: rest ) - done = true - } - } - if(!done) { fused = fused ::: List(added) } - assert(repOK(fused), fused) - - fused - } - - def pushScope(lv: Local, start: Label) { - val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label]) - st.push(start) - } - def popScope(lv: Local, end: Label, iPos: Position) { - pending.get(lv) match { - case Some(st) if st.nonEmpty => - val start = st.pop() - seen ::= LocVarEntry(lv, start, end) - case _ => - // TODO SI-6049 track down the cause for these. - devWarning(s"$iPos: Visited SCOPE_EXIT before visiting corresponding SCOPE_ENTER. SI-6191") - } - } - - def getMerged(): scala.collection.Map[Local, List[Interval]] = { - // TODO should but isn't: unbalanced start(s) of scope(s) - val shouldBeEmpty = pending filter { p => val (_, st) = p; st.nonEmpty } - val merged = mutable.Map[Local, List[Interval]]() - def addToMerged(lv: Local, start: Label, end: Label) { - val intv = Interval(start, end) - merged(lv) = if (merged contains lv) fuse(merged(lv), intv) else intv :: Nil - } - for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) } - - /* for each var with unbalanced start(s) of scope(s): - (a) take the earliest start (among unbalanced and balanced starts) - (b) take the latest end (onePastLast if none available) - (c) merge the thus made-up interval - */ - for((k, st) <- shouldBeEmpty) { - var start = st.toList.sortBy(_.getOffset).head - if(merged.isDefinedAt(k)) { - val balancedStart = merged(k).head.lstart - if(balancedStart.getOffset < start.getOffset) { - start = balancedStart - } - } - val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend - val end = endOpt.getOrElse(onePastLast) - addToMerged(k, start, end) - } - - merged - } - - private def repOK(fused: List[Interval]): Boolean = { - fused match { - case Nil => true - case h :: Nil => h.repOK - case h :: n :: rest => - h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest) - } - } - - } - - def genLocalVariableTable() { - // adding `this` and method params. - if (!isStatic) { - jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0) - } - for(lv <- m.params) { - jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv)) - } - // adding non-param locals - var anonCounter = 0 - var fltnd: List[Tuple3[String, Local, Interval]] = Nil - for((local, ranges) <- scoping.getMerged()) { - var name = javaName(local.sym) - if (name == null) { - anonCounter += 1 - name = "<anon" + anonCounter + ">" - } - for(intrvl <- ranges) { - fltnd ::= (name, local, intrvl) - } - } - // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain). - val srtd = fltnd.sortBy { kr => - val (name: String, _, intrvl: Interval) = kr - - (intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name) - } - - for((name, local, Interval(start, end)) <- srtd) { - jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local)) - } - // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute" - } - - // ------------------------------------------------------------------------------------------------------------ - // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position. - // ------------------------------------------------------------------------------------------------------------ - - case class LineNumberEntry(line: Int, start: asm.Label) - var lastLineNr: Int = -1 - var lnEntries: List[LineNumberEntry] = Nil - - // ------------------------------------------------------------------------------------------------------------ - // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()). - // ------------------------------------------------------------------------------------------------------------ - - var nextBlock: BasicBlock = linearization.head - - def genBlocks(l: List[BasicBlock]): Unit = l match { - case Nil => () - case x :: Nil => nextBlock = null; genBlock(x) - case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys) - } - - def genCallMethod(call: CALL_METHOD) { - val CALL_METHOD(method, style) = call - val siteSymbol = clasz.symbol - val hostSymbol = call.hostClass - val methodOwner = method.owner - // info calls so that types are up to date; erasure may add lateINTERFACE to traits - hostSymbol.info ; methodOwner.info - - def needsInterfaceCall(sym: Symbol) = ( - sym.isInterface - || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass) - ) - // whether to reference the type of the receiver or - // the type of the method owner - val useMethodOwner = ( - style != Dynamic - || hostSymbol.isBottomClass - || methodOwner == ObjectClass - ) - val receiver = if (useMethodOwner) methodOwner else hostSymbol - val jowner = javaName(receiver) - val jname = javaName(method) - val jtype = javaType(method).getDescriptor() - - def dbg(invoke: String) { - debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype)) - } - - def initModule() { - // we initialize the MODULE$ field immediately after the super ctor - if (isStaticModule(siteSymbol) && !isModuleInitialized && - jMethodName == INSTANCE_CONSTRUCTOR_NAME && - jname == INSTANCE_CONSTRUCTOR_NAME) { - isModuleInitialized = true - jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0) - jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr) - } - } - - style match { - case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype) - case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype) - case Dynamic if needsInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype) - case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype) - case SuperCall(_) => - dbg("invokespecial") - jcode.invokespecial(jowner, jname, jtype) - initModule() - } - } // end of genCode()'s genCallMethod() - - def genBlock(b: BasicBlock) { - jmethod.visitLabel(labels(b)) - - debuglog("Generating code for block: " + b) - - // val lastInstr = b.lastInstruction - - for (instr <- b) { - - if(instr.pos.isDefined) { - val iPos = instr.pos - val currentLineNr = iPos.line - val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else - if(!skip) { - lastLineNr = currentLineNr - val lineLab = new asm.Label - jmethod.visitLabel(lineLab) - lnEntries ::= LineNumberEntry(iPos.finalPosition.line, lineLab) - } - } - - genInstr(instr, b) - - } - - } - - def genInstr(instr: Instruction, b: BasicBlock) { - import asm.Opcodes - (instr.category: @scala.annotation.switch) match { - - - case icodes.localsCat => - def genLocalInstr() = (instr: @unchecked) match { - case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0) - case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind) - case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind) - case STORE_THIS(_) => - // this only works for impl classes because the self parameter comes first - // in the method signature. If that changes, this code has to be revisited. - jmethod.visitVarInsn(Opcodes.ASTORE, 0) - - case SCOPE_ENTER(lv) => - // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored - val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) - if (relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars? - // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) - // similarly, these labels aren't tracked in the `labels` map. - val start = new asm.Label - jmethod.visitLabel(start) - scoping.pushScope(lv, start) - } - - case SCOPE_EXIT(lv) => - val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv)) - if (relevant) { - // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes) - // similarly, these labels aren't tracked in the `labels` map. - val end = new asm.Label - jmethod.visitLabel(end) - scoping.popScope(lv, end, instr.pos) - } - } - genLocalInstr() - - case icodes.stackCat => - def genStackInstr() = (instr: @unchecked) match { - - case LOAD_MODULE(module) => - // assert(module.isModule, "Expected module: " + module) - debuglog("generating LOAD_MODULE for: " + module + " flags: " + module.flagString) - def inStaticMethod = this.method != null && this.method.symbol.isStaticMember - if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString && !inStaticMethod) { - jmethod.visitVarInsn(Opcodes.ALOAD, 0) - } else { - jmethod.visitFieldInsn( - Opcodes.GETSTATIC, - javaName(module) /* + "$" */ , - strMODULE_INSTANCE_FIELD, - descriptor(module)) - } - - case DROP(kind) => emit(if (kind.isWideType) Opcodes.POP2 else Opcodes.POP) - - case DUP(kind) => emit(if (kind.isWideType) Opcodes.DUP2 else Opcodes.DUP) - - case LOAD_EXCEPTION(_) => () - } - genStackInstr() - - case icodes.constCat => genConstant(jmethod, instr.asInstanceOf[CONSTANT].constant) - - case icodes.arilogCat => genPrimitive(instr.asInstanceOf[CALL_PRIMITIVE].primitive, instr.pos) - - case icodes.castsCat => - def genCastInstr() = (instr: @unchecked) match { - - case IS_INSTANCE(tpe) => - val jtyp: asm.Type = - tpe match { - case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls)) - case ARRAY(elem) => javaArrayType(javaType(elem)) - case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) - } - jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName) - - case CHECK_CAST(tpe) => - tpe match { - - case REFERENCE(cls) => - if (cls != ObjectClass) { // No need to checkcast for Objects - jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls)) - } - - case ARRAY(elem) => - val iname = javaArrayType(javaType(elem)).getInternalName - jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname) - - case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe) - } - - } - genCastInstr() - - case icodes.objsCat => - def genObjsInstr() = (instr: @unchecked) match { - case BOX(kind) => - val MethodNameAndType(mname, mdesc) = jBoxTo(kind) - jcode.invokestatic(BoxesRunTime, mname, mdesc) - - case UNBOX(kind) => - val MethodNameAndType(mname, mdesc) = jUnboxTo(kind) - jcode.invokestatic(BoxesRunTime, mname, mdesc) - - case NEW(REFERENCE(cls)) => - val className = javaName(cls) - jmethod.visitTypeInsn(Opcodes.NEW, className) - - case MONITOR_ENTER() => emit(Opcodes.MONITORENTER) - case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT) - } - genObjsInstr() - - case icodes.fldsCat => - def genFldsInstr() = (instr: @unchecked) match { - - case lf @ LOAD_FIELD(field, isStatic) => - val owner = javaName(lf.hostClass) - debuglog("LOAD_FIELD with owner: " + owner + " flags: " + field.owner.flagString) - val fieldJName = javaName(field) - val fieldDescr = descriptor(field) - val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD - jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) - - case STORE_FIELD(field, isStatic) => - val owner = javaName(field.owner) - val fieldJName = javaName(field) - val fieldDescr = descriptor(field) - val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD - jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr) - - } - genFldsInstr() - - case icodes.mthdsCat => - def genMethodsInstr() = (instr: @unchecked) match { - - /* Special handling to access native Array.clone() */ - case call @ CALL_METHOD(definitions.Array_clone, Dynamic) => - val target: String = javaType(call.targetTypeKind).getInternalName - jcode.invokevirtual(target, "clone", mdesc_arrayClone) - - case call @ CALL_METHOD(method, style) => genCallMethod(call) - - } - genMethodsInstr() - - case icodes.arraysCat => - def genArraysInstr() = (instr: @unchecked) match { - case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind) - case STORE_ARRAY_ITEM(kind) => jcode.astore(kind) - case CREATE_ARRAY(elem, 1) => jcode newarray elem - case CREATE_ARRAY(elem, dims) => jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims) - } - genArraysInstr() - - case icodes.jumpsCat => - def genJumpInstr() = (instr: @unchecked) match { - - case sw @ SWITCH(tagss, branches) => - assert(branches.length == tagss.length + 1, sw) - val flatSize = sw.flatTagsCount - val flatKeys = new Array[Int](flatSize) - val flatBranches = new Array[asm.Label](flatSize) - - var restTagss = tagss - var restBranches = branches - var k = 0 // ranges over flatKeys and flatBranches - while (restTagss.nonEmpty) { - val currLabel = labels(restBranches.head) - for (cTag <- restTagss.head) { - flatKeys(k) = cTag - flatBranches(k) = currLabel - k += 1 - } - restTagss = restTagss.tail - restBranches = restBranches.tail - } - val defaultLabel = labels(restBranches.head) - assert(restBranches.tail.isEmpty) - debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches) - jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY) - - case JUMP(whereto) => - if (nextBlock != whereto) - jcode goTo labels(whereto) - // SI-6102: Determine whether eliding this JUMP results in an empty range being covered by some EH. - // If so, emit a NOP in place of the elided JUMP, to avoid "java.lang.ClassFormatError: Illegal exception table range" - else if (newNormal.isJumpOnly(b) && m.exh.exists(eh => eh.covers(b))) { - devWarning("Had a jump only block that wasn't collapsed") - emit(asm.Opcodes.NOP) - } - - case CJUMP(success, failure, cond, kind) => - if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT - if (nextBlock == success) { - jcode.emitIF_ICMP(cond.negate(), labels(failure)) - // .. and fall through to success label - } else { - jcode.emitIF_ICMP(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) - if (nextBlock == success) { - jcode.emitIF_ACMP(cond.negate(), labels(failure)) - // .. and fall through to success label - } else { - jcode.emitIF_ACMP(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } else { - (kind: @unchecked) match { - case LONG => emit(Opcodes.LCMP) - case FLOAT => - if (cond == LT || cond == LE) emit(Opcodes.FCMPG) - else emit(Opcodes.FCMPL) - case DOUBLE => - if (cond == LT || cond == LE) emit(Opcodes.DCMPG) - else emit(Opcodes.DCMPL) - } - if (nextBlock == success) { - jcode.emitIF(cond.negate(), labels(failure)) - // .. and fall through to success label - } else { - jcode.emitIF(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } - - case CZJUMP(success, failure, cond, kind) => - if (kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT - if (nextBlock == success) { - jcode.emitIF(cond.negate(), labels(failure)) - } else { - jcode.emitIF(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } else if (kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) - val Success = success - val Failure = failure - // @unchecked because references aren't compared with GT, GE, LT, LE. - ((cond, nextBlock): @unchecked) match { - case (EQ, Success) => jcode emitIFNONNULL labels(failure) - case (NE, Failure) => jcode emitIFNONNULL labels(success) - case (EQ, Failure) => jcode emitIFNULL labels(success) - case (NE, Success) => jcode emitIFNULL labels(failure) - case (EQ, _) => - jcode emitIFNULL labels(success) - jcode goTo labels(failure) - case (NE, _) => - jcode emitIFNONNULL labels(success) - jcode goTo labels(failure) - } - } else { - (kind: @unchecked) match { - case LONG => - emit(Opcodes.LCONST_0) - emit(Opcodes.LCMP) - case FLOAT => - emit(Opcodes.FCONST_0) - if (cond == LT || cond == LE) emit(Opcodes.FCMPG) - else emit(Opcodes.FCMPL) - case DOUBLE => - emit(Opcodes.DCONST_0) - if (cond == LT || cond == LE) emit(Opcodes.DCMPG) - else emit(Opcodes.DCMPL) - } - if (nextBlock == success) { - jcode.emitIF(cond.negate(), labels(failure)) - } else { - jcode.emitIF(cond, labels(success)) - if (nextBlock != failure) { jcode goTo labels(failure) } - } - } - - } - genJumpInstr() - - case icodes.retCat => - def genRetInstr() = (instr: @unchecked) match { - case RETURN(kind) => jcode emitRETURN kind - case THROW(_) => emit(Opcodes.ATHROW) - } - genRetInstr() - } - } - - /* - * Emits one or more conversion instructions based on the types given as arguments. - * - * @param from The type of the value to be converted into another type. - * @param to The type the value will be converted into. - */ - def emitT2T(from: TypeKind, to: TypeKind) { - assert(isNonUnitValueTK(from) && isNonUnitValueTK(to), s"Cannot emit primitive conversion from $from to $to") - - def pickOne(opcs: Array[Int]) { - val chosen = (to: @unchecked) match { - case BYTE => opcs(0) - case SHORT => opcs(1) - case CHAR => opcs(2) - case INT => opcs(3) - case LONG => opcs(4) - case FLOAT => opcs(5) - case DOUBLE => opcs(6) - } - if(chosen != -1) { emit(chosen) } - } - - if(from == to) { return } - // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) - assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") - - if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already) - - val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT) - val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing - val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing - val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) } - - (from: @unchecked) match { - case BYTE => pickOne(fromByte) - case SHORT => pickOne(fromShort) - case CHAR => pickOne(fromChar) - case INT => pickOne(fromInt) - } - - } else { // FLOAT, LONG, DOUBLE - - (from: @unchecked) match { - case FLOAT => - import asm.Opcodes.{ F2L, F2D, F2I } - (to: @unchecked) match { - case LONG => emit(F2L) - case DOUBLE => emit(F2D) - case _ => emit(F2I); emitT2T(INT, to) - } - - case LONG => - import asm.Opcodes.{ L2F, L2D, L2I } - (to: @unchecked) match { - case FLOAT => emit(L2F) - case DOUBLE => emit(L2D) - case _ => emit(L2I); emitT2T(INT, to) - } - - case DOUBLE => - import asm.Opcodes.{ D2L, D2F, D2I } - (to: @unchecked) match { - case FLOAT => emit(D2F) - case LONG => emit(D2L) - case _ => emit(D2I); emitT2T(INT, to) - } - } - } - } // end of genCode()'s emitT2T() - - def genPrimitive(primitive: Primitive, pos: Position) { - - import asm.Opcodes - - primitive match { - - case Negation(kind) => jcode.neg(kind) - - case Arithmetic(op, kind) => - def genArith() = { - op match { - - case ADD => jcode.add(kind) - case SUB => jcode.sub(kind) - case MUL => jcode.mul(kind) - case DIV => jcode.div(kind) - case REM => jcode.rem(kind) - - case NOT => - if(kind.isIntSizedType) { - emit(Opcodes.ICONST_M1) - emit(Opcodes.IXOR) - } else if(kind == LONG) { - jmethod.visitLdcInsn(new java.lang.Long(-1)) - jmethod.visitInsn(Opcodes.LXOR) - } else { - abort("Impossible to negate an " + kind) - } - - case _ => - abort("Unknown arithmetic primitive " + primitive) - } - } - genArith() - - // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey) - // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead. - // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed). - case Logical(op, kind) => - def genLogical() = op match { - case AND => - kind match { - case LONG => emit(Opcodes.LAND) - case INT => emit(Opcodes.IAND) - case _ => - emit(Opcodes.IAND) - if (kind != BOOL) { emitT2T(INT, kind) } - } - case OR => - kind match { - case LONG => emit(Opcodes.LOR) - case INT => emit(Opcodes.IOR) - case _ => - emit(Opcodes.IOR) - if (kind != BOOL) { emitT2T(INT, kind) } - } - case XOR => - kind match { - case LONG => emit(Opcodes.LXOR) - case INT => emit(Opcodes.IXOR) - case _ => - emit(Opcodes.IXOR) - if (kind != BOOL) { emitT2T(INT, kind) } - } - } - genLogical() - - case Shift(op, kind) => - def genShift() = op match { - case LSL => - kind match { - case LONG => emit(Opcodes.LSHL) - case INT => emit(Opcodes.ISHL) - case _ => - emit(Opcodes.ISHL) - emitT2T(INT, kind) - } - case ASR => - kind match { - case LONG => emit(Opcodes.LSHR) - case INT => emit(Opcodes.ISHR) - case _ => - emit(Opcodes.ISHR) - emitT2T(INT, kind) - } - case LSR => - kind match { - case LONG => emit(Opcodes.LUSHR) - case INT => emit(Opcodes.IUSHR) - case _ => - emit(Opcodes.IUSHR) - emitT2T(INT, kind) - } - } - genShift() - - case Comparison(op, kind) => - def genCompare() = op match { - case CMP => - (kind: @unchecked) match { - case LONG => emit(Opcodes.LCMP) - } - case CMPL => - (kind: @unchecked) match { - case FLOAT => emit(Opcodes.FCMPL) - case DOUBLE => emit(Opcodes.DCMPL) - } - case CMPG => - (kind: @unchecked) match { - case FLOAT => emit(Opcodes.FCMPG) - case DOUBLE => emit(Opcodes.DCMPL) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se6/html/Instructions2.doc3.html - - } - } - genCompare() - - case Conversion(src, dst) => - debuglog("Converting from: " + src + " to: " + dst) - emitT2T(src, dst) - - case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH) - - case StartConcat => - jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) - jmethod.visitInsn(Opcodes.DUP) - jcode.invokespecial( - StringBuilderClassName, - INSTANCE_CONSTRUCTOR_NAME, - mdesc_arglessvoid - ) - - case StringConcat(el) => - val jtype = el match { - case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT - case _ => javaType(el) - } - jcode.invokevirtual( - StringBuilderClassName, - "append", - asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*) - ) - - case EndConcat => - jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString) - - case _ => abort("Unimplemented primitive " + primitive) - } - } // end of genCode()'s genPrimitive() - - // ------------------------------------------------------------------------------------------------------------ - // Part 6 of genCode(): the executable part of genCode() starts here. - // ------------------------------------------------------------------------------------------------------------ - - genBlocks(linearization) - - jmethod.visitLabel(onePastLast) - - if(emitLines) { - for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) } - } - if(emitVars) { genLocalVariableTable() } - - } // end of BytecodeGenerator.genCode() - - - ////////////////////// local vars /////////////////////// - - def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1 - - final def indexOf(local: Local): Int = { - assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ") - local.index - } - - /** - * Compute the indexes of each local variable of the given method. - * *Does not assume the parameters come first!* - */ - def computeLocalVarsIndex(m: IMethod) { - var idx = if (m.symbol.isStaticMember) 0 else 1 - - for (l <- m.params) { - debuglog("Index value for " + l + "{" + l.## + "}: " + idx) - l.index = idx - idx += sizeOf(l.kind) - } - - for (l <- m.locals if !l.arg) { - debuglog("Index value for " + l + "{" + l.## + "}: " + idx) - l.index = idx - idx += sizeOf(l.kind) - } - } - - } // end of class JPlainBuilder - - - /** builder of mirror classes */ - class JMirrorBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JCommonBuilder(bytecodeWriter, needsOutfile) { - - private var cunit: CompilationUnit = _ - def getCurrentCUnit(): CompilationUnit = cunit - - /** Generate a mirror class for a top-level module. A mirror class is a class - * containing only static methods that forward to the corresponding method - * on the MODULE instance of the given Scala object. It will only be - * generated if there is no companion class: if there is, an attempt will - * instead be made to add the forwarder methods to the companion class. - */ - def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) { - assert(modsym.companionClass == NoSymbol, modsym) - innerClassBuffer.clear() - this.cunit = cunit - val moduleName = javaName(modsym) // + "$" - val mirrorName = moduleName.substring(0, moduleName.length() - 1) - - val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) - val mirrorClass = createJClass(flags, - mirrorName, - null /* no java-generic-signature */, - JAVA_LANG_OBJECT.getInternalName, - EMPTY_STRING_ARRAY) - - log(s"Dumping mirror class for '$mirrorName'") - - // typestate: entering mode with valid call sequences: - // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* - - if(emitSource) { - mirrorClass.visitSource("" + cunit.source, - null /* SourceDebugExtension */) - } - - val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) - mirrorClass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) - emitAnnotations(mirrorClass, modsym.annotations ++ ssa) - - // typestate: entering mode with valid call sequences: - // ( visitInnerClass | visitField | visitMethod )* visitEnd - - addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) - - addInnerClasses(modsym, mirrorClass, isMirror = true) - mirrorClass.visitEnd() - writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) - } - } // end of class JMirrorBuilder - - - /** builder of bean info classes */ - class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { - - /** - * Generate a bean info class that describes the given class. - * - * @author Ross Judson (ross.judson@soletta.com) - */ - def genBeanInfoClass(clasz: IClass) { - - // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip") - // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName") - // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription") - // val description = c.symbol getAnnotation BeanDescriptionAttr - // informProgress(description.toString) - innerClassBuffer.clear() - - val flags = mkFlags( - javaFlags(clasz.symbol), - if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - val beanInfoName = (javaName(clasz.symbol) + "BeanInfo") - val beanInfoClass = createJClass( - flags, - beanInfoName, - null, // no java-generic-signature - "scala/beans/ScalaBeanInfo", - EMPTY_STRING_ARRAY - ) - - // beanInfoClass typestate: entering mode with valid call sequences: - // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* - - beanInfoClass.visitSource( - clasz.cunit.source.toString, - null /* SourceDebugExtension */ - ) - - var fieldList = List[String]() - - for (f <- clasz.fields if f.symbol.hasGetter; - g = f.symbol.getterIn(clasz.symbol); - s = f.symbol.setterIn(clasz.symbol) - if g.isPublic && !(f.symbol.name startsWith "$") - ) { - // inserting $outer breaks the bean - fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList - } - - val methodList: List[String] = - for (m <- clasz.methods - if !m.symbol.isConstructor && - m.symbol.isPublic && - !(m.symbol.name startsWith "$") && - !m.symbol.isGetter && - !m.symbol.isSetter) - yield javaName(m.symbol) - - // beanInfoClass typestate: entering mode with valid call sequences: - // ( visitInnerClass | visitField | visitMethod )* visitEnd - - val constructor = beanInfoClass.visitMethod( - asm.Opcodes.ACC_PUBLIC, - INSTANCE_CONSTRUCTOR_NAME, - mdesc_arglessvoid, - null, // no java-generic-signature - EMPTY_STRING_ARRAY // no throwable exceptions - ) - - // constructor typestate: entering mode with valid call sequences: - // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )* - - val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING) - val conJType: asm.Type = - asm.Type.getMethodType( - asm.Type.VOID_TYPE, - Array(javaType(ClassClass), stringArrayJType, stringArrayJType): _* - ) - - def push(lst: List[String]) { - var fi = 0 - for (f <- lst) { - constructor.visitInsn(asm.Opcodes.DUP) - constructor.visitLdcInsn(new java.lang.Integer(fi)) - if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } - else { constructor.visitLdcInsn(f) } - constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE)) - fi += 1 - } - } - - // constructor typestate: entering mode with valid call sequences: - // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd - - constructor.visitCode() - - constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) - // push the class - constructor.visitLdcInsn(javaType(clasz.symbol)) - - // push the string array of field information - constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) - push(fieldList) - - // push the string array of method information - constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) - push(methodList) - - // invoke the superclass constructor, which will do the - // necessary java reflection and create Method objects. - constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor, false) - constructor.visitInsn(asm.Opcodes.RETURN) - - constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments - constructor.visitEnd() - - addInnerClasses(clasz.symbol, beanInfoClass) - beanInfoClass.visitEnd() - - writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol) - } - - } // end of class JBeanInfoBuilder - - /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for. - * In particular, IMethod.normalize() doesn't collapseJumpChains(). - * - * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them). - */ - object newNormal { - /** - * True if a block is "jump only" which is defined - * as being a block that consists only of 0 or more instructions that - * won't make it to the JVM followed by a JUMP. - */ - def isJumpOnly(b: BasicBlock): Boolean = { - val nonICode = firstNonIcodeOnlyInstructions(b) - // by definition a block has to have a jump, conditional jump, return, or throw - assert(nonICode.hasNext, "empty block") - nonICode.next.isInstanceOf[JUMP] - } - - /** - * Returns the list of instructions in a block that follow all ICode only instructions, - * where an ICode only instruction is one that won't make it to the JVM - */ - private def firstNonIcodeOnlyInstructions(b: BasicBlock): Iterator[Instruction] = { - def isICodeOnlyInstruction(i: Instruction) = i match { - case LOAD_EXCEPTION(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) => true - case _ => false - } - b.iterator dropWhile isICodeOnlyInstruction - } - - /** - * Returns the target of a block that is "jump only" which is defined - * as being a block that consists only of 0 or more instructions that - * won't make it to the JVM followed by a JUMP. - * - * @param b The basic block to examine - * @return Some(target) if b is a "jump only" block or None if it's not - */ - private def getJumpOnlyTarget(b: BasicBlock): Option[BasicBlock] = { - val nonICode = firstNonIcodeOnlyInstructions(b) - // by definition a block has to have a jump, conditional jump, return, or throw - assert(nonICode.nonEmpty, "empty block") - nonICode.next match { - case JUMP(whereto) => - assert(!nonICode.hasNext, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)") - Some(whereto) - case _ => None - } - } - - /** - * Collapse a chain of "jump-only" blocks such as: - * - * JUMP b1; - * b1: JUMP b2; - * b2: JUMP ... etc. - * - * by re-wiring predecessors to target directly the "final destination". - * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed. - - * Returns true if any replacement was made, false otherwise. - * - * In more detail: - * Starting at each of the entry points (m.startBlock, the start block of each exception handler) - * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D. - * The blocks thus skipped become eligible to removed by the reachability analyzer - * - * Rationale for this normalization: - * test/files/run/private-inline.scala after -optimize is chock full of - * BasicBlocks containing just JUMP(whereto), where no exception handler straddles them. - * They should be collapsed by IMethod.normalize() but aren't. - * That was fine in FJBG times when by the time the exception table was emitted, - * it already contained "anchored" labels (ie instruction offsets were known) - * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question) - * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range" - * Now that visitTryCatchBlock() must be called before Labels are resolved, - * renders the BasicBlocks described above (to recap, consisting of just a JUMP) unreachable. - */ - private def collapseJumpOnlyBlocks(m: IMethod) { - assert(m.hasCode, "code-less method") - - def rephraseGotos(detour: mutable.Map[BasicBlock, BasicBlock]) { - def lookup(b: BasicBlock) = detour.getOrElse(b, b) - - m.code.startBlock = lookup(m.code.startBlock) - - for(eh <- m.exh) - eh.setStartBlock(lookup(eh.startBlock)) - - for (b <- m.blocks) { - def replaceLastInstruction(i: Instruction) = { - if (b.lastInstruction != i) { - val idxLast = b.size - 1 - debuglog(s"In block $b, replacing last instruction ${b.lastInstruction} with ${i}") - b.replaceInstruction(idxLast, i) - } - } - - b.lastInstruction match { - case JUMP(whereto) => - replaceLastInstruction(JUMP(lookup(whereto))) - case CJUMP(succ, fail, cond, kind) => - replaceLastInstruction(CJUMP(lookup(succ), lookup(fail), cond, kind)) - case CZJUMP(succ, fail, cond, kind) => - replaceLastInstruction(CZJUMP(lookup(succ), lookup(fail), cond, kind)) - case SWITCH(tags, labels) => - val newLabels = (labels map lookup) - replaceLastInstruction(SWITCH(tags, newLabels)) - case _ => () - } - } - } - - /* - * Computes a mapping from jump only block to its - * final destination which is either a non-jump-only - * block or, if it's in a jump-only block cycle, is - * itself - */ - def computeDetour: mutable.Map[BasicBlock, BasicBlock] = { - // fetch the jump only blocks and their immediate destinations - val pairs = for { - block <- m.blocks.toIterator - target <- getJumpOnlyTarget(block) - } yield(block, target) - - // mapping from a jump-only block to our current knowledge of its - // final destination. Initially it's just jump block to immediate jump - // target - val detour = mutable.Map[BasicBlock, BasicBlock](pairs.toSeq:_*) - - // for each jump-only block find its final destination - // taking advantage of the destinations we found for previous - // blocks - for (key <- detour.keySet) { - // we use the Robert Floyd's classic Tortoise and Hare algorithm - @tailrec - def findDestination(tortoise: BasicBlock, hare: BasicBlock): BasicBlock = { - if (tortoise == hare) - // cycle detected, map key to key - key - else if (detour contains hare) { - // advance hare once - val hare1 = detour(hare) - // make sure we can advance hare a second time - if (detour contains hare1) - // advance tortoise once and hare a second time - findDestination(detour(tortoise), detour(hare1)) - else - // hare1 is not in the map so it's not a jump-only block, it's the destination - hare1 - } else - // hare is not in the map so it's not a jump-only block, it's the destination - hare - } - // update the mapping for key based on its final destination - detour(key) = findDestination(key, detour(key)) - } - detour - } - - val detour = computeDetour - rephraseGotos(detour) - - if (settings.debug) { - val (remappings, cycles) = detour partition {case (source, target) => source != target} - for ((source, target) <- remappings) { - debuglog(s"Will elide jump only block $source because it can be jumped around to get to $target.") - if (m.startBlock == source) devWarning("startBlock should have been re-wired by now") - } - val sources = remappings.keySet - val targets = remappings.values.toSet - val intersection = sources intersect targets - - if (intersection.nonEmpty) devWarning(s"contradiction: we seem to have some source and target overlap in blocks ${intersection.mkString}. Map was ${detour.mkString}") - - for ((source, _) <- cycles) { - debuglog(s"Block $source is in a do-nothing infinite loop. Did the user write 'while(true){}'?") - } - } - } - - /** - * Removes all blocks that are unreachable in a method using a standard reachability analysis. - */ - def elimUnreachableBlocks(m: IMethod) { - assert(m.hasCode, "code-less method") - - // assume nothing is reachable until we prove it can be reached - val reachable = mutable.Set[BasicBlock]() - - // the set of blocks that we know are reachable but have - // yet to be marked reachable, initially only the start block - val worklist = mutable.Set(m.startBlock) - - while (worklist.nonEmpty) { - val block = worklist.head - worklist remove block - // we know that one is reachable - reachable add block - // so are its successors, so go back around and add the ones we still - // think are unreachable - worklist ++= (block.successors filterNot reachable) - } - - // exception handlers need to be told not to cover unreachable blocks - // and exception handlers that no longer cover any blocks need to be - // removed entirely - val unusedExceptionHandlers = mutable.Set[ExceptionHandler]() - for (exh <- m.exh) { - exh.covered = exh.covered filter reachable - if (exh.covered.isEmpty) { - unusedExceptionHandlers += exh - } - } - - // remove the unused exception handler references - if (settings.debug) - for (exh <- unusedExceptionHandlers) debuglog(s"eliding exception handler $exh because it does not cover any reachable blocks") - m.exh = m.exh filterNot unusedExceptionHandlers - - // everything not in the reachable set is unreachable, unused, and unloved. buh bye - for (b <- m.blocks filterNot reachable) { - debuglog(s"eliding block $b because it is unreachable") - m.code removeBlock b - } - } - - def normalize(m: IMethod) { - if(!m.hasCode) { return } - collapseJumpOnlyBlocks(m) - if (settings.optimise) - elimUnreachableBlocks(m) - icodes checkValid m - } - - } - - // @M don't generate java generics sigs for (members of) implementation - // classes, as they are monomorphic (TODO: ok?) - private def needsGenericSignature(sym: Symbol) = !( - // PP: This condition used to include sym.hasExpandedName, but this leads - // to the total loss of generic information if a private member is - // accessed from a closure: both the field and the accessor were generated - // without it. This is particularly bad because the availability of - // generic information could disappear as a consequence of a seemingly - // unrelated change. - settings.Ynogenericsig - || sym.isArtifact - || sym.isLiftedMethod - || sym.isBridge - || (sym.ownerChain exists (_.isImplClass)) - ) - - final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol, unit: CompilationUnit): String = { - if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 - else { - // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. - // By rights, it should use the signature as-seen-from the module class, and add suitable - // primitive and value-class boxing/unboxing. - // But for now, just like we did in mixin, we just avoid writing a wrong generic signature - // (one that doesn't erase to the actual signature). See run/t3452b for a test case. - val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) - val erasedMemberType = erasure.erasure(sym)(memberTpe) - if (erasedMemberType =:= sym.info) - getGenericSignature(sym, moduleClass, memberTpe, unit) - else null - } - } - - /** @return - * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). - * - otherwise the signature in question - */ - def getGenericSignature(sym: Symbol, owner: Symbol, unit: CompilationUnit): String = { - val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) - getGenericSignature(sym, owner, memberTpe, unit) - } - def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type, unit: CompilationUnit): String = { - if (!needsGenericSignature(sym)) { return null } - - val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) - if (jsOpt.isEmpty) { return null } - - val sig = jsOpt.get - log(sig) // This seems useful enough in the general case. - - def wrap(op: => Unit) = { - try { op; true } - catch { case _: Throwable => false } - } - - if (settings.Xverify) { - // Run the signature parser to catch bogus signatures. - val isValidSignature = wrap { - // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) - import scala.tools.asm.util.CheckClassAdapter - if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar - else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } - else { CheckClassAdapter checkClassSignature sig } - } - - if(!isValidSignature) { - reporter.warning(sym.pos, - """|compiler bug: created invalid generic signature for %s in %s - |signature: %s - |if this is reproducible, please report bug at https://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) - return null - } - } - - if ((settings.check containsName phaseName)) { - val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) - val bytecodeTpe = owner.thisType.memberInfo(sym) - if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { - reporter.warning(sym.pos, - """|compiler bug: created generic signature for %s in %s that does not conform to its erasure - |signature: %s - |original type: %s - |normalized type: %s - |erasure type: %s - |if this is reproducible, please report bug at http://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) - return null - } - } - - sig - } - - def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { - val ca = new Array[Char](bytes.length) - var idx = 0 - while(idx < bytes.length) { - val b: Byte = bytes(idx) - assert((b & ~0x7f) == 0) - ca(idx) = b.asInstanceOf[Char] - idx += 1 - } - - ca - } - - final def arrEncode(sb: ScalaSigBytes): Array[String] = { - var strs: List[String] = Nil - val bSeven: Array[Byte] = sb.sevenBitsMayBeZero - // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) - var prevOffset = 0 - var offset = 0 - var encLength = 0 - while(offset < bSeven.length) { - val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) - val newEncLength = encLength.toLong + deltaEncLength - if(newEncLength >= 65535) { - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - encLength = 0 - prevOffset = offset - } else { - encLength += deltaEncLength - offset += 1 - } - } - if(prevOffset < offset) { - assert(offset == bSeven.length) - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - } - assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? - strs.reverse.toArray - } - - private def strEncode(sb: ScalaSigBytes): String = { - val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) - new java.lang.String(ca) - // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) - // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) - // debug assert(enc(idx) == bvA.getByte(idx + 2)) - // debug assert(bvA.getLength == enc.size + 2) - } -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index af962c4ce0..65bd62a413 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -14,6 +14,7 @@ import scala.reflect.internal.util.Statistics import scala.tools.asm import scala.tools.asm.tree.ClassNode +import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository /* * Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk. @@ -186,7 +187,7 @@ abstract class GenBCode extends BCodeSyncAndTry { // -------------- "plain" class -------------- val pcb = new PlainClassBuilder(cunit) pcb.genPlainClass(cd) - val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null; + val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisBType.internalName, cunit) else null val plainC = pcb.cnode // -------------- bean info class, if needed -------------- @@ -221,16 +222,22 @@ abstract class GenBCode extends BCodeSyncAndTry { class Worker2 { def runGlobalOptimizations(): Unit = { import scala.collection.convert.decorateAsScala._ - if (settings.YoptBuildCallGraph) { - q2.asScala foreach { - case Item2(_, _, plain, _, _) => - // skip mirror / bean: wd don't inline into tem, and they are not used in the plain class - if (plain != null) callGraph.addClass(plain) - } + + // add classes to the bytecode repo before building the call graph: the latter needs to + // look up classes and methods in the code repo. + if (settings.YoptAddToBytecodeRepository) q2.asScala foreach { + case Item2(_, mirror, plain, bean, _) => + if (mirror != null) byteCodeRepository.add(mirror, ByteCodeRepository.CompilationUnit) + if (plain != null) byteCodeRepository.add(plain, ByteCodeRepository.CompilationUnit) + if (bean != null) byteCodeRepository.add(bean, ByteCodeRepository.CompilationUnit) + } + if (settings.YoptBuildCallGraph) q2.asScala foreach { item => + // skip call graph for mirror / bean: wd don't inline into tem, and they are not used in the plain class + if (item.plain != null) callGraph.addClass(item.plain) } if (settings.YoptInlinerEnabled) bTypes.inliner.runInliner() - if (settings.YoptClosureElimination) + if (settings.YoptClosureInvocations) closureOptimizer.rewriteClosureApplyInvocations() } @@ -238,6 +245,11 @@ abstract class GenBCode extends BCodeSyncAndTry { BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode)) } + def setInnerClasses(classNode: ClassNode): Unit = if (classNode != null) { + classNode.innerClasses.clear() + addInnerClasses(classNode, bTypes.backendUtils.collectNestedClasses(classNode)) + } + def run() { runGlobalOptimizations() @@ -250,8 +262,14 @@ abstract class GenBCode extends BCodeSyncAndTry { else { try { localOptimizations(item.plain) + setInnerClasses(item.plain) + setInnerClasses(item.mirror) + setInnerClasses(item.bean) addToQ3(item) } catch { + case e: java.lang.RuntimeException if e.getMessage != null && (e.getMessage contains "too large!") => + reporter.error(NoPosition, + s"Could not write class ${item.plain.name} because it exceeds JVM code size limits. ${e.getMessage}") case ex: Throwable => ex.printStackTrace() error(s"Error while emitting ${item.plain.name}\n${ex.getMessage}") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala index 7bbe1e2a49..086946e4e3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala @@ -3,17 +3,22 @@ package backend.jvm package analysis import scala.annotation.switch -import scala.collection.{mutable, immutable} +import scala.collection.mutable import scala.tools.asm.Opcodes import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis.{Analyzer, Value, Frame, Interpreter} import opt.BytecodeUtils._ +import AliasSet.SmallBitSet -object AliasingFrame { - private var _idCounter: Long = 0l - private def nextId = { _idCounter += 1; _idCounter } -} - +/** + * A subclass of Frame that tracks aliasing of values stored in local variables and on the stack. + * + * Note: an analysis tracking aliases is roughly 5x slower than a usual analysis (assuming a simple + * value domain with a fast merge function). For example, nullness analysis is roughly 5x slower + * than a BasicValue analysis. + * + * See the doc of package object `analysis` for some notes on the performance of alias analysis. + */ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLocals, nStack) { import Opcodes._ @@ -23,63 +28,80 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc init(src) } - /** - * For each slot (entry in the `values` array of the frame), an id that uniquely represents - * the object stored in it. If two values have the same id, they are aliases of the same - * object. - */ - private val aliasIds: Array[Long] = Array.fill(nLocals + nStack)(AliasingFrame.nextId) + override def toString: String = super.toString + " - " + aliases.toList.filter(s => s != null && s.size > 1).map(_.toString).distinct.mkString(",") /** - * The object alias id of for a value index. - */ - def aliasId(entry: Int) = aliasIds(entry) - - /** - * Returns the indices of the values array which are aliases of the object `id`. + * For every value the set of values that are aliases of it. + * + * Invariants: + * - If `aliases(i) == null` then i has no aliases. This is equivalent to having + * `aliases(i) == SingletonSet(i)`. + * - If `aliases(i) != null` then `aliases(i) contains i`. + * - If `aliases(i) contains j` then `aliases(i) eq aliases(j)`, i.e., they are references to the + * same (mutable) AliasSet. */ - def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.iterator.filter(i => aliasId(i) == id) + val aliases: Array[AliasSet] = new Array[AliasSet](getLocals + getMaxStackSize) /** * The set of aliased values for a given entry in the `values` array. */ - def aliasesOf(entry: Int): Set[Int] = valuesWithAliasId(aliasIds(entry)) + def aliasesOf(entry: Int): AliasSet = { + if (aliases(entry) != null) aliases(entry) + else { + val init = new AliasSet(new AliasSet.SmallBitSet(entry, -1, -1, -1), 1) + aliases(entry) = init + init + } + } /** - * Define a new alias. For example, given - * var a = this // this, a have the same aliasId - * then an assignment + * Define a new alias. For example, an assignment * b = a - * will set the same the aliasId for `b`. + * adds b to the set of aliases of a. */ private def newAlias(assignee: Int, source: Int): Unit = { - aliasIds(assignee) = aliasIds(source) + removeAlias(assignee) + val sourceAliases = aliasesOf(source) + sourceAliases += assignee + aliases(assignee) = sourceAliases } /** - * An assignment + * Remove an alias. For example, an assignment * a = someUnknownValue() - * sets a fresh alias id for `a`. - * A stack value is also removed from its alias set when being consumed. + * removes a from its former alias set. + * As another example, stack values are removed from their alias sets when being consumed. */ private def removeAlias(assignee: Int): Unit = { - aliasIds(assignee) = AliasingFrame.nextId + if (aliases(assignee) != null) { + aliases(assignee) -= assignee + aliases(assignee) = null + } + } + + /** + * Define the alias set for a given value. + */ + private def setAliasSet(assignee: Int, set: AliasSet): Unit = { + if (aliases(assignee) != null) { + aliases(assignee) -= assignee + } + aliases(assignee) = set } override def execute(insn: AbstractInsnNode, interpreter: Interpreter[V]): Unit = { - // Make the extendsion methods easier to use (otherwise we have to repeat `this`.stackTop) + // Make the extension methods easier to use (otherwise we have to repeat `this`.stackTop) def stackTop: Int = this.stackTop def peekStack(n: Int): V = this.peekStack(n) - // the val pattern `val (p, c) = f` still allocates a tuple (https://github.com/scala-opt/scala/issues/28) - val prodCons = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc - val consumed = prodCons._1 - val produced = prodCons._2 + val prodCons = InstructionStackEffect.forAsmAnalysis(insn, this) // needs to be called before super.execute, see its doc + val consumed = InstructionStackEffect.cons(prodCons) + val produced = InstructionStackEffect.prod(prodCons) super.execute(insn, interpreter) (insn.getOpcode: @switch) match { - case ALOAD => + case ILOAD | LLOAD | FLOAD | DLOAD | ALOAD => newAlias(assignee = stackTop, source = insn.asInstanceOf[VarInsnNode].`var`) case DUP => @@ -166,31 +188,54 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc } case SWAP => + // could be written more elegantly with higher-order combinators, but thinking of performance val top = stackTop - val idTop = aliasIds(top) - aliasIds(top) = aliasIds(top - 1) - aliasIds(top - 1) = idTop - case opcode => - if (opcode == ASTORE) { - // Not a separate case because we need to remove the consumed stack value from alias sets after. - val stackTopBefore = stackTop - produced + consumed - val local = insn.asInstanceOf[VarInsnNode].`var` - newAlias(assignee = local, source = stackTopBefore) - // if the value written is size 2, it overwrites the subsequent slot, which is then no - // longer an alias of anything. see the corresponding case in `Frame.execute`. - if (getLocal(local).getSize == 2) - removeAlias(local + 1) - - // if the value at the preceding index is size 2, it is no longer valid, so we remove its - // aliasing. see corresponding case in `Frame.execute` - if (local > 0) { - val precedingValue = getLocal(local - 1) - if (precedingValue != null && precedingValue.getSize == 2) - removeAlias(local - 1) + def moveNextToTop(): Unit = { + val nextAliases = aliases(top - 1) + aliases(top) = nextAliases + nextAliases -= (top - 1) + nextAliases += top + } + + if (aliases(top) != null) { + val topAliases = aliases(top) + if (aliases(top - 1) != null) moveNextToTop() + else aliases(top) = null + // move top to next + aliases(top - 1) = topAliases + topAliases -= top + topAliases += (top - 1) + } else { + if (aliases(top - 1) != null) { + moveNextToTop() + aliases(top - 1) = null } } + case opcode => + (opcode: @switch) match { + case ISTORE | LSTORE | FSTORE | DSTORE | ASTORE => + // not a separate case: we re-use the code below that removes the consumed stack value from alias sets + val stackTopBefore = stackTop - produced + consumed + val local = insn.asInstanceOf[VarInsnNode].`var` + newAlias(assignee = local, source = stackTopBefore) + // if the value written is size 2, it overwrites the subsequent slot, which is then no + // longer an alias of anything. see the corresponding case in `Frame.execute`. + if (getLocal(local).getSize == 2) + removeAlias(local + 1) + + // if the value at the preceding index is size 2, it is no longer valid, so we remove its + // aliasing. see corresponding case in `Frame.execute` + if (local > 0) { + val precedingValue = getLocal(local - 1) + if (precedingValue != null && precedingValue.getSize == 2) + removeAlias(local - 1) + } + + case _ => + } + // Remove consumed stack values from aliasing sets. // Example: iadd // - before: local1, local2, stack1, consumed1, consumed2 @@ -198,10 +243,22 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc val firstConsumed = stackTop - produced + 1 // firstConsumed = 3 for (i <- 0 until consumed) removeAlias(firstConsumed + i) // remove aliases for 3 and 4 + } + } - // We don't need to set the aliases ids for the produced values: the aliasIds array already - // contains fresh ids for non-used stack values (ensured by removeAlias). + /** + * When entering an exception handler, all values are dropped from the stack (and the exception + * value is pushed). The ASM analyzer invokes `firstHandlerInstructionFrame.clearStack()`. To + * ensure consistent aliasing sets, we need to remove the dropped values from aliasing sets. + */ + override def clearStack(): Unit = { + var i = getLocals + val end = i + getStackSize + while (i < end) { + removeAlias(i) + i += 1 } + super.clearStack() } /** @@ -217,30 +274,131 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc * x = a * y = b // (x, a) and (y, b) * } - * [...] // (x, a) + * [...] // (x, a) -- merge of ((x, y, a)) and ((x, a), (y, b)) */ override def merge(other: Frame[_ <: V], interpreter: Interpreter[V]): Boolean = { + // merge is the main performance hot spot of a data flow analysis. + + // in nullness analysis, super.merge (which actually merges the nullness values) takes 20% of + // the overall analysis time. val valuesChanged = super.merge(other, interpreter) + + // in nullness analysis, merging the alias sets takes ~55% of the analysis time. therefore, this + // code has been heavily optimized. most of the time is spent in the `hasNext` method of the + // andNotIterator, see its comment. + var aliasesChanged = false val aliasingOther = other.asInstanceOf[AliasingFrame[_]] - for (i <- aliasIds.indices) { - val thisAliases = aliasesOf(i) - val thisNotOther = thisAliases diff (thisAliases intersect aliasingOther.aliasesOf(i)) - if (thisNotOther.nonEmpty) { - aliasesChanged = true - thisNotOther foreach removeAlias + + val numValues = getLocals + getStackSize + // assume (a, b) are aliases both in this frame, and the other frame. when merging the alias set + // for a, we already see that a and b will be aliases in the final result. so we can skip over + // merging the alias set for b. in this case, while merging the sets for a, knownOk(b) will be + // set to `true`. + val knownOk = new Array[Boolean](numValues) + var i = 0 + while (i < numValues) { + if (!knownOk(i)) { + val thisAliases = this.aliases(i) + val otherAliases = aliasingOther.aliases(i) + if (thisAliases != null) { + if (otherAliases == null) { + if (thisAliases.size > 1) { + aliasesChanged = true + removeAlias(i) + } + } else { + // The iterator yields elements that are in `thisAliases` but not in `otherAliases`. + // As a side-effect, for every index `i` that is in both alias sets, the iterator sets + // `knownOk(i) = true`: the alias sets for these values don't need to be merged again. + val thisNotOtherIt = AliasSet.andNotIterator(thisAliases, otherAliases, knownOk) + if (thisNotOtherIt.hasNext) { + aliasesChanged = true + val newSet = AliasSet.empty + while (thisNotOtherIt.hasNext) { + val next = thisNotOtherIt.next() + newSet += next + setAliasSet(next, newSet) + } + } + } + } } + i += 1 } + valuesChanged || aliasesChanged } + private def min(s: SmallBitSet) = { + var r = s.a + if ( s.b < r) r = s.b + if (s.c != -1 && s.c < r) r = s.c + if (s.d != -1 && s.d < r) r = s.d + r + } + override def init(src: Frame[_ <: V]): Frame[V] = { - super.init(src) - compat.Platform.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliasIds, 0, aliasIds, 0, aliasIds.length) + super.init(src) // very quick (just an arraycopy) + System.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliases, 0, aliases, 0, aliases.length) // also quick + + val newSets = mutable.HashMap.empty[AliasSet, AliasSet] + + // the rest of this method (cloning alias sets) is the second performance˙hotspot (next to + // AliasingFrame.merge). for nullness, it takes ~20% of the analysis time. + // the difficulty here is that we have to clone the alias sets correctly. if two values a, b are + // aliases, then aliases(a) eq aliases(b). we need to make sure to use the same clone for the + // two values. + + var i = 0 + while (i < aliases.length) { + val set = aliases(i) + if (set != null) { + // size cannot be 0 - alias sets are always at least singletons. + // for sets of size 1-4, don't use the `newSets` map - lookup / update is slow + if (set.size == 1) { + aliases(i) = null + } else if (set.size <= 4) { + val small = set.set.asInstanceOf[AliasSet.SmallBitSet] + val firstOfSet = i == min(small) + if (firstOfSet) { + val newSet = set.clone() + aliases(small.a) = newSet + aliases(small.b) = newSet + if (small.c != -1) aliases(small.c) = newSet + if (small.d != -1) aliases(small.d) = newSet + } + } else { + // the actual hot spot is the hash map operations here: this is where almost all of the 20% + // mentioned above is spent. + // i also benchmarked an alternative implementation: keep an array of booleans for indexes + // that already contain the cloned set. iterate through all elements of the cloned set and + // assign the cloned set. this approach is 50% slower than using a hash map. + if (newSets contains set) aliases(i) = newSets(set) + else { + val newSet = set.clone() + newSets(set) = newSet + aliases(i) = newSet + } + } + } + i += 1 + } this } } +object AliasingFrame { +// val start1 = AliasingFrame.timer1.start() +// AliasingFrame.timer1.stop(start1) + import scala.reflect.internal.util.Statistics._ + val timer1 = newTimer("t1", "jvm") + val timer2 = newTimer("t2", "jvm") + val timer3 = newTimer("t3", "jvm") + val timers = List(timer1, timer2, timer3) + def reset(): Unit = for (t <- timers) { t.nanos = 0; t.timings = 0 } +} + /** * An analyzer that uses AliasingFrames instead of bare Frames. This can be used when an analysis * needs to track aliases, but doesn't require a more specific Frame subclass. @@ -249,3 +407,269 @@ class AliasingAnalyzer[V <: Value](interpreter: Interpreter[V]) extends Analyzer override def newFrame(nLocals: Int, nStack: Int): AliasingFrame[V] = new AliasingFrame(nLocals, nStack) override def newFrame(src: Frame[_ <: V]): AliasingFrame[V] = new AliasingFrame(src) } + +/** + * An iterator over Int (required to prevent boxing the result of next). + */ +abstract class IntIterator extends Iterator[Int] { + def hasNext: Boolean + def next(): Int +} + +/** + * An efficient mutable bit set. + * + * @param set Either a SmallBitSet or an Array[Long] + * @param size The size of the set, useful for performance of certain operations + */ +class AliasSet(var set: Object /*SmallBitSet | Array[Long]*/, var size: Int) { + import AliasSet._ + + override def toString: String = iterator.toSet.mkString("<", ",", ">") + + /** + * An iterator for the elements of this bit set. Note that only one iterator can be used at a + * time. Also make sure not to change the underlying AliasSet during iteration. + */ + def iterator: IntIterator = andNotIterator(this, empty, null) + + def +=(value: Int): Unit = this.set match { + case s: SmallBitSet => (size: @switch) match { + case 0 => s.a = value; size = 1 + case 1 => if (value != s.a) { s.b = value; size = 2 } + case 2 => if (value != s.a && value != s.b) { s.c = value; size = 3 } + case 3 => if (value != s.a && value != s.b && value != s.c) { s.d = value; size = 4 } + case 4 => + if (value != s.a && value != s.b && value != s.c && value != s.d) { + this.set = bsEmpty + this.size = 0 + bsAdd(this, s.a) + bsAdd(this, s.b) + bsAdd(this, s.c) + bsAdd(this, s.d) + bsAdd(this, value) + } + } + case bits: Array[Long] => + bsAdd(this, value) + } + + def -=(value: Int): Unit = this.set match { + case s: SmallBitSet => (size: @switch) match { + case 0 => + case 1 => + if (value == s.a) { s.a = -1; size = 0 } + case 2 => + if (value == s.a) { s.a = s.b; s.b = -1; size = 1 } + else if (value == s.b) { s.b = -1; size = 1 } + case 3 => + if (value == s.a) { s.a = s.b; s.b = s.c; s.c = -1; size = 2 } + else if (value == s.b) { s.b = s.c; s.c = -1; size = 2 } + else if (value == s.c) { s.c = -1; size = 2 } + case 4 => + if (value == s.a) { s.a = s.b; s.b = s.c; s.c = s.d; s.d = -1; size = 3 } + else if (value == s.b) { s.b = s.c; s.c = s.d; s.d = -1; size = 3 } + else if (value == s.c) { s.c = s.d; s.d = -1; size = 3 } + else if (value == s.d) { s.d = -1; size = 3 } + } + case bits: Array[Long] => + bsRemove(this, value) + if (this.size == 4) + this.set = bsToSmall(this.set.asInstanceOf[Array[Long]]) + } + + override def clone(): AliasSet = { + val resSet = this.set match { + case s: SmallBitSet => new SmallBitSet(s.a, s.b, s.c, s.d) + case bits: Array[Long] => bits.clone() + } + new AliasSet(resSet, this.size) + } +} + +object AliasSet { + def empty = new AliasSet(new SmallBitSet(-1, -1, -1, -1), 0) + + final class SmallBitSet(var a: Int, var b: Int, var c: Int, var d: Int) { + override def toString = s"($a, $b, $c, $d)" + } + + def bsEmpty: Array[Long] = new Array[Long](1) + + private def bsEnsureCapacity(set: Array[Long], index: Int): Array[Long] = { + if (index < set.length) set + else { + var newLength = set.length + while (index >= newLength) newLength *= 2 + val newSet = new Array[Long](newLength) + Array.copy(set, 0, newSet, 0, set.length) + newSet + } + } + + def bsAdd(set: AliasSet, bit: Int): Unit = { + val bits = set.set.asInstanceOf[Array[Long]] + val index = bit >> 6 + val resSet = bsEnsureCapacity(bits, index) + val before = resSet(index) + val result = before | (1l << bit) + if (result != before) { + resSet(index) = result + set.set = resSet + set.size += 1 + } + } + + def bsRemove(set: AliasSet, bit: Int): Unit = { + val bits = set.set.asInstanceOf[Array[Long]] + val index = bit >> 6 + if (index < bits.length) { + val before = bits(index) + val result = before & ~(1l << bit) + if (result != before) { + bits(index) = result + set.size -= 1 + } + } + } + + def bsContains(set: Array[Long], bit: Int): Boolean = { + val index = bit >> 6 + bit >= 0 && index < set.length && (set(index) & (1L << bit)) != 0L + } + +// var sizesHist: Array[Int] = new Array[Int](1000) + + /** + * Convert a bit array to a SmallBitSet. Requires the bit array to contain exactly four bits. + */ + def bsToSmall(bits: Array[Long]): SmallBitSet = { + var a = -1 + var b = -1 + var c = -1 + var i = 0 + val end = bits.length * 64 + while (i < end) { + if (bsContains(bits, i)) { + if (a == -1) a = i + else if (b == -1) b = i + else if (c == -1) c = i + else return new SmallBitSet(a, b, c, i) + } + i += 1 + } + null + } + + /** + * An iterator that yields the elements that are in one bit set and not in another (&~). + */ + private class AndNotIt(setA: AliasSet, setB: AliasSet, thisAndOther: Array[Boolean]) extends IntIterator { + // values in the first bit set + private var a, b, c, d = -1 + private var xs: Array[Long] = null + + // values in the second bit set + private var notA, notB, notC, notD = -1 + private var notXs: Array[Long] = null + + // holds the next value of `x`, `y` or `z` that should be returned. assigned in hasNext + private var abcdNext = -1 + + // counts through elements in the `xs` bit set + private var i = 0 + // true if the current value of `i` should be returned by this iterator + private var iValid = false + + setA.set match { + case s: SmallBitSet => a = s.a; b = s.b; c = s.c; d = s.d + case bits: Array[Long] => xs = bits + } + + setB.set match { + case s: SmallBitSet => notA = s.a; notB = s.b; notC = s.c; notD = s.d + case bits: Array[Long] => notXs = bits + } + + // for each value that exists both in this AND (&) the other bit, `thisAndOther` is set to true. + // hacky side-effect, used for performance of AliasingFrame.merge. + private def setThisAndOther(x: Int) = if (thisAndOther != null) thisAndOther(x) = true + + private def checkABCD(x: Int, num: Int): Boolean = { + // assert(x == a && num == 1 || x == b && num == 2 || ...) + x != -1 && { + val otherHasA = x == notA || x == notB || x == notC || x == notD || (notXs != null && bsContains(notXs, x)) + if (otherHasA) setThisAndOther(x) + else abcdNext = x + (num: @switch) match { + case 1 => a = -1 + case 2 => b = -1 + case 3 => c = -1 + case 4 => d = -1 + } + !otherHasA + } + } + + // main performance hot spot + private def checkXs = { + (xs != null) && { + val end = xs.length * 64 + + while (i < end && { + val index = i >> 6 + if (xs(index) == 0l) { // boom. for nullness, this saves 35% of the overall analysis time. + i = ((index + 1) << 6) - 1 // -1 required because i is incremented in the loop body + true + } else { + val mask = 1l << i + // if (mask > xs(index)) we could also advance i to the next value, but that didn't pay off in benchmarks + val thisHasI = (xs(index) & mask) != 0l + !thisHasI || { + val otherHasI = i == notA || i == notB || i == notC || i == notD || (notXs != null && index < notXs.length && (notXs(index) & mask) != 0l) + if (otherHasI) setThisAndOther(i) + otherHasI + } + } + }) i += 1 + + iValid = i < end + iValid + } + } + + // this is the main hot spot of alias analysis. for nullness, 38% of the overall analysis time + // is spent here. within hasNext, almost the entire time is spent in `checkXs`. + // + def hasNext: Boolean = iValid || abcdNext != -1 || checkABCD(a, 1) || checkABCD(b, 2) || checkABCD(c, 3) || checkABCD(d, 4) || checkXs + + def next(): Int = { + if (hasNext) { + if (abcdNext != -1) { + val r = abcdNext; abcdNext = -1; r + } else { + val r = i; i += 1; iValid = false; r + } + } else Iterator.empty.next() + } + } + +// The number of bits in a bit array. Useful for debugging. +// def bsSize(bits: Array[Long]) = { +// var r = 0 +// var i = 0 +// while (i < bits.length) { +// r += java.lang.Long.bitCount(bits(i)) +// i += 1 +// } +// r +// } + + /** + * An iterator returning the elements in a that are not also in b (a &~ b). + * + * If `thisAndOther` is non-null, the iterator sets thisAndOther(i) to true for every value that + * is both in a and b (&). + */ + def andNotIterator(a: AliasSet, b: AliasSet, thisAndOther: Array[Boolean]): IntIterator = new AndNotIt(a, b, thisAndOther) +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala new file mode 100644 index 0000000000..f1facce173 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -0,0 +1,509 @@ +package scala.tools.nsc +package backend.jvm +package analysis + +import scala.annotation.switch +import scala.tools.asm.{Handle, Type} +import scala.tools.asm.Opcodes._ +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis.{Frame, BasicInterpreter, Analyzer, Value} +import GenBCode._ +import scala.tools.nsc.backend.jvm.BTypes._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ +import java.lang.invoke.LambdaMetafactory +import scala.collection.mutable +import scala.collection.convert.decorateAsJava._ +import scala.collection.convert.decorateAsScala._ + +/** + * This component hosts tools and utilities used in the backend that require access to a `BTypes` + * instance. + * + * One example is the AsmAnalyzer class, which runs `computeMaxLocalsMaxStack` on the methodNode to + * be analyzed. This method in turn lives inside the BTypes assembly because it queries the per-run + * cache `maxLocalsMaxStackComputed` defined in there. + */ +class BackendUtils[BT <: BTypes](val btypes: BT) { + import btypes._ + import btypes.coreBTypes._ + import callGraph.ClosureInstantiation + + /** + * A wrapper to make ASM's Analyzer a bit easier to use. + */ + class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) { + computeMaxLocalsMaxStack(methodNode) + analyzer.analyze(classInternalName, methodNode) + def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) + } + + /** + * See the doc comment on package object `analysis` for a discussion on performance. + */ + object AsmAnalyzer { + // jvm limit is 65535 for both number of instructions and number of locals + + private def size(method: MethodNode) = method.instructions.size.toLong * method.maxLocals * method.maxLocals + + // with the limits below, analysis should not take more than one second + + private val nullnessSizeLimit = 5000l * 600l * 600l // 5000 insns, 600 locals + private val basicValueSizeLimit = 9000l * 1000l * 1000l + private val sourceValueSizeLimit = 8000l * 950l * 950l + + def sizeOKForAliasing(method: MethodNode): Boolean = size(method) < nullnessSizeLimit + def sizeOKForNullness(method: MethodNode): Boolean = size(method) < nullnessSizeLimit + def sizeOKForBasicValue(method: MethodNode): Boolean = size(method) < basicValueSizeLimit + def sizeOKForSourceValue(method: MethodNode): Boolean = size(method) < sourceValueSizeLimit + } + + class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl + + class NonLubbingTypeFlowAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new NonLubbingTypeFlowInterpreter)) + + /** + * Add: + * private static Object $deserializeLambda$(SerializedLambda l) { + * return indy[scala.runtime.LambdaDeserialize.bootstrap](l) + * } + * + * We use invokedynamic here to enable caching within the deserializer without needing to + * host a static field in the enclosing class. This allows us to add this method to interfaces + * that define lambdas in default methods. + */ + def addLambdaDeserialize(classNode: ClassNode): Unit = { + val cw = classNode + + // Make sure to reference the ClassBTypes of all types that are used in the code generated + // here (e.g. java/util/Map) are initialized. Initializing a ClassBType adds it to the + // `classBTypeFromInternalName` map. When writing the classfile, the asm ClassWriter computes + // stack map frames and invokes the `getCommonSuperClass` method. This method expects all + // ClassBTypes mentioned in the source code to exist in the map. + + val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor + val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor + + { + val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null) + mv.visitCode() + mv.visitVarInsn(ALOAD, 0) + mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle) + mv.visitInsn(ARETURN) + mv.visitEnd() + } + } + + /** + * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to + * the `labelMap`. Returns the new instruction list and a map from old to new instructions, and + * a boolean indicating if the instruction list contains an instantiation of a serializable SAM + * type. + */ + def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], Boolean) = { + val javaLabelMap = labelMap.asJava + val result = new InsnList + var map = Map.empty[AbstractInsnNode, AbstractInsnNode] + var hasSerializableClosureInstantiation = false + for (ins <- methodNode.instructions.iterator.asScala) { + if (!hasSerializableClosureInstantiation) ins match { + case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => indy.bsmArgs match { + case Array(_, _, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 => + hasSerializableClosureInstantiation = true + case _ => + } + case _ => + } + val cloned = ins.clone(javaLabelMap) + result add cloned + map += ((ins, cloned)) + } + (result, map, hasSerializableClosureInstantiation) + } + + def getBoxedUnit: FieldInsnNode = new FieldInsnNode(GETSTATIC, srBoxedUnitRef.internalName, "UNIT", srBoxedUnitRef.descriptor) + + private val anonfunAdaptedName = """.*\$anonfun\$\d+\$adapted""".r + def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = { + isBuiltinFunctionType(Type.getReturnType(closureInit.lambdaMetaFactoryCall.indy.desc).getInternalName) && + anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches + } + + private def primitiveAsmTypeToBType(primitiveType: Type): PrimitiveBType = (primitiveType.getSort: @switch) match { + case Type.BOOLEAN => BOOL + case Type.BYTE => BYTE + case Type.CHAR => CHAR + case Type.SHORT => SHORT + case Type.INT => INT + case Type.LONG => LONG + case Type.FLOAT => FLOAT + case Type.DOUBLE => DOUBLE + case _ => null + } + + def isScalaBox(insn: MethodInsnNode): Boolean = { + insn.owner == srBoxesRunTimeRef.internalName && { + val args = Type.getArgumentTypes(insn.desc) + args.length == 1 && (srBoxesRuntimeBoxToMethods.get(primitiveAsmTypeToBType(args(0))) match { + case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc + case _ => false + }) + } + } + + def getScalaBox(primitiveType: Type): MethodInsnNode = { + val bType = primitiveAsmTypeToBType(primitiveType) + val MethodNameAndType(name, methodBType) = srBoxesRuntimeBoxToMethods(bType) + new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false) + } + + def isScalaUnbox(insn: MethodInsnNode): Boolean = { + insn.owner == srBoxesRunTimeRef.internalName && (srBoxesRuntimeUnboxToMethods.get(primitiveAsmTypeToBType(Type.getReturnType(insn.desc))) match { + case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc + case _ => false + }) + } + + def getScalaUnbox(primitiveType: Type): MethodInsnNode = { + val bType = primitiveAsmTypeToBType(primitiveType) + val MethodNameAndType(name, methodBType) = srBoxesRuntimeUnboxToMethods(bType) + new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false) + } + + private def calleeInMap(insn: MethodInsnNode, map: Map[InternalName, MethodNameAndType]): Boolean = map.get(insn.owner) match { + case Some(MethodNameAndType(name, tp)) => insn.name == name && insn.desc == tp.descriptor + case _ => false + } + + def isJavaBox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaBoxMethods) + def isJavaUnbox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaUnboxMethods) + + def isPredefAutoBox(insn: MethodInsnNode): Boolean = { + insn.owner == PredefRef.internalName && (predefAutoBoxMethods.get(insn.name) match { + case Some(tp) => insn.desc == tp.descriptor + case _ => false + }) + } + + def isPredefAutoUnbox(insn: MethodInsnNode): Boolean = { + insn.owner == PredefRef.internalName && (predefAutoUnboxMethods.get(insn.name) match { + case Some(tp) => insn.desc == tp.descriptor + case _ => false + }) + } + + def isRefCreate(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefCreateMethods) + def isRefZero(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefZeroMethods) + + def runtimeRefClassBoxedType(refClass: InternalName): Type = Type.getArgumentTypes(srRefCreateMethods(refClass).methodType.descriptor)(0) + + def isSideEffectFreeCall(insn: MethodInsnNode): Boolean = { + isScalaBox(insn) || isScalaUnbox(insn) || + isJavaBox(insn) || // not java unbox, it may NPE + isSideEffectFreeConstructorCall(insn) + } + + def isNonNullMethodInvocation(mi: MethodInsnNode): Boolean = { + isJavaBox(mi) || isScalaBox(mi) || isPredefAutoBox(mi) || isRefCreate(mi) || isRefZero(mi) + } + + def isModuleLoad(insn: AbstractInsnNode, moduleName: InternalName): Boolean = insn match { + case fi: FieldInsnNode => fi.getOpcode == GETSTATIC && fi.owner == moduleName && fi.name == "MODULE$" && fi.desc == ("L" + moduleName + ";") + case _ => false + } + + def isPredefLoad(insn: AbstractInsnNode) = isModuleLoad(insn, PredefRef.internalName) + + def isPrimitiveBoxConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, primitiveBoxConstructors) + def isRuntimeRefConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefConstructors) + def isTupleConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, tupleClassConstructors) + + // unused objects created by these constructors are eliminated by pushPop + private lazy val sideEffectFreeConstructors: Set[(String, String)] = { + val ownerDesc = (p: (InternalName, MethodNameAndType)) => (p._1, p._2.methodType.descriptor) + primitiveBoxConstructors.map(ownerDesc).toSet ++ + srRefConstructors.map(ownerDesc) ++ + tupleClassConstructors.map(ownerDesc) ++ Set( + (ObjectRef.internalName, MethodBType(Nil, UNIT).descriptor), + (StringRef.internalName, MethodBType(Nil, UNIT).descriptor), + (StringRef.internalName, MethodBType(List(StringRef), UNIT).descriptor), + (StringRef.internalName, MethodBType(List(ArrayBType(CHAR)), UNIT).descriptor)) + } + + def isSideEffectFreeConstructorCall(insn: MethodInsnNode): Boolean = { + insn.name == INSTANCE_CONSTRUCTOR_NAME && sideEffectFreeConstructors((insn.owner, insn.desc)) + } + + private lazy val classesOfSideEffectFreeConstructors = sideEffectFreeConstructors.map(_._1) + + def isNewForSideEffectFreeConstructor(insn: AbstractInsnNode) = { + insn.getOpcode == NEW && { + val ti = insn.asInstanceOf[TypeInsnNode] + classesOfSideEffectFreeConstructors.contains(ti.desc) + } + } + + def isBoxedUnit(insn: AbstractInsnNode) = { + insn.getOpcode == GETSTATIC && { + val fi = insn.asInstanceOf[FieldInsnNode] + fi.owner == srBoxedUnitRef.internalName && fi.name == "UNIT" && fi.desc == srBoxedUnitRef.descriptor + } + } + + def isBuiltinFunctionType(internalName: InternalName): Boolean = functionRefs(internalName) + + /** + * Visit the class node and collect all referenced nested classes. + */ + def collectNestedClasses(classNode: ClassNode): List[ClassBType] = { + val innerClasses = mutable.Set.empty[ClassBType] + + def visitInternalName(internalName: InternalName): Unit = if (internalName != null) { + val t = classBTypeFromParsedClassfile(internalName) + if (t.isNestedClass.get) innerClasses += t + } + + // either an internal/Name or [[Linternal/Name; -- there are certain references in classfiles + // that are either an internal name (without the surrounding `L;`) or an array descriptor + // `[Linternal/Name;`. + def visitInternalNameOrArrayReference(ref: String): Unit = if (ref != null) { + val bracket = ref.lastIndexOf('[') + if (bracket == -1) visitInternalName(ref) + else if (ref.charAt(bracket + 1) == 'L') visitInternalName(ref.substring(bracket + 2, ref.length - 1)) + } + + // we are only interested in the class references in the descriptor, so we can skip over + // primitives and the brackets of array descriptors + def visitDescriptor(desc: String): Unit = (desc.charAt(0): @switch) match { + case '(' => + val internalNames = mutable.ListBuffer.empty[String] + var i = 1 + while (i < desc.length) { + if (desc.charAt(i) == 'L') { + val start = i + 1 // skip the L + while (desc.charAt(i) != ';') i += 1 + internalNames append desc.substring(start, i) + } + // skips over '[', ')', primitives + i += 1 + } + internalNames foreach visitInternalName + + case 'L' => + visitInternalName(desc.substring(1, desc.length - 1)) + + case '[' => + visitInternalNameOrArrayReference(desc) + + case _ => // skip over primitive types + } + + def visitConstant(const: AnyRef): Unit = const match { + case t: Type => visitDescriptor(t.getDescriptor) + case _ => + } + + // in principle we could references to annotation types, as they only end up as strings in the + // constant pool, not as class references. however, the java compiler still includes nested + // annotation classes in the innerClass table, so we do the same. explained in detail in the + // large comment in class BTypes. + def visitAnnotation(annot: AnnotationNode): Unit = { + visitDescriptor(annot.desc) + if (annot.values != null) annot.values.asScala foreach visitConstant + } + + def visitAnnotations(annots: java.util.List[_ <: AnnotationNode]) = if (annots != null) annots.asScala foreach visitAnnotation + def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations + + def visitHandle(handle: Handle): Unit = { + visitInternalNameOrArrayReference(handle.getOwner) + visitDescriptor(handle.getDesc) + } + + visitInternalName(classNode.name) + innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses + + visitInternalName(classNode.superName) + classNode.interfaces.asScala foreach visitInternalName + visitInternalName(classNode.outerClass) + + visitAnnotations(classNode.visibleAnnotations) + visitAnnotations(classNode.visibleTypeAnnotations) + visitAnnotations(classNode.invisibleAnnotations) + visitAnnotations(classNode.invisibleTypeAnnotations) + + for (f <- classNode.fields.asScala) { + visitDescriptor(f.desc) + visitAnnotations(f.visibleAnnotations) + visitAnnotations(f.visibleTypeAnnotations) + visitAnnotations(f.invisibleAnnotations) + visitAnnotations(f.invisibleTypeAnnotations) + } + + for (m <- classNode.methods.asScala) { + visitDescriptor(m.desc) + + visitAnnotations(m.visibleAnnotations) + visitAnnotations(m.visibleTypeAnnotations) + visitAnnotations(m.invisibleAnnotations) + visitAnnotations(m.invisibleTypeAnnotations) + visitAnnotationss(m.visibleParameterAnnotations) + visitAnnotationss(m.invisibleParameterAnnotations) + visitAnnotations(m.visibleLocalVariableAnnotations) + visitAnnotations(m.invisibleLocalVariableAnnotations) + + m.exceptions.asScala foreach visitInternalName + for (tcb <- m.tryCatchBlocks.asScala) visitInternalName(tcb.`type`) + + val iter = m.instructions.iterator() + while (iter.hasNext) iter.next() match { + case ti: TypeInsnNode => visitInternalNameOrArrayReference(ti.desc) + case fi: FieldInsnNode => visitInternalNameOrArrayReference(fi.owner); visitDescriptor(fi.desc) + case mi: MethodInsnNode => visitInternalNameOrArrayReference(mi.owner); visitDescriptor(mi.desc) + case id: InvokeDynamicInsnNode => visitDescriptor(id.desc); visitHandle(id.bsm); id.bsmArgs foreach visitConstant + case ci: LdcInsnNode => visitConstant(ci.cst) + case ma: MultiANewArrayInsnNode => visitDescriptor(ma.desc) + case _ => + } + } + innerClasses.toList + } + + /** + * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM + * framework only computes these values during bytecode generation. + * + * NOTE 1: as explained in the `analysis` package object, the maxStack value used by the Analyzer + * may be smaller than the correct maxStack value in the classfile (Analyzers only use a single + * slot for long / double values). The maxStack computed here are correct for running an analyzer, + * but not for writing in the classfile. We let the ClassWriter recompute max's. + * + * NOTE 2: the maxStack value computed here may be larger than the smallest correct value + * that would allow running an analyzer, see `InstructionStackEffect.forAsmAnalysisConservative`. + * + * NOTE 3: the implementation doesn't look at instructions that cannot be reached, it computes + * the max local / stack size in the reachable code. These max's work just fine for running an + * Analyzer: its implementation also skips over unreachable code in the same way. + */ + def computeMaxLocalsMaxStack(method: MethodNode): Unit = { + if (isAbstractMethod(method) || isNativeMethod(method)) { + method.maxLocals = 0 + method.maxStack = 0 + } else if (!maxLocalsMaxStackComputed(method)) { + val size = method.instructions.size + + var maxLocals = parametersSize(method) + var maxStack = 0 + + // queue of instruction indices where analysis should start + var queue = new Array[Int](8) + var top = -1 + def enq(i: Int): Unit = { + if (top == queue.length - 1) { + val nq = new Array[Int](queue.length * 2) + Array.copy(queue, 0, nq, 0, queue.length) + queue = nq + } + top += 1 + queue(top) = i + } + def deq(): Int = { + val r = queue(top) + top -= 1 + r + } + + val subroutineRetTargets = new mutable.Stack[AbstractInsnNode] + + // for each instruction in the queue, contains the stack height at this instruction. + // once an instruction has been treated, contains -1 to prevent re-enqueuing + val stackHeights = new Array[Int](size) + + def enqInsn(insn: AbstractInsnNode, height: Int): Unit = { + enqInsnIndex(method.instructions.indexOf(insn), height) + } + + def enqInsnIndex(insnIndex: Int, height: Int): Unit = { + if (insnIndex < size && stackHeights(insnIndex) != -1) { + stackHeights(insnIndex) = height + enq(insnIndex) + } + } + + val tcbIt = method.tryCatchBlocks.iterator() + while (tcbIt.hasNext) { + val tcb = tcbIt.next() + enqInsn(tcb.handler, 1) + if (maxStack == 0) maxStack = 1 + } + + enq(0) + while (top != -1) { + val insnIndex = deq() + val insn = method.instructions.get(insnIndex) + val initHeight = stackHeights(insnIndex) + stackHeights(insnIndex) = -1 // prevent i from being enqueued again + + if (insn.getOpcode == -1) { // frames, labels, line numbers + enqInsnIndex(insnIndex + 1, initHeight) + } else { + val stackGrowth = InstructionStackEffect.maxStackGrowth(insn) + val heightAfter = initHeight + stackGrowth + if (heightAfter > maxStack) maxStack = heightAfter + + // update maxLocals + insn match { + case v: VarInsnNode => + val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0 + maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 because local numbers are 0-based + + case i: IincInsnNode => + maxLocals = math.max(maxLocals, i.`var` + 1) + + case _ => + } + + insn match { + case j: JumpInsnNode => + if (j.getOpcode == JSR) { + val jsrTargetHeight = heightAfter + 1 + if (jsrTargetHeight > maxStack) maxStack = jsrTargetHeight + subroutineRetTargets.push(j.getNext) + enqInsn(j.label, jsrTargetHeight) + } else { + enqInsn(j.label, heightAfter) + val opc = j.getOpcode + if (opc != GOTO) enqInsnIndex(insnIndex + 1, heightAfter) // jump is conditional, so the successor is also a possible control flow target + } + + case l: LookupSwitchInsnNode => + var j = 0 + while (j < l.labels.size) { + enqInsn(l.labels.get(j), heightAfter); j += 1 + } + enqInsn(l.dflt, heightAfter) + + case t: TableSwitchInsnNode => + var j = 0 + while (j < t.labels.size) { + enqInsn(t.labels.get(j), heightAfter); j += 1 + } + enqInsn(t.dflt, heightAfter) + + case r: VarInsnNode if r.getOpcode == RET => + enqInsn(subroutineRetTargets.pop(), heightAfter) + + case _ => + val opc = insn.getOpcode + if (opc != ATHROW && !isReturn(insn)) + enqInsnIndex(insnIndex + 1, heightAfter) + } + } + } + + method.maxLocals = maxLocals + method.maxStack = maxStack + + maxLocalsMaxStackComputed += method + } + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala index 8d8ea839e6..dd19ad594f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala @@ -5,35 +5,74 @@ package analysis import scala.annotation.switch import scala.tools.asm.Opcodes._ import scala.tools.asm.Type -import scala.tools.asm.tree.{MultiANewArrayInsnNode, InvokeDynamicInsnNode, MethodInsnNode, AbstractInsnNode} +import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis.{Frame, Value} import opt.BytecodeUtils._ -import collection.immutable object InstructionStackEffect { - private var cache: immutable.IntMap[(Int, Int)] = immutable.IntMap.empty - private def t(x: Int, y: Int): (Int, Int) = { - // x can go up to 255 (number of parameters of a method, dimensions in multianewarray) we cache - // x up to 10, which covers most cases and limits the cache. y doesn't go above 6 (see cases). - if (x > 10 || y > 6) (x, y) - else { - val key = (x << 8) + y // this would work for any x < 256 - if (cache contains key) { - cache(key) - } else { - val r = (x, y) - cache += key -> r - r - } - } + val consShift = 3 + val prodMask = (1 << consShift) - 1 + + def cons(i: Int) = i >>> consShift + def prod(i: Int) = i & prodMask + + private def t(x: Int, y: Int): Int = (x << consShift) | y + + /** + * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int` + * (the `cons` / `prod` extract individual values). The returned values are correct for use in + * asm's Analyzer framework. For example, a LLOAD instruction produces one stack value. See also + * doc in `analysis` package object. + * + * This method requires the `frame` to be in the state **before** executing / interpreting the + * `insn`. + */ + def forAsmAnalysis[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): Int = computeConsProd(insn, forClassfile = false, conservative = false, frame = frame) + + /** + * Returns the maximal possible growth of the stack when executing `insn`. The returned value + * is usually the same as expected by asm's Analyzer framework, but it may be larger. For + * example, consider a POP2 instruction: + * - if two size-1 values are popped, then the asm Analyzer consumes two values + * - if a size-2 value is popped, the asm Analyzer consumes only one stack slot (see doc in the + * `analysis` package object) + * + * If a precise result is needed, invoke the `forAsmAnalysis` and provide a `frame` value that + * allows looking up the sizes of values on the stack. + */ + def maxStackGrowth(insn: AbstractInsnNode): Int = { + val prodCons = computeConsProd(insn, forClassfile = false, conservative = true) + prod(prodCons) - cons(prodCons) } /** - * Returns a pair with the number of stack values consumed and produced by `insn`. - * This method requires the `frame` to be in the state **before** executing / interpreting - * the `insn`. + * Returns the number of stack values consumed and produced by `insn`, encoded in a single `Int` + * (the `cons` / `prod` extract individual values). The returned values are correct for writing + * into a classfile (see doc on the `analysis` package object). */ - def apply[V <: Value](insn: AbstractInsnNode, frame: Frame[V]): (Int, Int) = { + def forClassfile(insn: AbstractInsnNode): Int = computeConsProd(insn, forClassfile = true, conservative = false) + + private def invokeConsProd(methodDesc: String, insn: AbstractInsnNode, forClassfile: Boolean): Int = { + val consumesReceiver = insn.getOpcode != INVOKESTATIC && insn.getOpcode != INVOKEDYNAMIC + if (forClassfile) { + val sizes = Type.getArgumentsAndReturnSizes(methodDesc) + val cons = (sizes >> 2) - (if (consumesReceiver) 0 else 1) + val prod = sizes & 0x03 + t(cons, prod) + } else { + val cons = Type.getArgumentTypes(methodDesc).length + (if (consumesReceiver) 1 else 0) + val prod = if (Type.getReturnType(methodDesc) == Type.VOID_TYPE) 0 else 1 + t(cons, prod) + } + } + + private def fieldInsnIsLongOrDouble(insn: AbstractInsnNode) = { + val d = insn.asInstanceOf[FieldInsnNode].desc + d == "J" || d == "D" + } + + private def computeConsProd[V <: Value](insn: AbstractInsnNode, forClassfile: Boolean, conservative: Boolean, frame: Frame[V] = null): Int = { + // not used if `forClassfile || conservative`: in these cases, `frame` is allowed to be `null` def peekStack(n: Int): V = frame.peekStack(n) (insn.getOpcode: @switch) match { @@ -48,142 +87,176 @@ object InstructionStackEffect { ICONST_3 | ICONST_4 | ICONST_5 | - LCONST_0 | - LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | - DCONST_0 | - DCONST_1 | BIPUSH | SIPUSH | - LDC | ILOAD | - LLOAD | FLOAD | - DLOAD | ALOAD => t(0, 1) + case LDC => + if (forClassfile) insn.asInstanceOf[LdcInsnNode].cst match { + case _: java.lang.Long | _: java.lang.Double => t(0, 2) + case _ => t(0, 1) + } else + t(0, 1) + + case LCONST_0 | + LCONST_1 | + DCONST_0 | + DCONST_1 | + LLOAD | + DLOAD => if (forClassfile) t(0, 2) else t(0, 1) + case IALOAD | - LALOAD | FALOAD | - DALOAD | AALOAD | BALOAD | CALOAD | SALOAD => t(2, 1) + case LALOAD | + DALOAD => if (forClassfile) t(2, 2) else t(2, 1) + case ISTORE | - LSTORE | FSTORE | - DSTORE | ASTORE => t(1, 0) + case LSTORE | + DSTORE => if (forClassfile) t(2, 0) else t(1, 0) + case IASTORE | - LASTORE | FASTORE | - DASTORE | AASTORE | BASTORE | CASTORE | SASTORE => t(3, 0) + case LASTORE | + DASTORE => if (forClassfile) t(4, 0) else t(3, 0) + case POP => t(1, 0) case POP2 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(1, 0) else t(2, 0) + if (forClassfile) t(2, 0) + else if (conservative) t(1, 0) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(1, 0) else t(2, 0) + } case DUP => t(1, 2) case DUP_X1 => t(2, 3) case DUP_X2 => - val isSize2 = peekStack(1).getSize == 2 - if (isSize2) t(2, 3) else t(3, 4) + if (forClassfile || conservative) t(3, 4) + else { + val isSize2 = peekStack(1).getSize == 2 + if (isSize2) t(2, 3) else t(3, 4) + } case DUP2 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(1, 2) else t(2, 4) + if (forClassfile || conservative) t(2, 4) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(1, 2) else t(2, 4) + } case DUP2_X1 => - val isSize2 = peekStack(0).getSize == 2 - if (isSize2) t(2, 3) else t(3, 4) + if (forClassfile || conservative) t(3, 5) + else { + val isSize2 = peekStack(0).getSize == 2 + if (isSize2) t(2, 3) else t(3, 5) + } case DUP2_X2 => - val v1isSize2 = peekStack(0).getSize == 2 - if (v1isSize2) { - val v2isSize2 = peekStack(1).getSize == 2 - if (v2isSize2) t(2, 3) else t(3, 4) - } else { - val v3isSize2 = peekStack(2).getSize == 2 - if (v3isSize2) t(3, 5) else t(4, 6) + if (forClassfile || conservative) t(4, 6) + else { + val v1isSize2 = peekStack(0).getSize == 2 + if (v1isSize2) { + val v2isSize2 = peekStack(1).getSize == 2 + if (v2isSize2) t(2, 3) else t(3, 4) + } else { + val v3isSize2 = peekStack(2).getSize == 2 + if (v3isSize2) t(3, 5) else t(4, 6) + } } case SWAP => t(2, 2) case IADD | - LADD | FADD | - DADD | ISUB | - LSUB | FSUB | - DSUB | IMUL | - LMUL | FMUL | - DMUL | IDIV | - LDIV | FDIV | - DDIV | IREM | + FREM => t(2, 1) + + case LADD | + DADD | + LSUB | + DSUB | + LMUL | + DMUL | + LDIV | + DDIV | LREM | - FREM | - DREM => t(2, 1) + DREM => if (forClassfile) t(4, 2) else t(2, 1) case INEG | - LNEG | - FNEG | - DNEG => t(1, 1) + FNEG => t(1, 1) + + case LNEG | + DNEG => if (forClassfile) t(2, 2) else t(1, 1) case ISHL | - LSHL | ISHR | - LSHR | IUSHR | - LUSHR | IAND | - LAND | IOR | + IXOR => t(2, 1) + + case LSHL | + LSHR | + LUSHR => if (forClassfile) t(3, 2) else t(2, 1) + + case LAND | LOR | - IXOR | - LXOR => t(2, 1) + LXOR => if (forClassfile) t(4, 2) else t(2, 1) case IINC => t(0, 0) - case I2L | - I2F | - I2D | - L2I | - L2F | - L2D | + case I2F | F2I | - F2L | - F2D | - D2I | - D2L | - D2F | I2B | I2C | I2S => t(1, 1) + case I2L | + I2D | + F2L | + F2D => if (forClassfile) t(1, 2) else t(1, 1) + + case L2I | + L2F | + D2I | + D2F => if (forClassfile) t(2, 1) else t(1, 1) + + case L2D | + D2L => if (forClassfile) t(2, 2) else t(1, 1) + + case FCMPL | + FCMPG => t(2, 1) + case LCMP | - FCMPL | - FCMPG | DCMPL | - DCMPG => t(2, 1) + DCMPG => if (forClassfile) t(4, 1) else t(2, 1) case IFEQ | IFNE | @@ -211,35 +284,36 @@ object InstructionStackEffect { LOOKUPSWITCH => t(1, 0) case IRETURN | - LRETURN | FRETURN | - DRETURN | ARETURN => t(1, 0) // Frame.execute consumes one stack value + case LRETURN | + DRETURN => if (forClassfile) t(2, 0) else t(1, 0) + case RETURN => t(0, 0) // Frame.execute does not change the stack - case GETSTATIC => t(0, 1) + case GETSTATIC => + val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(0, prod) - case PUTSTATIC => t(1, 0) + case PUTSTATIC => + val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(cons, 0) - case GETFIELD => t(1, 1) + case GETFIELD => + val prod = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 2 else 1 + t(1, prod) - case PUTFIELD => t(2, 0) + case PUTFIELD => + val cons = if (forClassfile && fieldInsnIsLongOrDouble(insn)) 3 else 2 + t(cons, 0) case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | - INVOKEINTERFACE => - val desc = insn.asInstanceOf[MethodInsnNode].desc - val cons = Type.getArgumentTypes(desc).length + (if (insn.getOpcode == INVOKESTATIC) 0 else 1) - val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1 - t(cons, prod) - - case INVOKEDYNAMIC => - val desc = insn.asInstanceOf[InvokeDynamicInsnNode].desc - val cons = Type.getArgumentTypes(desc).length - val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1 - t(cons, prod) + INVOKEINTERFACE => invokeConsProd(insn.asInstanceOf[MethodInsnNode].desc, insn, forClassfile) + + case INVOKEDYNAMIC => invokeConsProd(insn.asInstanceOf[InvokeDynamicInsnNode].desc, insn, forClassfile) case NEW => t(0, 1) @@ -261,5 +335,4 @@ object InstructionStackEffect { IFNONNULL => t(1, 0) } } - } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala index 31b62f747e..30e73f8ac2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala @@ -7,66 +7,12 @@ import java.util import scala.annotation.switch import scala.tools.asm.{Type, Opcodes} import scala.tools.asm.tree.{MethodInsnNode, LdcInsnNode, AbstractInsnNode} -import scala.tools.asm.tree.analysis.{Frame, Analyzer, Interpreter, Value} +import scala.tools.asm.tree.analysis._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils import BytecodeUtils._ /** - * Some notes on the ASM analyzer framework. - * - * Value - * - Abstract, needs to be implemented for each analysis. - * - Represents the desired information about local variables and stack values, for example: - * - Is this value known to be null / not null? - * - What are the instructions that could potentially have produced this value? - * - * Interpreter - * - Abstract, needs to be implemented for each analysis. Sometimes one can subclass an existing - * interpreter, e.g., SourceInterpreter or BasicInterpreter. - * - Multiple abstract methods that receive an instruction and the instruction's input values, and - * return a value representing the result of that instruction. - * - Note: due to control flow, the interpreter can be invoked multiple times for the same - * instruction, until reaching a fixed point. - * - Abstract `merge` function that computes the least upper bound of two values. Used by - * Frame.merge (see below). - * - * Frame - * - Can be used directly for many analyses, no subclass required. - * - Every frame has an array of values: one for each local variable and for each stack slot. - * - A `top` index stores the index of the current stack top - * - NOTE: for a size-2 local variable at index i, the local variable at i+1 is set to an empty - * value. However, for a size-2 value at index i on the stack, the value at i+1 holds the next - * stack value. - * - Defines the `execute(instruction)` method. - * - executing mutates the state of the frame according to the effect of the instruction - * - pop consumed values from the stack - * - pass them to the interpreter together with the instruction - * - if applicable, push the resulting value on the stack - * - Defines the `merge(otherFrame)` method - * - called by the analyzer when multiple control flow paths lead to an instruction - * - the frame at the branching instruction is merged into the current frame of the - * instruction (held by the analyzer) - * - mutates the values of the current frame, merges all values using interpreter.merge. - * - * Analyzer - * - Stores a frame for each instruction - * - `merge` function takes an instruction and a frame, merges the existing frame for that instr - * (from the frames array) with the new frame passed as argument. - * if the frame changed, puts the instruction on the work queue (fixpiont). - * - initial frame: initialized for first instr by calling interpreter.new[...]Value - * for each slot (locals and params), stored in frames[firstInstr] by calling `merge` - * - work queue of instructions (`queue` array, `top` index for next instruction to analyze) - * - analyze(method): simulate control flow. while work queue non-empty: - * - copy the state of `frames[instr]` into a local frame `current` - * - call `current.execute(instr, interpreter)`, mutating the `current` frame - * - if it's a branching instruction - * - for all potential destination instructions - * - merge the destination instruction frame with the `current` frame - * (this enqueues the destination instr if its frame changed) - * - invoke `newControlFlowEdge` (see below) - * - the analyzer also tracks active exception handlers at each instruction - * - the empty method `newControlFlowEdge` can be overridden to track control flow if required - * + * See the package object `analysis` for details on the ASM analysis framework. * * Some notes on nullness analysis. * @@ -87,59 +33,37 @@ import BytecodeUtils._ */ /** - * Type to represent nullness of values. - */ -sealed trait Nullness { - final def merge(other: Nullness) = if (this == other) this else Unknown -} -case object NotNull extends Nullness -case object Unknown extends Nullness -case object Null extends Nullness - -/** * Represents the nullness state for a local variable or stack value. * - * Note that nullness of primitive values is not tracked, it will be always [[Unknown]]. + * Note that nullness of primitive values is not tracked, it will be always unknown. */ -sealed trait NullnessValue extends Value { - /** - * The nullness of this value. - */ - def nullness: Nullness - - /** - * True if this value is a long or double. The Analyzer framework needs to know - * the size of each value when interpreting instructions, see `Frame.execute`. - */ - def isSize2: Boolean +sealed abstract class NullnessValue(final val isSize2: Boolean) extends Value { /** * The size of the slot described by this value. Cannot be 0 because no values are allocated * for void-typed slots, see NullnessInterpreter.newValue. **/ def getSize: Int = if (isSize2) 2 else 1 - def merge(other: NullnessValue) = NullnessValue(nullness merge other.nullness, isSize2) + def merge(other: NullnessValue) = { + if (this eq other) this + else if (this eq UnknownValue2) this // the only possible value of size two + else UnknownValue1 + } + + final override def equals(other: Any) = this eq other.asInstanceOf[Object] } -object NullValue extends NullnessValue { def nullness = Null; def isSize2 = false; override def toString = "Null" } -object UnknownValue1 extends NullnessValue { def nullness = Unknown; def isSize2 = false; override def toString = "Unknown1" } -object UnknownValue2 extends NullnessValue { def nullness = Unknown; def isSize2 = true; override def toString = "Unknown2" } -object NotNullValue extends NullnessValue { def nullness = NotNull; def isSize2 = false; override def toString = "NotNull" } +object NullValue extends NullnessValue(isSize2 = false) { override def toString = "Null" } +object UnknownValue1 extends NullnessValue(isSize2 = false) { override def toString = "Unknown1" } +object UnknownValue2 extends NullnessValue(isSize2 = true ) { override def toString = "Unknown2" } +object NotNullValue extends NullnessValue(isSize2 = false) { override def toString = "NotNull" } object NullnessValue { - def apply(nullness: Nullness, isSize2: Boolean): NullnessValue = { - if (nullness == Null) NullValue - else if (nullness == NotNull) NotNullValue - else if (isSize2) UnknownValue2 - else UnknownValue1 - } - - def apply(nullness: Nullness, insn: AbstractInsnNode): NullnessValue = { - apply(nullness, isSize2 = BytecodeUtils.instructionResultSize(insn) == 2) - } + def unknown(isSize2: Boolean) = if (isSize2) UnknownValue2 else UnknownValue1 + def unknown(insn: AbstractInsnNode) = if (BytecodeUtils.instructionResultSize(insn) == 2) UnknownValue2 else UnknownValue1 } -final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5) { +final class NullnessInterpreter(bTypes: BTypes) extends Interpreter[NullnessValue](Opcodes.ASM5) { def newValue(tp: Type): NullnessValue = { // ASM loves giving semantics to null. The behavior here is the same as in SourceInterpreter, // which is provided by the framework. @@ -151,29 +75,25 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5) // (2) `tp` may also be `null`. When creating the initial frame, the analyzer invokes // `newValue(null)` for each local variable. We have to return a value of size 1. if (tp == Type.VOID_TYPE) null // (1) - else NullnessValue(Unknown, isSize2 = tp != null /*(2)*/ && tp.getSize == 2 ) + else NullnessValue.unknown(isSize2 = tp != null /*(2)*/ && tp.getSize == 2 ) } override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): NullnessValue = { // For instance methods, the `this` parameter is known to be not null. - if (isInstanceMethod && local == 0) NullnessValue(NotNull, isSize2 = false) + if (isInstanceMethod && local == 0) NotNullValue else super.newParameterValue(isInstanceMethod, local, tp) } - def newOperation(insn: AbstractInsnNode): NullnessValue = { - val nullness = (insn.getOpcode: @switch) match { - case Opcodes.ACONST_NULL => Null + def newOperation(insn: AbstractInsnNode): NullnessValue = (insn.getOpcode: @switch) match { + case Opcodes.ACONST_NULL => NullValue - case Opcodes.LDC => insn.asInstanceOf[LdcInsnNode].cst match { - case _: String | _: Type => NotNull - case _ => Unknown - } - - case _ => Unknown + case Opcodes.LDC => insn.asInstanceOf[LdcInsnNode].cst match { + case _: String | _: Type => NotNullValue + case _ => NullnessValue.unknown(insn) } // for Opcodes.NEW, we use Unknown. The value will become NotNull after the constructor call. - NullnessValue(nullness, insn) + case _ => NullnessValue.unknown(insn) } def copyOperation(insn: AbstractInsnNode, value: NullnessValue): NullnessValue = value @@ -182,26 +102,24 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5) case Opcodes.CHECKCAST => value case Opcodes.NEWARRAY | - Opcodes.ANEWARRAY => NullnessValue(NotNull, isSize2 = false) + Opcodes.ANEWARRAY => NotNullValue - case _ => NullnessValue(Unknown, insn) + case _ => NullnessValue.unknown(insn) } def binaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue): NullnessValue = { - NullnessValue(Unknown, insn) + NullnessValue.unknown(insn) } - def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = { - NullnessValue(Unknown, isSize2 = false) - } + def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = UnknownValue1 - def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = (insn.getOpcode: @switch) match { - case Opcodes.MULTIANEWARRAY => - NullnessValue(NotNull, isSize2 = false) + def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = insn match { + case mi: MethodInsnNode if bTypes.backendUtils.isNonNullMethodInvocation(mi) => + NotNullValue case _ => - // TODO: use a list of methods that are known to return non-null values - NullnessValue(Unknown, insn) + if (insn.getOpcode == Opcodes.MULTIANEWARRAY) NotNullValue + else NullnessValue.unknown(insn) } def returnOperation(insn: AbstractInsnNode, value: NullnessValue, expected: NullnessValue): Unit = () @@ -219,8 +137,10 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal override def execute(insn: AbstractInsnNode, interpreter: Interpreter[NullnessValue]): Unit = { import Opcodes._ - // get the object id of the object that is known to be not-null after this operation - val nullCheckedAliasId: Long = (insn.getOpcode: @switch) match { + // get the alias set the object that is known to be not-null after this operation. + // alias sets are mutable / mutated, so after super.execute, this set contains the remaining + // aliases of the value that becomes not-null. + val nullCheckedAliases: AliasSet = (insn.getOpcode: @switch) match { case IALOAD | LALOAD | FALOAD | @@ -229,7 +149,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal BALOAD | CALOAD | SALOAD => - aliasId(this.stackTop - 1) + aliasesOf(this.stackTop - 1) case IASTORE | FASTORE | @@ -239,35 +159,36 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal SASTORE | LASTORE | DASTORE => - aliasId(this.stackTop - 2) + aliasesOf(this.stackTop - 2) case GETFIELD => - aliasId(this.stackTop) + aliasesOf(this.stackTop) case PUTFIELD => - aliasId(this.stackTop - 1) + aliasesOf(this.stackTop - 1) case INVOKEVIRTUAL | INVOKESPECIAL | INVOKEINTERFACE => val desc = insn.asInstanceOf[MethodInsnNode].desc val numArgs = Type.getArgumentTypes(desc).length - aliasId(this.stackTop - numArgs) + aliasesOf(this.stackTop - numArgs) case ARRAYLENGTH | MONITORENTER | MONITOREXIT => - aliasId(this.stackTop) + aliasesOf(this.stackTop) case _ => - -1 + null } super.execute(insn, interpreter) - if (nullCheckedAliasId != -1) { - for (i <- valuesWithAliasId(nullCheckedAliasId)) - this.setValue(i, NotNullValue) + if (nullCheckedAliases != null) { + val it = nullCheckedAliases.iterator + while (it.hasNext) + this.setValue(it.next(), NotNullValue) } } } @@ -276,7 +197,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal * This class is required to override the `newFrame` methods, which makes makes sure the analyzer * uses NullnessFrames. */ -class NullnessAnalyzer extends Analyzer[NullnessValue](new NullnessInterpreter) { +class NullnessAnalyzer(bTypes: BTypes) extends Analyzer[NullnessValue](new NullnessInterpreter(bTypes)) { override def newFrame(nLocals: Int, nStack: Int): NullnessFrame = new NullnessFrame(nLocals, nStack) override def newFrame(src: Frame[_ <: NullnessValue]): NullnessFrame = new NullnessFrame(src) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala index 594fd8923c..6b645cb803 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerImpl.scala @@ -15,7 +15,6 @@ import scala.tools.asm.{Type, MethodVisitor} import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName import opt.BytecodeUtils._ @@ -55,24 +54,16 @@ import scala.collection.convert.decorateAsScala._ * * If ever needed, we could introduce a mode where primitive conversions (l2i) are considered as * copying operations. + * + * Note on performance: thee data flow analysis (SourceValue / SourceInterpreter, provided by ASM) + * is roughly 2-3x slower than a simple analysis (like BasicValue). The reason is that the merge + * function (merging producer sets) is more complex than merging simple basic values. + * See also the doc comment in the package object `analysis`. */ -class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) { - - /* Timers for benchmarking ProdCons - import scala.reflect.internal.util.Statistics._ - import ProdConsAnalyzer._ - val analyzerTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - analysis", prodConsAnalyzerTimer) - val consumersTimer = newSubTimer(classInternalName + "#" + methodNode.name + " - consumers", prodConsAnalyzerTimer) - */ - - val analyzer = new Analyzer(new InitialProducerSourceInterpreter) +trait ProdConsAnalyzerImpl { + val methodNode: MethodNode -// val start = analyzerTimer.start() - analyzer.analyze(classInternalName, methodNode) -// analyzerTimer.stop(start) -// println(analyzerTimer.line) - - def frameAt(insn: AbstractInsnNode) = analyzer.frameAt(insn, methodNode) + def frameAt(insn: AbstractInsnNode): Frame[SourceValue] /** * Returns the potential producer instructions of a (local or stack) value in the frame of `insn`. @@ -102,8 +93,13 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) inputValues(insn).iterator.flatMap(v => v.insns.asScala).toSet } - def consumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = - _consumersOfOutputsFrom.get(insn).map(v => v.indices.flatMap(v.apply)(collection.breakOut): Set[AbstractInsnNode]).getOrElse(Set.empty) + def consumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = insn match { + case _: UninitializedLocalProducer => Set.empty + case ParameterProducer(local) => consumersOfValueAt(methodNode.instructions.getFirst, local) + case ExceptionProducer(handlerLabel, handlerFrame) => consumersOfValueAt(handlerLabel, handlerFrame.stackTop) + case _ => + _consumersOfOutputsFrom.get(insn).map(v => v.indices.flatMap(v.apply)(collection.breakOut): Set[AbstractInsnNode]).getOrElse(Set.empty) + } /** * Returns the potential initial producer instructions of a value in the frame of `insn`. @@ -159,13 +155,19 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) inputValueSlots(insn).flatMap(slot => initialProducersForValueAt(insn, slot)).toSet } - def ultimateConsumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = { - lazy val next = insn.getNext - outputValueSlots(insn).flatMap(slot => ultimateConsumersOfValueAt(next, slot)).toSet + def ultimateConsumersOfOutputsFrom(insn: AbstractInsnNode): Set[AbstractInsnNode] = insn match { + case _: UninitializedLocalProducer => Set.empty + case _ => + lazy val next = insn match { + case _: ParameterProducer => methodNode.instructions.getFirst + case ExceptionProducer(handlerLabel, _) => handlerLabel + case _ => insn.getNext + } + outputValueSlots(insn).flatMap(slot => ultimateConsumersOfValueAt(next, slot)).toSet } private def isCopyOperation(insn: AbstractInsnNode): Boolean = { - isVarInstruction(insn) || { + isLoadOrStore(insn) || { (insn.getOpcode: @switch) match { case DUP | DUP_X1 | DUP_X2 | DUP2 | DUP2_X1 | DUP2_X2 | SWAP | CHECKCAST => true case _ => false @@ -376,9 +378,9 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) Seq(insn.asInstanceOf[IincInsnNode].`var`) } else { val frame = frameAt(insn) - val stackEffect = InstructionStackEffect(insn, frame) + val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame) val stackSize = frame.getLocals + frame.getStackSize - (stackSize - stackEffect._1) until stackSize + (stackSize - InstructionStackEffect.cons(prodCons)) until stackSize } } @@ -386,7 +388,7 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) private def outputValueSlots(insn: AbstractInsnNode): Seq[Int] = insn match { case ParameterProducer(local) => Seq(local) case UninitializedLocalProducer(local) => Seq(local) - case ExceptionProducer(frame) => Seq(frame.stackTop) + case ExceptionProducer(_, frame) => Seq(frame.stackTop) case _ => if (insn.getOpcode == -1) return Seq.empty if (isStore(insn)) { @@ -395,16 +397,15 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) Seq(insn.asInstanceOf[IincInsnNode].`var`) } else { val frame = frameAt(insn) - val stackEffect = InstructionStackEffect(insn, frame) + val prodCons = InstructionStackEffect.forAsmAnalysis(insn, frame) val nextFrame = frameAt(insn.getNext) val stackSize = nextFrame.getLocals + nextFrame.getStackSize - (stackSize - stackEffect._2) until stackSize + (stackSize - InstructionStackEffect.prod(prodCons)) until stackSize } } /** For each instruction, a set of potential consumers of the produced values. */ private lazy val _consumersOfOutputsFrom: Map[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] = { -// val start = consumersTimer.start() var res = Map.empty[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] for { insn <- methodNode.instructions.iterator.asScala @@ -417,8 +418,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) val outputIndex = producedSlots.indexOf(i) res = res.updated(producer, currentConsumers.updated(outputIndex, currentConsumers(outputIndex) + insn)) } -// consumersTimer.stop(start) -// println(consumersTimer.line) res } @@ -426,11 +425,6 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) private val _ultimateConsumersCache: mutable.AnyRefMap[(AbstractInsnNode, Int), Set[AbstractInsnNode]] = mutable.AnyRefMap.empty } -object ProdConsAnalyzer { - import scala.reflect.internal.util.Statistics._ - val prodConsAnalyzerTimer = newTimer("Time in ProdConsAnalyzer", "jvm") -} - /** * A class for pseudo-instructions representing the initial producers of local values that have * no producer instruction in the method: @@ -446,10 +440,10 @@ object ProdConsAnalyzer { * return a; * } * - * In the first frame of the method, the SoruceValue for parameter `a` gives an empty set of + * In the first frame of the method, the SourceValue for parameter `a` gives an empty set of * producer instructions. * - * In the frame of the `IRETURN` instruction, the SoruceValue for parameter `a` lists a single + * In the frame of the `IRETURN` instruction, the SourceValue for parameter `a` lists a single * producer instruction: the `ISTORE 1`. This makes it look as if there was a single producer for * `a`, where in fact it might still hold the parameter's initial value. */ @@ -459,9 +453,9 @@ abstract class InitialProducer extends AbstractInsnNode(-1) { override def accept(cv: MethodVisitor): Unit = throw new UnsupportedOperationException } -case class ParameterProducer(local: Int) extends InitialProducer -case class UninitializedLocalProducer(local: Int) extends InitialProducer -case class ExceptionProducer(handlerFrame: Frame[_ <: Value]) extends InitialProducer +case class ParameterProducer(local: Int) extends InitialProducer +case class UninitializedLocalProducer(local: Int) extends InitialProducer +case class ExceptionProducer[V <: Value](handlerLabel: LabelNode, handlerFrame: Frame[V]) extends InitialProducer class InitialProducerSourceInterpreter extends SourceInterpreter { override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): SourceValue = { @@ -473,6 +467,6 @@ class InitialProducerSourceInterpreter extends SourceInterpreter { } override def newExceptionValue(tryCatchBlockNode: TryCatchBlockNode, handlerFrame: Frame[_ <: Value], exceptionType: Type): SourceValue = { - new SourceValue(1, ExceptionProducer(handlerFrame)) + new SourceValue(1, ExceptionProducer(tryCatchBlockNode.handler, handlerFrame)) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala new file mode 100644 index 0000000000..bcf9978c16 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/TypeFlowInterpreter.scala @@ -0,0 +1,36 @@ +package scala.tools.nsc +package backend.jvm +package analysis + +import scala.tools.asm.Type +import scala.tools.asm.tree.analysis.{BasicValue, BasicInterpreter} + +abstract class TypeFlowInterpreter extends BasicInterpreter { + override def newValue(tp: Type) = { + if (tp == null) super.newValue(tp) + else if (isRef(tp)) new BasicValue(tp) + else super.newValue(tp) + } + + def isRef(tp: Type) = tp != null && (tp.getSort match { + case Type.OBJECT | Type.ARRAY => true + case _ => false + }) + + def refLub(a: BasicValue, b: BasicValue): BasicValue + + override def merge(a: BasicValue, b: BasicValue): BasicValue = { + if (a == b) a + else if (isRef(a.getType) && isRef(b.getType)) refLub(a, b) + else BasicValue.UNINITIALIZED_VALUE + } +} + +/** + * A [[TypeFlowInterpreter]] which collapses LUBs of non-equal reference types to Object. + * This could be made more precise by looking up ClassBTypes for the two reference types and using + * the `jvmWiseLUB` method. + */ +class NonLubbingTypeFlowInterpreter extends TypeFlowInterpreter { + def refLub(a: BasicValue, b: BasicValue): BasicValue = BasicValue.REFERENCE_VALUE // java/lang/Object +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala new file mode 100644 index 0000000000..ef961941a0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/package.scala @@ -0,0 +1,374 @@ +package scala.tools.nsc.backend.jvm + +/** + * Summary on the ASM analyzer framework + * -------------------------------------- + * + * Value + * - Abstract, needs to be implemented for each analysis. + * - Represents the desired information about local variables and stack values, for example: + * - Is this value known to be null / not null? + * - What are the instructions that could potentially have produced this value? + * + * Interpreter + * - Abstract, needs to be implemented for each analysis. Sometimes one can subclass an existing + * interpreter, e.g., SourceInterpreter or BasicInterpreter. + * - Multiple abstract methods that receive an instruction and the instruction's input values, and + * return a value representing the result of that instruction. + * - Note: due to control flow, the interpreter can be invoked multiple times for the same + * instruction, until reaching a fixed point. + * - Abstract `merge` function that computes the least upper bound of two values. Used by + * Frame.merge (see below). + * + * Frame + * - Can be used directly for many analyses, no subclass required. + * - Every frame has an array of values: one for each local variable and for each stack slot. + * - A `top` index stores the index of the current stack top + * - NOTE: for a size-2 local variable at index i, the local variable at i+1 is set to an empty + * value. However, for a size-2 value at index i on the stack, the value at i+1 holds the next + * stack value. IMPORTANT: this is only the case in ASM's analysis framework, not in bytecode. + * See comment below. + * - Defines the `execute(instruction)` method. + * - executing mutates the state of the frame according to the effect of the instruction + * - pop consumed values from the stack + * - pass them to the interpreter together with the instruction + * - if applicable, push the resulting value on the stack + * - Defines the `merge(otherFrame)` method + * - called by the analyzer when multiple control flow paths lead to an instruction + * - the frame at the branching instruction is merged into the current frame of the + * instruction (held by the analyzer) + * - mutates the values of the current frame, merges all values using interpreter.merge. + * + * Analyzer + * - Stores a frame for each instruction + * - `merge` function takes an instruction and a frame, merges the existing frame for that instr + * (from the frames array) with the new frame passed as argument. + * if the frame changed, puts the instruction on the work queue (fixpoint). + * - initial frame: initialized for first instr by calling interpreter.new[...]Value + * for each slot (locals and params), stored in frames[firstInstr] by calling `merge` + * - work queue of instructions (`queue` array, `top` index for next instruction to analyze) + * - analyze(method): simulate control flow. while work queue non-empty: + * - copy the state of `frames[instr]` into a local frame `current` + * - call `current.execute(instr, interpreter)`, mutating the `current` frame + * - if it's a branching instruction + * - for all potential destination instructions + * - merge the destination instruction frame with the `current` frame + * (this enqueues the destination instr if its frame changed) + * - invoke `newControlFlowEdge` (see below) + * - the analyzer also tracks active exception handlers at each instruction + * - the empty method `newControlFlowEdge` can be overridden to track control flow if required + * + * + * MaxLocals and MaxStack + * ---------------------- + * + * At the JVM level, long and double values occupy two slots, both as local variables and on the + * stack, as specified in the JVM spec 2.6.2: + * "At any point in time, an operand stack has an associated depth, where a value of type long or + * double contributes two units to the depth and a value of any other type contributes one unit." + * + * For example, a method + * class A { def f(a: Long, b: Long) = a + b } + * has MAXSTACK=4 in the classfile. This value is computed by the ClassWriter / MethodWriter when + * generating the classfile (we always pass COMPUTE_MAXS to the ClassWriter). + * + * For running an ASM Analyzer, long and double values occupy two local variable slots, but only + * a single slot on the call stack, as shown by the following snippet: + * + * import scala.tools.nsc.backend.jvm._ + * import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ + * import scala.collection.convert.decorateAsScala._ + * import scala.tools.asm.tree.analysis._ + * + * val cn = AsmUtils.readClass("/Users/luc/scala/scala/sandbox/A.class") + * val m = cn.methods.iterator.asScala.find(_.name == "f").head + * + * // the value is read from the classfile, so it's 4 + * println(s"maxLocals: ${m.maxLocals}, maxStack: ${m.maxStack}") // maxLocals: 5, maxStack: 4 + * + * // we can safely set it to 2 for running the analyzer. + * m.maxStack = 2 + * + * val a = new Analyzer(new BasicInterpreter) + * a.analyze(cn.name, m) + * val addInsn = m.instructions.iterator.asScala.find(_.getOpcode == 97).get // LADD Opcode + * val addFrame = a.frameAt(addInsn, m) + * + * addFrame.getStackSize // 2: the two long values only take one slot each + * addFrame.getLocals // 5: this takes one slot, the two long parameters take 2 slots each + * + * + * While running the optimizer, we need to make sure that the `maxStack` value of a method is + * large enough for running an ASM analyzer. We don't need to worry if the value is incorrect in + * the JVM perspective: the value will be re-computed and overwritten in the ClassWriter. + * + * + * Lessons learnt while benchmarking the alias tracking analysis + * ------------------------------------------------------------- + * + * Profiling + * - Use YourKit for finding hotspots (cpu profiling). when it comes to drilling down into the details + * of a hotspot, don't pay too much attention to the percentages / time counts. + * - Should also try other profilers. + * - Use timers. When a method showed up as a hotspot, i added a timer around that method, and a + * second one within the method to measure specific parts. The timers slow things down, but the + * relative numbers show what parts of a method are slow. + * + * ASM analyzer insights + * - The time for running an analysis depends on the number of locals and the number of instructions. + * Reducing the number of locals helps speeding up the analysis: there are less values to + * merge when merging to frames. + * See also https://github.com/scala/scala-dev/issues/47 + * - The common hot spot of an ASM analysis is Frame.merge, for example in producers / consumers. + * - For nullness analysis the time is spent as follows + * - 20% merging nullness values. this is as expected: for example, the same absolute amount of + * time is spent in merging BasicValues when running a BasicInterpreter. + * - 50% merging alias sets. i tried to optimize what i could out of this. + * - 20% is spent creating new frames from existing ones, see comment on AliasingFrame.init. + * - The implementation of Frame.merge (the main hot spot) contains a megamorphic callsite to + * `interpreter.merge`. This can be observed easily by running a test program that either runs + * a BasicValue analysis only, versus a program that first runs a nullness analysis and then + * a BasicValue. In an example, the time for the BasicValue analysis goes from 519ms to 1963ms, + * a 3.8x slowdown. + * - I added counters to the Frame.merge methods for nullness and BasicValue analysis. In the + * examples I benchmarked, the number of merge invocations was always exactly the same. + * It would probably be possible to come up with an example where alias set merging forces + * additional analysis rounds until reaching the fixpoint, but I did not observe such cases. + * + * To benchmark an analysis, instead of benchmarking analysis while it runs in the compiler + * backend, one can easily run it from a separate program (or the repl). The bytecode to analyze + * can simply be parsed from a classfile. See example at the end of this comment. + * + * + * Nullness Analysis in Miguel's Optimizer + * --------------------------------------- + * + * Miguel implemented alias tracking for nullness analysis differently [1]. Remember that every + * frame has an array of values. Miguel's idea was to represent aliasing using reference equality + * in the values array: if two entries in the array point to the same value object, the two entries + * are aliases in the frame of the given instruction. + * + * While this idea seems elegant at first sight, Miguel's implementation does not merge frames + * correctly when it comes to aliasing. Assume in frame 1, values (a, b, c) are aliases, while in + * frame 2 (a, b) are aliases. When merging the second into the first, we have to make sure that + * c is removed as an alias of (a, b). + * + * It would be possible to implement correct alias set merging in Miguel's approach. However, frame + * merging is the main hot spot of analysis. The computational complexity of implementing alias set + * merging by traversing the values array and comparing references is too high. The concrete + * alias set representation that is used in the current implementation (see class AliasingFrame) + * makes alias set merging more efficient. + * + * [1] https://github.com/scala-opt/scala/blob/opt/rebase/src/compiler/scala/tools/nsc/backend/bcode/NullnessPropagator.java + * + * + * Complexity and scaling of analysis + * ---------------------------------- + * + * The time complexity of a data flow analysis depends on: + * + * - The size of the method. The complexity factor is linear (assuming the number of locals and + * branching instructions remains constant). The main analysis loop runs through all + * instructions of a method once. Instructions are only re-enqueued if a control flow merge + * changes the frame at some instruction. + * + * - The branching instructions. When a second (third, ..) control flow edge arrives at an + * instruction, the existing frame at the instruction is merged with the one computed on the + * new branch. If the merge function changes the existing frame, the instruction is enqueued + * for another analysis. This results in a merge operation for the successors of the + * instruction. + * + * - The number of local variables. The hot spot of analysis is frame merging. The merge function + * iterates through the values in the frame (locals and stack values) and merges them. + * + * I measured the running time of an analysis for two examples: + * - Keep the number of locals and branching instructions constant, increase the number of + * instructions. The running time grows linearly with the method size. + * - Increase the size and number of locals in a method. The method size and number of locals + * grow in the same pace. Here, the running time increase is polynomial. It looks like the + * complexity is be #instructions * #locals^2 (see below). + * + * I measured nullness analysis (which tracks aliases) and a SimpleValue analysis. Nullness runs + * roughly 5x slower (because of alias tracking) at every problem size - this factor doesn't change. + * + * The numbers below are for nullness. Note that the last column is constant, i.e., the running + * time is proportional to #ins * #loc^2. Therefore we use this factor when limiting the maximal + * method size for running an analysis. + * + * #insns #locals time (ms) time / #ins * #loc^2 * 10^6 + * 1305 156 34 1.07 + * 2610 311 165 0.65 + * 3915 466 490 0.57 + * 5220 621 1200 0.59 + * 6525 776 2220 0.56 + * 7830 931 3830 0.56 + * 9135 1086 6570 0.60 + * 10440 1241 9700 0.60 + * 11745 1396 13800 0.60 + * + * As a second experiment, nullness analysis was run with varying #insns but constant #locals. + * The last column shows linear complexity with respect to the method size (linearOffset = 2279): + * + * #insns #locals time (ms) (time + linearOffset) / #insns + * 5220 621 1090 0.645 + * 6224 621 1690 0.637 + * 7226 621 2280 0.630 + * 8228 621 2870 0.625 + * 9230 621 3530 0.629 + * 10232 621 4130 0.626 + * 11234 621 4770 0.627 + * 12236 621 5520 0.637 + * 13238 621 6170 0.638 + * + * + * When running a BasicValue analysis, the complexity observation is the same (time is proportional + * to #ins * #loc^2). + * + * + * Measuring analysis execution time + * --------------------------------- + * + * See code below. + */ + +/* +object Test { + val overwrite: Option[String] = null + + @noinline def serialize(o: AnyRef): String = null + + @noinline def deserialize(string: String): AnyRef = null + + @inline def checkRoundTrip[T <: AnyRef](instance: T)(f: T => AnyRef) { + val result = serialize(instance) + val reconstituted = deserialize(result).asInstanceOf[T] + assert(f(instance) == f(reconstituted), (f(instance), f(reconstituted))) + } + + @inline def check[T <: AnyRef](instance: => T)(prevResult: String, f: T => AnyRef = (x: T) => x) { + // pattern match to introduce a lot of control flow, i.e., a lot of frame merges + overwrite match { + case Some(f) => + case None => + checkRoundTrip(instance)(f) + assert(f(deserialize(prevResult).asInstanceOf[T]) == f(instance), instance) + assert(prevResult == "res", instance) + } + } + + // @inline def fun[T <: AnyRef](instance: => T) = (x: T) => x + + def testMain(): Unit = { + // every call to check creates quite a number of locals, and also quite a number of aliases + // of the same value (x1). First of all, the default argument call is expanded as below. Then + // method check is inlined, and within the body of check, checkRoundTrip and assert have + // already been inlined as well. + + // { + // val x1 = () => "" + // val x2 = fun(x1()) // the compiler optimizes this: instead of passing `() => x1()`, it just passes x1 + // check(x1())("", x2) // same here for x1 + // } + + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 5 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 10 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 15 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 20 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 25 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 30 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 35 + check("")("") + check("")("") + check("")("") + check("")("") + check("")("") // 40 + // check("")("") + // check("")("") + // check("")("") + // check("")("") + // check("")("") // 45 + // check("")("") + // check("")("") + // check("")("") + // check("")("") + // check("")("") // 50 + // check("")("") + // check("")("") + // check("")("") + // check("")("") + // check("")("") // 55 + + // 1000 bytecode instructions, 0 locals + // println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); println((1,2,3,4,5,6,7,8,9,10)); + } + + def timed[T](f: => T): T = { + val start = System.nanoTime() + val r = f + val nanos = System.nanoTime() - start + println(s"took ${nanos/1000000}ms") + r + } + + def main(args: Array[String]): Unit = { + import scala.tools.nsc.backend.jvm._ + val cn = AsmUtils.readClass("/Users/luc/scala/scala/sandbox/Test$.class") + import scala.collection.convert.decorateAsScala._ + val m = cn.methods.iterator.asScala.find(_.name == "testMain").head + + println(s"${m.instructions.size} instructions - ${m.maxLocals} locals") + + val a = new analysis.NullnessAnalyzer + a.analyze(cn.name, m) // warm up + + analysis.AliasingFrame.reset() + timed(a.analyze(cn.name, m)) + analysis.AliasingFrame.timers foreach println + + println("---") + + // NOTE: if we don't run nullness analysis above (comment it out), then the BasicValue + // analysis runs 3.5x faster. Most likely because the call to Interpreter.merge inside + // Frame.merge is no longer megamorphic. + + import scala.tools.asm.tree.analysis._ + val ba = new Analyzer(new BasicInterpreter) + ba.analyze(cn.name, m) // warm up + + timed(ba.analyze(cn.name, m)) + + println("---") + + timed(a.analyze(cn.name, m)) + } +} +*/ +package object analysis diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala new file mode 100644 index 0000000000..16fe2e5cff --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala @@ -0,0 +1,907 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.annotation.tailrec +import scala.tools.asm.Type +import scala.tools.asm.Opcodes._ +import scala.tools.asm.tree._ +import scala.collection.mutable +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ + +class BoxUnbox[BT <: BTypes](val btypes: BT) { + import btypes._ + import backendUtils._ + + /** + * Eliminate box-unbox pairs within `method`. Such appear commonly after closure elimination: + * + * def t2 = { + * val f = (b: Byte, i: Int) => i + b // no specialized variant for this function type + * f(1, 2) // invokes the generic `apply` + * } + * + * The closure optimizer re-writes the `apply` call to `anonfun$adapted` method, which takes + * boxed arguments. After inlining this method, we get + * + * def t2 = { + * val a = boxByte(1) + * val b = boxInteger(2) + * val r = boxInteger(anonfun$(unboxByte(a), unboxInt(b))) + * unboxInt(r) + * } + * + * All these box/unbox operations are eliminated here. + * + * Implementation: for every box operation, find all consumers of the boxed value, then all + * producers of these consumers, repeat until reaching a fixpoint. If this results in a set of + * boxing and unboxing operations, the box can be eliminated. + * + * There are two methods for eliminating boxes: + * M1: If there is a single boxing operation, the boxed value(s) are stored into new local + * variable(s) at the allocation site. Accesses to the boxed value are re-written to reads / + * writes of these locals. Advantages: + * - supports mutable boxes (IntRef and friends) + * - supports eliminating unbox operations even if the box object needs to be created + * because it escapes (see E4) + * - works by keeping the unboxed value(s) in locals AND the box in its original form + * - only for immutable boxes: modifications to the escaped box cannot be applied to + * the local variable(s) holding the boxed value(s). + * Restriction: + * - does not work if there are multiple boxing operations (see E1) + * + * M2: If there are multiple boxing operations, the boxing operations are simply eliminated, + * leaving the unboxed value(s) on the stack. Store / load operations that previously + * acted on the box are adapted to handle the boxed type(s). If the box contains multiple + * values (or a size-2 value, which doesn't fit into locals that were used for the box), + * new local slots are used for store / load operations. Restrictions: + * - does not support re-writing writes to (mutable) boxes (see E2) + * - does not support re-writing reads of boxes that also escape (see E3) + * + * + * E1: M1 only works if there's a single boxing operation. + * def e1(b: Boolean) = { + * val i: Integer = box(10) // 10 is stored into a new local, box operation and i removed + * val j: Integer = box(20) // 20 is stored into a new local, box operation adn j removed + * val r = if (b) i else j // loads and stores of the box are eliminated, r no longer exists + * unbox(r) // cannot rewrite: we don't know which local to load + * } + * Note: the example has no write and the box does not escape, so M2 works here. + * + * E2: mutable boxes with multiple boxing operations cannot be eliminated. + * M1: see E1 + * M2: cannot replace an `IntRef` on the stack by an `Int` value on the stack, an Int on the + * stack cannot be modified. + * + * def e2(b: Boolean) = { + * val r1 = new IntRef(0) + * val r2 = new IntRef(1) + * val modRef = if (b) r1 else r2 + * modRef.elem += 10 // M1: cannot rewrite: which local to write? same as E1. + * (if (b) r1 else r2).elem += 10 // M2: cannot change an Int on the stack + * (r1.elem, r2.elem) + * } + * + * + * E3: escaping boxes with multiple boxing operations cannot be rewritten. + * M1: see E1. + * M2: at *, instead of an Integer, an Int is on the stack, but the escape method expects an + * Integer. We cannot just create a box at this point: if there are multiple escapes (or + * an escape is executed more than once), the difference could be observed (reference + * equality). + * + * def e3(b: Boolean) = { + * val i: Integer = box(1) + * val j: Integer = box(2) + * escape(if (b) i else j) // * + * unbox(if (b) i else j) + * } + * + * + * E4: M1 supports rewriting unbox operations of immutable boxes that escape + * def e4 = { + * val i: Integer = box(10) // 10 is stored into a new local, loaded as argument for the box call + * escape(i) // not changed, still loads the local i holding the box + * unbox(i) // rewritten to a pop (of the box) and a load of the local variable + * } + * + * + * E4 seems to be a bit of a corner case, but it's necessary to unblock box eliminations with + * mutual dependencies. Example: + * + * val ((a, b), c) = ((1, 2), 3) + * a + b + c + * + * generates (after a few cleanups) the following (pseudo-bytecode, ignoring primitive boxing, specialization): + * + * load 1, load 2, new Tuple2 // stack: Tuple2 + * load 3 // stack: Tuple2; Int + * val local1 = new Tuple2 + * val local2 = local1._1.asInstanceOf[Tuple2] + * val c = local1._2.asInstanceOf[Int] + * if (local2 == null) throw new MatchError(local1) + * val a = local2._1 + * val b = local2._2 + * a + b + c + * + * In order to eliminate the tuples, we first need to eliminate the outer tuple (stored in local1) + * - single box operation, so we use M1 + * - there are three consumers of the outer tuple: `local1._1`, `local1._2` and + * `new MatchError(local1)`. in the last one, the tuple escapes. + * - note that the MatchError creation is dead code: local2 is never null. However, our nullness + * analysis cannot identify this: it does not track nullness through tuple stores and loads. + * - if we re-write the non-escaping consumers of the outer tuple, but keep the tuple allocation + * and the escaping consumer, we get the follwoing: + * + * load 1, load 2 + * val newLocal1 = new Tuple2; load newLocal1 // stack: Tuple2 + * val newLocal2 = 3; load newLocal2 // stack: Tuple2; Int + * val local1 = new Tuple2 + * val local2 = newLocal1 + * val c = newLocal2 + * if (local2 == null) throw new MatchError(local1) + * val a = local2._1 + * val b = local2._2 + * a + b + c + * + * At this point, the nullness analysis sees that `local2 == null` is false, dead code elimination + * removes the `throw new MatchError(local1)`. After eliminating the allocation of the outer tuple, + * the inner tuple (stored in newLocal1) can also be eliminated. + * + * + * Special case for tuples wrt specialization: a tuple getter may box or unbox the value stored + * in the tuple: calling `_1` on a `Tuple2$mcII$sp` boxes the primitive Int stored in the tuple. + * Similarly, calling `_1$mcI$sp` on a non-specialized `Tuple2` unboxes the Integer in the tuple. + * When eliminating such getters, we have to introduce appropriate box / unbox calls. + * + * + * TODO: add new calls (box / unbox) to the call graph (not urgent) + * TODO: update the call graph because stack heights change (not urgent). + * this may also affect other optimizations, we ignored the issue so far. check how stack + * heights stored in the call graph are used. + * Note: these tasks are not urgent because the call graph is not currently used during / after + * method-local optimizations, only before to perform inlining and closure rewriting. + */ + def boxUnboxElimination(method: MethodNode, owner: InternalName): Boolean = { + AsmAnalyzer.sizeOKForSourceValue(method) && { + val toInsertBefore = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]] + val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]] + val toDelete = mutable.Set.empty[AbstractInsnNode] + + val knownHandled = mutable.Set.empty[AbstractInsnNode] + + lazy val prodCons = new ProdConsAnalyzer(method, owner) + + var nextLocal = method.maxLocals + def getLocal(size: Int) = { + val r = nextLocal + nextLocal += size + r + } + + var maxStackGrowth = 0 + + /** Mehtod M1 for eliminating box-unbox pairs (see doc comment in the beginning of this file) */ + def replaceBoxOperationsSingleCreation(creation: BoxCreation, finalCons: Set[BoxConsumer], boxKind: BoxKind, keepBox: Boolean): Unit = { + /** + * If the box is eliminated, all copy operations (loads, stores, others) of the box need to + * be removed. This method returns all copy operations that should be removed. + * + * Returns `None` in case some exotic copy operation is found that cannot be removed + * (DUP2_X1 and friends - these are never emitted by scalac). In this case, the box cannot + * be eliminated. + */ + def copyOpsToEliminate: Option[Set[AbstractInsnNode]] = { + var elidableCopyOps = Set.empty[AbstractInsnNode] + var replaceOK = true + val copyOps = new CopyOpsIterator(Set(creation), finalCons, prodCons) + while (replaceOK && copyOps.hasNext) copyOps.next() match { + case vi: VarInsnNode => + elidableCopyOps += vi + + case copyOp if copyOp.getOpcode == DUP => + elidableCopyOps += copyOp + + case _ => + replaceOK = false + } + if (replaceOK) Some(elidableCopyOps) else None + } + + val canRewrite = keepBox || (copyOpsToEliminate match { + case Some(copyOps) => + toDelete ++= copyOps + true + + case _ => false + }) + + if (canRewrite) { + val localSlots: Vector[(Int, Type)] = boxKind.boxedTypes.map(tp => (getLocal(tp.getSize), tp))(collection.breakOut) + + // store boxed value(s) into localSlots + val storeOps = localSlots.toList reverseMap { case (slot, tp) => + new VarInsnNode(tp.getOpcode(ISTORE), slot) + } + val storeInitialValues = creation.loadInitialValues match { + case Some(ops) => ops ::: storeOps + case None => storeOps + } + if (keepBox) { + val loadOps: List[VarInsnNode] = localSlots.map({ case (slot, tp) => + new VarInsnNode(tp.getOpcode(ILOAD), slot) + })(collection.breakOut) + toInsertBefore(creation.valuesConsumer) = storeInitialValues ::: loadOps + } else { + toReplace(creation.valuesConsumer) = storeInitialValues + toDelete ++= creation.allInsns - creation.valuesConsumer + } + + // rewrite consumers + finalCons foreach { + case write: StaticSetterOrInstanceWrite => + assert(!keepBox, s"cannot eliminate box write if the box remains (and escapes): $write") + val (slot, tp) = localSlots(boxKind.extractedValueIndex(write)) + val storeOp = new VarInsnNode(tp.getOpcode(ISTORE), slot) + toReplace(write.consumer) = List(storeOp) + + case c: EscapingConsumer => + assert(keepBox, s"found escaping consumer, but box is eliminated: $c") + + case extraction => + val (slot, tp) = localSlots(boxKind.extractedValueIndex(extraction)) + val loadOps = new VarInsnNode(tp.getOpcode(ILOAD), slot) :: extraction.postExtractionAdaptationOps(tp) + if (keepBox) toReplace(extraction.consumer) = getPop(1) :: loadOps + else toReplace(extraction.consumer) = loadOps + toDelete ++= extraction.allInsns - extraction.consumer + } + } + } + + /** Method M2 for eliminating box-unbox pairs (see doc comment in the beginning of this file) */ + def replaceBoxOperationsMultipleCreations(allCreations: Set[BoxCreation], allConsumers: Set[BoxConsumer], boxKind: BoxKind): Unit = { + /** + * If a single-value size-1 box is eliminated, local variables slots holding the box are + * reused to hold the unboxed value. In case there's an entry for that local variable in the + * method's local variables table (debug info), adapt the type. + * + * If there are multiple entries for a local variable that's changing types, then all + * entries for that variable are deleted - it's not obvious how to find the correct entry. + * Note that scalac never re-uses local variable slots for non-overlapping locals. Also note + * that all locals that are newly created during the optimizer don't have an entry either. + * + * Finally, note that variables that become unused are removed later from the table by + * removeUnusedLocalVariableNodes in LocalOpt. + * + * Unlike modifications that affect the method's instructions (which uses toReplace etc), + * we can directly modify the local variable table - it does not affect the frames of the + * ProdCons analysis. + */ + def updateLocalVariableTypes(reTypedLocals: Map[Int, Type]): Unit = { + lazy val localsByIndex = method.localVariables.asScala.groupBy(_.index) + for ((index, tp) <- reTypedLocals) localsByIndex.get(index).map(_.toList) match { + case Some(List(local)) => + local.desc = tp.getDescriptor + case Some(locals) => + locals foreach method.localVariables.remove + case _ => + } + } + + /** Remove box creations - leave the boxed value(s) on the stack instead. */ + def replaceCreationOps(): Unit = { + for (creation <- allCreations) creation.loadInitialValues match { + case None => + toDelete ++= creation.allInsns + + case Some(ops) => + toReplace(creation.valuesConsumer) = ops + toDelete ++= (creation.allInsns - creation.valuesConsumer) + } + } + + /** + * Replace a value extraction operation. For a single-value box, the extraction operation can + * just be removed. An extraction from a multi-value box is replaced by POP operations for the + * non-used values, and an xSTORE / xLOAD for the extracted value. Example: tuple3._2 becomes + * POP; xSTORE n; POP; xLOAD n. + */ + def replaceExtractionOps(): Unit = { + if (boxKind.boxedTypes.lengthCompare(1) == 0) { + // fast path for single-value boxes + allConsumers.foreach(extraction => extraction.postExtractionAdaptationOps(boxKind.boxedTypes.head) match { + case Nil => + toDelete ++= extraction.allInsns + case ops => + toReplace(extraction.consumer) = ops + toDelete ++= extraction.allInsns - extraction.consumer + }) + } else { + for (extraction <- allConsumers) { + val valueIndex = boxKind.extractedValueIndex(extraction) + val replacementOps = if (valueIndex == 0) { + val pops = boxKind.boxedTypes.tail.map(t => getPop(t.getSize)) + pops ::: extraction.postExtractionAdaptationOps(boxKind.boxedTypes.head) + } else { + var loadOps: List[AbstractInsnNode] = null + val consumeStack = boxKind.boxedTypes.zipWithIndex reverseMap { + case (tp, i) => + if (i == valueIndex) { + val resultSlot = getLocal(tp.getSize) + loadOps = new VarInsnNode(tp.getOpcode(ILOAD), resultSlot) :: extraction.postExtractionAdaptationOps(tp) + new VarInsnNode(tp.getOpcode(ISTORE), resultSlot) + } else { + getPop(tp.getSize) + } + } + consumeStack ::: loadOps + } + toReplace(extraction.consumer) = replacementOps + toDelete ++= extraction.allInsns - extraction.consumer + } + } + } + + checkCopyOpReplacements(allCreations, allConsumers, boxKind.boxedTypes, nextLocal, prodCons) match { + case Some((replacements, nextCopyOpLocal, reTypedLocals)) => + toReplace ++= replacements + updateLocalVariableTypes(reTypedLocals) + nextLocal = nextCopyOpLocal + replaceCreationOps() + replaceExtractionOps() + // Conservative (safe) value for stack growth. In every frame that initially has a multi-value + // box on the stack, the stack now contains all of the individual values. So for every eliminated + // box, the maxStack may be up to N-1 slots larger. + maxStackGrowth += boxKind.boxedTypes.length - 1 + + case None => + } + } + + val it = method.instructions.iterator + while (it.hasNext) { + val insn = it.next() + if (!knownHandled(insn)) BoxKind.valueCreationKind(insn, prodCons) match { + case Some((boxCreation, boxKind)) => + allCreationsConsumers(boxCreation, boxKind, prodCons) match { + case Some((allCreations, allConsumers)) => + val (escapingConsumers, boxConsumers) = allConsumers.partition(_.isEscaping) + if (boxConsumers.nonEmpty) { + for (c <- allCreations) knownHandled ++= c.allInsns + for (e <- allConsumers) knownHandled ++= e.allInsns + + val hasEscaping = escapingConsumers.nonEmpty + val hasWrite = allConsumers.exists(_.isWrite) + if (!hasEscaping && !hasWrite) { + // M2 -- see doc comment in the beginning of this file + // If both M1 and M2 can be applied, we prefer M2 because it doesn't introduce new locals. + replaceBoxOperationsMultipleCreations(allCreations, allConsumers, boxKind) + } else if (allCreations.size == 1 && (!hasEscaping || !boxKind.isMutable)) { + // M1 -- see doc comment in the beginning of this file + replaceBoxOperationsSingleCreation(allCreations.head, allConsumers, boxKind, keepBox = hasEscaping) + } + } + + case None => + } + + case None => + } + } + + def removeFromCallGraph(insn: AbstractInsnNode): Unit = insn match { + case mi: MethodInsnNode => callGraph.removeCallsite(mi, method) + case _ => + } + + for ((location, ops) <- toInsertBefore; op <- ops) + method.instructions.insertBefore(location, op) + + for ((oldOp, newOps) <- toReplace) { + for (newOp <- newOps) method.instructions.insertBefore(oldOp, newOp) + method.instructions.remove(oldOp) + removeFromCallGraph(oldOp) + } + + for (op <- toDelete) { + method.instructions.remove(op) + removeFromCallGraph(op) + } + + method.maxLocals = nextLocal + method.maxStack += maxStackGrowth + toInsertBefore.nonEmpty || toReplace.nonEmpty || toDelete.nonEmpty + } + } + + /** + * Given a box creations operation + * - find all ultimate consumers for the produced value. then: + * - for all consumed values, find all producer operations. check that all are box creations + * - recurse until reaching a fixpoint + * + * Returns a set of box creations and a set of box consumers. Note that the box consumers may + * contain [[EscapingConsumer]]s, even if there are multiple box creation operations. The callee + * will handle this case (and not attempt to eliminate the box). + */ + def allCreationsConsumers(initialCreation: BoxCreation, boxKind: BoxKind, prodCons: ProdConsAnalyzer): Option[(Set[BoxCreation], Set[BoxConsumer])] = { + var creations = Set(initialCreation) + var consumers = Set.empty[BoxConsumer] + + def addCreations(boxConsumer: BoxConsumer): Boolean = { + val newProds = boxConsumer.boxProducers(prodCons).filterNot(prod => creations.exists(_.producer == prod)) + newProds.forall(prod => boxKind.checkBoxCreation(prod, prodCons) match { + case Some(boxCreation) => + creations += boxCreation + addBoxConsumers(boxCreation) + + case _ => false + }) + } + + def addBoxConsumers(creation: BoxCreation): Boolean = { + val newCons = creation.boxConsumers(prodCons, ultimate = true).filterNot(cons => consumers.exists(_.consumer == cons)) + newCons.forall(cons => boxKind.checkBoxConsumer(cons, prodCons) match { + case Some(boxConsumer) => + consumers += boxConsumer + addCreations(boxConsumer) + + case _ => + creations.size <= 1 && { + // If there's a single box creation, the box operations can still be rewritten + consumers += EscapingConsumer(cons) + true + } + }) + } + + if (addBoxConsumers(initialCreation)) Some((creations, consumers)) + else None + } + + /** + * Takes two sets `initialProds` and `finalCons` such that all boxes produced by the first set + * are only consumed by an operation in the second set. + * + * Returns a map that replaces copy operations (ALOAD / ASTORE) between the producers and + * consumers with corresponding copy operations for the values stored in the box. The returned + * `Int` value returns the next free local variable slot. + * + * Examples: + * - for an Integer box, an ASTORE x is simply replaced by ISTORE x + * - for a pair of two references, an ASTORE x is replaced by `ASTORE x1; ASTORE x2` where x1 + * and x2 are fresh locals + * + * Not all copy operations can be supported: DUP only works for single-value boxes, the more + * exotic copy operations (DUP2_X2) are not supported (note that Scalac never emits them). If a + * copy operation cannot be replaced, this method returns `None`. + */ + def checkCopyOpReplacements(initialProds: Set[BoxCreation], finalCons: Set[BoxConsumer], valueTypes: List[Type], nextLocal: Int, prodCons: ProdConsAnalyzer): Option[(Map[AbstractInsnNode, List[AbstractInsnNode]], Int, Map[Int, Type])] = { + var replacements = Map.empty[AbstractInsnNode, List[AbstractInsnNode]] + var reTypedLocals = Map.empty[Int, Type] + + var nextCopyOpLocal = nextLocal + val newLocalsMap: mutable.LongMap[List[(Type, Int)]] = mutable.LongMap.empty + def newLocals(index: Int) = newLocalsMap.getOrElseUpdate(index, valueTypes match { + case List(t) if t.getSize == 1 => + reTypedLocals += index -> t + List((t, index)) + case _ => valueTypes.map(t => { + val newIndex = nextCopyOpLocal + nextCopyOpLocal += t.getSize + (t, newIndex) + }) + }) + + var replaceOK = true + val copyOps = new CopyOpsIterator(initialProds, finalCons, prodCons) + while (replaceOK && copyOps.hasNext) copyOps.next() match { + case vi: VarInsnNode => + val isLoad = vi.getOpcode == ALOAD + val typedVarOp = (tp: (Type, Int)) => { + val opc = tp._1.getOpcode(if (isLoad) ILOAD else ISTORE) + new VarInsnNode(opc, tp._2) + } + val locs = newLocals(vi.`var`) + replacements += vi -> (if (isLoad) locs.map(typedVarOp) else locs.reverseMap(typedVarOp)) + + case copyOp => + if (copyOp.getOpcode == DUP && valueTypes.lengthCompare(1) == 0) { + if (valueTypes.head.getSize == 2) + replacements += copyOp -> List(new InsnNode(DUP2)) + } else { + replaceOK = false + } + } + if (replaceOK) Some((replacements, nextCopyOpLocal, reTypedLocals)) else None + } + + /** + * For a set of box creation operations and a corresponding set of box consumer operations, + * this iterator returns all copy operations (load, store, dup) that are in between. + */ + class CopyOpsIterator(initialCreations: Set[BoxCreation], finalCons: Set[BoxConsumer], prodCons: ProdConsAnalyzer) extends Iterator[AbstractInsnNode] { + private var queue = mutable.Queue.empty[AbstractInsnNode] ++ initialCreations.iterator.flatMap(_.boxConsumers(prodCons, ultimate = false)) + + // a single copy operation can consume multiple producers: val a = if (b) box(1) else box(2). + // the `ASTORE a` has two producers (the two box operations). we need to handle it only once. + private val visited = mutable.Set.empty[AbstractInsnNode] + + private val boxConsumingOps = finalCons.map(_.consumer) + + @tailrec private def advanceToNextCopyOp(): Unit = { + if (queue.nonEmpty) { + val h = queue.front + if (visited(h) || boxConsumingOps(h)) { + queue.dequeue() + advanceToNextCopyOp() + } + } + } + + def hasNext: Boolean = { + advanceToNextCopyOp() + queue.nonEmpty + } + + def next(): AbstractInsnNode = { + advanceToNextCopyOp() + val r = queue.dequeue() + visited += r + queue ++= prodCons.consumersOfOutputsFrom(r) + r + } + } + + trait BoxKind { + def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] + def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] + def boxedTypes: List[Type] + def extractedValueIndex(extraction: BoxConsumer): Int + def isMutable: Boolean + } + + object BoxKind { + def valueCreationKind(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[(BoxCreation, BoxKind)] = { + PrimitiveBox.checkPrimitiveBox(insn, None, prodCons) orElse + Ref.checkRefCreation(insn, None, prodCons) orElse + Tuple.checkTupleCreation(insn, None, prodCons) + } + + /** + * Check if `newOp` is part of a standard object construction pattern in which: + * + * NEW T + * DUP + * [load constructor args] + * INVOKESPECIAL T.init + * + * The method ensures that the entire construction pattern is closed in itself, without any + * branches going in or out. This is checked by looking at producers / consumers: + * - `DUP` is the only consumer of `NEW`, and vice versa + * - `DUP` the only producer for the receiver of the constructor call + * - The set of consumers of `DUP` without the constructor call is the same as + * the set of consumers of the value on the stack top after the constructor call + */ + def checkInstanceCreation(newOp: TypeInsnNode, prodCons: ProdConsAnalyzer): Option[(InsnNode, MethodInsnNode)] = { + val newCons = prodCons.consumersOfOutputsFrom(newOp) + if (newCons.size == 1 && newCons.head.getOpcode == DUP) { + val dupOp = newCons.head.asInstanceOf[InsnNode] + if (prodCons.producersForInputsOf(dupOp) == Set(newOp)) { + val dupCons = prodCons.consumersOfOutputsFrom(dupOp) + val initCalls = dupCons collect { + case mi: MethodInsnNode if mi.name == GenBCode.INSTANCE_CONSTRUCTOR_NAME && mi.owner == newOp.desc => mi + } + if (initCalls.size == 1) { + val initCall = initCalls.head + val numArgs = Type.getArgumentTypes(initCall.desc).length + val receiverProds = prodCons.producersForValueAt(initCall, prodCons.frameAt(initCall).stackTop - numArgs) + if (receiverProds == Set(dupOp)) { + val dupConsWithoutInit = dupCons - initCall + val afterInit = initCall.getNext + val stackTopAfterInit = prodCons.frameAt(afterInit).stackTop + val initializedInstanceCons = prodCons.consumersOfValueAt(afterInit, stackTopAfterInit) + if (initializedInstanceCons == dupConsWithoutInit && prodCons.producersForValueAt(afterInit, stackTopAfterInit) == Set(dupOp)) { + return Some((dupOp, initCall)) + } + } + } + } + } + None + } + + /** + * If `mi` is an invocation of a method on Predef, check if the receiver is a GETSTATIC of + * Predef.MODULE$ and return it. + */ + def checkReceiverPredefLoad(mi: MethodInsnNode, prodCons: ProdConsAnalyzer): Option[AbstractInsnNode] = { + val numArgs = Type.getArgumentTypes(mi.desc).length + val receiverProds = prodCons.producersForValueAt(mi, prodCons.frameAt(mi).stackTop - numArgs) + if (receiverProds.size == 1) { + val prod = receiverProds.head + if (isPredefLoad(prod) && prodCons.consumersOfOutputsFrom(prod) == Set(mi)) return Some(prod) + } + None + } + } + + case class PrimitiveBox(boxedType: Type, boxClass: InternalName) extends BoxKind { + import PrimitiveBox._ + def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkPrimitiveBox(insn, Some(this), prodCons).map(_._1) + def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkPrimitiveUnbox(insn, this, prodCons) + def boxedTypes: List[Type] = List(boxedType) + def extractedValueIndex(extraction: BoxConsumer): Int = 0 + def isMutable = false + } + + object PrimitiveBox { + private def boxedType(mi: MethodInsnNode) = Type.getArgumentTypes(mi.desc)(0) + + private def boxClass(mi: MethodInsnNode) = { + if (mi.name == GenBCode.INSTANCE_CONSTRUCTOR_NAME) mi.owner + else Type.getReturnType(mi.desc).getInternalName + } + + def checkPrimitiveBox(insn: AbstractInsnNode, expectedKind: Option[PrimitiveBox], prodCons: ProdConsAnalyzer): Option[(BoxCreation, PrimitiveBox)] = { + // mi is either a box factory or a box constructor invocation + def checkKind(mi: MethodInsnNode) = expectedKind match { + case Some(kind) => if (kind.boxClass == boxClass(mi)) expectedKind else None + case None => Some(PrimitiveBox(boxedType(mi), boxClass(mi))) + } + + insn match { + case mi: MethodInsnNode => + if (isScalaBox(mi) || isJavaBox(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = None), _)) + else if (isPredefAutoBox(mi)) + for (predefLoad <- BoxKind.checkReceiverPredefLoad(mi, prodCons); kind <- checkKind(mi)) + yield (ModuleFactory(predefLoad, mi), kind) + else None + + case ti: TypeInsnNode if ti.getOpcode == NEW => + for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isPrimitiveBoxConstructor(initCall); kind <- checkKind(initCall)) + yield (InstanceCreation(ti, dupOp, initCall), kind) + + case _ => None + } + } + + def checkPrimitiveUnbox(insn: AbstractInsnNode, kind: PrimitiveBox, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = { + def typeOK(mi: MethodInsnNode) = kind.boxedType == Type.getReturnType(mi.desc) + insn match { + case mi: MethodInsnNode => + if ((isScalaUnbox(mi) || isJavaUnbox(mi)) && typeOK(mi)) Some(StaticGetterOrInstanceRead(mi)) + else if (isPredefAutoUnbox(mi) && typeOK(mi)) BoxKind.checkReceiverPredefLoad(mi, prodCons).map(ModuleGetter(_, mi)) + else None + + case _ => None + } + } + } + + case class Ref(boxedType: Type, refClass: InternalName) extends BoxKind { + import Ref._ + def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkRefCreation(insn, Some(this), prodCons).map(_._1) + def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkRefConsumer(insn, this, prodCons) + def boxedTypes: List[Type] = List(boxedType) + def extractedValueIndex(extraction: BoxConsumer): Int = 0 + def isMutable = true + } + + object Ref { + private def boxedType(mi: MethodInsnNode): Type = runtimeRefClassBoxedType(mi.owner) + private def refClass(mi: MethodInsnNode): InternalName = mi.owner + private def loadZeroValue(refZeroCall: MethodInsnNode): List[AbstractInsnNode] = List(loadZeroForTypeSort(runtimeRefClassBoxedType(refZeroCall.owner).getSort)) + + def checkRefCreation(insn: AbstractInsnNode, expectedKind: Option[Ref], prodCons: ProdConsAnalyzer): Option[(BoxCreation, Ref)] = { + def checkKind(mi: MethodInsnNode): Option[Ref] = expectedKind match { + case Some(kind) => if (kind.refClass == refClass(mi)) expectedKind else None + case None => Some(Ref(boxedType(mi), refClass(mi))) + } + + insn match { + case mi: MethodInsnNode => + if (isRefCreate(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = None), _)) + else if (isRefZero(mi)) checkKind(mi).map((StaticFactory(mi, loadInitialValues = Some(loadZeroValue(mi))), _)) + else None + + case ti: TypeInsnNode if ti.getOpcode == NEW => + for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isRuntimeRefConstructor(initCall); kind <- checkKind(initCall)) + yield (InstanceCreation(ti, dupOp, initCall), kind) + + case _ => None + } + } + + def checkRefConsumer(insn: AbstractInsnNode, kind: Ref, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = insn match { + case fi: FieldInsnNode if fi.owner == kind.refClass && fi.name == "elem" => + if (fi.getOpcode == GETFIELD) Some(StaticGetterOrInstanceRead(fi)) + else if (fi.getOpcode == PUTFIELD) Some(StaticSetterOrInstanceWrite(fi)) + else None + + case _ => None + } + } + + case class Tuple(boxedTypes: List[Type], tupleClass: InternalName) extends BoxKind { + import Tuple._ + def checkBoxCreation(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxCreation] = checkTupleCreation(insn, Some(this), prodCons).map(_._1) + def checkBoxConsumer(insn: AbstractInsnNode, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = checkTupleExtraction(insn, this, prodCons) + def extractedValueIndex(extraction: BoxConsumer): Int = extraction match { + case StaticGetterOrInstanceRead(mi: MethodInsnNode) => tupleGetterIndex(mi.name) + case PrimitiveBoxingGetter(mi) => tupleGetterIndex(mi.name) + case PrimitiveUnboxingGetter(mi, _) => tupleGetterIndex(mi.name) + case _ => throw new AssertionError(s"Expected tuple getter, found $extraction") + } + def isMutable = false + } + + object Tuple { + private def boxedTypes(mi: MethodInsnNode): List[Type] = Type.getArgumentTypes(mi.desc).toList + private def tupleClass(mi: MethodInsnNode): InternalName = mi.owner + + def checkTupleCreation(insn: AbstractInsnNode, expectedKind: Option[Tuple], prodCons: ProdConsAnalyzer): Option[(BoxCreation, Tuple)] = { + def checkKind(mi: MethodInsnNode): Option[Tuple] = expectedKind match { + case Some(kind) => if (kind.tupleClass == tupleClass(mi)) expectedKind else None + case None => Some(Tuple(boxedTypes(mi), tupleClass(mi))) + } + + insn match { + // no need to check for TupleN.apply: the compiler transforms case companion apply calls to constructor invocations + case ti: TypeInsnNode if ti.getOpcode == NEW => + for ((dupOp, initCall) <- BoxKind.checkInstanceCreation(ti, prodCons) if isTupleConstructor(initCall); kind <- checkKind(initCall)) + yield (InstanceCreation(ti, dupOp, initCall), kind) + + case _ => None + } + } + + private val specializedTupleClassR = "scala/Tuple[12]\\$mc[IJDCZ]{1,2}\\$sp".r + private def isSpecializedTupleClass(tupleClass: InternalName) = specializedTupleClassR.pattern.matcher(tupleClass).matches + + private val specializedTupleGetterR = "_[12]\\$mc[IJDCZ]\\$sp".r + private def isSpecializedTupleGetter(mi: MethodInsnNode) = specializedTupleGetterR.pattern.matcher(mi.name).matches + + private val tupleGetterR = "_\\d\\d?".r + private def isTupleGetter(mi: MethodInsnNode) = tupleGetterR.pattern.matcher(mi.name).matches + + def checkTupleExtraction(insn: AbstractInsnNode, kind: Tuple, prodCons: ProdConsAnalyzer): Option[BoxConsumer] = { + val expectedTupleClass = kind.tupleClass + insn match { + case mi: MethodInsnNode => + val tupleClass = mi.owner + if (isSpecializedTupleClass(expectedTupleClass)) { + val typeOK = tupleClass == expectedTupleClass || tupleClass == expectedTupleClass.substring(0, expectedTupleClass.indexOf('$')) + if (typeOK) { + if (isSpecializedTupleGetter(mi)) return Some(StaticGetterOrInstanceRead(mi)) + else if (isTupleGetter(mi)) return Some(PrimitiveBoxingGetter(mi)) + } + } else if (expectedTupleClass == tupleClass) { + if (isSpecializedTupleGetter(mi)) return Some(PrimitiveUnboxingGetter(mi, Type.getReturnType(mi.desc))) + else if (isTupleGetter(mi)) return Some(StaticGetterOrInstanceRead(mi)) + } + + case _ => + } + None + } + + private val getterIndexPattern = "_(\\d{1,2}).*".r + def tupleGetterIndex(getterName: String) = getterName match { case getterIndexPattern(i) => i.toInt - 1 } + } + + // TODO: add more + // case class ValueClass(valueClass: Type, valueType: Type) extends BoxKind + + sealed trait BoxCreation { + // to support box creation operations that don't consume an initial value from the stack, e.g., IntRef.zero + val loadInitialValues: Option[List[AbstractInsnNode]] + + /** + * The instruction that produces the box value; for instance creations, the `NEW` operation. + */ + def producer: AbstractInsnNode + + /** + * The instruction that consumes the boxed values; for instance creations, the `init` call. + */ + def valuesConsumer: MethodInsnNode = this match { + case StaticFactory(call, _) => call + case ModuleFactory(_, call) => call + case InstanceCreation(_, _, initCall) => initCall + } + + def allInsns: Set[AbstractInsnNode] = this match { + case StaticFactory(c, _) => Set(c) + case ModuleFactory(m, c) => Set(m, c) + case InstanceCreation(n, d, i) => Set(n, d, i) + } + + /** + * The consumers of the box produced by this box creation. If `ultimate` is true, then the + * final consumers are returned (e.g., an unbox operation), otherwise direct consumers (e.g., + * a store operation). + */ + def boxConsumers(prodCons: ProdConsAnalyzer, ultimate: Boolean): Set[AbstractInsnNode] = { + val startInsn = this match { + // for the non-transitive case (ultimate == false), it's important to start at the `dupOp`, + // not the `newOp` - look at the BoxCreation as a black box, get its consumers. + case InstanceCreation(_, dupOp, _) => dupOp + case _ => producer + } + val cons = if (ultimate) prodCons.ultimateConsumersOfOutputsFrom(startInsn) else prodCons.consumersOfOutputsFrom(startInsn) + this match { + case InstanceCreation(_, _, initCall) => cons - initCall + case _ => cons + } + } + } + + case class StaticFactory(producer: MethodInsnNode, loadInitialValues: Option[List[AbstractInsnNode]]) extends BoxCreation + case class ModuleFactory(moduleLoad: AbstractInsnNode, producer: MethodInsnNode) extends BoxCreation { + val loadInitialValues: Option[List[AbstractInsnNode]] = None + } + case class InstanceCreation(newOp: TypeInsnNode, dupOp: InsnNode, initCall: MethodInsnNode) extends BoxCreation { + def producer = newOp + val loadInitialValues: Option[List[AbstractInsnNode]] = None + } + + sealed trait BoxConsumer { + val consumer: AbstractInsnNode + + def allInsns: Set[AbstractInsnNode] = this match { + case ModuleGetter(m, c) => Set(m, c) + case _ => Set(consumer) + } + + /** + * The initial producers of the box value consumed by this box consumer + */ + def boxProducers(prodCons: ProdConsAnalyzer): Set[AbstractInsnNode] = { + val stackTop = prodCons.frameAt(consumer).stackTop + val slot = if (isWrite) stackTop - 1 else stackTop + prodCons.initialProducersForValueAt(consumer, slot) + } + + def isEscaping = this match { + case _: EscapingConsumer => true + case _ => false + } + + def isWrite = this match { + case _: StaticSetterOrInstanceWrite => true + case _ => false + } + + /** + * If this box consumer extracts a boxed value and applies a conversion, this method returns + * equivalent conversion operations. For example, invoking `_1$mcI$sp` on a non-specialized + * `Tuple2` extracts the Integer value and unboxes it. + */ + def postExtractionAdaptationOps(typeOfExtractedValue: Type): List[AbstractInsnNode] = this match { + case PrimitiveBoxingGetter(_) => List(getScalaBox(typeOfExtractedValue)) + case PrimitiveUnboxingGetter(_, unboxedPrimitive) => List(getScalaUnbox(unboxedPrimitive)) + case _ => Nil + } + } + + /** Static extractor (BoxesRunTime.unboxToInt) or GETFIELD or getter invocation */ + case class StaticGetterOrInstanceRead(consumer: AbstractInsnNode) extends BoxConsumer + /** A getter that boxes the returned value, e.g., `Tuple2$mcII$sp._1` */ + case class PrimitiveBoxingGetter(consumer: MethodInsnNode) extends BoxConsumer + /** A getter that unboxes the returned value, e.g., `Tuple2._1$mcI$sp` */ + case class PrimitiveUnboxingGetter(consumer: MethodInsnNode, unboxedPrimitive: Type) extends BoxConsumer + /** An extractor method in a Scala module, e.g., `Predef.Integer2int` */ + case class ModuleGetter(moduleLoad: AbstractInsnNode, consumer: MethodInsnNode) extends BoxConsumer + /** PUTFIELD or setter invocation */ + case class StaticSetterOrInstanceWrite(consumer: AbstractInsnNode) extends BoxConsumer + /** An unknown box consumer */ + case class EscapingConsumer(consumer: AbstractInsnNode) extends BoxConsumer +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index a5b85e54e7..4492d0baf5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -10,6 +10,7 @@ package opt import scala.tools.asm import asm.tree._ import scala.collection.convert.decorateAsScala._ +import scala.collection.concurrent import scala.tools.asm.Attribute import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.io.AbstractFile @@ -24,39 +25,52 @@ import java.util.concurrent.atomic.AtomicLong * classpath. Parsed classes are cached in the `classes` map. * * @param classPath The compiler classpath where classfiles are searched and read from. - * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode: - * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode - * corresponds to a class being compiled. - * The `Long` field encodes the age of the node in the map, which allows removing - * old entries when the map grows too large. - * For Java classes in mixed compilation, the map contains an error message: no - * ClassNode is generated by the backend and also no classfile that could be parsed. */ -class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) { +class ByteCodeRepository[BT <: BTypes](val classPath: ClassFileLookup[AbstractFile], val btypes: BT) { + import btypes._ + + /** + * ClassNodes for classes being compiled in the current compilation run. + */ + val compilingClasses: concurrent.Map[InternalName, ClassNode] = recordPerRunCache(concurrent.TrieMap.empty) + + /** + * Cache for parsed ClassNodes. + * The `Long` field encodes the age of the node in the map, which allows removing old entries when + * the map grows too large (see limitCacheSize). + * For Java classes in mixed compilation, the map contains an error message: no ClassNode is + * generated by the backend and also no classfile that could be parsed. + */ + val parsedClasses: concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Long)]] = recordPerRunCache(concurrent.TrieMap.empty) private val maxCacheSize = 1500 private val targetSize = 500 - private val idCounter = new AtomicLong(0) + private object lruCounter extends AtomicLong(0l) with collection.generic.Clearable { + def clear(): Unit = { this.set(0l) } + } + recordPerRunCache(lruCounter) /** * Prevent the code repository from growing too large. Profiling reveals that the average size * of a ClassNode is about 30 kb. I observed having 17k+ classes in the cache, i.e., 500 mb. - * - * We can only remove classes with `Source == Classfile`, those can be parsed again if requested. */ private def limitCacheSize(): Unit = { - if (classes.count(c => c._2.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) { - val removeId = idCounter.get - targetSize - val toRemove = classes.iterator.collect({ - case (name, Right((_, Classfile, id))) if id < removeId => name - }).toList - toRemove foreach classes.remove + if (parsedClasses.size > maxCacheSize) { + // OK if multiple threads get here + val minimalLRU = parsedClasses.valuesIterator.collect({ + case Right((_, lru)) => lru + }).toList.sorted(Ordering.Long.reverse).drop(targetSize).headOption.getOrElse(Long.MaxValue) + parsedClasses retain { + case (_, Right((_, lru))) => lru > minimalLRU + case _ => false + } } } def add(classNode: ClassNode, source: Source) = { - classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet())) + if (source == CompilationUnit) compilingClasses(classNode.name) = classNode + else parsedClasses(classNode.name) = Right((classNode, lruCounter.incrementAndGet())) } /** @@ -64,18 +78,32 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav * parsed from the classfile on the compile classpath. */ def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = { - val r = classes.getOrElseUpdate(internalName, { - limitCacheSize() - parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet())) + classNode(internalName) map (n => { + val source = if (compilingClasses contains internalName) CompilationUnit else Classfile + (n, source) }) - r.map(v => (v._1, v._2)) } /** * The class node for an internal name. If the class node is not yet available, it is parsed from * the classfile on the compile classpath. */ - def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1) + def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = { + compilingClasses.get(internalName).map(Right(_)) getOrElse { + val r = parsedClasses.get(internalName) match { + case Some(l @ Left(_)) => l + case Some(r @ Right((classNode, _))) => + parsedClasses(internalName) = Right((classNode, lruCounter.incrementAndGet())) + r + case None => + limitCacheSize() + val res = parseClass(internalName).map((_, lruCounter.incrementAndGet())) + parsedClasses(internalName) = res + res + } + r.map(_._1) + } + } /** * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. @@ -86,7 +114,6 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav */ def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Either[FieldNotFound, (FieldNode, InternalName)] = { def fieldNodeImpl(parent: InternalName): Either[FieldNotFound, (FieldNode, InternalName)] = { - def msg = s"The field node $name$descriptor could not be found in class $classInternalName or any of its superclasses." classNode(parent) match { case Left(e) => Left(FieldNotFound(name, descriptor, classInternalName, Some(e))) case Right(c) => @@ -105,6 +132,11 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav * The method node for a method matching `name` and `descriptor`, accessed in class `ownerInternalNameOrArrayDescriptor`. * The declaration of the method may be in one of the parents. * + * TODO: make sure we always return the right method, the one being invoked. write tests. + * - if there's an abstract and a concrete one. could possibly somehow the abstract be returned? + * - with traits and default methods, if there is more than one default method inherited and + * no override: what should be returned? We should not just inline one of the two. + * * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring * class, or an error message if the method could not be found. */ @@ -157,7 +189,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav classNode } match { case Some(node) => Right(node) - case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName))) + case None => Left(ClassNotFound(internalName, javaDefinedClasses(internalName))) } } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 7aadd2c466..2afc095af6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -8,28 +8,29 @@ package backend.jvm package opt import scala.annotation.{tailrec, switch} + import scala.collection.mutable import scala.reflect.internal.util.Collections._ import scala.tools.asm.commons.CodeSizeEvaluator import scala.tools.asm.tree.analysis._ -import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type} +import scala.tools.asm.{Label, Type} +import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ import GenBCode._ import scala.collection.convert.decorateAsScala._ -import scala.collection.convert.decorateAsJava._ -import scala.tools.nsc.backend.jvm.BTypes._ +import scala.tools.nsc.backend.jvm.analysis.InstructionStackEffect object BytecodeUtils { // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9.1 - final val maxJVMMethodSize = 65535 + final val maxJVMMethodSize = 65535 // 5% margin, more than enough for the instructions added by the inliner (store / load args, null check for instance methods) final val maxMethodSizeAfterInline = maxJVMMethodSize - (maxJVMMethodSize / 20) object Goto { def unapply(instruction: AbstractInsnNode): Option[JumpInsnNode] = { - if (instruction.getOpcode == Opcodes.GOTO) Some(instruction.asInstanceOf[JumpInsnNode]) + if (instruction.getOpcode == GOTO) Some(instruction.asInstanceOf[JumpInsnNode]) else None } } @@ -49,8 +50,9 @@ object BytecodeUtils { } object VarInstruction { - def unapply(instruction: AbstractInsnNode): Option[VarInsnNode] = { - if (isVarInstruction(instruction)) Some(instruction.asInstanceOf[VarInsnNode]) + def unapply(instruction: AbstractInsnNode): Option[(AbstractInsnNode, Int)] = { + if (isLoadStoreOrRet(instruction)) Some((instruction, instruction.asInstanceOf[VarInsnNode].`var`)) + else if (instruction.getOpcode == IINC) Some((instruction, instruction.asInstanceOf[IincInsnNode].`var`)) else None } @@ -59,30 +61,37 @@ object BytecodeUtils { def isJumpNonJsr(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode // JSR is deprecated in classfile version 50, disallowed in 51. historically, it was used to implement finally. - op == Opcodes.GOTO || isConditionalJump(instruction) + op == GOTO || isConditionalJump(instruction) } def isConditionalJump(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - (op >= Opcodes.IFEQ && op <= Opcodes.IF_ACMPNE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL + (op >= IFEQ && op <= IF_ACMPNE) || op == IFNULL || op == IFNONNULL } def isReturn(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op >= Opcodes.IRETURN && op <= Opcodes.RETURN + op >= IRETURN && op <= RETURN } def isLoad(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op >= Opcodes.ILOAD && op <= Opcodes.ALOAD + op >= ILOAD && op <= ALOAD } def isStore(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op >= Opcodes.ISTORE && op <= Opcodes.ASTORE + op >= ISTORE && op <= ASTORE } - def isVarInstruction(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) + def isLoadStoreOrRet(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) || instruction.getOpcode == RET + + def isLoadOrStore(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) + + def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = { + val op = instruction.getOpcode + op == INVOKESPECIAL || op == INVOKESTATIC + } def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 @@ -90,27 +99,34 @@ object BytecodeUtils { methodNode.name == INSTANCE_CONSTRUCTOR_NAME || methodNode.name == CLASS_CONSTRUCTOR_NAME } - def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STATIC) != 0 + def isStaticMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STATIC) != 0 + + def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_ABSTRACT) != 0 - def isAbstractMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_ABSTRACT) != 0 + def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_SYNCHRONIZED) != 0 - def isSynchronizedMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) != 0 + def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_NATIVE) != 0 - def isNativeMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_NATIVE) != 0 + def hasCallerSensitiveAnnotation(methodNode: MethodNode) = methodNode.visibleAnnotations != null && methodNode.visibleAnnotations.asScala.exists(_.desc == "Lsun/reflect/CallerSensitive;") - def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0 + def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & ACC_FINAL) != 0 - def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0 + def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (ACC_FINAL | ACC_PRIVATE | ACC_STATIC)) != 0 - def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0 + def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & ACC_STRICT) != 0 def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY - def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { - var result = instruction - do { result = result.getNext } - while (result != null && !isExecutable(result) && !alsoKeep(result)) - Option(result) + @tailrec def nextExecutableInstruction(insn: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { + val next = insn.getNext + if (next == null || isExecutable(next) || alsoKeep(next)) Option(next) + else nextExecutableInstruction(next, alsoKeep) + } + + @tailrec def nextExecutableInstructionOrLabel(insn: AbstractInsnNode): Option[AbstractInsnNode] = { + val next = insn.getNext + if (next == null || isExecutable(next) || next.isInstanceOf[LabelNode]) Option(next) + else nextExecutableInstructionOrLabel(next) } def sameTargetExecutableInstruction(a: JumpInsnNode, b: JumpInsnNode): Boolean = { @@ -124,14 +140,14 @@ object BytecodeUtils { def removeJumpAndAdjustStack(method: MethodNode, jump: JumpInsnNode) { val instructions = method.instructions val op = jump.getOpcode - if ((op >= Opcodes.IFEQ && op <= Opcodes.IFGE) || op == Opcodes.IFNULL || op == Opcodes.IFNONNULL) { + if ((op >= IFEQ && op <= IFLE) || op == IFNULL || op == IFNONNULL) { instructions.insert(jump, getPop(1)) - } else if ((op >= Opcodes.IF_ICMPEQ && op <= Opcodes.IF_ICMPLE) || op == Opcodes.IF_ACMPEQ || op == Opcodes.IF_ACMPNE) { + } else if ((op >= IF_ICMPEQ && op <= IF_ICMPLE) || op == IF_ACMPEQ || op == IF_ACMPNE) { instructions.insert(jump, getPop(1)) instructions.insert(jump, getPop(1)) } else { // we can't remove JSR: its execution does not only jump, it also adds a return address to the stack - assert(jump.getOpcode == Opcodes.GOTO) + assert(jump.getOpcode == GOTO) } instructions.remove(jump) } @@ -148,37 +164,61 @@ object BytecodeUtils { } def negateJumpOpcode(jumpOpcode: Int): Int = (jumpOpcode: @switch) match { - case Opcodes.IFEQ => Opcodes.IFNE - case Opcodes.IFNE => Opcodes.IFEQ + case IFEQ => IFNE + case IFNE => IFEQ - case Opcodes.IFLT => Opcodes.IFGE - case Opcodes.IFGE => Opcodes.IFLT + case IFLT => IFGE + case IFGE => IFLT - case Opcodes.IFGT => Opcodes.IFLE - case Opcodes.IFLE => Opcodes.IFGT + case IFGT => IFLE + case IFLE => IFGT - case Opcodes.IF_ICMPEQ => Opcodes.IF_ICMPNE - case Opcodes.IF_ICMPNE => Opcodes.IF_ICMPEQ + case IF_ICMPEQ => IF_ICMPNE + case IF_ICMPNE => IF_ICMPEQ - case Opcodes.IF_ICMPLT => Opcodes.IF_ICMPGE - case Opcodes.IF_ICMPGE => Opcodes.IF_ICMPLT + case IF_ICMPLT => IF_ICMPGE + case IF_ICMPGE => IF_ICMPLT - case Opcodes.IF_ICMPGT => Opcodes.IF_ICMPLE - case Opcodes.IF_ICMPLE => Opcodes.IF_ICMPGT + case IF_ICMPGT => IF_ICMPLE + case IF_ICMPLE => IF_ICMPGT - case Opcodes.IF_ACMPEQ => Opcodes.IF_ACMPNE - case Opcodes.IF_ACMPNE => Opcodes.IF_ACMPEQ + case IF_ACMPEQ => IF_ACMPNE + case IF_ACMPNE => IF_ACMPEQ - case Opcodes.IFNULL => Opcodes.IFNONNULL - case Opcodes.IFNONNULL => Opcodes.IFNULL + case IFNULL => IFNONNULL + case IFNONNULL => IFNULL + } + + def isSize2LoadOrStore(opcode: Int): Boolean = (opcode: @switch) match { + case LLOAD | DLOAD | LSTORE | DSTORE => true + case _ => false } def getPop(size: Int): InsnNode = { - val op = if (size == 1) Opcodes.POP else Opcodes.POP2 + val op = if (size == 1) POP else POP2 new InsnNode(op) } - def instructionResultSize(instruction: AbstractInsnNode) = InstructionResultSize(instruction) + def instructionResultSize(insn: AbstractInsnNode) = InstructionStackEffect.prod(InstructionStackEffect.forClassfile(insn)) + + def loadZeroForTypeSort(sort: Int) = (sort: @switch) match { + case Type.BOOLEAN | + Type.BYTE | + Type.CHAR | + Type.SHORT | + Type.INT => new InsnNode(ICONST_0) + case Type.LONG => new InsnNode(LCONST_0) + case Type.FLOAT => new InsnNode(FCONST_0) + case Type.DOUBLE => new InsnNode(DCONST_0) + case Type.OBJECT => new InsnNode(ACONST_NULL) + } + + /** + * The number of local variable slots used for parameters and for the `this` reference. + */ + def parametersSize(methodNode: MethodNode): Int = { + (Type.getArgumentsAndReturnSizes(methodNode.desc) >> 2) - (if (isStaticMethod(methodNode)) 1 else 0) + } def labelReferences(method: MethodNode): Map[LabelNode, Set[AnyRef]] = { val res = mutable.Map.empty[LabelNode, Set[AnyRef]] @@ -222,29 +262,6 @@ object BytecodeUtils { } } - /** - * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM - * framework only computes these values during bytecode generation. - * - * Since there's currently no better way, we run a bytecode generator on the method and extract - * the computed values. This required changes to the ASM codebase: - * - the [[MethodWriter]] class was made public - * - accessors for maxLocals / maxStack were added to the MethodWriter class - * - * We could probably make this faster (and allocate less memory) by hacking the ASM framework - * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be - * to create a separate visitor for computing those values, duplicating the functionality from the - * MethodWriter. - */ - def computeMaxLocalsMaxStack(method: MethodNode): Unit = { - val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) - val excs = method.exceptions.asScala.toArray - val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter] - method.accept(mw) - method.maxLocals = mw.getMaxLocals - method.maxStack = mw.getMaxStack - } - def codeSizeOKForInlining(caller: MethodNode, callee: MethodNode): Boolean = { // Looking at the implementation of CodeSizeEvaluator, all instructions except tableswitch and // lookupswitch are <= 8 bytes. These should be rare enough for 8 to be an OK rough upper bound. @@ -289,33 +306,17 @@ object BytecodeUtils { } /** - * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to - * the `labelMap`. Returns the new instruction list and a map from old to new instructions. - */ - def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode]) = { - val javaLabelMap = labelMap.asJava - val result = new InsnList - var map = Map.empty[AbstractInsnNode, AbstractInsnNode] - for (ins <- methodNode.instructions.iterator.asScala) { - val cloned = ins.clone(javaLabelMap) - result add cloned - map += ((ins, cloned)) - } - (result, map) - } - - /** * Clone the local variable descriptors of `methodNode` and map their `start` and `end` labels * according to the `labelMap`. */ - def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String): List[LocalVariableNode] = { + def cloneLocalVariableNodes(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], prefix: String, shift: Int): List[LocalVariableNode] = { methodNode.localVariables.iterator().asScala.map(localVariable => new LocalVariableNode( prefix + localVariable.name, localVariable.desc, localVariable.signature, labelMap(localVariable.start), labelMap(localVariable.end), - localVariable.index + localVariable.index + shift )).toList } @@ -344,23 +345,14 @@ object BytecodeUtils { * method which explains the issue with such phantom values. */ def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { - if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) { - methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW)) - } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) { - methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL)) - methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP)) + if (loadedType == bTypes.coreBTypes.srNothingRef.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(ATHROW)) + } else if (loadedType == bTypes.coreBTypes.srNullRef.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(ACONST_NULL)) + methodNode.instructions.insert(loadInstr, new InsnNode(POP)) } } - /** - * A wrapper to make ASM's Analyzer a bit easier to use. - */ - class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) { - val analyzer = new Analyzer(interpreter) - analyzer.analyze(classInternalName, methodNode) - def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) - } - implicit class AnalyzerExtensions[V <: Value](val analyzer: Analyzer[V]) extends AnyVal { def frameAt(instruction: AbstractInsnNode, methodNode: MethodNode): Frame[V] = analyzer.getFrames()(methodNode.instructions.indexOf(instruction)) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index 96455c0e38..5f06e13560 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -7,182 +7,331 @@ package scala.tools.nsc package backend.jvm package opt +import scala.collection.immutable.IntMap import scala.reflect.internal.util.{NoPosition, Position} -import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter} import scala.tools.asm.{Opcodes, Type, Handle} import scala.tools.asm.tree._ -import scala.collection.concurrent +import scala.collection.{concurrent, mutable} import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.tools.nsc.backend.jvm.BackendReporting._ -import scala.tools.nsc.backend.jvm.analysis.{NotNull, NullnessAnalyzer} +import scala.tools.nsc.backend.jvm.analysis._ import ByteCodeRepository.{Source, CompilationUnit} import BytecodeUtils._ class CallGraph[BT <: BTypes](val btypes: BT) { import btypes._ + import backendUtils._ - val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty) + /** + * The call graph contains the callsites in the program being compiled. + * + * Indexing the call graph by the containing MethodNode and the invocation MethodInsnNode allows + * finding callsites efficiently. For example, an inlining heuristic might want to know all + * callsites withing a callee method. + * + * Note that the call graph is not guaranteed to be complete: callsites may be missing. In + * particular, if a method is very large, all of its callsites might not be in the hash map. + * The reason is that adding a method to the call graph requires running an ASM analyzer, which + * can be too slow. + * + * Note that call graph entries (Callsite instances) keep a reference to the invocation + * MethodInsnNode, which keeps all AbstractInsnNodes of the method reachable. Adding classes + * from the classpath to the call graph (in addition to classes being compiled) may prevent + * method instruction nodes from being GCd. The ByteCodeRepository has a fixed size cache for + * parsed ClassNodes - keeping all ClassNodes alive consumed too much memory. + * The call graph is less problematic because only methods being called are kept alive, not entire + * classes. But we should keep an eye on this. + */ + val callsites: mutable.Map[MethodNode, Map[MethodInsnNode, Callsite]] = recordPerRunCache(concurrent.TrieMap.empty withDefaultValue Map.empty) - val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, ClosureInstantiation] = recordPerRunCache(concurrent.TrieMap.empty) + /** + * Closure instantiations in the program being compiled. + * + * Indexing closure instantiations by the containing MethodNode is beneficial for the closure + * optimizer: finding callsites to re-write requires running a producers-consumers analysis on + * the method. Here the closure instantiations are already grouped by method. + */ + val closureInstantiations: mutable.Map[MethodNode, Map[InvokeDynamicInsnNode, ClosureInstantiation]] = recordPerRunCache(concurrent.TrieMap.empty withDefaultValue Map.empty) + + def removeCallsite(invocation: MethodInsnNode, methodNode: MethodNode): Option[Callsite] = { + val methodCallsites = callsites(methodNode) + val newCallsites = methodCallsites - invocation + if (newCallsites.isEmpty) callsites.remove(methodNode) + else callsites(methodNode) = newCallsites + methodCallsites.get(invocation) + } - def addClass(classNode: ClassNode): Unit = { - val classType = classBTypeFromClassNode(classNode) - for { - m <- classNode.methods.asScala - (calls, closureInits) = analyzeCallsites(m, classType) - } { - calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite) - closureInits foreach (lmf => closureInstantiations(lmf.indy) = ClosureInstantiation(lmf, m, classType)) - } + def addCallsite(callsite: Callsite): Unit = { + val methodCallsites = callsites(callsite.callsiteMethod) + callsites(callsite.callsiteMethod) = methodCallsites + (callsite.callsiteInstruction -> callsite) } - /** - * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions. - */ - def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[LambdaMetaFactoryCall]) = { + def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction - case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean, - annotatedInline: Boolean, annotatedNoInline: Boolean, - warning: Option[CalleeInfoWarning]) + def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = { + val methodClosureInits = closureInstantiations(methodNode) + val newClosureInits = methodClosureInits - indy + if (newClosureInits.isEmpty) closureInstantiations.remove(methodNode) + else closureInstantiations(methodNode) = newClosureInits + methodClosureInits.get(indy) + } - /** - * Analyze a callsite and gather meta-data that can be used for inlining decisions. - */ - def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = { - val methodSignature = calleeMethodNode.name + calleeMethodNode.desc + def addClosureInstantiation(closureInit: ClosureInstantiation) = { + val methodClosureInits = closureInstantiations(closureInit.ownerMethod) + closureInstantiations(closureInit.ownerMethod) = methodClosureInits + (closureInit.lambdaMetaFactoryCall.indy -> closureInit) + } - try { - // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* - // within a class (not for inherited methods). Since we already have the classBType of the - // callee, we only check there for the methodInlineInfo, we should find it there. - calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { - case Some(methodInlineInfo) => - val canInlineFromSource = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit + def addClass(classNode: ClassNode): Unit = { + val classType = classBTypeFromClassNode(classNode) + classNode.methods.asScala.foreach(addMethod(_, classType)) + } - val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode) + def addIfMissing(methodNode: MethodNode, definingClass: ClassBType): Unit = { + if (!callsites.contains(methodNode)) addMethod(methodNode, definingClass) + } - // (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example: - // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined - // - // TODO: type analysis can render more calls statically resolved. Example: - // new A.f // can be inlined, the receiver type is known to be exactly A. - val isStaticallyResolved: Boolean = { - methodInlineInfo.effectivelyFinal || - classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal // (1) + def addMethod(methodNode: MethodNode, definingClass: ClassBType): Unit = { + if (!BytecodeUtils.isAbstractMethod(methodNode) && !BytecodeUtils.isNativeMethod(methodNode)) { + // TODO: run dataflow analyses to make the call graph more precise + // - producers to get forwarded parameters (ForwardedParam) + // - typeAnalysis for more precise argument types, more precise callee + + // For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance + // call is known to be not-null, in which case we don't have to emit a null check when inlining. + // It is also used to get the stack height at the call site. + + val analyzer = { + if (compilerSettings.YoptNullnessTracking && AsmAnalyzer.sizeOKForNullness(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName, new NullnessAnalyzer(btypes))) + } else if (AsmAnalyzer.sizeOKForBasicValue(methodNode)) { + Some(new AsmAnalyzer(methodNode, definingClass.internalName)) + } else None + } + + // if the method is too large to run an analyzer, it is not added to the call graph + if (analyzer.nonEmpty) { + val Some(a) = analyzer + def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = a.analyzer match { + case nullnessAnalyzer: NullnessAnalyzer => + val frame = nullnessAnalyzer.frameAt(call, methodNode) + frame.getStack(frame.getStackSize - 1 - numArgs) eq NotNullValue + case _ => false + } + + var methodCallsites = Map.empty[MethodInsnNode, Callsite] + var methodClosureInstantiations = Map.empty[InvokeDynamicInsnNode, ClosureInstantiation] + + // lazy so it is only computed if actually used by computeArgInfos + lazy val prodCons = new ProdConsAnalyzer(methodNode, definingClass.internalName) + + methodNode.instructions.iterator.asScala foreach { + case call: MethodInsnNode if a.frameAt(call) != null => // skips over unreachable code + val callee: Either[OptimizerWarning, Callee] = for { + (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] + } yield { + val declarationClassBType = classBTypeFromClassNode(declarationClassNode) + val info = analyzeCallsite(method, declarationClassBType, call, source) + import info._ + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + canInlineFromSource = canInlineFromSource, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + samParamTypes = info.samParamTypes, + calleeInfoWarning = warning) + } + + val argInfos = computeArgInfos(callee, call, prodCons) + + val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || { + val numArgs = Type.getArgumentTypes(call.desc).length + receiverNotNullByAnalysis(call, numArgs) } - val isRewritableTraitCall = isStaticallyResolved && methodInlineInfo.traitMethodWithStaticImplementation + methodCallsites += call -> Callsite( + callsiteInstruction = call, + callsiteMethod = methodNode, + callsiteClass = definingClass, + callee = callee, + argInfos = argInfos, + callsiteStackHeight = a.frameAt(call).getStackSize, + receiverKnownNotNull = receiverNotNull, + callsitePosition = callsitePositions.getOrElse(call, NoPosition), + annotatedInline = inlineAnnotatedCallsites(call), + annotatedNoInline = noInlineAnnotatedCallsites(call) + ) - val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( - MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _)) + case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) if a.frameAt(indy) != null => + val lmf = LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) + val capturedArgInfos = computeCapturedArgInfos(lmf, prodCons) + methodClosureInstantiations += indy -> ClosureInstantiation( + lmf, + methodNode, + definingClass, + capturedArgInfos) - // (1) For invocations of final trait methods, the callee isStaticallyResolved but also - // abstract. Such a callee is not safe to inline - it needs to be re-written to the - // static impl method first (safeToRewrite). - // (2) Final trait methods can be rewritten from the interface to the static implementation - // method to enable inlining. - CallsiteInfo( - safeToInline = - canInlineFromSource && - isStaticallyResolved && // (1) - !isAbstract && - !BytecodeUtils.isConstructor(calleeMethodNode) && - !BytecodeUtils.isNativeMethod(calleeMethodNode), - safeToRewrite = canInlineFromSource && isRewritableTraitCall, // (2) - annotatedInline = methodInlineInfo.annotatedInline, - annotatedNoInline = methodInlineInfo.annotatedNoInline, - warning = warning) - - case None => - val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) - CallsiteInfo(false, false, false, false, Some(warning)) + case _ => } - } catch { - case Invalid(noInfo: NoClassBTypeInfo) => - val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo) - CallsiteInfo(false, false, false, false, Some(warning)) + + callsites(methodNode) = methodCallsites + closureInstantiations(methodNode) = methodClosureInstantiations } } + } - // TODO: run dataflow analyses to make the call graph more precise - // - producers to get forwarded parameters (ForwardedParam) - // - typeAnalysis for more precise argument types, more precise callee - - // For now we run a NullnessAnalyzer. It is used to determine if the receiver of an instance - // call is known to be not-null, in which case we don't have to emit a null check when inlining. - // It is also used to get the stack height at the call site. - localOpt.minimalRemoveUnreachableCode(methodNode, definingClass.internalName) - - val analyzer: Analyzer[_ <: Value] = { - if (compilerSettings.YoptNullnessTracking) new NullnessAnalyzer - else new Analyzer(new BasicInterpreter) + def computeArgInfos(callee: Either[OptimizerWarning, Callee], callsiteInsn: MethodInsnNode, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = { + if (callee.isLeft) IntMap.empty + else { + lazy val numArgs = Type.getArgumentTypes(callsiteInsn.desc).length + (if (callsiteInsn.getOpcode == Opcodes.INVOKESTATIC) 0 else 1) + argInfosForSams(callee.get.samParamTypes, callsiteInsn, numArgs, prodCons) } - analyzer.analyze(definingClass.internalName, methodNode) + } - def receiverNotNullByAnalysis(call: MethodInsnNode, numArgs: Int) = analyzer match { - case nullnessAnalyzer: NullnessAnalyzer => - val frame = nullnessAnalyzer.frameAt(call, methodNode) - frame.getStack(frame.getStackSize - 1 - numArgs).nullness == NotNull + def computeCapturedArgInfos(lmf: LambdaMetaFactoryCall, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = { + val capturedSams = capturedSamTypes(lmf) + val numCaptures = Type.getArgumentTypes(lmf.indy.desc).length + argInfosForSams(capturedSams, lmf.indy, numCaptures, prodCons) + } - case _ => false + private def argInfosForSams(sams: IntMap[ClassBType], consumerInsn: AbstractInsnNode, numConsumed: => Int, prodCons: => ProdConsAnalyzer): IntMap[ArgInfo] = { + // TODO: use type analysis instead of ProdCons - should be more efficient + // some random thoughts: + // - assign special types to parameters and indy-lambda-functions to track them + // - upcast should not change type flow analysis: don't lose information. + // - can we do something about factory calls? Foo(x) for case class foo gives a Foo. + // inline the factory? analysis across method boundary? + + // assign to a lazy val to prevent repeated evaluation of the by-name arg + lazy val prodConsI = prodCons + lazy val firstConsumedSlot = { + val consumerFrame = prodConsI.frameAt(consumerInsn) + consumerFrame.stackTop - numConsumed + 1 } - - val callsites = new collection.mutable.ListBuffer[Callsite] - val closureInstantiations = new collection.mutable.ListBuffer[LambdaMetaFactoryCall] - - methodNode.instructions.iterator.asScala foreach { - case call: MethodInsnNode => - val callee: Either[OptimizerWarning, Callee] = for { - (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] - (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] - declarationClassBType = classBTypeFromClassNode(declarationClassNode) - } yield { - val CallsiteInfo(safeToInline, safeToRewrite, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) - Callee( - callee = method, - calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, - safeToRewrite = safeToRewrite, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline, - calleeInfoWarning = warning) + sams flatMap { + case (index, _) => + val prods = prodConsI.initialProducersForValueAt(consumerInsn, firstConsumedSlot + index) + if (prods.size != 1) None + else { + val argInfo = prods.head match { + case LambdaMetaFactoryCall(_, _, _, _) => Some(FunctionLiteral) + case ParameterProducer(local) => Some(ForwardedParam(local)) + case _ => None + } + argInfo.map((index, _)) } + } + } - val argInfos = if (callee.isLeft) Nil else { - // TODO: for now it's Nil, because we don't run any data flow analysis - // there's no point in using the parameter types, that doesn't add any information. - // NOTE: need to run the same analyses after inlining, to re-compute the argInfos for the - // new duplicated callsites, see Inliner.inline - Nil - } + def samParamTypes(methodNode: MethodNode, receiverType: ClassBType): IntMap[ClassBType] = { + val paramTypes = { + val params = Type.getMethodType(methodNode.desc).getArgumentTypes.map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor)) + val isStatic = BytecodeUtils.isStaticMethod(methodNode) + if (isStatic) params else receiverType +: params + } + samTypes(paramTypes) + } - val receiverNotNull = call.getOpcode == Opcodes.INVOKESTATIC || { - val numArgs = Type.getArgumentTypes(call.desc).length - receiverNotNullByAnalysis(call, numArgs) - } + def capturedSamTypes(lmf: LambdaMetaFactoryCall): IntMap[ClassBType] = { + val capturedTypes = Type.getArgumentTypes(lmf.indy.desc).map(t => bTypeForDescriptorOrInternalNameFromClassfile(t.getDescriptor)) + samTypes(capturedTypes) + } - callsites += Callsite( - callsiteInstruction = call, - callsiteMethod = methodNode, - callsiteClass = definingClass, - callee = callee, - argInfos = argInfos, - callsiteStackHeight = analyzer.frameAt(call, methodNode).getStackSize, - receiverKnownNotNull = receiverNotNull, - callsitePosition = callsitePositions.getOrElse(call, NoPosition) - ) - - case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) => - closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) - - case _ => - } + private def samTypes(types: Array[BType]): IntMap[ClassBType] = { + var res = IntMap.empty[ClassBType] + for (i <- types.indices) { + types(i) match { + case c: ClassBType => + if (c.info.get.inlineInfo.sam.isDefined) res = res.updated(i, c) - (callsites.toList, closureInstantiations.toList) + case _ => + } + } + res } /** + * Just a named tuple used as return type of `analyzeCallsite`. + */ + private case class CallsiteInfo(safeToInline: Boolean, canInlineFromSource: Boolean, + annotatedInline: Boolean, annotatedNoInline: Boolean, + samParamTypes: IntMap[ClassBType], + warning: Option[CalleeInfoWarning]) + + /** + * Analyze a callsite and gather meta-data that can be used for inlining decisions. + */ + private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSource: Source): CallsiteInfo = { + val methodSignature = calleeMethodNode.name + calleeMethodNode.desc + + try { + // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* + // within a class (not for inherited methods). Since we already have the classBType of the + // callee, we only check there for the methodInlineInfo, we should find it there. + calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { + case Some(methodInlineInfo) => + val canInlineFromSource = compilerSettings.YoptInlineGlobal || calleeSource == CompilationUnit + + val isAbstract = BytecodeUtils.isAbstractMethod(calleeMethodNode) + + val receiverType = classBTypeFromParsedClassfile(call.owner) + // (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example: + // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined + // + // TODO: (1) doesn't cover the following example: + // trait TravLike { def map = ... } + // sealed trait List extends TravLike { ... } // assume map is not overridden + // final case class :: / final case object Nil + // (l: List).map // can be inlined + // we need to know that + // - the recevier is sealed + // - what are the children of the receiver + // - all children are final + // - none of the children overrides map + // + // TODO: type analysis can render more calls statically resolved. Example: + // new A.f // can be inlined, the receiver type is known to be exactly A. + val isStaticallyResolved: Boolean = { + isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined + methodInlineInfo.effectivelyFinal || + receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1) + } + + val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( + MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _)) + + // (1) For invocations of final trait methods, the callee isStaticallyResolved but also + // abstract. Such a callee is not safe to inline - it needs to be re-written to the + // static impl method first (safeToRewrite). + CallsiteInfo( + safeToInline = + canInlineFromSource && + isStaticallyResolved && // (1) + !isAbstract && + !BytecodeUtils.isConstructor(calleeMethodNode) && + !BytecodeUtils.isNativeMethod(calleeMethodNode) && + !BytecodeUtils.hasCallerSensitiveAnnotation(calleeMethodNode), + canInlineFromSource = canInlineFromSource, + annotatedInline = methodInlineInfo.annotatedInline, + annotatedNoInline = methodInlineInfo.annotatedNoInline, + samParamTypes = samParamTypes(calleeMethodNode, receiverType), + warning = warning) + + case None => + val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) + CallsiteInfo(false, false, false, false, IntMap.empty, Some(warning)) + } + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => + val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo) + CallsiteInfo(false, false, false, false, IntMap.empty, Some(warning)) + } + } + + /** * A callsite in the call graph. * * @param callsiteInstruction The invocation instruction @@ -197,8 +346,15 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * @param callsitePosition The source position of the callsite, used for inliner warnings. */ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: Either[OptimizerWarning, Callee], argInfos: List[ArgInfo], - callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position) { + callee: Either[OptimizerWarning, Callee], argInfos: IntMap[ArgInfo], + callsiteStackHeight: Int, receiverKnownNotNull: Boolean, callsitePosition: Position, + annotatedInline: Boolean, annotatedNoInline: Boolean) { + /** + * Contains callsites that were created during inlining by cloning this callsite. Used to find + * corresponding callsites when inlining post-inline requests. + */ + val inlinedClones = mutable.Set.empty[ClonedCallsite] + override def toString = "Invocation of" + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + @@ -206,12 +362,15 @@ class CallGraph[BT <: BTypes](val btypes: BT) { s" in ${callsiteClass.internalName}.${callsiteMethod.name}" } + final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite) + /** * Information about invocation arguments, obtained through data flow analysis of the callsite method. */ sealed trait ArgInfo - final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo + case object FunctionLiteral extends ArgInfo final case class ForwardedParam(index: Int) extends ArgInfo + // final case class ArgTypeInfo(argType: BType, isPrecise: Boolean, knownNotNull: Boolean) extends ArgInfo // can be extended, e.g., with constant types /** @@ -223,21 +382,35 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * @param calleeDeclarationClass The class in which the callee is declared * @param safeToInline True if the callee can be safely inlined: it cannot be overridden, * and the inliner settings (project / global) allow inlining it. - * @param safeToRewrite True if the callee is the interface method of a concrete trait method - * that can be safely re-written to the static implementation method. * @param annotatedInline True if the callee is annotated @inline * @param annotatedNoInline True if the callee is annotated @noinline + * @param samParamTypes A map from parameter positions to SAM parameter types * @param calleeInfoWarning An inliner warning if some information was not available while * gathering the information about this callee. */ - final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType, - safeToInline: Boolean, safeToRewrite: Boolean, - annotatedInline: Boolean, annotatedNoInline: Boolean, - calleeInfoWarning: Option[CalleeInfoWarning]) { - assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.") + final case class Callee( + callee: MethodNode, calleeDeclarationClass: btypes.ClassBType, + safeToInline: Boolean, canInlineFromSource: Boolean, + annotatedInline: Boolean, annotatedNoInline: Boolean, + samParamTypes: IntMap[btypes.ClassBType], + calleeInfoWarning: Option[CalleeInfoWarning]) { + override def toString = s"Callee($calleeDeclarationClass.${callee.name})" } - final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType) { + /** + * Metadata about a closure instantiation, stored in the call graph + * + * @param lambdaMetaFactoryCall the InvokeDynamic instruction + * @param ownerMethod the method where the closure is allocated + * @param ownerClass the class containing the above method + * @param capturedArgInfos information about captured arguments. Used for updating the call + * graph when re-writing a closure invocation to the body method. + */ + final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType, capturedArgInfos: IntMap[ArgInfo]) { + /** + * Contains closure instantiations that were created during inlining by cloning this instantiation. + */ + val inlinedClones = mutable.Set.empty[ClosureInstantiation] override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)" } final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type) @@ -284,7 +457,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // When re-writing the closure callsite to the implMethod, we have to insert a cast. // // The check below ensures that - // (1) the implMethod type has the expected singature (captured types plus argument types + // (1) the implMethod type has the expected signature (captured types plus argument types // from instantiatedMethodType) // (2) the receiver of the implMethod matches the first captured type // (3) all parameters that are not the same in samMethodType and instantiatedMethodType diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index b0dc6ead1b..dcfa9ef705 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -8,12 +8,12 @@ package backend.jvm package opt import scala.annotation.switch -import scala.collection.immutable +import scala.collection.mutable +import scala.collection.immutable.IntMap import scala.reflect.internal.util.NoPosition import scala.tools.asm.{Type, Opcodes} import scala.tools.asm.tree._ import scala.tools.nsc.backend.jvm.BTypes.InternalName -import scala.tools.nsc.backend.jvm.analysis.ProdConsAnalyzer import BytecodeUtils._ import BackendReporting._ import Opcodes._ @@ -23,6 +23,25 @@ import scala.collection.convert.decorateAsScala._ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ + import coreBTypes._ + import backendUtils._ + import ClosureOptimizer._ + + private object closureInitOrdering extends Ordering[ClosureInstantiation] { + override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = { + val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName + if (cls != 0) return cls + + val mName = x.ownerMethod.name compareTo y.ownerMethod.name + if (mName != 0) return mName + + val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc + if (mDesc != 0) return mDesc + + def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy) + pos(x) - pos(y) + } + } /** * If a closure is allocated and invoked within the same method, re-write the invocation to the @@ -54,55 +73,51 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * [invoke the closure body method] */ def rewriteClosureApplyInvocations(): Unit = { - implicit object closureInitOrdering extends Ordering[ClosureInstantiation] { - override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = { - val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName - if (cls != 0) return cls - - val mName = x.ownerMethod.name compareTo y.ownerMethod.name - if (mName != 0) return mName - val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc - if (mDesc != 0) return mDesc - - def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy) - pos(x) - pos(y) - } + // sort all closure invocations to rewrite to ensure bytecode stability + val toRewrite = mutable.TreeMap.empty[ClosureInstantiation, mutable.ArrayBuffer[(MethodInsnNode, Int)]](closureInitOrdering) + def addRewrite(init: ClosureInstantiation, invocation: MethodInsnNode, stackHeight: Int): Unit = { + val callsites = toRewrite.getOrElseUpdate(init, mutable.ArrayBuffer.empty[(MethodInsnNode, Int)]) + callsites += ((invocation, stackHeight)) } - // Grouping the closure instantiations by method allows running the ProdConsAnalyzer only once per - // method. Also sort the instantiations: If there are multiple closure instantiations in a method, - // closure invocations need to be re-written in a consistent order for bytecode stability. The local - // variable slots for storing captured values depends on the order of rewriting. - val closureInstantiationsByMethod: Map[MethodNode, immutable.TreeSet[ClosureInstantiation]] = { - closureInstantiations.values.groupBy(_.ownerMethod).mapValues(immutable.TreeSet.empty ++ _) - } + // For each closure instantiation find callsites of the closure and add them to the toRewrite + // buffer (cannot change a method's bytecode while still looking for further invocations to + // rewrite, the frame indices of the ProdCons analysis would get out of date). If a callsite + // cannot be rewritten, for example because the lambda body method is not accessible, issue a + // warning. The `toList` in the next line prevents modifying closureInstantiations while + // iterating it: minimalRemoveUnreachableCode (called in the loop) removes elements. + for (method <- closureInstantiations.keysIterator.toList if AsmAnalyzer.sizeOKForBasicValue(method)) closureInstantiations.get(method) match { + case Some(closureInitsBeforeDCE) if closureInitsBeforeDCE.nonEmpty => + val ownerClass = closureInitsBeforeDCE.head._2.ownerClass.internalName + + // Advanced ProdCons queries (initialProducersForValueAt) expect no unreachable code. + localOpt.minimalRemoveUnreachableCode(method, ownerClass) + + if (AsmAnalyzer.sizeOKForSourceValue(method)) closureInstantiations.get(method) match { + case Some(closureInits) => + // A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`) + lazy val prodCons = new ProdConsAnalyzer(method, ownerClass) + + for (init <- closureInits.valuesIterator) closureCallsites(init, prodCons) foreach { + case Left(warning) => + backendReporting.inlinerWarning(warning.pos, warning.toString) + + case Right((invocation, stackHeight)) => + addRewrite(init, invocation, stackHeight) + } + + case _ => + } - // For each closure instantiation, a list of callsites of the closure that can be re-written - // If a callsite cannot be rewritten, for example because the lambda body method is not accessible, - // a warning is returned instead. - val callsitesToRewrite: List[(ClosureInstantiation, List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]])] = { - closureInstantiationsByMethod.iterator.flatMap({ - case (methodNode, closureInits) => - // A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`) - lazy val prodCons = new ProdConsAnalyzer(methodNode, closureInits.head.ownerClass.internalName) - closureInits.iterator.map(init => (init, closureCallsites(init, prodCons))) - }).toList // mapping to a list (not a map) to keep the sorting of closureInstantiationsByMethod + case _ => } - // Rewrite all closure callsites (or issue inliner warnings for those that cannot be rewritten) - for ((closureInit, callsites) <- callsitesToRewrite) { + for ((closureInit, invocations) <- toRewrite) { // Local variables that hold the captured values and the closure invocation arguments. - // They are lazy vals to ensure that locals for captured values are only allocated if there's - // actually a callsite to rewrite (an not only warnings to be issued). - lazy val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit) - for (callsite <- callsites) callsite match { - case Left(warning) => - backendReporting.inlinerWarning(warning.pos, warning.toString) - - case Right((invocation, stackHeight)) => - rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList) - } + val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit) + for ((invocation, stackHeight) <- invocations) + rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList) } } @@ -122,20 +137,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { val argTypes = closureInit.lambdaMetaFactoryCall.samMethodType.getArgumentTypes val firstArgLocal = ownerMethod.maxLocals - // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce - // casts for arguments that have different types in samMethodType and instantiatedMethodType. - val castLoadTypes = { - val instantiatedMethodType = closureInit.lambdaMetaFactoryCall.instantiatedMethodType - (argTypes, instantiatedMethodType.getArgumentTypes).zipped map { - case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType => - // the LambdaMetaFactoryCall extractor ensures that the two types are reference types, - // so we don't end up casting primitive values. - Some(instantiatedArgType) - case _ => - None - } - } - val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes) + val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes) ownerMethod.maxLocals = firstArgLocal + argLocals.size (captureLocals, argLocals) @@ -154,7 +156,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // TODO: This is maybe over-cautious. // We are checking if the closure body method is accessible at the closure callsite. // If the closure allocation has access to the body method, then the callsite (in the same - // method as the alloction) should have access too. + // method as the allocation) should have access too. val bodyAccessible: Either[OptimizerWarning, Boolean] = for { (bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)] isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass) @@ -162,7 +164,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { isAccessible } - def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition) + def pos = callGraph.callsites(ownerMethod).get(invocation).map(_.callsitePosition).getOrElse(NoPosition) val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match { case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w)) case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName)) @@ -173,6 +175,28 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { }).toList } + /** + * Check whether `invocation` invokes the SAM of the IndyLambda `closureInit`. + * + * In addition to a perfect match, we also identify cases where a generic FunctionN is created + * but the invocation is to a specialized variant apply$sp... Vice-versa, we also allow the + * case where a specialized FunctionN$sp.. is created but the generic apply is invoked. In + * these cases, the translation will introduce the necessary box / unbox invocations. Example: + * + * val f: Int => Any = (x: Int) => 1 + * f(10) + * + * The IndyLambda creates a specialized `JFunction1$mcII$sp`, whose SAM is `apply$mcII$sp(I)I`. + * The invocation calls `apply(Object)Object`: the method name and type don't match. + * We identify these cases, insert the necessary unbox operation for the arguments, and invoke + * the `$anonfun(I)I` method. + * + * Tests in InlinerTest.optimizeSpecializedClosures. In that test, methods t4/t4a/t5/t8 show + * examples where the parameters have to be unboxed because generic `apply` is called, but the + * lambda body method takes primitive types. + * The opposite case is in t9: a the specialized `apply$sp..` is invoked, but the lambda body + * method takes boxed arguments, so we have to insert boxing operations. + */ private def isSamInvocation(invocation: MethodInsnNode, closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): Boolean = { val indy = closureInit.lambdaMetaFactoryCall.indy if (invocation.getOpcode == INVOKESTATIC) false @@ -187,11 +211,85 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { receiverProducers.size == 1 && receiverProducers.head == indy } - invocation.name == indy.name && { - val indySamMethodDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor - indySamMethodDesc == invocation.desc - } && - closureIsReceiver // most expensive check last + def isSpecializedVersion(specName: String, nonSpecName: String) = specName.startsWith(nonSpecName) && specializationSuffix.pattern.matcher(specName.substring(nonSpecName.length)).matches + + def sameOrSpecializedType(specTp: Type, nonSpecTp: Type) = { + specTp == nonSpecTp || { + val specDesc = specTp.getDescriptor + val nonSpecDesc = nonSpecTp.getDescriptor + specDesc.length == 1 && primitives.contains(specDesc) && nonSpecDesc == ObjectRef.descriptor + } + } + + def specializedDescMatches(specMethodDesc: String, nonSpecMethodDesc: String) = { + val specArgs = Type.getArgumentTypes(specMethodDesc) + val nonSpecArgs = Type.getArgumentTypes(nonSpecMethodDesc) + specArgs.corresponds(nonSpecArgs)(sameOrSpecializedType) && sameOrSpecializedType(Type.getReturnType(specMethodDesc), Type.getReturnType(nonSpecMethodDesc)) + } + + def nameAndDescMatch = { + val aName = invocation.name + val bName = indy.name + val aDesc = invocation.desc + val bDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor + if (aName == bName) aDesc == bDesc + else if (isSpecializedVersion(aName, bName)) specializedDescMatches(aDesc, bDesc) + else if (isSpecializedVersion(bName, aName)) specializedDescMatches(bDesc, aDesc) + else false + } + + nameAndDescMatch && closureIsReceiver // most expensive check last + } + } + + private def isPrimitiveType(asmType: Type) = { + val sort = asmType.getSort + Type.VOID <= sort && sort <= Type.DOUBLE + } + + /** + * The argument types of the lambda body method may differ in two ways from the argument types of + * the closure member method that is invoked (and replaced by a call to the body). + * - The lambda body method may have more specific types than the invoked closure member, see + * comment in [[LambdaMetaFactoryCall.unapply]]. + * - The invoked closure member might be a specialized variant of the SAM or vice-versa, see + * comment method [[isSamInvocation]]. + */ + private def adaptStoredArguments(closureInit: ClosureInstantiation, invocation: MethodInsnNode): Int => Option[AbstractInsnNode] = { + val invokeDesc = invocation.desc + // The lambda body method has additional parameters for captured values. Here we need to consider + // only those parameters of the body method that correspond to lambda parameters. This happens + // to be exactly LMF.instantiatedMethodType. In fact, `LambdaMetaFactoryCall.unapply` ensures + // that the body method signature is exactly (capturedParams + instantiatedMethodType). + val lambdaBodyMethodDescWithoutCaptures = closureInit.lambdaMetaFactoryCall.instantiatedMethodType.getDescriptor + if (invokeDesc == lambdaBodyMethodDescWithoutCaptures) { + _ => None + } else { + val invokeArgTypes = Type.getArgumentTypes(invokeDesc) + val implMethodArgTypes = Type.getArgumentTypes(lambdaBodyMethodDescWithoutCaptures) + val res = new Array[Option[AbstractInsnNode]](invokeArgTypes.length) + for (i <- invokeArgTypes.indices) { + if (invokeArgTypes(i) == implMethodArgTypes(i)) { + res(i) = None + } else if (isPrimitiveType(implMethodArgTypes(i)) && invokeArgTypes(i).getDescriptor == ObjectRef.descriptor) { + res(i) = Some(getScalaUnbox(implMethodArgTypes(i))) + } else if (isPrimitiveType(invokeArgTypes(i)) && implMethodArgTypes(i).getDescriptor == ObjectRef.descriptor) { + res(i) = Some(getScalaBox(invokeArgTypes(i))) + } else { + assert(!isPrimitiveType(invokeArgTypes(i)), invokeArgTypes(i)) + assert(!isPrimitiveType(implMethodArgTypes(i)), implMethodArgTypes(i)) + // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce + // casts for arguments that have different types in samMethodType and instantiatedMethodType. + // + // Note: + // - invokeArgTypes is the same as the argument types in the IndyLambda's samMethodType, + // this is ensured by the `isSamInvocation` filter in this file + // - implMethodArgTypes is the same as the arg types in the IndyLambda's instantiatedMethodType, + // this is ensured by the unapply method in LambdaMetaFactoryCall (file CallGraph) + res(i) = Some(new TypeInsnNode(CHECKCAST, implMethodArgTypes(i).getInternalName)) + } + } + res } } @@ -200,7 +298,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod // store arguments - insertStoreOps(invocation, ownerMethod, argumentLocalsList) + insertStoreOps(invocation, ownerMethod, argumentLocalsList, adaptStoredArguments(closureInit, invocation)) // drop the closure from the stack ownerMethod.instructions.insertBefore(invocation, new InsnNode(POP)) @@ -210,8 +308,9 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { insertLoadOps(invocation, ownerMethod, argumentLocalsList) // update maxStack - val capturesStackSize = localsForCapturedValues.size - val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone + // One slot per value is correct for long / double, see comment in the `analysis` package object. + val numCapturedValues = localsForCapturedValues.locals.length + val invocationStackHeight = stackHeight + numCapturedValues - 1 // -1 because the closure is gone if (invocationStackHeight > ownerMethod.maxStack) ownerMethod.maxStack = invocationStackHeight @@ -231,42 +330,73 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface) ownerMethod.instructions.insertBefore(invocation, bodyInvocation) - val returnType = Type.getReturnType(lambdaBodyHandle.getDesc) - fixLoadedNothingOrNullValue(returnType, bodyInvocation, ownerMethod, btypes) // see comment of that method + val bodyReturnType = Type.getReturnType(lambdaBodyHandle.getDesc) + val invocationReturnType = Type.getReturnType(invocation.desc) + if (isPrimitiveType(invocationReturnType) && bodyReturnType.getDescriptor == ObjectRef.descriptor) { + val op = + if (invocationReturnType.getSort == Type.VOID) getPop(1) + else getScalaUnbox(invocationReturnType) + ownerMethod.instructions.insertBefore(invocation, op) + } else if (isPrimitiveType(bodyReturnType) && invocationReturnType.getDescriptor == ObjectRef.descriptor) { + val op = + if (bodyReturnType.getSort == Type.VOID) getBoxedUnit + else getScalaBox(bodyReturnType) + ownerMethod.instructions.insertBefore(invocation, op) + } else { + // see comment of that method + fixLoadedNothingOrNullValue(bodyReturnType, bodyInvocation, ownerMethod, btypes) + } ownerMethod.instructions.remove(invocation) // update the call graph - val originalCallsite = callGraph.callsites.remove(invocation) + val originalCallsite = callGraph.removeCallsite(invocation, ownerMethod) // the method node is needed for building the call graph entry val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc) def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false) - val bodyMethodCallsite = Callsite( - callsiteInstruction = bodyInvocation, - callsiteMethod = ownerMethod, - callsiteClass = closureInit.ownerClass, - callee = bodyMethod.map({ - case (bodyMethodNode, bodyMethodDeclClass) => Callee( + val callee = bodyMethod.map({ + case (bodyMethodNode, bodyMethodDeclClass) => + val bodyDeclClassType = classBTypeFromParsedClassfile(bodyMethodDeclClass) + val canInlineFromSource = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled + Callee( callee = bodyMethodNode, - calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass), - safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled, - safeToRewrite = false, // the lambda body method is not a trait interface method + calleeDeclarationClass = bodyDeclClassType, + safeToInline = canInlineFromSource, + canInlineFromSource = canInlineFromSource, annotatedInline = false, annotatedNoInline = false, + samParamTypes = callGraph.samParamTypes(bodyMethodNode, bodyDeclClassType), calleeInfoWarning = None) - }), - argInfos = Nil, + }) + val argInfos = closureInit.capturedArgInfos ++ originalCallsite.map(cs => cs.argInfos map { + case (index, info) => (index + numCapturedValues, info) + }).getOrElse(IntMap.empty) + val bodyMethodCallsite = Callsite( + callsiteInstruction = bodyInvocation, + callsiteMethod = ownerMethod, + callsiteClass = closureInit.ownerClass, + callee = callee, + argInfos = argInfos, callsiteStackHeight = invocationStackHeight, receiverKnownNotNull = true, // see below (*) - callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition) + callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition), + annotatedInline = false, + annotatedNoInline = false ) // (*) The documentation in class LambdaMetafactory says: // "if implMethod corresponds to an instance method, the first capture argument // (corresponding to the receiver) must be non-null" // Explanation: If the lambda body method is non-static, the receiver is a captured // value. It can only be captured within some instance method, so we know it's non-null. - callGraph.callsites(bodyInvocation) = bodyMethodCallsite + callGraph.addCallsite(bodyMethodCallsite) + + // Rewriting a closure invocation may render code unreachable. For example, the body method of + // (x: T) => ??? has return type Nothing$, and an ATHROW is added (see fixLoadedNothingOrNullValue). + unreachableCodeEliminated -= ownerMethod + + if (hasAdaptedImplMethod(closureInit) && inliner.canInlineBody(bodyMethodCallsite).isEmpty) + inliner.inlineCallsite(bodyMethodCallsite) } /** @@ -283,13 +413,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // local. On the other hand, further optimizations (copy propagation, remove unused locals) will // clean it up. - // Captured variables don't need to be cast when loaded at the callsite (castLoadTypes are None). - // This is checked in `isClosureInstantiation`: the types of the captured variables in the indy - // instruction match exactly the corresponding parameter types in the body method. - val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None) + val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes) closureInit.ownerMethod.maxLocals = firstCaptureLocal + localsForCaptures.size - insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures) + insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures, _ => None) insertLoadOps(indy, closureInit.ownerMethod, localsForCaptures) localsForCaptures @@ -301,8 +428,16 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * * The lowest stack value is stored in the head of the locals list, so the last local is stored first. */ - private def insertStoreOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) = - insertLocalValueOps(before, methodNode, localsList, store = true) + private def insertStoreOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList, beforeStore: Int => Option[AbstractInsnNode]) = { + // The first instruction needs to store into the last local of the `localsList`. + // To avoid reversing the list, we use `insert(previous)`. + val previous = before.getPrevious + def ins(op: AbstractInsnNode) = methodNode.instructions.insert(previous, op) + for ((l, i) <- localsList.locals.zipWithIndex) { + ins(new VarInsnNode(l.storeOpcode, l.local)) + beforeStore(i) foreach ins + } + } /** * Insert load operations in front of the `before` instruction to copy the local values denoted @@ -310,20 +445,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * * The head of the locals list will be the lowest value on the stack, so the first local is loaded first. */ - private def insertLoadOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) = - insertLocalValueOps(before, methodNode, localsList, store = false) - - private def insertLocalValueOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList, store: Boolean): Unit = { - // If `store` is true, the first instruction needs to store into the last local of the `localsList`. - // Load instructions on the other hand are emitted in the order of the list. - // To avoid reversing the list, we use `insert(previousInstr)` for stores and `insertBefore(before)` for loads. - lazy val previous = before.getPrevious + private def insertLoadOps(before: AbstractInsnNode, methodNode: MethodNode, localsList: LocalsList) = { for (l <- localsList.locals) { - val varOp = new VarInsnNode(if (store) l.storeOpcode else l.loadOpcode, l.local) - if (store) methodNode.instructions.insert(previous, varOp) - else methodNode.instructions.insertBefore(before, varOp) - if (!store) for (castType <- l.castLoadedValue) - methodNode.instructions.insert(varOp, new TypeInsnNode(CHECKCAST, castType.getInternalName)) + val op = new VarInsnNode(l.loadOpcode, l.local) + methodNode.instructions.insertBefore(before, op) } } @@ -345,12 +470,12 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * Local(6, refOpOffset) :: * Nil */ - def fromTypes(firstLocal: Int, types: Array[Type], castLoadTypes: Int => Option[Type]): LocalsList = { + def fromTypes(firstLocal: Int, types: Array[Type]): LocalsList = { var sizeTwoOffset = 0 val locals: List[Local] = types.indices.map(i => { // The ASM method `type.getOpcode` returns the opcode for operating on a value of `type`. val offset = types(i).getOpcode(ILOAD) - ILOAD - val local = Local(firstLocal + i + sizeTwoOffset, offset, castLoadTypes(i)) + val local = Local(firstLocal + i + sizeTwoOffset, offset) if (local.size == 2) sizeTwoOffset += 1 local })(collection.breakOut) @@ -364,10 +489,15 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for * a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type]]. */ - case class Local(local: Int, opcodeOffset: Int, castLoadedValue: Option[Type]) { + case class Local(local: Int, opcodeOffset: Int) { def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD) 2 else 1 def loadOpcode = ILOAD + opcodeOffset def storeOpcode = ISTORE + opcodeOffset } } + +object ClosureOptimizer { + val primitives = "BSIJCFDZV" + val specializationSuffix = s"(\\$$mc[$primitives]+\\$$sp)".r +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala new file mode 100644 index 0000000000..d28565b9bc --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala @@ -0,0 +1,641 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.annotation.{switch, tailrec} +import scala.tools.asm.tree.analysis.BasicInterpreter +import scala.tools.asm.Type +import scala.tools.asm.Opcodes._ +import scala.tools.asm.tree._ +import scala.collection.mutable +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.analysis._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ + +class CopyProp[BT <: BTypes](val btypes: BT) { + import btypes._ + import backendUtils._ + + + /** + * For every `xLOAD n`, find all local variable slots that are aliases of `n` using an + * AliasingAnalyzer and change the instruction to `xLOAD m` where `m` is the smallest alias. + * This leaves behind potentially stale `xSTORE n` instructions, which are then eliminated + * by [[eliminateStaleStores]]. + */ + def copyPropagation(method: MethodNode, owner: InternalName): Boolean = { + AsmAnalyzer.sizeOKForAliasing(method) && { + var changed = false + val numParams = parametersSize(method) + lazy val aliasAnalysis = new AsmAnalyzer(method, owner, new AliasingAnalyzer(new BasicInterpreter)) + + // Remember locals that are used in a `LOAD` instruction. Assume a program has two LOADs: + // + // ... + // LOAD 3 // aliases of 3 here: <3> + // ... + // LOAD 1 // aliases of 1 here: <1, 3> + // + // In this example, we should change the second load from 1 to 3, which might render the + // local variable 1 unused. + val knownUsed = new Array[Boolean](method.maxLocals) + + def usedOrMinAlias(it: IntIterator, init: Int): Int = { + if (knownUsed(init)) init + else { + var r = init + while (it.hasNext) { + val n = it.next() + // knownUsed.length is the number of locals, `n` may be a stack slot + if (n < knownUsed.length && knownUsed(n)) return n + if (n < r) r = n + } + r + } + } + + val it = method.instructions.iterator + while (it.hasNext) it.next() match { + case vi: VarInsnNode if vi.`var` >= numParams && isLoad(vi) => + val aliases = aliasAnalysis.frameAt(vi).asInstanceOf[AliasingFrame[_]].aliasesOf(vi.`var`) + if (aliases.size > 1) { + val alias = usedOrMinAlias(aliases.iterator, vi.`var`) + if (alias != -1) { + changed = true + vi.`var` = alias + } + } + knownUsed(vi.`var`) = true + + case _ => + } + + changed + } + } + + /** + * Eliminate `xSTORE` instructions that have no consumer. If the instruction can be completely + * eliminated, it is replaced by a POP. The [[eliminatePushPop]] cleans up unnecessary POPs. + * + * Note that an `ASOTRE` can not always be eliminated: it removes a reference to the object that + * is currently stored in that local, which potentially frees it for GC (SI-5313). Therefore + * we replace such stores by `POP; ACONST_NULL; ASTORE x`. + */ + def eliminateStaleStores(method: MethodNode, owner: InternalName): Boolean = { + AsmAnalyzer.sizeOKForSourceValue(method) && { + lazy val prodCons = new ProdConsAnalyzer(method, owner) + def hasNoCons(varIns: AbstractInsnNode, slot: Int) = prodCons.consumersOfValueAt(varIns.getNext, slot).isEmpty + + // insns to delete: IINC that have no consumer + val toDelete = mutable.ArrayBuffer.empty[IincInsnNode] + + // xSTORE insns to be replaced by POP or POP2 + val storesToDrop = mutable.ArrayBuffer.empty[VarInsnNode] + + // ASTORE insn that have no consumer. + // - if the local is not live, the store is replaced by POP + // - otherwise, pop the argument value and store NULL instead. Unless the boolean field is + // `true`: then the store argument is already known to be ACONST_NULL. + val toNullOut = mutable.ArrayBuffer.empty[(VarInsnNode, Boolean)] + + // `true` for variables that are known to be live + val liveVars = new Array[Boolean](method.maxLocals) + + val it = method.instructions.iterator + while (it.hasNext) it.next() match { + case vi: VarInsnNode if isStore(vi) && hasNoCons(vi, vi.`var`) => + val canElim = vi.getOpcode != ASTORE || { + val currentFieldValueProds = prodCons.initialProducersForValueAt(vi, vi.`var`) + currentFieldValueProds.size == 1 && (currentFieldValueProds.head match { + case ParameterProducer(0) => !isStaticMethod(method) // current field value is `this`, which won't be gc'd anyway + case _: UninitializedLocalProducer => true // field is not yet initialized, so current value cannot leak + case _ => false + }) + } + if (canElim) storesToDrop += vi + else { + val prods = prodCons.producersForValueAt(vi, prodCons.frameAt(vi).stackTop) + val isStoreNull = prods.size == 1 && prods.head.getOpcode == ACONST_NULL + toNullOut += ((vi, isStoreNull)) + } + + case ii: IincInsnNode if hasNoCons(ii, ii.`var`) => + toDelete += ii + + case vi: VarInsnNode => + liveVars(vi.`var`) = true + + case ii: IincInsnNode => + liveVars(ii.`var`) = true + + case _ => + } + + def replaceByPop(vi: VarInsnNode): Unit = { + val size = if (isSize2LoadOrStore(vi.getOpcode)) 2 else 1 + method.instructions.set(vi, getPop(size)) + } + + toDelete foreach method.instructions.remove + + storesToDrop foreach replaceByPop + + for ((vi, isStoreNull) <- toNullOut) { + if (!liveVars(vi.`var`)) replaceByPop(vi) // can drop `ASTORE x` where x has only dead stores + else { + if (!isStoreNull) { + val prev = vi.getPrevious + method.instructions.insert(prev, new InsnNode(ACONST_NULL)) + method.instructions.insert(prev, getPop(1)) + } + } + } + + toDelete.nonEmpty || storesToDrop.nonEmpty || toNullOut.nonEmpty + } + } + + /** + * When a POP instruction has a single producer, remove the POP and eliminate the producer by + * bubbling up the POPs. For example, given + * ILOAD 1; ILOAD 2; IADD; POP + * we first eliminate the POP, then the IADD, then its inputs, so the entire sequence goes away. + * If a producer cannot be eliminated (need to keep side-effects), a POP is inserted. + * + * A special case eliminates the creation of unused objects with side-effect-free constructors: + * NEW scala/Tuple1; DUP; ALOAD 0; INVOKESPECIAL scala/Tuple1.<init>; POP + * The POP has a single producer (the DUP), it's easy to eliminate these two. A special case + * is needed to eliminate the INVOKESPECIAL and NEW. + */ + def eliminatePushPop(method: MethodNode, owner: InternalName): Boolean = { + AsmAnalyzer.sizeOKForSourceValue(method) && { + // A queue of instructions producing a value that has to be eliminated. If possible, the + // instruction (and its inputs) will be removed, otherwise a POP is inserted after + val queue = mutable.Queue.empty[ProducedValue] + // Contains constructor invocations for values that can be eliminated if unused. + val sideEffectFreeConstructorCalls = mutable.ArrayBuffer.empty[MethodInsnNode] + + // instructions to remove (we don't change the bytecode while analyzing it. this allows + // running the ProdConsAnalyzer only once.) + val toRemove = mutable.Set.empty[AbstractInsnNode] + // instructions to insert before some instruction + val toInsertBefore = mutable.Map.empty[AbstractInsnNode, List[InsnNode]] + // an instruction to insert after some instruction + val toInsertAfter = mutable.Map.empty[AbstractInsnNode, AbstractInsnNode] + + lazy val prodCons = new ProdConsAnalyzer(method, owner) + + /** + * Returns the producers for the stack value `inputSlot` consumed by `cons`, if the consumer + * instruction is the only consumer for all of these producers. + * + * If a producer has multiple consumers, or the value is the caught exception in a catch + * block, this method returns Set.empty. + */ + def producersIfSingleConsumer(cons: AbstractInsnNode, inputSlot: Int): Set[AbstractInsnNode] = { + /** + * True if the values produced by `prod` are all the same. Most instructions produce a single + * value. DUP and DUP2 (with a size-2 input) produce two equivalent values. However, there + * are some exotic instructions that produce multiple non-equal values (DUP_X1, SWAP, ...). + * + * Assume we have `DUP_X2; POP`. In order to remove the `POP` we need to change the DUP_X2 + * into something else, which is not straightforward. + * + * Since scalac never emits any of those exotic bytecodes, we don't optimize them. + */ + def producerHasSingleOutput(prod: AbstractInsnNode): Boolean = prod match { + case _: ExceptionProducer[_] | _: UninitializedLocalProducer => + // POP of an exception in a catch block cannot be removed. For an uninitialized local, + // there should not be a consumer. We are conservative and include it here, so the + // producer would not be removed. + false + + case _: ParameterProducer => + true + + case _ => (prod.getOpcode: @switch) match { + case DUP => true + case DUP2 => prodCons.frameAt(prod).peekStack(0).getSize == 2 + case _ => InstructionStackEffect.prod(InstructionStackEffect.forAsmAnalysis(prod, prodCons.frameAt(prod))) == 1 + } + } + + val prods = prodCons.producersForValueAt(cons, inputSlot) + val singleConsumer = prods forall { prod => + producerHasSingleOutput(prod) && { + // for DUP / DUP2, we only consider the value that is actually consumed by cons + val conss = prodCons.consumersOfValueAt(prod.getNext, inputSlot) + conss.size == 1 && conss.head == cons + } + } + if (singleConsumer) prods else Set.empty + } + + /** + * For a POP instruction that is the single consumer of its producers, remove the POP and + * enqueue the producers. + */ + def handleInitialPop(pop: AbstractInsnNode): Unit = { + val prods = producersIfSingleConsumer(pop, prodCons.frameAt(pop).stackTop) + if (prods.nonEmpty) { + toRemove += pop + val size = if (pop.getOpcode == POP2) 2 else 1 + queue ++= prods.map(ProducedValue(_, size)) + } + } + + /** + * Traverse the method in its initial state and collect all POP instructions and side-effect + * free constructor invocations that can be eliminated. + */ + def collectInitialPopsAndPureConstrs(): Unit = { + val it = method.instructions.iterator + while (it.hasNext) { + val insn = it.next() + (insn.getOpcode: @switch) match { + case POP | POP2 => + handleInitialPop(insn) + + case INVOKESPECIAL => + val mi = insn.asInstanceOf[MethodInsnNode] + if (isSideEffectFreeConstructorCall(mi)) sideEffectFreeConstructorCalls += mi + + case _ => + } + } + } + + /** + * Eliminate the `numArgs` inputs of the instruction `prod` (which was eliminated). Fo + * each input value + * - if the `prod` instruction is the single consumer, enqueue the producers of the input + * - otherwise, insert a POP instruction to POP the input value + */ + def handleInputs(prod: AbstractInsnNode, numArgs: Int): Unit = { + val frame = prodCons.frameAt(prod) + val pops = mutable.ListBuffer.empty[InsnNode] + @tailrec def handle(stackOffset: Int): Unit = { + if (stackOffset >= 0) { + val prods = producersIfSingleConsumer(prod, frame.stackTop - stackOffset) + val nSize = frame.peekStack(stackOffset).getSize + if (prods.isEmpty) pops append getPop(nSize) + else queue ++= prods.map(ProducedValue(_, nSize)) + handle(stackOffset - 1) + } + } + handle(numArgs - 1) // handle stack offsets (numArgs - 1) to 0 + if (pops.nonEmpty) toInsertBefore(prod) = pops.toList + } + + /** + * Eliminate the closure value produced by `indy`. If the SAM type is known to construct + * without side-effects (e.g. scala/FunctionN), the `indy` and its inputs + * are eliminated, otherwise a POP is inserted. + */ + def handleClosureInst(indy: InvokeDynamicInsnNode): Unit = { + if (isBuiltinFunctionType(Type.getReturnType(indy.desc).getInternalName)) { + toRemove += indy + callGraph.removeClosureInstantiation(indy, method) + handleInputs(indy, Type.getArgumentTypes(indy.desc).length) + } else { + toInsertAfter(indy) = getPop(1) + } + } + + def runQueue(): Unit = while (queue.nonEmpty) { + val ProducedValue(prod, size) = queue.dequeue() + + def prodString = s"Producer ${AsmUtils textify prod}@${method.instructions.indexOf(prod)}\n${AsmUtils textify method}" + def popAfterProd(): Unit = toInsertAfter(prod) = getPop(size) + + (prod.getOpcode: @switch) match { + case ACONST_NULL | ICONST_M1 | ICONST_0 | ICONST_1 | ICONST_2 | ICONST_3 | ICONST_4 | ICONST_5 | LCONST_0 | LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | DCONST_0 | DCONST_1 | + BIPUSH | SIPUSH | ILOAD | LLOAD | FLOAD | DLOAD | ALOAD=> + toRemove += prod + + case opc @ (DUP | DUP2) => + assert(opc != 2 || size == 2, s"DUP2 for two size-1 values; $prodString") // ensured in method `producerHasSingleOutput` + if (toRemove(prod)) + // the DUP is already scheduled for removal because one of its consumers is a POP. + // now the second consumer is also a POP, so we need to eliminate the DUP's input. + handleInputs(prod, 1) + else + toRemove += prod + + case DUP_X1 | DUP_X2 | DUP2_X1 | DUP2_X2 | SWAP => + // these are excluded in method `producerHasSingleOutput` + assert(false, s"Cannot eliminate value pushed by an instruction with multiple output values; $prodString") + + case IDIV | LDIV | IREM | LREM => + popAfterProd() // keep potential division by zero + + case IADD | LADD | FADD | DADD | ISUB | LSUB | FSUB | DSUB | IMUL | LMUL | FMUL | DMUL | FDIV | DDIV | FREM | DREM | + LSHL | LSHR | LUSHR | + IAND | IOR | IXOR | LAND | LOR | LXOR | + LCMP | FCMPL | FCMPG | DCMPL | DCMPG => + toRemove += prod + handleInputs(prod, 2) + + case INEG | LNEG | FNEG | DNEG | + I2L | I2F | I2D | L2I | L2F | L2D | F2I | F2L | F2D | D2I | D2L | D2F | I2B | I2C | I2S => + toRemove += prod + handleInputs(prod, 1) + + case GETFIELD | GETSTATIC => + // TODO eliminate side-effect free module loads (https://github.com/scala/scala-dev/issues/16) + if (isBoxedUnit(prod)) toRemove += prod + else popAfterProd() // keep potential class initialization (static field) or NPE (instance field) + + case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | INVOKEINTERFACE => + val methodInsn = prod.asInstanceOf[MethodInsnNode] + if (isSideEffectFreeCall(methodInsn)) { + toRemove += prod + callGraph.removeCallsite(methodInsn, method) + val receiver = if (methodInsn.getOpcode == INVOKESTATIC) 0 else 1 + handleInputs(prod, Type.getArgumentTypes(methodInsn.desc).length + receiver) + } else + popAfterProd() + + case INVOKEDYNAMIC => + prod match { + case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => handleClosureInst(indy) + case _ => popAfterProd() + } + + case NEW => + if (isNewForSideEffectFreeConstructor(prod)) toRemove += prod + else popAfterProd() + + case LDC => prod.asInstanceOf[LdcInsnNode].cst match { + case _: java.lang.Integer | _: java.lang.Float | _: java.lang.Long | _: java.lang.Double | _: String => + toRemove += prod + + case _ => + // don't remove class literals, method types, method handles: keep a potential NoClassDefFoundError + popAfterProd() + } + + case MULTIANEWARRAY => + toRemove += prod + handleInputs(prod, prod.asInstanceOf[MultiANewArrayInsnNode].dims) + + case _ => + popAfterProd() + } + } + + // there are two cases when we can eliminate a constructor call: + // - NEW T; INVOKESPECIAL T.<init> -- there's no DUP, the new object is consumed only by the constructor) + // - NEW T; DUP; INVOKESPECIAL T.<init>, where the DUP will be removed + def eliminateUnusedPureConstructorCalls(): Boolean = { + var changed = false + + def removeConstructorCall(mi: MethodInsnNode): Unit = { + toRemove += mi + callGraph.removeCallsite(mi, method) + sideEffectFreeConstructorCalls -= mi + changed = true + } + + for (mi <- sideEffectFreeConstructorCalls.toList) { // toList to allow removing elements while traversing + val frame = prodCons.frameAt(mi) + val stackTop = frame.stackTop + val numArgs = Type.getArgumentTypes(mi.desc).length + val receiverProds = producersIfSingleConsumer(mi, stackTop - numArgs) + if (receiverProds.size == 1) { + val receiverProd = receiverProds.head + if (receiverProd.getOpcode == NEW) { + removeConstructorCall(mi) + handleInputs(mi, numArgs + 1) // removes the producers of args and receiver + } else if (receiverProd.getOpcode == DUP && toRemove.contains(receiverProd)) { + val dupProds = producersIfSingleConsumer(receiverProd, prodCons.frameAt(receiverProd).stackTop) + if (dupProds.size == 1 && dupProds.head.getOpcode == NEW) { + removeConstructorCall(mi) + handleInputs(mi, numArgs) // removes the producers of args. the producer of the receiver is DUP and already in toRemove. + queue += ProducedValue(dupProds.head, 1) // removes the NEW (which is NOT the producer of the receiver!) + } + } + } + } + changed + } + + collectInitialPopsAndPureConstrs() + + // eliminating producers enables eliminating unused constructor calls (when a DUP gets removed). + // vice-versa, eliminating a constructor call adds producers of constructor parameters to the queue. + // so the two run in a loop. + runQueue() + while (eliminateUnusedPureConstructorCalls()) + runQueue() + + var changed = false + toInsertAfter foreach { + case (target, insn) => + nextExecutableInstructionOrLabel(target) match { + // `insn` is of type `InsnNode`, so we only need to check the Opcode when comparing to another instruction + case Some(next) if next.getOpcode == insn.getOpcode && toRemove(next) => + // Inserting and removing a POP at the same place should not enable `changed`. This happens + // when a POP directly follows a producer that cannot be eliminated, e.g. INVOKESTATIC A.m ()I; POP + // The POP is initially added to `toRemove`, and the `INVOKESTATIC` producer is added to the queue. + // Because the producer cannot be elided, a POP is added to `toInsertAfter`. + toRemove -= next + + case _ => + changed = true + method.instructions.insert(target, insn) + } + } + toInsertBefore foreach { + case (target, insns) => + changed = true + insns.foreach(method.instructions.insertBefore(target, _)) + } + toRemove foreach { insn => + changed = true + method.instructions.remove(insn) + } + changed + } + } + + case class ProducedValue(producer: AbstractInsnNode, size: Int) { + override def toString = s"<${AsmUtils textify producer}>" + } + + /** + * Remove `xSTORE n; xLOAD n` paris if + * - the local variable n is not used anywhere else in the method (1), and + * - there are no executable instructions and no live labels (jump targets) between the two (2) + * + * Note: store-load pairs that cannot be eliminated could be replaced by `DUP; xSTORE n`, but + * that's just cosmetic and doesn't help for anything. + * + * (1) This could be made more precise by running a prodCons analysis and checking that the load + * is the only user of the store. Then we could eliminate the pair even if the variable is live + * (except for ASTORE, SI-5313). Not needing an analyzer is more efficient, and catches most + * cases. + * + * (2) The implementation uses a conservative estimation for liveness (if some instruction uses + * local n, then n is considered live in the entire method). In return, it doesn't need to run an + * Analyzer on the method, making it more efficient. + * + * This method also removes `ACONST_NULL; ASTORE n` if the local n is not live. This pattern is + * introduced by [[eliminateStaleStores]]. + * + * The implementation is a little tricky to support the following case: + * ISTORE 1; ISTORE 2; ILOAD 2; ACONST_NULL; ASTORE 3; ILOAD 1 + * The outer store-load pair can be removed if two the inner pairs can be. + */ + def eliminateStoreLoad(method: MethodNode): Boolean = { + val removePairs = mutable.Set.empty[RemovePair] + val liveVars = new Array[Boolean](method.maxLocals) + val liveLabels = mutable.Set.empty[LabelNode] + + def mkRemovePair(store: VarInsnNode, other: AbstractInsnNode, depends: List[RemovePairDependency]): RemovePair = { + val r = RemovePair(store, other, depends) + removePairs += r + r + } + + def registerLiveVarsLabels(insn: AbstractInsnNode): Unit = insn match { + case vi: VarInsnNode => liveVars(vi.`var`) = true + case ii: IincInsnNode => liveVars(ii.`var`) = true + case j: JumpInsnNode => liveLabels += j.label + case s: TableSwitchInsnNode => liveLabels += s.dflt; liveLabels ++= s.labels.asScala + case s: LookupSwitchInsnNode => liveLabels += s.dflt; liveLabels ++= s.labels.asScala + case _ => + } + + val pairStartStack = new mutable.Stack[(AbstractInsnNode, mutable.ListBuffer[RemovePairDependency])] + + def push(insn: AbstractInsnNode) = { + pairStartStack push ((insn, mutable.ListBuffer.empty)) + } + + def addDepends(dependency: RemovePairDependency) = if (pairStartStack.nonEmpty) { + val (_, depends) = pairStartStack.top + depends += dependency + } + + def completesStackTop(load: AbstractInsnNode) = isLoad(load) && pairStartStack.nonEmpty && { + pairStartStack.top match { + case (store: VarInsnNode, _) => store.`var` == load.asInstanceOf[VarInsnNode].`var` + case _ => false + } + } + + /** + * Try to pair `insn` with its correspondent on the stack + * - if the stack top is a store and `insn` is a corresponding load, create a pair + * - otherwise, check the two top stack values for `null; store`. if it matches, create + * a pair and continue pairing `insn` on the remaining stack + * - otherwise, empty the stack and mark the local variables in it live + */ + def tryToPairInstruction(insn: AbstractInsnNode): Unit = { + @tailrec def emptyStack(): Unit = if (pairStartStack.nonEmpty) { + registerLiveVarsLabels(pairStartStack.pop()._1) + emptyStack() + } + + @tailrec def tryPairing(): Unit = { + if (completesStackTop(insn)) { + val (store: VarInsnNode, depends) = pairStartStack.pop() + addDepends(mkRemovePair(store, insn, depends.toList)) + } else if (pairStartStack.nonEmpty) { + val (top, topDepends) = pairStartStack.pop() + if (pairStartStack.nonEmpty) { + (pairStartStack.top, top) match { + case ((ldNull: InsnNode, depends), store: VarInsnNode) if ldNull.getOpcode == ACONST_NULL && store.getOpcode == ASTORE => + pairStartStack.pop() + addDepends(mkRemovePair(store, ldNull, depends.toList)) + // example: store; (null; store;) (store; load;) load + // s1^ ^^^^^p1^^^^^ // p1 is added to s1's depends + // then: store; (null; store;) load + // s2^ ^^^^p2^^^^^ // p1 and p2 are added to s2's depends + topDepends foreach addDepends + tryPairing() + + case _ => + // empty the stack - a non-matching insn was found, cannot create any pairs to remove + registerLiveVarsLabels(insn) + registerLiveVarsLabels(top) + emptyStack() + } + } else { + // stack only has one element + registerLiveVarsLabels(insn) + registerLiveVarsLabels(top) + } + } else { + // stack is empty already + registerLiveVarsLabels(insn) + } + } + + tryPairing() + } + + + var insn = method.instructions.getFirst + + @tailrec def advanceToNextExecutableOrLabel(): Unit = { + insn = insn.getNext + if (insn != null && !isExecutable(insn) && !insn.isInstanceOf[LabelNode]) advanceToNextExecutableOrLabel() + } + + while (insn != null) { + insn match { + case _ if insn.getOpcode == ACONST_NULL => push(insn) + case vi: VarInsnNode if isStore(vi) => push(insn) + case label: LabelNode if pairStartStack.nonEmpty => addDepends(LabelNotLive(label)) + case _ => tryToPairInstruction(insn) + } + advanceToNextExecutableOrLabel() + } + + // elide RemovePairs that depend on live labels or other RemovePair that have to be elided. + // example: store 1; store 2; label x; load 2; load 1 + // if x is live, the inner pair has to be elided, causing the outer pair to be elided too. + + var doneEliding = false + + def elide(removePair: RemovePair) = { + doneEliding = false + liveVars(removePair.store.`var`) = true + removePairs -= removePair + } + + while (!doneEliding) { + doneEliding = true + for (removePair <- removePairs.toList) { + val slot = removePair.store.`var` + if (liveVars(slot)) elide(removePair) + else removePair.depends foreach { + case LabelNotLive(label) => if (liveLabels(label)) elide(removePair) + case other: RemovePair => if (!removePairs(other)) elide(removePair) + } + } + } + + for (removePair <- removePairs) { + method.instructions.remove(removePair.store) + method.instructions.remove(removePair.other) + } + + removePairs.nonEmpty + } +} + +trait RemovePairDependency +case class RemovePair(store: VarInsnNode, other: AbstractInsnNode, depends: List[RemovePairDependency]) extends RemovePairDependency { + override def toString = s"<${AsmUtils textify store},${AsmUtils textify other}> [$depends]" +} +case class LabelNotLive(label: LabelNode) extends RemovePairDependency diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala index e7dd5abc57..79d26b0b4e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -27,7 +27,7 @@ import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersio * In principle we could encode the InlineInfo into a Java annotation (instead of a classfile attribute). * However, an attribute allows us to save many bits. In particular, note that the strings in an * InlineInfo are serialized as references to constants in the constant pool, and those strings - * (traitImplClassSelfType, method names, method signatures) would exist in there anyway. So the + * (method names, method signatures) would exist in there anyway. So the * ScalaInlineAttribute remains relatively compact. */ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineInfoAttribute.attributeName) { @@ -47,13 +47,17 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI result.putByte(InlineInfoAttribute.VERSION) - var hasSelfIsFinal = 0 - if (inlineInfo.isEffectivelyFinal) hasSelfIsFinal |= 1 - if (inlineInfo.traitImplClassSelfType.isDefined) hasSelfIsFinal |= 2 - result.putByte(hasSelfIsFinal) + var flags = 0 + if (inlineInfo.isEffectivelyFinal) flags |= 1 + // flags |= 2 // no longer written + if (inlineInfo.sam.isDefined) flags |= 4 + if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8 + result.putByte(flags) - for (selfInternalName <- inlineInfo.traitImplClassSelfType) { - result.putShort(cw.newUTF8(selfInternalName)) + for (samNameDesc <- inlineInfo.sam) { + val (name, desc) = samNameDesc.span(_ != '(') + result.putShort(cw.newUTF8(name)) + result.putShort(cw.newUTF8(desc)) } // The method count fits in a short (the methods_count in a classfile is also a short) @@ -68,13 +72,16 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI result.putShort(cw.newUTF8(desc)) var inlineInfo = 0 - if (info.effectivelyFinal) inlineInfo |= 1 - if (info.traitMethodWithStaticImplementation) inlineInfo |= 2 - if (info.annotatedInline) inlineInfo |= 4 - if (info.annotatedNoInline) inlineInfo |= 8 + if (info.effectivelyFinal) inlineInfo |= 1 + // inlineInfo |= 2 // no longer written + if (info.annotatedInline) inlineInfo |= 4 + if (info.annotatedNoInline) inlineInfo |= 8 result.putByte(inlineInfo) } + result.putShort(inlineInfo.lateInterfaces.length) + for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i)) + result } @@ -94,15 +101,18 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI val version = nextByte() if (version == 1) { - val hasSelfIsFinal = nextByte() - val isFinal = (hasSelfIsFinal & 1) != 0 - val hasSelf = (hasSelfIsFinal & 2) != 0 - - val self = if (hasSelf) { - val selfName = nextUTF8() - Some(selfName) - } else { - None + val flags = nextByte() + val isFinal = (flags & 1) != 0 + val hasSelf = (flags & 2) != 0 + val hasSam = (flags & 4) != 0 + val hasLateInterfaces = (flags & 8) != 0 + + if (hasSelf) nextUTF8() // no longer used + + val sam = if (!hasSam) None else { + val name = nextUTF8() + val desc = nextUTF8() + Some(name + desc) } val numEntries = nextShort() @@ -111,14 +121,21 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI val desc = nextUTF8() val inlineInfo = nextByte() - val isFinal = (inlineInfo & 1) != 0 - val traitMethodWithStaticImplementation = (inlineInfo & 2) != 0 - val isInline = (inlineInfo & 4) != 0 - val isNoInline = (inlineInfo & 8) != 0 - (name + desc, MethodInlineInfo(isFinal, traitMethodWithStaticImplementation, isInline, isNoInline)) + val isFinal = (inlineInfo & 1) != 0 + // = (inlineInfo & 2) != 0 // no longer used + val isInline = (inlineInfo & 4) != 0 + val isNoInline = (inlineInfo & 8) != 0 + (name + desc, MethodInlineInfo(isFinal, isInline, isNoInline)) }).toMap - InlineInfoAttribute(InlineInfo(self, isFinal, infos, None)) + val lateInterfaces = if (!hasLateInterfaces) Nil else { + val numLateInterfaces = nextShort() + (0 until numLateInterfaces).map(_ => nextUTF8()) + } + + val info = InlineInfo(isFinal, sam, infos, None) + info.lateInterfaces ++= lateInterfaces + InlineInfoAttribute(info) } else { val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version) InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg))) @@ -128,13 +145,24 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI object InlineInfoAttribute { /** + * Notes: + * - `traitImplClassSelfType` is no longer emitted, `hasTraitImplClassSelfType` is always emitted + * as 0. Similarly, `traitMethodWithStaticImplementation` is always emitted 0. + * - When reading an existing attribute where `hasTraitImplClassSelfType` is 1, the + * `traitImplClassSelfType` is ignored. Also the value of `traitMethodWithStaticImplementation` + * is ignored. + * * [u1] version - * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1) + * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1), hasSam (<< 2), hasLateInterfaces (<< 3) * [u2]? traitImplClassSelfType (reference) + * [u2]? samName (reference) + * [u2]? samDescriptor (reference) * [u2] numMethodEntries * [u2] name (reference) * [u2] descriptor (reference) * [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3) + * [u2]? numLateInterfaces + * [u2] lateInterface (reference) */ final val VERSION: Byte = 1 @@ -145,4 +173,4 @@ object InlineInfoAttribute { * In order to instruct the ASM framework to de-serialize the ScalaInlineInfo attribute, we need * to pass a prototype instance when running the class reader. */ -object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(null, false, null, null)) +object InlineInfoAttributePrototype extends InlineInfoAttribute(InlineInfo(false, null, null, null)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 6b2786c1a3..a0ef74b46a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -9,7 +9,6 @@ package opt import scala.annotation.tailrec import scala.tools.asm -import asm.Handle import asm.Opcodes._ import asm.tree._ import scala.collection.convert.decorateAsScala._ @@ -17,48 +16,30 @@ import scala.collection.convert.decorateAsJava._ import AsmUtils._ import BytecodeUtils._ import collection.mutable -import scala.tools.asm.tree.analysis.SourceInterpreter +import scala.tools.asm.tree.analysis.{Analyzer, SourceInterpreter} import BackendReporting._ import scala.tools.nsc.backend.jvm.BTypes.InternalName class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ - - def eliminateUnreachableCodeAndUpdateCallGraph(methodNode: MethodNode, definingClass: InternalName): Unit = { - localOpt.minimalRemoveUnreachableCode(methodNode, definingClass) foreach { - case invocation: MethodInsnNode => callGraph.callsites.remove(invocation) - case indy: InvokeDynamicInsnNode => callGraph.closureInstantiations.remove(indy) - case _ => - } - } + import inlinerHeuristics._ + import backendUtils._ def runInliner(): Unit = { - rewriteFinalTraitMethodInvocations() - for (request <- collectAndOrderInlineRequests) { - val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee - - // Inlining a method can create unreachable code. Example: - // def f = throw e - // def g = f; println() // println is unreachable after inlining f - // If we have an inline request for a call to g, and f has been already inlined into g, we - // need to run DCE before inlining g. - eliminateUnreachableCodeAndUpdateCallGraph(callee.callee, callee.calleeDeclarationClass.internalName) - - // DCE above removes unreachable callsites from the call graph. If the inlining request denotes - // such an eliminated callsite, do nothing. - if (callGraph.callsites contains request.callsiteInstruction) { - val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, - callee.callee, callee.calleeDeclarationClass, - request.receiverKnownNotNull, keepLineNumbers = false) - - for (warning <- r) { - if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { - val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" - val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" - backendReporting.inlinerWarning(request.callsitePosition, msg) - } + val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee + + // TODO: if the request has downstream requests, create a snapshot to which we could roll back in case some downstream callsite cannot be inlined + // (Needs to revert modifications to the callee method, but also the call graph) + // (This assumes that inlining a request only makes sense if its downstream requests are satisfied - sync with heuristics!) + + val warnings = inline(request) + for (warning <- warnings) { + if ((callee.annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { + val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" + val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" + backendReporting.inlinerWarning(request.callsite.callsitePosition, msg) } } } @@ -69,165 +50,21 @@ class Inliner[BT <: BTypes](val btypes: BT) { * - Always remove the same request when breaking inlining cycles * - Perform inlinings in a consistent order */ - object callsiteOrdering extends Ordering[Callsite] { - override def compare(x: Callsite, y: Callsite): Int = { - val cls = x.callsiteClass.internalName compareTo y.callsiteClass.internalName + object callsiteOrdering extends Ordering[InlineRequest] { + override def compare(x: InlineRequest, y: InlineRequest): Int = { + val xCs = x.callsite + val yCs = y.callsite + val cls = xCs.callsiteClass.internalName compareTo yCs.callsiteClass.internalName if (cls != 0) return cls - val name = x.callsiteMethod.name compareTo y.callsiteMethod.name + val name = xCs.callsiteMethod.name compareTo yCs.callsiteMethod.name if (name != 0) return name - val desc = x.callsiteMethod.desc compareTo y.callsiteMethod.desc + val desc = xCs.callsiteMethod.desc compareTo yCs.callsiteMethod.desc if (desc != 0) return desc def pos(c: Callsite) = c.callsiteMethod.instructions.indexOf(c.callsiteInstruction) - pos(x) - pos(y) - } - } - - /** - * Select callsites from the call graph that should be inlined. The resulting list of inlining - * requests is allowed to have cycles, and the callsites can appear in any order. - */ - def selectCallsitesForInlining: List[Callsite] = { - callsites.valuesIterator.filter({ - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, _, pos) => - val res = doInlineCallsite(callsite) - - if (!res) { - if (annotatedInline && btypes.compilerSettings.YoptWarningEmitAtInlineFailed) { - // if the callsite is annotated @inline, we report an inline warning even if the underlying - // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). - def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" - def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("") - if (doRewriteTraitCallsite(callsite)) - backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg) - else if (!safeToInline) - backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) - else - backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) - } else if (warning.isDefined && warning.get.emitWarning(compilerSettings)) { - // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. - backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get) - } - } - - res - - case Callsite(ins, _, _, Left(warning), _, _, _, pos) => - if (warning.emitWarning(compilerSettings)) - backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") - false - }).toList - } - - /** - * The current inlining heuristics are simple: inline calls to methods annotated @inline. - */ - def doInlineCallsite(callsite: Callsite): Boolean = callsite match { - case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, _, annotatedInline, _, warning)), _, _, _, pos) => - if (compilerSettings.YoptInlineHeuristics.value == "everything") safeToInline - else annotatedInline && safeToInline - - case _ => false - } - - def rewriteFinalTraitMethodInvocations(): Unit = { - // Rewriting final trait method callsites to the implementation class enables inlining. - // We cannot just iterate over the values of the `callsites` map because the rewrite changes the - // map. Therefore we first copy the values to a list. - callsites.values.toList.foreach(rewriteFinalTraitMethodInvocation) - } - - /** - * True for statically resolved trait callsites that should be rewritten to the static implementation method. - */ - def doRewriteTraitCallsite(callsite: Callsite) = callsite.callee match { - case Right(Callee(callee, calleeDeclarationClass, safeToInline, true, annotatedInline, annotatedNoInline, infoWarning)) => true - case _ => false - } - - /** - * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the - * corresponding method in the implementation class. This enables inlining final trait methods. - * - * In a final trait method callsite, the callee is safeToInline and the callee method is abstract - * (the receiver type is the interface, so the method is abstract). - */ - def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = { - if (doRewriteTraitCallsite(callsite)) { - val Right(Callee(callee, calleeDeclarationClass, _, _, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee - - val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) - - val implClassInternalName = calleeDeclarationClass.internalName + "$class" - - val selfParamTypeV: Either[OptimizerWarning, ClassBType] = calleeDeclarationClass.info.map(_.inlineInfo.traitImplClassSelfType match { - case Some(internalName) => classBTypeFromParsedClassfile(internalName) - case None => calleeDeclarationClass - }) - - def implClassMethodV(implMethodDescriptor: String): Either[OptimizerWarning, MethodNode] = { - byteCodeRepository.methodNode(implClassInternalName, callee.name, implMethodDescriptor).map(_._1) - } - - // The rewrite reading the implementation class and the implementation method from the bytecode - // repository. If either of the two fails, the rewrite is not performed. - val res = for { - selfParamType <- selfParamTypeV - implMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType.toASMType +: traitMethodArgumentTypes: _*) - implClassMethod <- implClassMethodV(implMethodDescriptor) - implClassBType = classBTypeFromParsedClassfile(implClassInternalName) - selfTypeOk <- calleeDeclarationClass.isSubtypeOf(selfParamType) - } yield { - - // The self parameter type may be incompatible with the trait type. - // trait T { self: S => def foo = 1 } - // The $self parameter type of T$class.foo is S, which may be unrelated to T. If we re-write - // a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a - // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the - // receiver value and add a cast to the self type after each. - if (!selfTypeOk) { - // there's no need to run eliminateUnreachableCode here. building the call graph does that - // already, no code can become unreachable in the meantime. - val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter) - val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekStack(traitMethodArgumentTypes.length) - for (i <- receiverValue.insns.asScala) { - val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName) - callsite.callsiteMethod.instructions.insert(i, cast) - } - } - - val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implMethodDescriptor, false) - callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction) - callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction) - - callGraph.callsites.remove(callsite.callsiteInstruction) - val staticCallsite = Callsite( - callsiteInstruction = newCallsiteInstruction, - callsiteMethod = callsite.callsiteMethod, - callsiteClass = callsite.callsiteClass, - callee = Right(Callee( - callee = implClassMethod, - calleeDeclarationClass = implClassBType, - safeToInline = true, - safeToRewrite = false, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline, - calleeInfoWarning = infoWarning)), - argInfos = Nil, - callsiteStackHeight = callsite.callsiteStackHeight, - receiverKnownNotNull = callsite.receiverKnownNotNull, - callsitePosition = callsite.callsitePosition - ) - callGraph.callsites(newCallsiteInstruction) = staticCallsite - } - - for (warning <- res.left) { - val Right(callee) = callsite.callee - val newCallee = callee.copy(calleeInfoWarning = Some(RewriteTraitCallToStaticImplMethodFailed(calleeDeclarationClass.internalName, callee.callee.name, callee.callee.desc, warning))) - callGraph.callsites(callsite.callsiteInstruction) = callsite.copy(callee = Right(newCallee)) - } + pos(xCs) - pos(yCs) } } @@ -238,15 +75,11 @@ class Inliner[BT <: BTypes](val btypes: BT) { * The resulting list is sorted such that the leaves of the inline request graph are on the left. * Once these leaves are inlined, the successive elements will be leaves, etc. */ - private def collectAndOrderInlineRequests: List[Callsite] = { - val requests = selectCallsitesForInlining + private def collectAndOrderInlineRequests: List[InlineRequest] = { + val requestsByMethod = selectCallsitesForInlining withDefaultValue Set.empty - // This map is an index to look up the inlining requests for a method. The value sets are mutable - // to allow removing elided requests (to break inlining cycles). The map itself is mutable to - // allow efficient building: requests.groupBy would build values as List[Callsite] that need to - // be transformed to mutable sets. - val inlineRequestsForMethod: mutable.Map[MethodNode, mutable.Set[Callsite]] = mutable.HashMap.empty.withDefaultValue(mutable.HashSet.empty) - for (r <- requests) inlineRequestsForMethod.getOrElseUpdate(r.callsiteMethod, mutable.HashSet.empty) += r + val elided = mutable.Set.empty[InlineRequest] + def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided /** * Break cycles in the inline request graph by removing callsites. @@ -254,7 +87,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * The list `requests` is traversed left-to-right, removing those callsites that are part of a * cycle. Elided callsites are also removed from the `inlineRequestsForMethod` map. */ - def breakInlineCycles(requests: List[Callsite]): List[Callsite] = { + def breakInlineCycles: List[InlineRequest] = { // is there a path of inline requests from start to goal? def isReachable(start: MethodNode, goal: MethodNode): Boolean = { @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match { @@ -262,7 +95,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (x == goal) true else if (visited(x)) reachableImpl(xs, visited) else { - val callees = inlineRequestsForMethod(x).map(_.callee.get.callee) + val callees = nonElidedRequests(x).map(_.callsite.callee.get.callee) reachableImpl(xs ::: callees.toList, visited + x) } @@ -272,12 +105,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { reachableImpl(List(start), Set.empty) } - val result = new mutable.ListBuffer[Callsite]() + val result = new mutable.ListBuffer[InlineRequest]() + val requests = requestsByMethod.valuesIterator.flatten.toArray // sort the inline requests to ensure that removing requests is deterministic - for (r <- requests.sorted(callsiteOrdering)) { + java.util.Arrays.sort(requests, callsiteOrdering) + for (r <- requests) { // is there a chain of inlining requests that would inline the callsite method into the callee? - if (isReachable(r.callee.get.callee, r.callsiteMethod)) - inlineRequestsForMethod(r.callsiteMethod) -= r + if (isReachable(r.callsite.callee.get.callee, r.callsite.callsiteMethod)) + elided += r else result += r } @@ -286,11 +121,11 @@ class Inliner[BT <: BTypes](val btypes: BT) { // sort the remaining inline requests such that the leaves appear first, then those requests // that become leaves, etc. - def leavesFirst(requests: List[Callsite], visited: Set[Callsite] = Set.empty): List[Callsite] = { + def leavesFirst(requests: List[InlineRequest], visited: Set[InlineRequest] = Set.empty): List[InlineRequest] = { if (requests.isEmpty) Nil else { val (leaves, others) = requests.partition(r => { - val inlineRequestsForCallee = inlineRequestsForMethod(r.callee.get.callee) + val inlineRequestsForCallee = nonElidedRequests(r.callsite.callee.get.callee) inlineRequestsForCallee.forall(visited) }) assert(leaves.nonEmpty, requests) @@ -298,192 +133,329 @@ class Inliner[BT <: BTypes](val btypes: BT) { } } - leavesFirst(breakInlineCycles(requests)) + leavesFirst(breakInlineCycles) + } + + /** + * Given an InlineRequest(mainCallsite, post = List(postCallsite)), the postCallsite is a callsite + * in the method `mainCallsite.callee`. Once the mainCallsite is inlined into the target method + * (mainCallsite.callsiteMethod), we need to find the cloned callsite that corresponds to the + * postCallsite so we can inline that into the target method as well. + * + * However, it is possible that there is no cloned callsite at all that corresponds to the + * postCallsite, for example if the corresponding callsite already inlined. Example: + * + * def a() = 1 + * def b() = a() + 2 + * def c() = b() + 3 + * def d() = c() + 4 + * + * We have the following callsite objects in the call graph: + * + * c1 = a() in b + * c2 = b() in c + * c3 = c() in d + * + * Assume we have the following inline request + * r = InlineRequest(c3, + * post = List(InlineRequest(c2, + * post = List(InlineRequest(c1, post = Nil))))) + * + * But before inlining r, assume a separate InlineRequest(c2, post = Nil) is inlined first. We get + * + * c1' = a() in c // added to the call graph + * c1.inlinedClones += (c1' at c2) // remember that c1' was created when inlining c2 + * ~c2~ // c2 is removed from the call graph + * + * If we now inline r, we first inline c3. We get + * + * c1'' = a() in d // added to call graph + * c1'.inlinedClones += (c1'' at c3) // remember that c1'' was created when inlining c3 + * ~c3~ + * + * Now we continue with the post-requests for r, i.e. c2. + * - we try to find the clone of c2 that was created when inlining c3 - but there is none. c2 + * was already inlined before + * - we continue with the post-request of c2: c1 + * - we search for the callsite of c1 that was cloned when inlining c2, we find c1' + * - recursively we search for the callsite of c1' that was cloned when inlining c3, we find c1'' + * - so we create an inline request for c1'' + */ + def adaptPostRequestForMainCallsite(post: InlineRequest, mainCallsite: Callsite): List[InlineRequest] = { + def impl(post: InlineRequest, at: Callsite): List[InlineRequest] = { + post.callsite.inlinedClones.find(_.clonedWhenInlining == at) match { + case Some(clonedCallsite) => + List(InlineRequest(clonedCallsite.callsite, post.post)) + case None => + post.post.flatMap(impl(_, post.callsite)).flatMap(impl(_, at)) + } + } + impl(post, mainCallsite) } /** + * Inline the callsite of an inlining request and its post-inlining requests. + * + * @return An inliner warning for each callsite that could not be inlined. + */ + def inline(request: InlineRequest): List[CannotInlineWarning] = canInlineBody(request.callsite) match { + case Some(w) => List(w) + case None => + inlineCallsite(request.callsite) + val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite)) + postRequests flatMap inline + } + + /** * Copy and adapt the instructions of a method to a callsite. * * Preconditions: + * - The callsite can safely be inlined (canInlineBody is true) * - The maxLocals and maxStack values of the callsite method are correctly computed - * - The callsite method contains no unreachable basic blocks, i.e., running an [[Analyzer]] - * does not produce any `null` frames * - * @param callsiteInstruction The invocation instruction - * @param callsiteStackHeight The stack height at the callsite - * @param callsiteMethod The method in which the invocation occurs - * @param callsiteClass The class in which the callsite method is defined - * @param callee The invoked method - * @param calleeDeclarationClass The class in which the invoked method is defined - * @param receiverKnownNotNull `true` if the receiver is known to be non-null - * @param keepLineNumbers `true` if LineNumberNodes should be copied to the call site - * @return `Some(message)` if inlining cannot be performed, `None` otherwise + * @return A map associating instruction nodes of the callee with the corresponding cloned + * instruction in the callsite method. */ - def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: MethodNode, calleeDeclarationClass: ClassBType, - receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[CannotInlineWarning] = { - canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse { - // New labels for the cloned instructions - val labelsMap = cloneLabels(callee) - val (clonedInstructions, instructionMap) = cloneInstructions(callee, labelsMap) - if (!keepLineNumbers) { - removeLineNumberNodes(clonedInstructions) - } + def inlineCallsite(callsite: Callsite): Unit = { + import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight} + val Right(callsiteCallee) = callsite.callee + import callsiteCallee.{callee, calleeDeclarationClass} + + // Inlining requires the callee not to have unreachable code, the analyzer used below should not + // return any `null` frames. Note that inlining a method can create unreachable code. Example: + // def f = throw e + // def g = f; println() // println is unreachable after inlining f + // If we have an inline request for a call to g, and f has been already inlined into g, we + // need to run DCE on g's body before inlining g. + localOpt.minimalRemoveUnreachableCode(callee, calleeDeclarationClass.internalName) + + // If the callsite was eliminated by DCE, do nothing. + if (!callGraph.containsCallsite(callsite)) return + + // New labels for the cloned instructions + val labelsMap = cloneLabels(callee) + val (clonedInstructions, instructionMap, hasSerializableClosureInstantiation) = cloneInstructions(callee, labelsMap) + val keepLineNumbers = callsiteClass == calleeDeclarationClass + if (!keepLineNumbers) { + removeLineNumberNodes(clonedInstructions) + } - // local vars in the callee are shifted by the number of locals at the callsite - val localVarShift = callsiteMethod.maxLocals - clonedInstructions.iterator.asScala foreach { - case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift - case iinc: IincInsnNode => iinc.`var` += localVarShift - case _ => () - } + // local vars in the callee are shifted by the number of locals at the callsite + val localVarShift = callsiteMethod.maxLocals + clonedInstructions.iterator.asScala foreach { + case varInstruction: VarInsnNode => varInstruction.`var` += localVarShift + case iinc: IincInsnNode => iinc.`var` += localVarShift + case _ => () + } - // add a STORE instruction for each expected argument, including for THIS instance if any - val argStores = new InsnList - var nextLocalIndex = callsiteMethod.maxLocals - if (!isStaticMethod(callee)) { - if (!receiverKnownNotNull) { - argStores.add(new InsnNode(DUP)) - val nonNullLabel = newLabelNode - argStores.add(new JumpInsnNode(IFNONNULL, nonNullLabel)) - argStores.add(new InsnNode(ACONST_NULL)) - argStores.add(new InsnNode(ATHROW)) - argStores.add(nonNullLabel) - } - argStores.add(new VarInsnNode(ASTORE, nextLocalIndex)) - nextLocalIndex += 1 + // add a STORE instruction for each expected argument, including for THIS instance if any + val argStores = new InsnList + var nextLocalIndex = callsiteMethod.maxLocals + if (!isStaticMethod(callee)) { + if (!receiverKnownNotNull) { + argStores.add(new InsnNode(DUP)) + val nonNullLabel = newLabelNode + argStores.add(new JumpInsnNode(IFNONNULL, nonNullLabel)) + argStores.add(new InsnNode(ACONST_NULL)) + argStores.add(new InsnNode(ATHROW)) + argStores.add(nonNullLabel) } + argStores.add(new VarInsnNode(ASTORE, nextLocalIndex)) + nextLocalIndex += 1 + } - // We just use an asm.Type here, no need to create the MethodBType. - val calleAsmType = asm.Type.getMethodType(callee.desc) + // We just use an asm.Type here, no need to create the MethodBType. + val calleAsmType = asm.Type.getMethodType(callee.desc) + val calleeParamTypes = calleAsmType.getArgumentTypes - for(argTp <- calleAsmType.getArgumentTypes) { - val opc = argTp.getOpcode(ISTORE) // returns the correct xSTORE instruction for argTp - argStores.insert(new VarInsnNode(opc, nextLocalIndex)) // "insert" is "prepend" - the last argument is on the top of the stack - nextLocalIndex += argTp.getSize - } + for(argTp <- calleeParamTypes) { + val opc = argTp.getOpcode(ISTORE) // returns the correct xSTORE instruction for argTp + argStores.insert(new VarInsnNode(opc, nextLocalIndex)) // "insert" is "prepend" - the last argument is on the top of the stack + nextLocalIndex += argTp.getSize + } - clonedInstructions.insert(argStores) - - // label for the exit of the inlined functions. xRETURNs are replaced by GOTOs to this label. - val postCallLabel = newLabelNode - clonedInstructions.add(postCallLabel) - - // replace xRETURNs: - // - store the return value (if any) - // - clear the stack of the inlined method (insert DROPs) - // - load the return value - // - GOTO postCallLabel - - val returnType = calleAsmType.getReturnType - val hasReturnValue = returnType.getSort != asm.Type.VOID - val returnValueIndex = callsiteMethod.maxLocals + callee.maxLocals - nextLocalIndex += returnType.getSize - - def returnValueStore(returnInstruction: AbstractInsnNode) = { - val opc = returnInstruction.getOpcode match { - case IRETURN => ISTORE - case LRETURN => LSTORE - case FRETURN => FSTORE - case DRETURN => DSTORE - case ARETURN => ASTORE - } - new VarInsnNode(opc, returnValueIndex) + clonedInstructions.insert(argStores) + + // label for the exit of the inlined functions. xRETURNs are replaced by GOTOs to this label. + val postCallLabel = newLabelNode + clonedInstructions.add(postCallLabel) + + // replace xRETURNs: + // - store the return value (if any) + // - clear the stack of the inlined method (insert DROPs) + // - load the return value + // - GOTO postCallLabel + + val returnType = calleAsmType.getReturnType + val hasReturnValue = returnType.getSort != asm.Type.VOID + val returnValueIndex = callsiteMethod.maxLocals + callee.maxLocals + nextLocalIndex += returnType.getSize + + def returnValueStore(returnInstruction: AbstractInsnNode) = { + val opc = returnInstruction.getOpcode match { + case IRETURN => ISTORE + case LRETURN => LSTORE + case FRETURN => FSTORE + case DRETURN => DSTORE + case ARETURN => ASTORE } + new VarInsnNode(opc, returnValueIndex) + } - // We run an interpreter to know the stack height at each xRETURN instruction and the sizes - // of the values on the stack. - val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName) + // We run an interpreter to know the stack height at each xRETURN instruction and the sizes + // of the values on the stack. + // We don't need to worry about the method being too large for running an analysis. Callsites of + // large methods are not added to the call graph. + val analyzer = new AsmAnalyzer(callee, calleeDeclarationClass.internalName) - for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) { - val frame = analyzer.frameAt(originalReturn) - var stackHeight = frame.getStackSize + for (originalReturn <- callee.instructions.iterator().asScala if isReturn(originalReturn)) { + val frame = analyzer.frameAt(originalReturn) + var stackHeight = frame.getStackSize - val inlinedReturn = instructionMap(originalReturn) - val returnReplacement = new InsnList + val inlinedReturn = instructionMap(originalReturn) + val returnReplacement = new InsnList - def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize) + def drop(slot: Int) = returnReplacement add getPop(frame.peekStack(slot).getSize) - // for non-void methods, store the stack top into the return local variable - if (hasReturnValue) { - returnReplacement add returnValueStore(originalReturn) - stackHeight -= 1 - } + // for non-void methods, store the stack top into the return local variable + if (hasReturnValue) { + returnReplacement add returnValueStore(originalReturn) + stackHeight -= 1 + } - // drop the rest of the stack - for (i <- 0 until stackHeight) drop(i) + // drop the rest of the stack + for (i <- 0 until stackHeight) drop(i) - returnReplacement add new JumpInsnNode(GOTO, postCallLabel) - clonedInstructions.insert(inlinedReturn, returnReplacement) - clonedInstructions.remove(inlinedReturn) - } + returnReplacement add new JumpInsnNode(GOTO, postCallLabel) + clonedInstructions.insert(inlinedReturn, returnReplacement) + clonedInstructions.remove(inlinedReturn) + } - // Load instruction for the return value - if (hasReturnValue) { - val retVarLoad = { - val opc = returnType.getOpcode(ILOAD) - new VarInsnNode(opc, returnValueIndex) - } - clonedInstructions.insert(postCallLabel, retVarLoad) + // Load instruction for the return value + if (hasReturnValue) { + val retVarLoad = { + val opc = returnType.getOpcode(ILOAD) + new VarInsnNode(opc, returnValueIndex) } + clonedInstructions.insert(postCallLabel, retVarLoad) + } - callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions) - callsiteMethod.instructions.remove(callsiteInstruction) - - callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_").asJava) - callsiteMethod.tryCatchBlocks.addAll(cloneTryCatchBlockNodes(callee, labelsMap).asJava) - - // Add all invocation instructions and closure instantiations that were inlined to the call graph - callee.instructions.iterator().asScala foreach { - case originalCallsiteIns: MethodInsnNode => - callGraph.callsites.get(originalCallsiteIns) match { - case Some(originalCallsite) => - val newCallsiteIns = instructionMap(originalCallsiteIns).asInstanceOf[MethodInsnNode] - callGraph.callsites(newCallsiteIns) = Callsite( - callsiteInstruction = newCallsiteIns, - callsiteMethod = callsiteMethod, - callsiteClass = callsiteClass, - callee = originalCallsite.callee, - argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them) - callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight, - receiverKnownNotNull = originalCallsite.receiverKnownNotNull, - callsitePosition = originalCallsite.callsitePosition - ) - - case None => - } + callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions) + callsiteMethod.instructions.remove(callsiteInstruction) + + callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_", localVarShift).asJava) + // prepend the handlers of the callee. the order of handlers matters: when an exception is thrown + // at some instruction, the first handler guarding that instruction and having a matching exception + // type is executed. prepending the callee's handlers makes sure to test those handlers first if + // an exception is thrown in the inlined code. + callsiteMethod.tryCatchBlocks.addAll(0, cloneTryCatchBlockNodes(callee, labelsMap).asJava) + + callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals + val maxStackOfInlinedCode = { + // One slot per value is correct for long / double, see comment in the `analysis` package object. + val numStoredArgs = calleeParamTypes.length + (if (isStaticMethod(callee)) 0 else 1) + callee.maxStack + callsiteStackHeight - numStoredArgs + } + val stackHeightAtNullCheck = { + // When adding a null check for the receiver, a DUP is inserted, which might cause a new maxStack. + // If the callsite has other argument values than the receiver on the stack, these are pop'ed + // and stored into locals before the null check, so in that case the maxStack doesn't grow. + val stackSlotForNullCheck = if (!isStaticMethod(callee) && !receiverKnownNotNull && calleeParamTypes.isEmpty) 1 else 0 + callsiteStackHeight + stackSlotForNullCheck + } - case indy: InvokeDynamicInsnNode => - callGraph.closureInstantiations.get(indy) match { - case Some(closureInit) => - val newIndy = instructionMap(indy).asInstanceOf[InvokeDynamicInsnNode] - callGraph.closureInstantiations(newIndy) = ClosureInstantiation(closureInit.lambdaMetaFactoryCall.copy(indy = newIndy), callsiteMethod, callsiteClass) + callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode)) - case None => - } + if (hasSerializableClosureInstantiation && !indyLambdaHosts(callsiteClass.internalName)) { + indyLambdaHosts += callsiteClass.internalName + addLambdaDeserialize(byteCodeRepository.classNode(callsiteClass.internalName).get) + } - case _ => - } - // Remove the elided invocation from the call graph - callGraph.callsites.remove(callsiteInstruction) + callGraph.addIfMissing(callee, calleeDeclarationClass) - // Inlining a method body can render some code unreachable, see example above (in runInliner). - unreachableCodeEliminated -= callsiteMethod + def mapArgInfo(argInfo: (Int, ArgInfo)): Option[(Int, ArgInfo)] = argInfo match { + case lit @ (_, FunctionLiteral) => Some(lit) + case (argIndex, ForwardedParam(paramIndex)) => callsite.argInfos.get(paramIndex).map((argIndex, _)) + } - callsiteMethod.maxLocals += returnType.getSize + callee.maxLocals - callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, callee.maxStack + callsiteStackHeight) + // Add all invocation instructions and closure instantiations that were inlined to the call graph + callGraph.callsites(callee).valuesIterator foreach { originalCallsite => + val newCallsiteIns = instructionMap(originalCallsite.callsiteInstruction).asInstanceOf[MethodInsnNode] + val argInfos = originalCallsite.argInfos flatMap mapArgInfo + val newCallsite = originalCallsite.copy( + callsiteInstruction = newCallsiteIns, + callsiteMethod = callsiteMethod, + callsiteClass = callsiteClass, + argInfos = argInfos, + callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight + ) + originalCallsite.inlinedClones += ClonedCallsite(newCallsite, callsite) + callGraph.addCallsite(newCallsite) + } - None + callGraph.closureInstantiations(callee).valuesIterator foreach { originalClosureInit => + val newIndy = instructionMap(originalClosureInit.lambdaMetaFactoryCall.indy).asInstanceOf[InvokeDynamicInsnNode] + val capturedArgInfos = originalClosureInit.capturedArgInfos flatMap mapArgInfo + val newClosureInit = ClosureInstantiation( + originalClosureInit.lambdaMetaFactoryCall.copy(indy = newIndy), + callsiteMethod, + callsiteClass, + capturedArgInfos) + originalClosureInit.inlinedClones += newClosureInit + callGraph.addClosureInstantiation(newClosureInit) } + + // Remove the elided invocation from the call graph + callGraph.removeCallsite(callsiteInstruction, callsiteMethod) + + // Inlining a method body can render some code unreachable, see example above in this method. + unreachableCodeEliminated -= callsiteMethod + } + + /** + * Check whether an inlining can be performed. This method performs tests that don't change even + * if the body of the callee is changed by the inliner / optimizer, so it can be used early + * (when looking at the call graph and collecting inline requests for the program). + * + * The tests that inspect the callee's instructions are implemented in method `canInlineBody`, + * which is queried when performing an inline. + * + * @return `Some(message)` if inlining cannot be performed, `None` otherwise + */ + def earlyCanInlineCheck(callsite: Callsite): Option[CannotInlineWarning] = { + import callsite.{callsiteMethod, callsiteClass} + val Right(callsiteCallee) = callsite.callee + import callsiteCallee.{callee, calleeDeclarationClass} + + if (isSynchronizedMethod(callee)) { + // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking + // in finally. But it's probably not worth the effort, scala never emits synchronized methods. + Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) + } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { + Some(StrictfpMismatch( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) + } else + None } /** - * Check whether an inling can be performed. Parmeters are described in method [[inline]]. + * Check whether the body of the callee contains any instructions that prevent the callsite from + * being inlined. See also method `earlyCanInlineCheck`. + * + * The result of this check depends on changes to the callee method's body. For example, if the + * callee initially invokes a private method, it cannot be inlined into a different class. If the + * private method is inlined into the callee, inlining the callee becomes possible. Therefore + * we don't query it while traversing the call graph and selecting callsites to inline - it might + * rule out callsites that can be inlined just fine. + * * @return `Some(message)` if inlining cannot be performed, `None` otherwise */ - def canInline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: MethodNode, calleeDeclarationClass: ClassBType): Option[CannotInlineWarning] = { + def canInlineBody(callsite: Callsite): Option[CannotInlineWarning] = { + import callsite.{callsiteInstruction, callsiteMethod, callsiteClass, callsiteStackHeight} + val Right(callsiteCallee) = callsite.callee + import callsiteCallee.{callee, calleeDeclarationClass} def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}" def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc" @@ -514,14 +486,6 @@ class Inliner[BT <: BTypes](val btypes: BT) { Some(ResultingMethodTooLarge( calleeDeclarationClass.internalName, callee.name, callee.desc, callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) - } else if (isSynchronizedMethod(callee)) { - // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking - // in finally. But it's probably not worth the effort, scala never emits synchronized methods. - Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) - } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { - Some(StrictfpMismatch( - calleeDeclarationClass.internalName, callee.name, callee.desc, - callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { Some(MethodWithHandlerCalledOnNonEmptyStack( calleeDeclarationClass.internalName, callee.name, callee.desc, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala new file mode 100644 index 0000000000..e132ae1792 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -0,0 +1,248 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm.tree.MethodNode +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning + +class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { + import bTypes._ + import inliner._ + import callGraph._ + + case class InlineRequest(callsite: Callsite, post: List[InlineRequest]) { + // invariant: all post inline requests denote callsites in the callee of the main callsite + for (pr <- post) assert(pr.callsite.callsiteMethod == callsite.callee.get.callee, s"Callsite method mismatch: main $callsite - post ${pr.callsite}") + } + + /** + * Select callsites from the call graph that should be inlined, grouped by the containing method. + * Cyclic inlining requests are allowed, the inliner will eliminate requests to break cycles. + */ + def selectCallsitesForInlining: Map[MethodNode, Set[InlineRequest]] = { + // We should only create inlining requests for callsites being compiled (not for callsites in + // classes on the classpath). The call graph may contain callsites of classes parsed from the + // classpath. In order to get only the callsites being compiled, we start at the map of + // compilingClasses in the byteCodeRepository. + val compilingMethods = for { + classNode <- byteCodeRepository.compilingClasses.valuesIterator + methodNode <- classNode.methods.iterator.asScala + } yield methodNode + + compilingMethods.map(methodNode => { + var requests = Set.empty[InlineRequest] + callGraph.callsites(methodNode).valuesIterator foreach { + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, canInlineFromSource, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => + inlineRequest(callsite) match { + case Some(Right(req)) => requests += req + case Some(Left(w)) => + if ((calleeAnnotatedInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { + val annotWarn = if (calleeAnnotatedInline) " is annotated @inline but" else "" + val msg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)}$annotWarn could not be inlined:\n$w" + backendReporting.inlinerWarning(callsite.callsitePosition, msg) + } + + case None => + if (canInlineFromSource && calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.YoptWarningEmitAtInlineFailed) { + // if the callsite is annotated @inline, we report an inline warning even if the underlying + // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). + def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" + def warnMsg = callsiteWarning.map(" Possible reason:\n" + _).getOrElse("") + if (!safeToInline) + backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) + else + backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) + } else if (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings)) { + // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. + backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ callsiteWarning.get) + } + } + + case Callsite(ins, _, _, Left(warning), _, _, _, pos, _, _) => + if (warning.emitWarning(compilerSettings)) + backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") + } + (methodNode, requests) + }).filterNot(_._2.isEmpty).toMap + } + + /** + * Returns the inline request for a callsite if the callsite should be inlined according to the + * current heuristics (`-Yopt-inline-heuristics`). + * + * The resulting inline request may contain post-inlining requests of callsites that in turn are + * also selected as individual inlining requests. + * + * @return `None` if this callsite should not be inlined according to the active heuristic + * `Some(Left)` if the callsite cannot be inlined (for example because that would cause + * an IllegalAccessError) but should be according to the heuristic + * TODO: what if a downstream inline request would cause an IAE and we don't create an + * InlineRequest for the original callsite? new subclass of OptimizerWarning. + * `Some(Right)` if the callsite should be and can be inlined + */ + def inlineRequest(callsite: Callsite): Option[Either[OptimizerWarning, InlineRequest]] = { + val callee = callsite.callee.get + def requestIfCanInline(callsite: Callsite): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { + case Some(w) => Left(w) + case None => Right(InlineRequest(callsite, Nil)) + } + + compilerSettings.YoptInlineHeuristics.value match { + case "everything" => + if (callee.safeToInline) Some(requestIfCanInline(callsite)) + else None + + case "at-inline-annotated" => + if (callee.safeToInline && callee.annotatedInline) Some(requestIfCanInline(callsite)) + else None + + case "default" => + if (callee.safeToInline && !callee.annotatedNoInline && !callsite.annotatedNoInline) { + def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { + case (index, _) => callsite.argInfos.contains(index) + }) + if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) Some(requestIfCanInline(callsite)) + else None + } else None + } + } + + /* + // using http://lihaoyi.github.io/Ammonite/ + + load.ivy("com.google.guava" % "guava" % "18.0") + val javaUtilFunctionClasses = { + val rt = System.getProperty("sun.boot.class.path").split(":").find(_.endsWith("lib/rt.jar")).get + val u = new java.io.File(rt).toURL + val l = new java.net.URLClassLoader(Array(u)) + val cp = com.google.common.reflect.ClassPath.from(l) + cp.getTopLevelClasses("java.util.function").toArray.map(_.toString).toList + } + + // found using IntelliJ's "Find Usages" on the @FunctionalInterface annotation + val otherClasses = List( + "com.sun.javafx.css.parser.Recognizer", + "java.awt.KeyEventDispatcher", + "java.awt.KeyEventPostProcessor", + "java.io.FileFilter", + "java.io.FilenameFilter", + "java.lang.Runnable", + "java.lang.Thread$UncaughtExceptionHandler", + "java.nio.file.DirectoryStream$Filter", + "java.nio.file.PathMatcher", + "java.time.temporal.TemporalAdjuster", + "java.time.temporal.TemporalQuery", + "java.util.Comparator", + "java.util.concurrent.Callable", + "java.util.logging.Filter", + "java.util.prefs.PreferenceChangeListener", + "javafx.animation.Interpolatable", + "javafx.beans.InvalidationListener", + "javafx.beans.value.ChangeListener", + "javafx.collections.ListChangeListener", + "javafx.collections.MapChangeListener", + "javafx.collections.SetChangeListener", + "javafx.event.EventHandler", + "javafx.util.Builder", + "javafx.util.BuilderFactory", + "javafx.util.Callback" + ) + + val allClasses = javaUtilFunctionClasses ::: otherClasses + + load.ivy("org.ow2.asm" % "asm" % "5.0.4") + val classesAndSamNameDesc = allClasses.map(c => { + val cls = Class.forName(c) + val internalName = org.objectweb.asm.Type.getDescriptor(cls).drop(1).dropRight(1) // drop L and ; + val sams = cls.getMethods.filter(m => { + (m.getModifiers & java.lang.reflect.Modifier.ABSTRACT) != 0 && + m.getName != "equals" // Comparator has an abstract override of "equals" for adding Javadoc + }) + assert(sams.size == 1, internalName + sams.map(_.getName)) + val sam = sams.head + val samDesc = org.objectweb.asm.Type.getMethodDescriptor(sam) + (internalName, sam.getName, samDesc) + }) + println(classesAndSamNameDesc map { + case (cls, nme, desc) => s"""("$cls", "$nme$desc")""" + } mkString ("", ",\n", "\n")) + */ + private val javaSams: Map[String, String] = Map( + ("java/util/function/BiConsumer", "accept(Ljava/lang/Object;Ljava/lang/Object;)V"), + ("java/util/function/BiFunction", "apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + ("java/util/function/BiPredicate", "test(Ljava/lang/Object;Ljava/lang/Object;)Z"), + ("java/util/function/BinaryOperator", "apply(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), + ("java/util/function/BooleanSupplier", "getAsBoolean()Z"), + ("java/util/function/Consumer", "accept(Ljava/lang/Object;)V"), + ("java/util/function/DoubleBinaryOperator", "applyAsDouble(DD)D"), + ("java/util/function/DoubleConsumer", "accept(D)V"), + ("java/util/function/DoubleFunction", "apply(D)Ljava/lang/Object;"), + ("java/util/function/DoublePredicate", "test(D)Z"), + ("java/util/function/DoubleSupplier", "getAsDouble()D"), + ("java/util/function/DoubleToIntFunction", "applyAsInt(D)I"), + ("java/util/function/DoubleToLongFunction", "applyAsLong(D)J"), + ("java/util/function/DoubleUnaryOperator", "applyAsDouble(D)D"), + ("java/util/function/Function", "apply(Ljava/lang/Object;)Ljava/lang/Object;"), + ("java/util/function/IntBinaryOperator", "applyAsInt(II)I"), + ("java/util/function/IntConsumer", "accept(I)V"), + ("java/util/function/IntFunction", "apply(I)Ljava/lang/Object;"), + ("java/util/function/IntPredicate", "test(I)Z"), + ("java/util/function/IntSupplier", "getAsInt()I"), + ("java/util/function/IntToDoubleFunction", "applyAsDouble(I)D"), + ("java/util/function/IntToLongFunction", "applyAsLong(I)J"), + ("java/util/function/IntUnaryOperator", "applyAsInt(I)I"), + ("java/util/function/LongBinaryOperator", "applyAsLong(JJ)J"), + ("java/util/function/LongConsumer", "accept(J)V"), + ("java/util/function/LongFunction", "apply(J)Ljava/lang/Object;"), + ("java/util/function/LongPredicate", "test(J)Z"), + ("java/util/function/LongSupplier", "getAsLong()J"), + ("java/util/function/LongToDoubleFunction", "applyAsDouble(J)D"), + ("java/util/function/LongToIntFunction", "applyAsInt(J)I"), + ("java/util/function/LongUnaryOperator", "applyAsLong(J)J"), + ("java/util/function/ObjDoubleConsumer", "accept(Ljava/lang/Object;D)V"), + ("java/util/function/ObjIntConsumer", "accept(Ljava/lang/Object;I)V"), + ("java/util/function/ObjLongConsumer", "accept(Ljava/lang/Object;J)V"), + ("java/util/function/Predicate", "test(Ljava/lang/Object;)Z"), + ("java/util/function/Supplier", "get()Ljava/lang/Object;"), + ("java/util/function/ToDoubleBiFunction", "applyAsDouble(Ljava/lang/Object;Ljava/lang/Object;)D"), + ("java/util/function/ToDoubleFunction", "applyAsDouble(Ljava/lang/Object;)D"), + ("java/util/function/ToIntBiFunction", "applyAsInt(Ljava/lang/Object;Ljava/lang/Object;)I"), + ("java/util/function/ToIntFunction", "applyAsInt(Ljava/lang/Object;)I"), + ("java/util/function/ToLongBiFunction", "applyAsLong(Ljava/lang/Object;Ljava/lang/Object;)J"), + ("java/util/function/ToLongFunction", "applyAsLong(Ljava/lang/Object;)J"), + ("java/util/function/UnaryOperator", "apply(Ljava/lang/Object;)Ljava/lang/Object;"), + ("com/sun/javafx/css/parser/Recognizer", "recognize(I)Z"), + ("java/awt/KeyEventDispatcher", "dispatchKeyEvent(Ljava/awt/event/KeyEvent;)Z"), + ("java/awt/KeyEventPostProcessor", "postProcessKeyEvent(Ljava/awt/event/KeyEvent;)Z"), + ("java/io/FileFilter", "accept(Ljava/io/File;)Z"), + ("java/io/FilenameFilter", "accept(Ljava/io/File;Ljava/lang/String;)Z"), + ("java/lang/Runnable", "run()V"), + ("java/lang/Thread$UncaughtExceptionHandler", "uncaughtException(Ljava/lang/Thread;Ljava/lang/Throwable;)V"), + ("java/nio/file/DirectoryStream$Filter", "accept(Ljava/lang/Object;)Z"), + ("java/nio/file/PathMatcher", "matches(Ljava/nio/file/Path;)Z"), + ("java/time/temporal/TemporalAdjuster", "adjustInto(Ljava/time/temporal/Temporal;)Ljava/time/temporal/Temporal;"), + ("java/time/temporal/TemporalQuery", "queryFrom(Ljava/time/temporal/TemporalAccessor;)Ljava/lang/Object;"), + ("java/util/Comparator", "compare(Ljava/lang/Object;Ljava/lang/Object;)I"), + ("java/util/concurrent/Callable", "call()Ljava/lang/Object;"), + ("java/util/logging/Filter", "isLoggable(Ljava/util/logging/LogRecord;)Z"), + ("java/util/prefs/PreferenceChangeListener", "preferenceChange(Ljava/util/prefs/PreferenceChangeEvent;)V"), + ("javafx/animation/Interpolatable", "interpolate(Ljava/lang/Object;D)Ljava/lang/Object;"), + ("javafx/beans/InvalidationListener", "invalidated(Ljavafx/beans/Observable;)V"), + ("javafx/beans/value/ChangeListener", "changed(Ljavafx/beans/value/ObservableValue;Ljava/lang/Object;Ljava/lang/Object;)V"), + ("javafx/collections/ListChangeListener", "onChanged(Ljavafx/collections/ListChangeListener$Change;)V"), + ("javafx/collections/MapChangeListener", "onChanged(Ljavafx/collections/MapChangeListener$Change;)V"), + ("javafx/collections/SetChangeListener", "onChanged(Ljavafx/collections/SetChangeListener$Change;)V"), + ("javafx/event/EventHandler", "handle(Ljavafx/event/Event;)V"), + ("javafx/util/Builder", "build()Ljava/lang/Object;"), + ("javafx/util/BuilderFactory", "getBuilder(Ljava/lang/Class;)Ljavafx/util/Builder;"), + ("javafx/util/Callback", "call(Ljava/lang/Object;)Ljava/lang/Object;") + ) + def javaSam(internalName: InternalName): Option[String] = javaSams.get(internalName) +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala deleted file mode 100644 index 8d744f6d13..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InstructionResultSize.scala +++ /dev/null @@ -1,240 +0,0 @@ -package scala.tools.nsc.backend.jvm.opt - -import scala.annotation.switch -import scala.tools.asm.{Handle, Type, Opcodes} -import scala.tools.asm.tree._ - -object InstructionResultSize { - import Opcodes._ - def apply(instruction: AbstractInsnNode): Int = (instruction.getOpcode: @switch) match { - // The order of opcodes is (almost) the same as in Opcodes.java - case ACONST_NULL => 1 - - case ICONST_M1 | - ICONST_0 | - ICONST_1 | - ICONST_2 | - ICONST_3 | - ICONST_4 | - ICONST_5 => 1 - - case LCONST_0 | - LCONST_1 => 2 - - case FCONST_0 | - FCONST_1 | - FCONST_2 => 1 - - case DCONST_0 | - DCONST_1 => 2 - - case BIPUSH | - SIPUSH => 1 - - case LDC => - instruction.asInstanceOf[LdcInsnNode].cst match { - case _: java.lang.Integer | - _: java.lang.Float | - _: String | - _: Type | - _: Handle => 1 - - case _: java.lang.Long | - _: java.lang.Double => 2 - } - - case ILOAD | - FLOAD | - ALOAD => 1 - - case LLOAD | - DLOAD => 2 - - case IALOAD | - FALOAD | - AALOAD | - BALOAD | - CALOAD | - SALOAD => 1 - - case LALOAD | - DALOAD => 2 - - case ISTORE | - LSTORE | - FSTORE | - DSTORE | - ASTORE => 0 - - case IASTORE | - LASTORE | - FASTORE | - DASTORE | - AASTORE | - BASTORE | - CASTORE | - SASTORE => 0 - - case POP | - POP2 => 0 - - case DUP | - DUP_X1 | - DUP_X2 | - DUP2 | - DUP2_X1 | - DUP2_X2 | - SWAP => throw new IllegalArgumentException("Can't compute the size of DUP/SWAP without knowing what's on stack top") - - case IADD | - FADD => 1 - - case LADD | - DADD => 2 - - case ISUB | - FSUB => 1 - - case LSUB | - DSUB => 2 - - case IMUL | - FMUL => 1 - - case LMUL | - DMUL => 2 - - case IDIV | - FDIV => 1 - - case LDIV | - DDIV => 2 - - case IREM | - FREM => 1 - - case LREM | - DREM => 2 - - case INEG | - FNEG => 1 - - case LNEG | - DNEG => 2 - - case ISHL | - ISHR => 1 - - case LSHL | - LSHR => 2 - - case IUSHR => 1 - - case LUSHR => 2 - - case IAND | - IOR | - IXOR => 1 - - case LAND | - LOR | - LXOR => 2 - - case IINC => 1 - - case I2F | - L2I | - L2F | - F2I | - D2I | - D2F | - I2B | - I2C | - I2S => 1 - - case I2L | - I2D | - L2D | - F2L | - F2D | - D2L => 2 - - case LCMP | - FCMPL | - FCMPG | - DCMPL | - DCMPG => 1 - - case IFEQ | - IFNE | - IFLT | - IFGE | - IFGT | - IFLE => 0 - - case IF_ICMPEQ | - IF_ICMPNE | - IF_ICMPLT | - IF_ICMPGE | - IF_ICMPGT | - IF_ICMPLE | - IF_ACMPEQ | - IF_ACMPNE => 0 - - case GOTO => 0 - - case JSR => throw new IllegalArgumentException("Subroutines are not supported.") - - case RET => 0 - - case TABLESWITCH | - LOOKUPSWITCH => 0 - - case IRETURN | - FRETURN | - ARETURN => 1 - - case LRETURN | - DRETURN => 2 - - case RETURN => 0 - - case GETSTATIC => Type.getType(instruction.asInstanceOf[FieldInsnNode].desc).getSize - - case PUTSTATIC => 0 - - case GETFIELD => Type.getType(instruction.asInstanceOf[FieldInsnNode].desc).getSize - - case PUTFIELD => 0 - - case INVOKEVIRTUAL | - INVOKESPECIAL | - INVOKESTATIC | - INVOKEINTERFACE => - val desc = instruction.asInstanceOf[MethodInsnNode].desc - Type.getReturnType(desc).getSize - - case INVOKEDYNAMIC => - val desc = instruction.asInstanceOf[InvokeDynamicInsnNode].desc - Type.getReturnType(desc).getSize - - case NEW => 1 - - case NEWARRAY | - ANEWARRAY | - ARRAYLENGTH => 1 - - case ATHROW => 0 - - case CHECKCAST | - INSTANCEOF => 1 - - case MONITORENTER | - MONITOREXIT => 0 - - case MULTIANEWARRAY => 1 - - case IFNULL | - IFNONNULL => 0 - } -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index 4132710a96..f120357c63 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -7,79 +7,180 @@ package scala.tools.nsc package backend.jvm package opt -import scala.annotation.switch -import scala.tools.asm.Opcodes -import scala.tools.asm.tree.analysis.{Analyzer, BasicInterpreter} +import scala.annotation.{tailrec, switch} + +import scala.tools.asm.Type +import scala.tools.asm.tree.analysis.Frame +import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ +import scala.collection.mutable import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.analysis._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ /** - * Optimizations within a single method. + * Optimizations within a single method. Certain optimizations enable others, for example removing + * unreachable code can render a `try` block empty and enable removeEmptyExceptionHandlers. The + * latter in turn enables more unreachable code to be eliminated (the `catch` block), so there is + * a cyclic dependency. Optimizations that depend on each other are therefore executed in a loop + * until reaching a fixpoint. + * + * The optimizations marked UPSTREAM enable optimizations that were already executed, so they cause + * another iteration in the fixpoint loop. + * + * nullness optimizations: rewrite null-checking branches to GOTO if nullness is known + * + enables downstream + * - unreachable code (null / non-null branch becomes unreachable) + * - box-unbox elimination (may render an escaping consumer of a box unreachable) + * - stale stores (aload x is replaced by aconst_null if it's known null) + * - simplify jumps (replaces conditional jumps by goto, so may enable goto chains) + * + * unreachable code / DCE (removes instructions of basic blocks to which there is no branch) + * + enables downstream: + * - stale stores (loads may be eliminated, removing consumers of a store) + * - empty handlers (try blocks may become empty) + * - simplify jumps (goto l; [dead code]; l: ..) => remove goto + * - stale local variable descriptors + * - (not box-unbox, which is implemented using prod-cons, so it doesn't consider dead code) + * + * note that eliminating empty handlers and stale local variable descriptors is required for + * correctness, see the comment in the body of `methodOptimizations`. + * + * box-unbox elimination (eliminates box-unbox pairs withing the same method) + * + enables UPSTREAM: + * - nullness optimizations (a box extraction operation (unknown nullness) may be rewritten to + * a read of a non-null local. example in doc comment of box-unbox implementation) + * - further box-unbox elimination (e.g. an Integer stored in a Tuple; eliminating the tuple may + * enable eliminating the Integer) + * + enables downstream: + * - copy propagation (new locals are introduced, may be aliases of existing) + * - stale stores (multi-value boxes where not all values are used) + * - redundant casts (`("a", "b")._1`: the generic `_1` method returns `Object`, a cast + * to String is added. The cast is redundant after eliminating the tuple.) + * - empty local variable descriptors (local variables that were holding the box may become unused) + * + * copy propagation (replaces LOAD n to the LOAD m for the smallest m that is an alias of n) + * + enables downstrem: + * - stale stores (a stored value may not be loaded anymore) + * - store-load pairs (a load n may now be right after a store n) + * + NOTE: copy propagation is only executed once, in the first fixpoint loop iteration. none of + * the other optimizations enables further copy prop. we still run it as part of the loop + * because it requires unreachable code to be eliminated. + * + * stale stores (replace STORE by POP) + * + enables downstream: + * - push-pop (the new pop may be the single consumer for an instruction) + * + * redundant casts: eliminates casts that are statically known to succeed (uses type propagation) + * + enables UPSTREAM: + * - box-unbox elimination (a removed checkcast may be a box consumer) + * + enables downstream: + * - push-pop for closure allocation elimination (every indyLambda is followed by a checkcast, see SI-9540) + * + * push-pop (when a POP is the only consumer of a value, remove the POP and its producer) + * + enables UPSTREAM: + * - stale stores (if a LOAD is removed, a corresponding STORE may become stale) + * - box-unbox elimination (push-pop may eliminate a closure allocation, rendering a captured + * box non-escaping) + * + enables downstream: + * - store-load pairs (a variable may become non-live) + * - stale handlers (push-pop removes code) + * - simplify jumps (push-pop removes code) + * + * store-load pairs (remove `STORE x; LOAD x` if x is otherwise not used in the method) + * + enables downstream: + * - empty handlers (code is removes, a try block may become empty + * - simplify jumps (code is removed, a goto may become redundant for example) + * - stale local variable descriptors * - * unreachable code - * - removes instructions of basic blocks to which no branch instruction points - * + enables eliminating some exception handlers and local variable descriptors - * > eliminating them is required for correctness, as explained in `removeUnreachableCode` + * empty handlers (removes exception handlers whose try block is empty) + * + enables UPSTREAM: + * - unreachable code (catch block becomes unreachable) + * - box-unbox (a box may be escape in an operation in a dead handler) + * + enables downstream: + * - simplify jumps * - * empty exception handlers - * - removes exception handlers whose try block is empty - * + eliminating a handler where the try block is empty and reachable will turn the catch block - * unreachable. in this case "unreachable code" is invoked recursively until reaching a fixpoint. - * > for try blocks that are unreachable, "unreachable code" removes also the instructions of the - * catch block, and the recursive invocation is not necessary. + * simplify jumps (various, like `GOTO l; l: ...`, see doc comments of individual optimizations) + * + enables UPSTREAM + * - unreachable code (`GOTO a; a: GOTO b; b: ...`, the first jump is changed to `GOTO b`, the second becomes unreachable) + * - store-load pairs (a `GOTO l; l: ...` is removed between store and load) + * - push-pop (`IFNULL l; l: ...` is replaced by `POP`) * - * simplify jumps - * - various simplifications, see doc comments of individual optimizations - * + changing or eliminating jumps may render some code unreachable, therefore "simplify jumps" is - * executed in a loop with "unreachable code" * - * empty local variable descriptors - * - removes entries from the local variable table where the variable is not actually used - * + enables eliminating labels that the entry points to (if they are not otherwise referenced) + * The following cleanup optimizations don't enable any upstream optimizations, so they can be + * executed once at the end, when the above optimizations reach a fixpoint. * - * empty line numbers - * - eliminates line number nodes that describe no executable instructions - * + enables eliminating the label of the line number node (if it's not otherwise referenced) * - * stale labels - * - eliminate labels that are not referenced, merge sequences of label definitions. + * empty local variable descriptors (removes unused variables from the local variable table) + * + enables downstream: + * - stale labels (labels that the entry points to, if not otherwise referenced) + * + * empty line numbers (eliminates line number nodes that describe no executable instructions) + * + enables downstream: + * - stale labels (label of the line number node, if not otherwise referenced) + * + * stale labels (eliminate labels that are not referenced, merge sequences of label definitions) + * + * + * Note on a method's maxLocals / maxStack: the backend only uses those values for running + * Analyzers. The values can be conservative approximations: if an optimization removes code and + * the maximal stack size is now smaller, the larger maxStack value will still work fine for + * running an Analyzer (just that frames allocate more space than required). The correct max + * values written to the bytecode are re-computed during classfile serialization. + * To keep things simpler, we don't update the max values in every optimization: + * - we do it in `removeUnreachableCodeImpl`, because it's quite straightforward + * - maxLocals is updated in `compactLocalVariables`, which runs at the end of method optimizations + * + * + * Note on updating the call graph: whenever an optimization eliminates a callsite or a closure + * instantiation, we eliminate the corresponding entry from the call graph. */ class LocalOpt[BT <: BTypes](val btypes: BT) { import LocalOptImpls._ import btypes._ + import coreBTypes._ + import backendUtils._ + + val boxUnbox = new BoxUnbox(btypes) + import boxUnbox._ + + val copyProp = new CopyProp(btypes) + import copyProp._ /** * Remove unreachable code from a method. * * This implementation only removes instructions that are unreachable for an ASM analyzer / * interpreter. This ensures that future analyses will not produce `null` frames. The inliner - * and call graph builder depend on this property. + * depends on this property. * * @return A set containing the eliminated instructions */ - def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Set[AbstractInsnNode] = { - if (method.instructions.size == 0) return Set.empty // fast path for abstract methods - if (unreachableCodeEliminated(method)) return Set.empty // we know there is no unreachable code + def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean = { + // In principle, for the inliner, a single removeUnreachableCodeImpl would be enough. But that + // would potentially leave behind stale handlers (empty try block) which is not legal in the + // classfile. So we run both removeUnreachableCodeImpl and removeEmptyExceptionHandlers. + if (method.instructions.size == 0) return false // fast path for abstract methods + if (unreachableCodeEliminated(method)) return false // we know there is no unreachable code + if (!AsmAnalyzer.sizeOKForBasicValue(method)) return false // the method is too large for running an analyzer // For correctness, after removing unreachable code, we have to eliminate empty exception // handlers, see scaladoc of def methodOptimizations. Removing an live handler may render more // code unreachable and therefore requires running another round. - def removalRound(): Set[AbstractInsnNode] = { - val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) - val removedRecursively = if (removedInstructions.nonEmpty) { + def removalRound(): Boolean = { + val (insnsRemoved, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) + if (insnsRemoved) { val liveHandlerRemoved = removeEmptyExceptionHandlers(method).exists(h => liveLabels(h.start)) if (liveHandlerRemoved) removalRound() - else Set.empty - } else Set.empty - removedInstructions ++ removedRecursively + } + insnsRemoved } - val removedInstructions = removalRound() - if (removedInstructions.nonEmpty) removeUnusedLocalVariableNodes(method)() + val changed = removalRound() + if (changed) removeUnusedLocalVariableNodes(method)() unreachableCodeEliminated += method - removedInstructions + changed } /** @@ -96,15 +197,7 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { } /** - * Remove unreachable code from a method. - * - * We rely on dead code elimination provided by the ASM framework, as described in the ASM User - * Guide (http://asm.ow2.org/index.html), Section 8.2.1. It runs a data flow analysis, which only - * computes Frame information for reachable instructions. Instructions for which no Frame data is - * available after the analysis are unreachable. - * - * Also simplifies branching instructions, removes unused local variable descriptors, empty - * exception handlers, unnecessary label declarations and empty line number nodes. + * Run method-level optimizations, see comment on class [[LocalOpt]]. * * Returns `true` if the bytecode of `method` was changed. */ @@ -137,36 +230,150 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { // This triggers "ClassFormatError: Illegal exception table range in class file C". Similar // for local variables in dead blocks. Maybe that's a bug in the ASM framework. - def removalRound(): Boolean = { - // unreachable-code, empty-handlers and simplify-jumps run until reaching a fixpoint (see doc on class LocalOpt) - val (codeRemoved, handlersRemoved, liveHandlerRemoved) = if (compilerSettings.YoptUnreachableCode) { - val (removedInstructions, liveLabels) = removeUnreachableCodeImpl(method, ownerClassName) - val removedHandlers = removeEmptyExceptionHandlers(method) - (removedInstructions.nonEmpty, removedHandlers.nonEmpty, removedHandlers.exists(h => liveLabels(h.start))) - } else { - (false, false, false) + var currentTrace: String = null + val doTrace = compilerSettings.YoptTrace.isSetByUser && compilerSettings.YoptTrace.value == ownerClassName + "." + method.name + def traceIfChanged(optName: String): Unit = if (doTrace) { + val after = AsmUtils.textify(method) + if (currentTrace != after) { + println(s"after $optName") + println(after) } - - val jumpsChanged = if (compilerSettings.YoptSimplifyJumps) simplifyJumps(method) else false - - // Eliminating live handlers and simplifying jump instructions may render more code - // unreachable, so we need to run another round. - if (liveHandlerRemoved || jumpsChanged) removalRound() - - codeRemoved || handlersRemoved || jumpsChanged + currentTrace = after } - val codeHandlersOrJumpsChanged = removalRound() + /** + * Runs the optimizations that depend on each other in a loop until reaching a fixpoint. See + * comment in class [[LocalOpt]]. + * + * Returns a pair of booleans (codeChanged, requireEliminateUnusedLocals). + */ + def removalRound( + requestNullness: Boolean, + requestDCE: Boolean, + requestBoxUnbox: Boolean, + requestStaleStores: Boolean, + requestPushPop: Boolean, + requestStoreLoad: Boolean, + firstIteration: Boolean, + maxRecursion: Int = 10): (Boolean, Boolean) = { + if (maxRecursion == 0) return (false, false) + + traceIfChanged("beforeMethodOpt") + + // NULLNESS OPTIMIZATIONS + val runNullness = compilerSettings.YoptNullnessTracking && requestNullness + val nullnessOptChanged = runNullness && nullnessOptimizations(method, ownerClassName) + traceIfChanged("nullness") + + // UNREACHABLE CODE + // Both AliasingAnalyzer (used in copyProp) and ProdConsAnalyzer (used in eliminateStaleStores, + // boxUnboxElimination) require not having unreachable instructions (null frames). + val runDCE = (compilerSettings.YoptUnreachableCode && (requestDCE || nullnessOptChanged)) || + compilerSettings.YoptBoxUnbox || + compilerSettings.YoptCopyPropagation + val (codeRemoved, liveLabels) = if (runDCE) removeUnreachableCodeImpl(method, ownerClassName) else (false, Set.empty[LabelNode]) + traceIfChanged("dce") + + // BOX-UNBOX + val runBoxUnbox = compilerSettings.YoptBoxUnbox && (requestBoxUnbox || nullnessOptChanged) + val boxUnboxChanged = runBoxUnbox && boxUnboxElimination(method, ownerClassName) + traceIfChanged("boxUnbox") + + // COPY PROPAGATION + val runCopyProp = compilerSettings.YoptCopyPropagation && (firstIteration || boxUnboxChanged) + val copyPropChanged = runCopyProp && copyPropagation(method, ownerClassName) + traceIfChanged("copyProp") + + // STALE STORES + val runStaleStores = compilerSettings.YoptCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged) + val storesRemoved = runStaleStores && eliminateStaleStores(method, ownerClassName) + traceIfChanged("staleStores") + + // REDUNDANT CASTS + val runRedundantCasts = compilerSettings.YoptRedundantCasts && (firstIteration || boxUnboxChanged) + val castRemoved = runRedundantCasts && eliminateRedundantCasts(method, ownerClassName) + traceIfChanged("redundantCasts") + + // PUSH-POP + val runPushPop = compilerSettings.YoptCopyPropagation && (requestPushPop || firstIteration || storesRemoved || castRemoved) + val pushPopRemoved = runPushPop && eliminatePushPop(method, ownerClassName) + traceIfChanged("pushPop") + + // STORE-LOAD PAIRS + val runStoreLoad = compilerSettings.YoptCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved) + val storeLoadRemoved = runStoreLoad && eliminateStoreLoad(method) + traceIfChanged("storeLoadPairs") + + // STALE HANDLERS + val removedHandlers = if (runDCE) removeEmptyExceptionHandlers(method) else Set.empty[TryCatchBlockNode] + val handlersRemoved = removedHandlers.nonEmpty + val liveHandlerRemoved = removedHandlers.exists(h => liveLabels(h.start)) + traceIfChanged("staleHandlers") + + // SIMPLIFY JUMPS + // almost all of the above optimizations enable simplifying more jumps, so we just run it in every iteration + val runSimplifyJumps = compilerSettings.YoptSimplifyJumps + val jumpsChanged = runSimplifyJumps && simplifyJumps(method) + traceIfChanged("simplifyJumps") + + // See doc comment in the beginning of this file (optimizations marked UPSTREAM) + val runNullnessAgain = boxUnboxChanged + val runDCEAgain = liveHandlerRemoved || jumpsChanged + val runBoxUnboxAgain = boxUnboxChanged || castRemoved || pushPopRemoved || liveHandlerRemoved + val runStaleStoresAgain = pushPopRemoved + val runPushPopAgain = jumpsChanged + val runStoreLoadAgain = jumpsChanged + val runAgain = runNullnessAgain || runDCEAgain || runBoxUnboxAgain || pushPopRemoved || runStaleStoresAgain || runPushPopAgain || runStoreLoadAgain + + val downstreamRequireEliminateUnusedLocals = runAgain && removalRound( + requestNullness = runNullnessAgain, + requestDCE = runDCEAgain, + requestBoxUnbox = runBoxUnboxAgain, + requestStaleStores = runStaleStoresAgain, + requestPushPop = runPushPopAgain, + requestStoreLoad = runStoreLoadAgain, + firstIteration = false, + maxRecursion = maxRecursion - 1)._2 + + val requireEliminateUnusedLocals = downstreamRequireEliminateUnusedLocals || + nullnessOptChanged || // nullness opt may eliminate stores / loads, rendering a local unused + codeRemoved || // see comment in method `methodOptimizations` + boxUnboxChanged || // box-unbox renders locals (holding boxes) unused + storesRemoved || + storeLoadRemoved || + handlersRemoved + + val codeChanged = nullnessOptChanged || codeRemoved || boxUnboxChanged || castRemoved || copyPropChanged || storesRemoved || pushPopRemoved || storeLoadRemoved || handlersRemoved || jumpsChanged + (codeChanged, requireEliminateUnusedLocals) + } - // (*) Removing stale local variable descriptors is required for correctness of unreachable-code + val (nullnessDceBoxesCastsCopypropPushpopOrJumpsChanged, requireEliminateUnusedLocals) = if (AsmAnalyzer.sizeOKForBasicValue(method)) { + // we run DCE even if the method is already in the `unreachableCodeEliminated` map: the DCE + // here is more thorough than `minimalRemoveUnreachableCode` that run before inlining. + val r = removalRound( + requestNullness = true, + requestDCE = true, + requestBoxUnbox = true, + requestStaleStores = true, + requestPushPop = true, + requestStoreLoad = true, + firstIteration = true) + if (compilerSettings.YoptUnreachableCode) unreachableCodeEliminated += method + r + } else (false, false) + + // (*) Removing stale local variable descriptors is required for correctness, see comment in `methodOptimizations` val localsRemoved = if (compilerSettings.YoptCompactLocals) compactLocalVariables(method) // also removes unused - else if (compilerSettings.YoptUnreachableCode) removeUnusedLocalVariableNodes(method)() // (*) + else if (requireEliminateUnusedLocals) removeUnusedLocalVariableNodes(method)() // (*) else false + traceIfChanged("localVariables") - val lineNumbersRemoved = if (compilerSettings.YoptEmptyLineNumbers) removeEmptyLineNumbers(method) else false + val lineNumbersRemoved = if (compilerSettings.YoptUnreachableCode) removeEmptyLineNumbers(method) else false + traceIfChanged("lineNumbers") - val labelsRemoved = if (compilerSettings.YoptEmptyLabels) removeEmptyLabelNodes(method) else false + val labelsRemoved = if (compilerSettings.YoptUnreachableCode) removeEmptyLabelNodes(method) else false + traceIfChanged("labels") // assert that local variable annotations are empty (we don't emit them) - otherwise we'd have // to eliminate those covering an empty range, similar to removeUnusedLocalVariableNodes. @@ -174,53 +381,198 @@ class LocalOpt[BT <: BTypes](val btypes: BT) { assert(nullOrEmpty(method.visibleLocalVariableAnnotations), method.visibleLocalVariableAnnotations) assert(nullOrEmpty(method.invisibleLocalVariableAnnotations), method.invisibleLocalVariableAnnotations) - unreachableCodeEliminated += method - - codeHandlersOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved + nullnessDceBoxesCastsCopypropPushpopOrJumpsChanged || localsRemoved || lineNumbersRemoved || labelsRemoved } -} + /** + * Apply various optimizations based on nullness analysis information. + * - IFNULL / IFNONNULL are rewritten to GOTO if nullness is known + * - IF_ACMPEQ / IF_ACMPNE are rewritten to GOTO if the both references are known null, or if + * one is known null and the other known not-null + * - ALOAD is replaced by ACONST_NULL if the local is known to hold null + * - ASTORE of null is removed if the local is known to hold null + * - INSTANCEOF of null is replaced by `ICONST_0` + * - scala.runtime.BoxesRunTime.unboxToX(null) is rewritten to a zero-value load + */ + def nullnessOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = { + AsmAnalyzer.sizeOKForNullness(method) && { + lazy val nullnessAnalyzer = new AsmAnalyzer(method, ownerClassName, new NullnessAnalyzer(btypes)) + + // When running nullness optimizations the method may still have unreachable code. Analyzer + // frames of unreachable instructions are `null`. + def frameAt(insn: AbstractInsnNode): Option[Frame[NullnessValue]] = Option(nullnessAnalyzer.frameAt(insn)) + + def nullness(insn: AbstractInsnNode, slot: Int): Option[NullnessValue] = { + frameAt(insn).map(_.getValue(slot)) + } + + def isNull(insn: AbstractInsnNode, slot: Int) = nullness(insn, slot).contains(NullValue) + + // cannot change instructions while iterating, it gets the analysis out of synch (indexed by instructions) + val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]] + + val it = method.instructions.iterator() + while (it.hasNext) it.next() match { + case vi: VarInsnNode if isNull(vi, vi.`var`) => + if (vi.getOpcode == ALOAD) + toReplace(vi) = List(new InsnNode(ACONST_NULL)) + else if (vi.getOpcode == ASTORE) + for (frame <- frameAt(vi) if frame.peekStack(0) == NullValue) + toReplace(vi) = List(getPop(1)) + + case ji: JumpInsnNode => + val isIfNull = ji.getOpcode == IFNULL + val isIfNonNull = ji.getOpcode == IFNONNULL + if (isIfNull || isIfNonNull) for (frame <- frameAt(ji)) { + val nullness = frame.peekStack(0) + val taken = nullness == NullValue && isIfNull || nullness == NotNullValue && isIfNonNull + val avoided = nullness == NotNullValue && isIfNull || nullness == NullValue && isIfNonNull + if (taken || avoided) { + val jump = if (taken) List(new JumpInsnNode(GOTO, ji.label)) else Nil + toReplace(ji) = getPop(1) :: jump + } + } else { + val isIfEq = ji.getOpcode == IF_ACMPEQ + val isIfNe = ji.getOpcode == IF_ACMPNE + if (isIfEq || isIfNe) for (frame <- frameAt(ji)) { + val aNullness = frame.peekStack(1) + val bNullness = frame.peekStack(0) + val eq = aNullness == NullValue && bNullness == NullValue + val ne = aNullness == NullValue && bNullness == NotNullValue || aNullness == NotNullValue && bNullness == NullValue + val taken = isIfEq && eq || isIfNe && ne + val avoided = isIfEq && ne || isIfNe && eq + if (taken || avoided) { + val jump = if (taken) List(new JumpInsnNode(GOTO, ji.label)) else Nil + toReplace(ji) = getPop(1) :: getPop(1) :: jump + } + } + } + + case ti: TypeInsnNode => + if (ti.getOpcode == INSTANCEOF) for (frame <- frameAt(ti) if frame.peekStack(0) == NullValue) { + toReplace(ti) = List(getPop(1), new InsnNode(ICONST_0)) + } + + case mi: MethodInsnNode => + if (isScalaUnbox(mi)) for (frame <- frameAt(mi) if frame.peekStack(0) == NullValue) { + toReplace(mi) = List( + getPop(1), + loadZeroForTypeSort(Type.getReturnType(mi.desc).getSort)) + } + + case _ => + } + + def removeFromCallGraph(insn: AbstractInsnNode): Unit = insn match { + case mi: MethodInsnNode => callGraph.removeCallsite(mi, method) + case _ => + } + + for ((oldOp, newOps) <- toReplace) { + for (newOp <- newOps) method.instructions.insertBefore(oldOp, newOp) + method.instructions.remove(oldOp) + removeFromCallGraph(oldOp) + } + + toReplace.nonEmpty + } + } -object LocalOptImpls { /** * Removes unreachable basic blocks. * - * TODO: rewrite, don't use computeMaxLocalsMaxStack (runs a ClassWriter) / Analyzer. Too slow. - * * @return A set containing eliminated instructions, and a set containing all live label nodes. */ - def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Set[AbstractInsnNode], Set[LabelNode]) = { - // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed. - computeMaxLocalsMaxStack(method) - val a = new Analyzer(new BasicInterpreter) - a.analyze(ownerClassName, method) - val frames = a.getFrames + def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): (Boolean, Set[LabelNode]) = { + val a = new AsmAnalyzer(method, ownerClassName) + val frames = a.analyzer.getFrames - val initialSize = method.instructions.size var i = 0 var liveLabels = Set.empty[LabelNode] - var removedInstructions = Set.empty[AbstractInsnNode] + var changed = false + var maxLocals = parametersSize(method) + var maxStack = 0 val itr = method.instructions.iterator() while (itr.hasNext) { - itr.next() match { - case l: LabelNode => - if (frames(i) != null) liveLabels += l + val insn = itr.next() + val isLive = frames(i) != null + if (isLive) maxStack = math.max(maxStack, frames(i).getStackSize) - case ins => + insn match { + case l: LabelNode => // label nodes are not removed: they might be referenced for example in a LocalVariableNode - if (frames(i) == null || ins.getOpcode == Opcodes.NOP) { + if (isLive) liveLabels += l + + case v: VarInsnNode if isLive => + val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0 + maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 because local numbers are 0-based + + case i: IincInsnNode if isLive => + maxLocals = math.max(maxLocals, i.`var` + 1) + + case _ => + if (!isLive || insn.getOpcode == NOP) { // Instruction iterators allow removing during iteration. // Removing is O(1): instructions are doubly linked list elements. itr.remove() - removedInstructions += ins + changed = true + insn match { + case invocation: MethodInsnNode => callGraph.removeCallsite(invocation, method) + case indy: InvokeDynamicInsnNode => callGraph.removeClosureInstantiation(indy, method) + case _ => + } } } i += 1 } - (removedInstructions, liveLabels) + method.maxLocals = maxLocals + method.maxStack = maxStack + (changed, liveLabels) } /** + * Eliminate `CHECKCAST` instructions that are statically known to succeed. This is safe if the + * tested object is null: `null.asInstanceOf` always succeeds. + * + * The type of the tested object is determined using a NonLubbingTypeFlowAnalyzer. Note that this + * analysis collapses LUBs of non-equal references types to Object for simplicity. Example: + * given `B <: A <: Object`, the cast in `(if (..) new B else new A).asInstanceOf[A]` would not + * be eliminated. + * + * Note: we cannot replace `INSTANCEOF` tests by only looking at the types, `null.isInstanceOf` + * always returns false, so we'd also need nullness information. + */ + def eliminateRedundantCasts(method: MethodNode, owner: InternalName): Boolean = { + AsmAnalyzer.sizeOKForBasicValue(method) && { + def isSubType(aRefDesc: String, bClass: InternalName): Boolean = aRefDesc == bClass || bClass == ObjectRef.internalName || { + (bTypeForDescriptorOrInternalNameFromClassfile(aRefDesc) conformsTo classBTypeFromParsedClassfile(bClass)).getOrElse(false) + } + + lazy val typeAnalyzer = new NonLubbingTypeFlowAnalyzer(method, owner) + + // cannot remove instructions while iterating, it gets the analysis out of synch (indexed by instructions) + val toRemove = mutable.Set.empty[TypeInsnNode] + + val it = method.instructions.iterator() + while (it.hasNext) it.next() match { + case ti: TypeInsnNode if ti.getOpcode == CHECKCAST => + val frame = typeAnalyzer.frameAt(ti) + val valueTp = frame.getValue(frame.stackTop) + if (valueTp.isReference && isSubType(valueTp.getType.getDescriptor, ti.desc)) { + toRemove += ti + } + + case _ => + } + + toRemove foreach method.instructions.remove + toRemove.nonEmpty + } + } +} + +object LocalOptImpls { + /** * Remove exception handlers that cover empty code blocks. A block is considered empty if it * consist only of labels, frames, line numbers, nops and gotos. * @@ -235,16 +587,16 @@ object LocalOptImpls { def removeEmptyExceptionHandlers(method: MethodNode): Set[TryCatchBlockNode] = { /** True if there exists code between start and end. */ def containsExecutableCode(start: AbstractInsnNode, end: LabelNode): Boolean = { - start != end && ((start.getOpcode : @switch) match { + start != end && ((start.getOpcode: @switch) match { // FrameNode, LabelNode and LineNumberNode have opcode == -1. - case -1 | Opcodes.GOTO => containsExecutableCode(start.getNext, end) + case -1 | GOTO => containsExecutableCode(start.getNext, end) case _ => true }) } var removedHandlers = Set.empty[TryCatchBlockNode] val handlersIter = method.tryCatchBlocks.iterator() - while(handlersIter.hasNext) { + while (handlersIter.hasNext) { val handler = handlersIter.next() if (!containsExecutableCode(handler.start, handler.end)) { removedHandlers += handler @@ -263,9 +615,10 @@ object LocalOptImpls { * same type or name. */ def removeUnusedLocalVariableNodes(method: MethodNode)(firstLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = { - def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = { + @tailrec def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = { start != end && (start match { case v: VarInsnNode if v.`var` == varIndex => true + case i: IincInsnNode if i.`var` == varIndex => true case _ => variableIsUsed(start.getNext, end, varIndex) }) } @@ -285,17 +638,6 @@ object LocalOptImpls { } /** - * The number of local variable slots used for parameters and for the `this` reference. - */ - private def parametersSize(method: MethodNode): Int = { - // Double / long fields occupy two slots, so we sum up the sizes. Since getSize returns 0 for - // void, we have to add `max 1`. - val paramsSize = scala.tools.asm.Type.getArgumentTypes(method.desc).iterator.map(_.getSize max 1).sum - val thisSize = if ((method.access & Opcodes.ACC_STATIC) == 0) 1 else 0 - paramsSize + thisSize - } - - /** * Compact the local variable slots used in the method's implementation. This prevents having * unused slots for example after eliminating unreachable code. * @@ -310,12 +652,9 @@ object LocalOptImpls { val renumber = collection.mutable.ArrayBuffer.empty[Int] // Add the index of the local variable used by `varIns` to the `renumber` array. - def addVar(varIns: VarInsnNode): Unit = { - val index = varIns.`var` - val isWide = (varIns.getOpcode: @switch) match { - case Opcodes.LLOAD | Opcodes.DLOAD | Opcodes.LSTORE | Opcodes.DSTORE => true - case _ => false - } + def addVar(varIns: AbstractInsnNode, slot: Int): Unit = { + val index = slot + val isWide = isSize2LoadOrStore(varIns.getOpcode) // Ensure the length of `renumber`. Unused variable indices are mapped to -1. val minLength = if (isWide) index + 2 else index + 1 @@ -332,7 +671,7 @@ object LocalOptImpls { val firstLocalIndex = parametersSize(method) for (i <- 0 until firstLocalIndex) renumber += i // parameters and `this` are always used. method.instructions.iterator().asScala foreach { - case VarInstruction(varIns) => addVar(varIns) + case VarInstruction(varIns, slot) => addVar(varIns, slot) case _ => } @@ -353,10 +692,12 @@ object LocalOptImpls { // update variable instructions according to the renumber table method.maxLocals = nextIndex method.instructions.iterator().asScala.foreach { - case VarInstruction(varIns) => - val oldIndex = varIns.`var` - if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex) - varIns.`var` = renumber(varIns.`var`) + case VarInstruction(varIns, slot) => + val oldIndex = slot + if (oldIndex >= firstLocalIndex && renumber(oldIndex) != oldIndex) varIns match { + case vi: VarInsnNode => vi.`var` = renumber(slot) + case ii: IincInsnNode => ii.`var` = renumber(slot) + } case _ => } true @@ -431,154 +772,173 @@ object LocalOptImpls { // A set of all exception handlers that guard the current instruction, required for simplifyGotoReturn var activeHandlers = Set.empty[TryCatchBlockNode] - // Instructions that need to be removed. simplifyBranchOverGoto returns an instruction to be - // removed. It cannot remove it itself because the instruction may be the successor of the current - // instruction of the iterator, which is not supported in ASM. - var instructionsToRemove = Set.empty[AbstractInsnNode] + val jumpInsns = mutable.LinkedHashMap.empty[JumpInsnNode, Boolean] - val iterator = method.instructions.iterator() - while (iterator.hasNext) { - val instruction = iterator.next() + for (insn <- method.instructions.iterator().asScala) insn match { + case l: LabelNode => + activeHandlers ++= allHandlers.filter(_.start == l) + activeHandlers = activeHandlers.filter(_.end != l) - instruction match { - case l: LabelNode => - activeHandlers ++= allHandlers.filter(_.start == l) - activeHandlers = activeHandlers.filter(_.end != l) - case _ => + case ji: JumpInsnNode => + jumpInsns(ji) = activeHandlers.nonEmpty + + case _ => + } + + var _jumpTargets: Set[AbstractInsnNode] = null + def jumpTargets = { + if (_jumpTargets == null) { + _jumpTargets = jumpInsns.keysIterator.map(_.label).toSet } + _jumpTargets + } - if (instructionsToRemove(instruction)) { - iterator.remove() - instructionsToRemove -= instruction - } else if (isJumpNonJsr(instruction)) { // fast path - all of the below only treat jumps - var jumpRemoved = simplifyThenElseSameTarget(method, instruction) + def removeJumpFromMap(jump: JumpInsnNode) = { + jumpInsns.remove(jump) + _jumpTargets = null + } - if (!jumpRemoved) { - changed = collapseJumpChains(instruction) || changed - jumpRemoved = removeJumpToSuccessor(method, instruction) + def replaceJumpByPop(jump: JumpInsnNode) = { + removeJumpAndAdjustStack(method, jump) + removeJumpFromMap(jump) + } - if (!jumpRemoved) { - val staleGoto = simplifyBranchOverGoto(method, instruction) - instructionsToRemove ++= staleGoto - changed ||= staleGoto.nonEmpty - changed = simplifyGotoReturn(method, instruction, inTryBlock = activeHandlers.nonEmpty) || changed - } + /** + * Removes a conditional jump if it is followed by a GOTO to the same destination. + * + * CondJump l; [nops]; GOTO l; [...] + * POP*; [nops]; GOTO l; [...] + * + * Introduces 1 or 2 POP instructions, depending on the number of values consumed by the CondJump. + */ + def simplifyThenElseSameTarget(insn: AbstractInsnNode): Boolean = insn match { + case ConditionalJump(jump) => + nextExecutableInstruction(insn) match { + case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) => + replaceJumpByPop(jump) + true + + case _ => false } - changed ||= jumpRemoved - } + + case _ => false } - assert(instructionsToRemove.isEmpty, "some optimization required removing a previously traversed instruction. add `instructionsToRemove.foreach(method.instructions.remove)`") - changed - } - /** - * Removes a conditional jump if it is followed by a GOTO to the same destination. - * - * CondJump l; [nops]; GOTO l; [...] - * POP*; [nops]; GOTO l; [...] - * - * Introduces 1 or 2 POP instructions, depending on the number of values consumed by the CondJump. - */ - private def simplifyThenElseSameTarget(method: MethodNode, instruction: AbstractInsnNode): Boolean = instruction match { - case ConditionalJump(jump) => - nextExecutableInstruction(instruction) match { - case Some(Goto(elseJump)) if sameTargetExecutableInstruction(jump, elseJump) => - removeJumpAndAdjustStack(method, jump) + /** + * Replace jumps to a sequence of GOTO instructions by a jump to the final destination. + * + * Jump l; [any ops]; l: GOTO m; [any ops]; m: GOTO n; [any ops]; n: NotGOTO; [...] + * => Jump n; [rest unchanged] + * + * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop. + */ + def collapseJumpChains(insn: AbstractInsnNode): Boolean = insn match { + case JumpNonJsr(jump) => + val target = finalJumpTarget(jump) + if (jump.label == target) false else { + jump.label = target + _jumpTargets = null true + } - case _ => false - } - case _ => false - } + case _ => false + } - /** - * Replace jumps to a sequence of GOTO instructions by a jump to the final destination. - * - * Jump l; [any ops]; l: GOTO m; [any ops]; m: GOTO n; [any ops]; n: NotGOTO; [...] - * => Jump n; [rest unchanged] - * - * If there's a loop of GOTOs, the initial jump is replaced by one of the labels in the loop. - */ - private def collapseJumpChains(instruction: AbstractInsnNode): Boolean = instruction match { - case JumpNonJsr(jump) => - val target = finalJumpTarget(jump) - if (jump.label == target) false else { - jump.label = target + /** + * Eliminates unnecessary jump instructions + * + * Jump l; [nops]; l: [...] + * => POP*; [nops]; l: [...] + * + * Introduces 0, 1 or 2 POP instructions, depending on the number of values consumed by the Jump. + */ + def removeJumpToSuccessor(insn: AbstractInsnNode): Boolean = insn match { + case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) contains jump.label => + replaceJumpByPop(jump) true - } - case _ => false - } + case _ => false + } - /** - * Eliminates unnecessary jump instructions - * - * Jump l; [nops]; l: [...] - * => POP*; [nops]; l: [...] - * - * Introduces 0, 1 or 2 POP instructions, depending on the number of values consumed by the Jump. - */ - private def removeJumpToSuccessor(method: MethodNode, instruction: AbstractInsnNode) = instruction match { - case JumpNonJsr(jump) if nextExecutableInstruction(jump, alsoKeep = Set(jump.label)) == Some(jump.label) => - removeJumpAndAdjustStack(method, jump) - true - case _ => false - } + /** + * If the "else" part of a conditional branch is a simple GOTO, negates the conditional branch + * and eliminates the GOTO. + * + * CondJump l; [nops, no jump targets]; GOTO m; [nops]; l: [...] + * => NegatedCondJump m; [nops, no jump targets]; [nops]; l: [...] + * + * Note that no jump targets are allowed in the first [nops] section. Otherwise, there could + * be some other jump to the GOTO, and eliminating it would change behavior. + */ + def simplifyBranchOverGoto(insn: AbstractInsnNode, inTryBlock: Boolean): Boolean = insn match { + case ConditionalJump(jump) => + // don't skip over jump targets, see doc comment + nextExecutableInstruction(jump, alsoKeep = jumpTargets) match { + case Some(Goto(goto)) => + if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) contains jump.label) { + val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label) + method.instructions.set(jump, newJump) + removeJumpFromMap(jump) + jumpInsns(newJump) = inTryBlock + replaceJumpByPop(goto) + true + } else false + + case _ => false + } + case _ => false + } - /** - * If the "else" part of a conditional branch is a simple GOTO, negates the conditional branch - * and eliminates the GOTO. - * - * CondJump l; [nops, no labels]; GOTO m; [nops]; l: [...] - * => NegatedCondJump m; [nops, no labels]; [nops]; l: [...] - * - * Note that no label definitions are allowed in the first [nops] section. Otherwise, there could - * be some other jump to the GOTO, and eliminating it would change behavior. - * - * For technical reasons, we cannot remove the GOTO here (*).Instead this method returns an Option - * containing the GOTO that needs to be eliminated. - * - * (*) The ASM instruction iterator (used in the caller [[simplifyJumps]]) has an undefined - * behavior if the successor of the current instruction is removed, which may be the case here - */ - private def simplifyBranchOverGoto(method: MethodNode, instruction: AbstractInsnNode): Option[JumpInsnNode] = instruction match { - case ConditionalJump(jump) => - // don't skip over labels, see doc comment - nextExecutableInstruction(jump, alsoKeep = _.isInstanceOf[LabelNode]) match { - case Some(Goto(goto)) => - if (nextExecutableInstruction(goto, alsoKeep = Set(jump.label)) == Some(jump.label)) { - val newJump = new JumpInsnNode(negateJumpOpcode(jump.getOpcode), goto.label) - method.instructions.set(jump, newJump) - Some(goto) - } else None - - case _ => None - } - case _ => None - } + /** + * Inlines xRETURN and ATHROW + * + * GOTO l; [any ops]; l: xRETURN/ATHROW + * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW + * + * inlining is only done if the GOTO instruction is not part of a try block, otherwise the + * rewrite might change the behavior. For xRETURN, the reason is that return instructions may throw + * an IllegalMonitorStateException, as described here: + * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return + */ + def simplifyGotoReturn(instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match { + case Goto(jump) => + nextExecutableInstruction(jump.label) match { + case Some(target) => + if (isReturn(target) || target.getOpcode == ATHROW) { + method.instructions.set(jump, target.clone(null)) + removeJumpFromMap(jump) + true + } else false + + case _ => false + } + case _ => false + }) - /** - * Inlines xRETURN and ATHROW - * - * GOTO l; [any ops]; l: xRETURN/ATHROW - * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW - * - * inlining is only done if the GOTO instruction is not part of a try block, otherwise the - * rewrite might change the behavior. For xRETURN, the reason is that return instructions may throw - * an IllegalMonitorStateException, as described here: - * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return - */ - private def simplifyGotoReturn(method: MethodNode, instruction: AbstractInsnNode, inTryBlock: Boolean): Boolean = !inTryBlock && (instruction match { - case Goto(jump) => - nextExecutableInstruction(jump.label) match { - case Some(target) => - if (isReturn(target) || target.getOpcode == Opcodes.ATHROW) { - method.instructions.set(jump, target.clone(null)) - true - } else false + def run(): Boolean = { + var changed = false + + // `.toList` because we're modifying the map while iterating over it + for ((jumpInsn, inTryBlock) <- jumpInsns.toList if jumpInsns.contains(jumpInsn) && isJumpNonJsr(jumpInsn)) { + var jumpRemoved = simplifyThenElseSameTarget(jumpInsn) + + if (!jumpRemoved) { + changed = collapseJumpChains(jumpInsn) || changed + jumpRemoved = removeJumpToSuccessor(jumpInsn) + + if (!jumpRemoved) { + changed = simplifyBranchOverGoto(jumpInsn, inTryBlock) || changed + changed = simplifyGotoReturn(jumpInsn, inTryBlock) || changed + } + } - case _ => false + changed ||= jumpRemoved } - case _ => false - }) + + if (changed) run() + changed + } + + run() + } } diff --git a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala deleted file mode 100644 index a866173a88..0000000000 --- a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala +++ /dev/null @@ -1,235 +0,0 @@ - /* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Iulian Dragos - */ - -package scala.tools.nsc -package backend.opt - -import scala.tools.nsc.backend.icode.analysis.LubException - -/** - * @author Iulian Dragos - */ -abstract class ClosureElimination extends SubComponent { - import global._ - import icodes._ - import icodes.opcodes._ - - val phaseName = "closelim" - - override val enabled: Boolean = settings.Xcloselim - - /** Create a new phase */ - override def newPhase(p: Phase) = new ClosureEliminationPhase(p) - - /** A simple peephole optimizer. */ - val peephole = new PeepholeOpt { - - def peep(bb: BasicBlock, i1: Instruction, i2: Instruction) = (i1, i2) match { - case (CONSTANT(c), DROP(_)) => - if (c.tag == UnitTag) Some(List(i2)) else Some(Nil) - - case (LOAD_LOCAL(x), STORE_LOCAL(y)) => - if (x eq y) Some(Nil) else None - - case (STORE_LOCAL(x), LOAD_LOCAL(y)) if (x == y) => - var liveOut = liveness.out(bb) - if (!liveOut(x)) { - debuglog("store/load to a dead local? " + x) - val instrs = bb.getArray - var idx = instrs.length - 1 - while (idx > 0 && (instrs(idx) ne i2)) { - liveOut = liveness.interpret(liveOut, instrs(idx)) - idx -= 1 - } - if (!liveOut(x)) { - log("Removing dead store/load of " + x.sym.initialize.defString) - Some(Nil) - } else None - } else - Some(List(DUP(x.kind), STORE_LOCAL(x))) - - case (LOAD_LOCAL(_), DROP(_)) | (DUP(_), DROP(_)) => - Some(Nil) - - case (BOX(t1), UNBOX(t2)) if (t1 == t2) => - Some(Nil) - - case (LOAD_FIELD(sym, /* isStatic */false), DROP(_)) if !sym.hasAnnotation(definitions.VolatileAttr) && inliner.isClosureClass(sym.owner) => - Some(DROP(REFERENCE(definitions.ObjectClass)) :: Nil) - - case _ => None - } - } - - /** The closure elimination phase. - */ - class ClosureEliminationPhase(prev: Phase) extends ICodePhase(prev) { - - def name = phaseName - val closser = new ClosureElim - - override def apply(c: IClass): Unit = { - if (closser ne null) - closser analyzeClass c - } - } - - /** - * Remove references to the environment through fields of a closure object. - * This has to be run after an 'apply' method has been inlined, but it still - * references the closure object. - * - */ - class ClosureElim { - def analyzeClass(cls: IClass): Unit = if (settings.Xcloselim) { - log(s"Analyzing ${cls.methods.size} methods in $cls.") - cls.methods foreach { m => - analyzeMethod(m) - peephole(m) - }} - - val cpp = new copyPropagation.CopyAnalysis - - import copyPropagation._ - - /* Some embryonic copy propagation. */ - def analyzeMethod(m: IMethod): Unit = try {if (m.hasCode) { - cpp.init(m) - cpp.run() - - m.linearizedBlocks() foreach { bb => - var info = cpp.in(bb) - debuglog("Cpp info at entry to block " + bb + ": " + info) - - for (i <- bb) { - i match { - case LOAD_LOCAL(l) if info.bindings isDefinedAt LocalVar(l) => - val t = info.getBinding(l) - t match { - case Deref(This) | Const(_) => - bb.replaceInstruction(i, valueToInstruction(t)) - debuglog(s"replaced $i with $t") - - case _ => - val t = info.getAlias(l) - bb.replaceInstruction(i, LOAD_LOCAL(t)) - debuglog(s"replaced $i with $t") - } - - case LOAD_FIELD(f, false) /* if accessible(f, m.symbol) */ => - def replaceFieldAccess(r: Record) { - val Record(cls, _) = r - info.getFieldNonRecordValue(r, f) foreach { v => - bb.replaceInstruction(i, DROP(REFERENCE(cls)) :: valueToInstruction(v) :: Nil) - debuglog(s"replaced $i with $v") - } - } - - info.stack(0) match { - case r @ Record(_, bindings) if bindings isDefinedAt f => - replaceFieldAccess(r) - - case Deref(LocalVar(l)) => - info.getBinding(l) match { - case r @ Record(_, bindings) if bindings isDefinedAt f => - replaceFieldAccess(r) - case _ => - } - case Deref(Field(r1, f1)) => - info.getFieldValue(r1, f1) match { - case Some(r @ Record(_, bindings)) if bindings isDefinedAt f => - replaceFieldAccess(r) - case _ => - } - - case _ => - } - - case UNBOX(boxType) => - info.stack match { - case Deref(LocalVar(loc1)) :: _ if info.bindings isDefinedAt LocalVar(loc1) => - val value = info.getBinding(loc1) - value match { - case Boxed(LocalVar(loc2)) if loc2.kind == boxType => - bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(info.getBinding(loc2)) :: Nil) - debuglog("replaced " + i + " with " + info.getBinding(loc2)) - case _ => - () - } - case Boxed(LocalVar(loc1)) :: _ if loc1.kind == boxType => - val loc2 = info.getAlias(loc1) - bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(Deref(LocalVar(loc2))) :: Nil) - debuglog("replaced " + i + " with " + LocalVar(loc2)) - case _ => - } - - case _ => - } - info = cpp.interpret(info, i) - } - } - }} catch { - case e: LubException => - Console.println("In method: " + m) - Console.println(e) - e.printStackTrace - } - - /* Partial mapping from values to instructions that load them. */ - def valueToInstruction(v: Value): Instruction = (v: @unchecked) match { - case Deref(LocalVar(v)) => - LOAD_LOCAL(v) - case Const(k) => - CONSTANT(k) - case Deref(This) => - THIS(definitions.ObjectClass) - case Boxed(LocalVar(v)) => - LOAD_LOCAL(v) - } - } /* class ClosureElim */ - - - /** Peephole optimization. */ - abstract class PeepholeOpt { - /** Concrete implementations will perform their optimizations here */ - def peep(bb: BasicBlock, i1: Instruction, i2: Instruction): Option[List[Instruction]] - - var liveness: global.icodes.liveness.LivenessAnalysis = null - - def apply(m: IMethod): Unit = if (m.hasCode) { - liveness = new global.icodes.liveness.LivenessAnalysis - liveness.init(m) - liveness.run() - m foreachBlock transformBlock - } - - def transformBlock(b: BasicBlock): Unit = if (b.size >= 2) { - var newInstructions: List[Instruction] = b.toList - var redo = false - - do { - var h = newInstructions.head - var t = newInstructions.tail - var seen: List[Instruction] = Nil - redo = false - - while (t != Nil) { - peep(b, h, t.head) match { - case Some(newInstrs) => - newInstructions = seen reverse_::: newInstrs ::: t.tail - redo = true - case None => - () - } - seen = h :: seen - h = t.head - t = t.tail - } - } while (redo) - b fromList newInstructions - } - } - -} /* class ClosureElimination */ diff --git a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala deleted file mode 100644 index a7ce7dfa04..0000000000 --- a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala +++ /dev/null @@ -1,626 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author James Iry - */ - -package scala -package tools.nsc -package backend.opt - -import scala.annotation.tailrec - -/** - * ConstantOptimization uses abstract interpretation to approximate for - * each instruction what constants a variable or stack slot might hold - * or cannot hold. From this it will eliminate unreachable conditionals - * where only one branch is reachable, e.g. to eliminate unnecessary - * null checks. - * - * With some more work it could be extended to - * - cache stable values (final fields, modules) in locals - * - replace the copy propagation in ClosureElimination - * - fold constants - * - eliminate unnecessary stores and loads - * - propagate knowledge gathered from conditionals for further optimization - */ -abstract class ConstantOptimization extends SubComponent { - import global._ - import icodes._ - import icodes.opcodes._ - - val phaseName = "constopt" - - /** Create a new phase */ - override def newPhase(p: Phase) = new ConstantOptimizationPhase(p) - - override val enabled: Boolean = settings.YconstOptimization - - /** - * The constant optimization phase. - */ - class ConstantOptimizationPhase(prev: Phase) extends ICodePhase(prev) { - - def name = phaseName - - override def apply(c: IClass) { - if (settings.YconstOptimization) { - val analyzer = new ConstantOptimizer - analyzer optimizeClass c - } - } - } - - class ConstantOptimizer { - def optimizeClass(cls: IClass) { - log(s"Analyzing ${cls.methods.size} methods in $cls.") - cls.methods foreach { m => - optimizeMethod(m) - } - } - - def optimizeMethod(m: IMethod) { - if (m.hasCode) { - log(s"Analyzing ${m.symbol}") - val replacementInstructions = interpretMethod(m) - for (block <- m.blocks) { - if (replacementInstructions contains block) { - val instructions = replacementInstructions(block) - block.replaceInstruction(block.lastInstruction, instructions) - } - } - } - } - - /** - * A single possible (or impossible) datum that can be held in Contents - */ - private sealed abstract class Datum - /** - * A constant datum - */ - private case class Const(c: Constant) extends Datum { - def isIntAssignable = c.tag >= BooleanTag && c.tag <= IntTag - def toInt = c.tag match { - case BooleanTag => if (c.booleanValue) 1 else 0 - case _ => c.intValue - } - - /** - * True if this constant would compare to other as true under primitive eq - */ - override def equals(other: Any) = other match { - case oc @ Const(o) => (this eq oc) || (if (this.isIntAssignable && oc.isIntAssignable) this.toInt == oc.toInt else c.value == o.value) - case _ => false - } - - /** - * Hash code consistent with equals - */ - override def hashCode = if (this.isIntAssignable) this.toInt else c.hashCode - - } - /** - * A datum that has been Boxed via a BOX instruction - */ - private case class Boxed(c: Datum) extends Datum - - /** - * The knowledge we have about the abstract state of one location in terms - * of what constants it might or cannot hold. Forms a lower - * lattice where lower elements in the lattice indicate less knowledge. - * - * With the following partial ordering (where '>' indicates more precise knowledge) - * - * Possible(xs) > Possible(xs + y) - * Possible(xs) > Impossible(ys) - * Impossible(xs + y) > Impossible(xs) - * - * and the following merges, which indicate merging knowledge from two paths through - * the code, - * - * // left must be 1 or 2, right must be 2 or 3 then we must have a 1, 2 or 3 - * Possible(xs) merge Possible(ys) => Possible(xs union ys) - * - * // Left says can't be 2 or 3, right says can't be 3 or 4 - * // then it's not 3 (it could be 2 from the right or 4 from the left) - * Impossible(xs) merge Impossible(ys) => Impossible(xs intersect ys) - * - * // Left says it can't be 2 or 3, right says it must be 3 or 4, then - * // it can't be 2 (left rules out 4 and right says 3 is possible) - * Impossible(xs) merge Possible(ys) => Impossible(xs -- ys) - * - * Intuitively, Possible(empty) says that a location can't hold anything, - * it's uninitialized. However, Possible(empty) never appears in the code. - * - * Conversely, Impossible(empty) says nothing is impossible, it could be - * anything. Impossible(empty) is given a synonym UNKNOWN and is used - * for, e.g., the result of an arbitrary method call. - */ - private sealed abstract class Contents { - /** - * Join this Contents with another coming from another path. Join enforces - * the lattice structure. It is symmetrical and never moves upward in the - * lattice - */ - final def merge(other: Contents): Contents = if (this eq other) this else (this, other) match { - case (Possible(possible1), Possible(possible2)) => - Possible(possible1 union possible2) - case (Impossible(impossible1), Impossible(impossible2)) => - Impossible(impossible1 intersect impossible2) - case (Impossible(impossible), Possible(possible)) => - Impossible(impossible -- possible) - case (Possible(possible), Impossible(impossible)) => - Impossible(impossible -- possible) - } - // TODO we could have more fine-grained knowledge, e.g. know that 0 < x < 3. But for now equality/inequality is a good start. - def mightEqual(other: Contents): Boolean - def mightNotEqual(other: Contents): Boolean - } - private def SingleImpossible(x: Datum) = new Impossible(Set(x)) - - /** - * The location is known to have one of a set of values. - */ - private case class Possible(possible: Set[Datum]) extends Contents { - assert(possible.nonEmpty, "Contradiction: had an empty possible set indicating an uninitialized location") - def mightEqual(other: Contents): Boolean = (this eq other) || (other match { - // two Possibles might be equal if they have any possible members in common - case Possible(possible2) => (possible intersect possible2).nonEmpty - // a possible can be equal to an impossible if the impossible doesn't rule - // out all the possibilities - case Impossible(possible2) => (possible -- possible2).nonEmpty - }) - def mightNotEqual(other: Contents): Boolean = (other match { - case Possible(possible2) => - // two Possibles must equal if each is known to be of the same, single value - val mustEqual = possible.size == 1 && possible == possible2 - !mustEqual - case Impossible(_) => true - }) - } - private def SinglePossible(x: Datum) = new Possible(Set(x)) - - /** - * The location is known to not have any of a set of values value (e.g null). - */ - private case class Impossible(impossible: Set[Datum]) extends Contents { - def mightEqual(other: Contents): Boolean = (this eq other) || (other match { - case Possible(_) => other mightEqual this - case _ => true - }) - def mightNotEqual(other: Contents): Boolean = (this eq other) || (other match { - case Possible(_) => other mightNotEqual this - case _ => true - }) - } - - /** - * Our entire knowledge about the contents of all variables and the stack. It forms - * a lattice primarily driven by the lattice structure of Contents. - * - * In addition to the rules of contents, State has the following properties: - * - The merge of two sets of locals holds the merges of locals found in the intersection - * of the two sets of locals. Locals not found in a - * locals map are thus possibly uninitialized and attempting to load them results - * in an error. - * - The stack heights of two states must match otherwise it's an error to merge them - * - * State is immutable in order to aid in structure sharing of local maps and stacks - */ - private case class State(locals: Map[Local, Contents], stack: List[Contents]) { - def mergeLocals(olocals: Map[Local, Contents]): Map[Local, Contents] = if (locals eq olocals) locals else Map((for { - key <- (locals.keySet intersect olocals.keySet).toSeq - } yield (key, locals(key) merge olocals(key))): _*) - - def merge(other: State): State = if (this eq other) this else { - @tailrec def mergeStacks(l: List[Contents], r: List[Contents], out: List[Contents]): List[Contents] = (l, r) match { - case (Nil, Nil) => out.reverse - case (l, r) if l eq r => out.reverse ++ l - case (lhead :: ltail, rhead :: rtail) => mergeStacks(ltail, rtail, (lhead merge rhead) :: out) - case _ => sys.error("Mismatched stack heights") - } - - val newLocals = mergeLocals(other.locals) - - val newStack = if (stack eq other.stack) stack else mergeStacks(stack, other.stack, Nil) - State(newLocals, newStack) - } - - /** - * Peek at the top of the stack without modifying it. Error if the stack is empty - */ - def peek(n: Int): Contents = stack(n) - /** - * Push contents onto a stack - */ - def push(contents: Contents): State = this copy (stack = contents :: stack) - /** - * Drop n elements from the stack - */ - def drop(number: Int): State = this copy (stack = stack drop number) - /** - * Store the top of the stack into the specified local. An error if the stack - * is empty - */ - def store(variable: Local): State = { - val contents = stack.head - val newVariables = locals + ((variable, contents)) - new State(newVariables, stack.tail) - } - /** - * Load the specified local onto the top of the stack. An error if the local is uninitialized. - */ - def load(variable: Local): State = { - val contents: Contents = locals.getOrElse(variable, sys.error(s"$variable is not initialized")) - push(contents) - } - /** - * A copy of this State with an empty stack - */ - def cleanStack: State = if (stack.isEmpty) this else this copy (stack = Nil) - } - - // some precomputed constants - private val NULL = Const(Constant(null: Any)) - private val UNKNOWN = Impossible(Set.empty) - private val NOT_NULL = SingleImpossible(NULL) - private val CONST_UNIT = SinglePossible(Const(Constant(()))) - private val CONST_FALSE = SinglePossible(Const(Constant(false))) - private val CONST_ZERO_BYTE = SinglePossible(Const(Constant(0: Byte))) - private val CONST_ZERO_SHORT = SinglePossible(Const(Constant(0: Short))) - private val CONST_ZERO_CHAR = SinglePossible(Const(Constant(0: Char))) - private val CONST_ZERO_INT = SinglePossible(Const(Constant(0: Int))) - private val CONST_ZERO_LONG = SinglePossible(Const(Constant(0: Long))) - private val CONST_ZERO_FLOAT = SinglePossible(Const(Constant(0.0f))) - private val CONST_ZERO_DOUBLE = SinglePossible(Const(Constant(0.0d))) - private val CONST_NULL = SinglePossible(NULL) - - /** - * Given a TypeKind, figure out what '0' for it means in order to interpret CZJUMP - */ - private def getZeroOf(k: TypeKind): Contents = k match { - case UNIT => CONST_UNIT - case BOOL => CONST_FALSE - case BYTE => CONST_ZERO_BYTE - case SHORT => CONST_ZERO_SHORT - case CHAR => CONST_ZERO_CHAR - case INT => CONST_ZERO_INT - case LONG => CONST_ZERO_LONG - case FLOAT => CONST_ZERO_FLOAT - case DOUBLE => CONST_ZERO_DOUBLE - case REFERENCE(_) => CONST_NULL - case ARRAY(_) => CONST_NULL - case BOXED(_) => CONST_NULL - case ConcatClass => abort("no zero of ConcatClass") - } - - // normal locals can't be null, so we use null to mean the magic 'this' local - private val THIS_LOCAL: Local = null - - /** - * interpret a single instruction to find its impact on the abstract state - */ - private def interpretInst(in: State, inst: Instruction): State = { - // pop the consumed number of values off the `in` state's stack, producing a new state - def dropConsumed: State = in drop inst.consumed - - inst match { - case THIS(_) => - in load THIS_LOCAL - - case CONSTANT(k) => - // treat NaN as UNKNOWN because NaN must never equal NaN - val const = if (k.isNaN) UNKNOWN - else SinglePossible(Const(k)) - in push const - - case LOAD_ARRAY_ITEM(_) | LOAD_FIELD(_, _) | CALL_PRIMITIVE(_) => - dropConsumed push UNKNOWN - - case LOAD_LOCAL(local) => - // TODO if a local is known to hold a constant then we can replace this instruction with a push of that constant - in load local - - case STORE_LOCAL(local) => - in store local - - case STORE_THIS(_) => - // if a local is already known to have a constant and we're replacing with the same constant then we can - // replace this with a drop - in store THIS_LOCAL - - case CALL_METHOD(_, _) => - // TODO we could special case implementations of equals that are known, e.g. String#equals - // We could turn Possible(string constants).equals(Possible(string constants) into an eq check - // We could turn nonConstantString.equals(constantString) into constantString.equals(nonConstantString) - // and eliminate the null check that likely precedes this call - val initial = dropConsumed - (0 until inst.produced).foldLeft(initial) { case (know, _) => know push UNKNOWN } - - case BOX(_) => - val value = in peek 0 - // we simulate boxing by, um, boxing the possible/impossible contents - // so if we have Possible(1,2) originally then we'll end up with - // a Possible(Boxed(1), Boxed(2)) - // Similarly, if we know the input is not a 0 then we'll know the - // output is not a Boxed(0) - val newValue = value match { - case Possible(values) => Possible(values map Boxed) - case Impossible(values) => Impossible(values map Boxed) - } - dropConsumed push newValue - - case UNBOX(_) => - val value = in peek 0 - val newValue = value match { - // if we have a Possible, then all the possibilities - // should themselves be Boxes. In that - // case we can merge them to figure out what the UNBOX will produce - case Possible(inners) => - assert(inners.nonEmpty, "Empty possible set indicating an uninitialized location") - val sanitized: Set[Contents] = (inners map { - case Boxed(content) => SinglePossible(content) - case _ => UNKNOWN - }) - sanitized reduce (_ merge _) - // if we have an impossible then the thing that's impossible - // should be a box. We'll unbox that to see what we get - case unknown@Impossible(inners) => - if (inners.isEmpty) { - unknown - } else { - val sanitized: Set[Contents] = (inners map { - case Boxed(content) => SingleImpossible(content) - case _ => UNKNOWN - }) - sanitized reduce (_ merge _) - } - } - dropConsumed push newValue - - case LOAD_MODULE(_) | NEW(_) | LOAD_EXCEPTION(_) => - in push NOT_NULL - - case CREATE_ARRAY(_, _) => - dropConsumed push NOT_NULL - - case IS_INSTANCE(_) => - // TODO IS_INSTANCE is going to be followed by a C(Z)JUMP - // and if IS_INSTANCE/C(Z)JUMP the branch for "true" can - // know that whatever was checked was not a null - // see the TODO on CJUMP for more information about propagating null - // information - // TODO if the top of stack is guaranteed null then we can eliminate this IS_INSTANCE check and - // replace with a constant false, but how often is a knowable null checked for instanceof? - // TODO we could track type information and statically know to eliminate IS_INSTANCE - // which might be a nice win under specialization - dropConsumed push UNKNOWN // it's actually a Possible(true, false) but since the following instruction - // will be a conditional jump comparing to true or false there - // nothing to be gained by being more precise - - case CHECK_CAST(_) => - // TODO we could track type information and statically know to eliminate CHECK_CAST - // but that's probably not a huge win - in - - case DUP(_) => - val value = in peek 0 - in push value - - case DROP(_) | MONITOR_ENTER() | MONITOR_EXIT() | STORE_ARRAY_ITEM(_) | STORE_FIELD(_, _) => - dropConsumed - - case SCOPE_ENTER(_) | SCOPE_EXIT(_) => - in - - case JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | RETURN(_) | THROW(_) | SWITCH(_, _) => - dumpClassesAndAbort("Unexpected block ending instruction: " + inst) - } - } - /** - * interpret the last instruction of a block which will be jump, a conditional branch, a throw, or a return. - * It will result in a map from target blocks to the input state computed for that block. It - * also computes a replacement list of instructions - */ - private def interpretLast(in: State, inst: Instruction): (Map[BasicBlock, State], List[Instruction]) = { - def canSwitch(in1: Contents, tagSet: List[Int]) = { - in1 mightEqual Possible(tagSet.toSet map { tag: Int => Const(Constant(tag)) }) - } - - /* common code for interpreting CJUMP and CZJUMP */ - def interpretConditional(kind: TypeKind, val1: Contents, val2: Contents, success: BasicBlock, failure: BasicBlock, cond: TestOp): (Map[BasicBlock, State], List[Instruction]) = { - // TODO use reaching analysis to update the state in the two branches - // e.g. if the comparison was checking null equality on local x - // then the in the success branch we know x is null and - // on the failure branch we know it is not - // in fact, with copy propagation we could propagate that knowledge - // back through a chain of locations - // - // TODO if we do all that we need to be careful in the - // case that success and failure are the same target block - // because we're using a Map and don't want one possible state to clobber the other - // alternative maybe we should just replace the conditional with a jump if both targets are the same - - def mightEqual = val1 mightEqual val2 - def mightNotEqual = val1 mightNotEqual val2 - def guaranteedEqual = mightEqual && !mightNotEqual - - def succPossible = cond match { - case EQ => mightEqual - case NE => mightNotEqual - case LT | GT => !guaranteedEqual // if the two are guaranteed to be equal then they can't be LT/GT - case LE | GE => true - } - - def failPossible = cond match { - case EQ => mightNotEqual - case NE => mightEqual - case LT | GT => true - case LE | GE => !guaranteedEqual // if the two are guaranteed to be equal then they must be LE/GE - } - - val out = in drop inst.consumed - - var result = Map[BasicBlock, State]() - if (succPossible) { - result += ((success, out)) - } - - if (failPossible) { - result += ((failure, out)) - } - - val replacements = if (result.size == 1) List.fill(inst.consumed)(DROP(kind)) :+ JUMP(result.keySet.head) - else inst :: Nil - - (result, replacements) - } - - inst match { - case JUMP(whereto) => - (Map((whereto, in)), inst :: Nil) - - case CJUMP(success, failure, cond, kind) => - val in1 = in peek 0 - val in2 = in peek 1 - interpretConditional(kind, in1, in2, success, failure, cond) - - case CZJUMP(success, failure, cond, kind) => - val in1 = in peek 0 - val in2 = getZeroOf(kind) - interpretConditional(kind, in1, in2, success, failure, cond) - - case SWITCH(tags, labels) => - val in1 = in peek 0 - val reachableNormalLabels = tags zip labels collect { case (tagSet, label) if canSwitch(in1, tagSet) => label } - val reachableLabels = if (tags.isEmpty) { - assert(labels.size == 1, s"When SWITCH node has empty array of tags it should have just one (default) label: $labels") - labels - } else if (labels.lengthCompare(tags.length) > 0) { - // if we've got an extra label then it's the default - val defaultLabel = labels.last - // see if the default is reachable by seeing if the input might be out of the set - // of all tags - val allTags = Possible(tags.flatten.toSet map { tag: Int => Const(Constant(tag)) }) - if (in1 mightNotEqual allTags) { - reachableNormalLabels :+ defaultLabel - } else { - reachableNormalLabels - } - } else { - reachableNormalLabels - } - // TODO similar to the comment in interpretConditional, we should update our the State going into each - // branch based on which tag is being matched. Also, just like interpretConditional, if target blocks - // are the same we need to merge State rather than clobber - - // alternative, maybe we should simplify the SWITCH to not have same target labels - val newState = in drop inst.consumed - val result = Map(reachableLabels map { label => (label, newState) }: _*) - if (reachableLabels.size == 1) (result, DROP(INT) :: JUMP(reachableLabels.head) :: Nil) - else (result, inst :: Nil) - - // these instructions don't have target blocks - // (exceptions are assumed to be reachable from all instructions) - case RETURN(_) | THROW(_) => - (Map.empty, inst :: Nil) - - case _ => - dumpClassesAndAbort("Unexpected non-block ending instruction: " + inst) - } - } - - /** - * Analyze a single block to find how it transforms an input state into a states for its successor blocks - * Also computes a list of instructions to be used to replace its last instruction - */ - private def interpretBlock(in: State, block: BasicBlock): (Map[BasicBlock, State], Map[BasicBlock, State], List[Instruction]) = { - debuglog(s"interpreting block $block") - // number of instructions excluding the last one - val normalCount = block.size - 1 - - 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/AggregateFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala index 3f06264e3c..f97d97548e 100644 --- a/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala @@ -18,7 +18,6 @@ import scala.tools.nsc.util.ClassRepresentation * @param aggregates classpath instances containing entries which this class processes */ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatClassPath { - override def findClassFile(className: String): Option[AbstractFile] = { @tailrec def find(aggregates: Seq[FlatClassPath]): Option[AbstractFile] = @@ -37,8 +36,7 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl @tailrec def findEntry[T <: ClassRepClassPathEntry](aggregates: Seq[FlatClassPath], getEntries: FlatClassPath => Seq[T]): Option[T] = if (aggregates.nonEmpty) { - val entry = getEntries(aggregates.head) - .find(_.name == simpleClassName) + val entry = getEntries(aggregates.head).find(_.name == simpleClassName) if (entry.isDefined) entry else findEntry(aggregates.tail, getEntries) } else None @@ -46,7 +44,11 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl val classEntry = findEntry(aggregates, classesGetter(pkg)) val sourceEntry = findEntry(aggregates, sourcesGetter(pkg)) - mergeClassesAndSources(classEntry.toList, sourceEntry.toList).headOption + (classEntry, sourceEntry) match { + case (Some(c), Some(s)) => Some(ClassAndSourceFilesEntry(c.file, s.file)) + case (c @ Some(_), _) => c + case (_, s) => s + } } override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs) @@ -123,3 +125,15 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl private def classesGetter(pkg: String) = (cp: FlatClassPath) => cp.classes(pkg) private def sourcesGetter(pkg: String) = (cp: FlatClassPath) => cp.sources(pkg) } + +object AggregateFlatClassPath { + def createAggregate(parts: FlatClassPath*): FlatClassPath = { + val elems = new ArrayBuffer[FlatClassPath]() + parts foreach { + case AggregateFlatClassPath(ps) => elems ++= ps + case p => elems += p + } + if (elems.size == 1) elems.head + else AggregateFlatClassPath(elems.toIndexedSeq) + } +} diff --git a/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala index 81d2f7320f..e3964dfa78 100644 --- a/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala @@ -4,7 +4,6 @@ package scala.tools.nsc.classpath import java.io.File -import java.io.FileFilter import java.net.URL import scala.reflect.io.AbstractFile import scala.reflect.io.PlainFile @@ -12,97 +11,101 @@ import scala.tools.nsc.util.ClassRepresentation import FileUtils._ /** - * A trait allowing to look for classpath entries of given type in directories. - * It provides common logic for classes handling class and source files. + * A trait allowing to look for classpath entries in directories. It provides common logic for + * classes handling class and source files. * It makes use of the fact that in the case of nested directories it's easy to find a file * when we have a name of a package. + * It abstracts over the file representation to work with both JFile and AbstractFile. */ -trait DirectoryFileLookup[FileEntryType <: ClassRepClassPathEntry] extends FlatClassPath { - val dir: File - assert(dir != null, "Directory file in DirectoryFileLookup cannot be null") +trait DirectoryLookup[FileEntryType <: ClassRepClassPathEntry] extends FlatClassPath { + type F + + val dir: F + + protected def emptyFiles: Array[F] // avoids reifying ClassTag[F] + protected def getSubDir(dirName: String): Option[F] + protected def listChildren(dir: F, filter: Option[F => Boolean] = None): Array[F] + protected def getName(f: F): String + protected def toAbstractFile(f: F): AbstractFile + protected def isPackage(f: F): Boolean - override def asURLs: Seq[URL] = Seq(dir.toURI.toURL) - override def asClassPathStrings: Seq[String] = Seq(dir.getPath) + protected def createFileEntry(file: AbstractFile): FileEntryType + protected def isMatchingFile(f: F): Boolean - import FlatClassPath.RootPackage - private def getDirectory(forPackage: String): Option[File] = { - if (forPackage == RootPackage) { + private def getDirectory(forPackage: String): Option[F] = { + if (forPackage == FlatClassPath.RootPackage) { Some(dir) } else { val packageDirName = FileUtils.dirPath(forPackage) - val packageDir = new File(dir, packageDirName) - if (packageDir.exists && packageDir.isDirectory) { - Some(packageDir) - } else None + getSubDir(packageDirName) } } - override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = { + private[nsc] def packages(inPackage: String): Seq[PackageEntry] = { val dirForPackage = getDirectory(inPackage) - val nestedDirs: Array[File] = dirForPackage match { - case None => Array.empty - case Some(directory) => directory.listFiles(DirectoryFileLookup.packageDirectoryFileFilter) + val nestedDirs: Array[F] = dirForPackage match { + case None => emptyFiles + case Some(directory) => listChildren(directory, Some(isPackage)) } val prefix = PackageNameUtils.packagePrefix(inPackage) - val entries = nestedDirs map { file => - PackageEntryImpl(prefix + file.getName) - } - entries + nestedDirs.map(f => PackageEntryImpl(prefix + getName(f))) } protected def files(inPackage: String): Seq[FileEntryType] = { val dirForPackage = getDirectory(inPackage) - val files: Array[File] = dirForPackage match { - case None => Array.empty - case Some(directory) => directory.listFiles(fileFilter) - } - val entries = files map { file => - val wrappedFile = new scala.reflect.io.File(file) - createFileEntry(new PlainFile(wrappedFile)) + val files: Array[F] = dirForPackage match { + case None => emptyFiles + case Some(directory) => listChildren(directory, Some(isMatchingFile)) } - entries + files.map(f => createFileEntry(toAbstractFile(f))) } - override private[nsc] def list(inPackage: String): FlatClassPathEntries = { + private[nsc] def list(inPackage: String): FlatClassPathEntries = { val dirForPackage = getDirectory(inPackage) - val files: Array[File] = dirForPackage match { - case None => Array.empty - case Some(directory) => directory.listFiles() + val files: Array[F] = dirForPackage match { + case None => emptyFiles + case Some(directory) => listChildren(directory) } val packagePrefix = PackageNameUtils.packagePrefix(inPackage) val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] for (file <- files) { - if (file.isPackage) { - val pkgEntry = PackageEntryImpl(packagePrefix + file.getName) - packageBuf += pkgEntry - } else if (fileFilter.accept(file)) { - val wrappedFile = new scala.reflect.io.File(file) - val abstractFile = new PlainFile(wrappedFile) - fileBuf += createFileEntry(abstractFile) - } + if (isPackage(file)) + packageBuf += PackageEntryImpl(packagePrefix + getName(file)) + else if (isMatchingFile(file)) + fileBuf += createFileEntry(toAbstractFile(file)) } FlatClassPathEntries(packageBuf, fileBuf) } - - protected def createFileEntry(file: AbstractFile): FileEntryType - protected def fileFilter: FileFilter } -object DirectoryFileLookup { +trait JFileDirectoryLookup[FileEntryType <: ClassRepClassPathEntry] extends DirectoryLookup[FileEntryType] { + type F = File - private[classpath] object packageDirectoryFileFilter extends FileFilter { - override def accept(pathname: File): Boolean = pathname.isPackage + protected def emptyFiles: Array[File] = Array.empty + protected def getSubDir(packageDirName: String): Option[File] = { + val packageDir = new File(dir, packageDirName) + if (packageDir.exists && packageDir.isDirectory) Some(packageDir) + else None } -} + protected def listChildren(dir: File, filter: Option[File => Boolean]): Array[File] = filter match { + case Some(f) => dir.listFiles(mkFileFilter(f)) + case None => dir.listFiles() + } + protected def getName(f: File): String = f.getName + protected def toAbstractFile(f: File): AbstractFile = new PlainFile(new scala.reflect.io.File(f)) + protected def isPackage(f: File): Boolean = f.isPackage -case class DirectoryFlatClassPath(dir: File) - extends DirectoryFileLookup[ClassFileEntryImpl] - with NoSourcePaths { + assert(dir != null, "Directory file in DirectoryFileLookup cannot be null") + def asURLs: Seq[URL] = Seq(dir.toURI.toURL) + def asClassPathStrings: Seq[String] = Seq(dir.getPath) +} + +case class DirectoryFlatClassPath(dir: File) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = findClassFile(className) map ClassFileEntryImpl - override def findClassFile(className: String): Option[AbstractFile] = { + def findClassFile(className: String): Option[AbstractFile] = { val relativePath = FileUtils.dirPath(className) val classFile = new File(s"$dir/$relativePath.class") if (classFile.exists) { @@ -112,31 +115,19 @@ case class DirectoryFlatClassPath(dir: File) } else None } - override protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) - override protected def fileFilter: FileFilter = DirectoryFlatClassPath.classFileFilter - - override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) -} - -object DirectoryFlatClassPath { + protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) + protected def isMatchingFile(f: File): Boolean = f.isClass - private val classFileFilter = new FileFilter { - override def accept(pathname: File): Boolean = pathname.isClass - } + private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) } -case class DirectoryFlatSourcePath(dir: File) - extends DirectoryFileLookup[SourceFileEntryImpl] - with NoClassPaths { +case class DirectoryFlatSourcePath(dir: File) extends JFileDirectoryLookup[SourceFileEntryImpl] with NoClassPaths { + def asSourcePathString: String = asClassPathString - override def asSourcePathString: String = asClassPathString + protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file) + protected def isMatchingFile(f: File): Boolean = endsScalaOrJava(f.getName) - override protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file) - override protected def fileFilter: FileFilter = DirectoryFlatSourcePath.sourceFileFilter - - override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = { - findSourceFile(className) map SourceFileEntryImpl - } + override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = findSourceFile(className) map SourceFileEntryImpl private def findSourceFile(className: String): Option[AbstractFile] = { val relativePath = FileUtils.dirPath(className) @@ -151,12 +142,5 @@ case class DirectoryFlatSourcePath(dir: File) } } - override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) -} - -object DirectoryFlatSourcePath { - - private val sourceFileFilter = new FileFilter { - override def accept(pathname: File): Boolean = endsScalaOrJava(pathname.getName) - } + private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) } diff --git a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala index ee2528e15c..bbcfcb24ca 100644 --- a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala +++ b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala @@ -3,7 +3,7 @@ */ package scala.tools.nsc.classpath -import java.io.{ File => JFile } +import java.io.{File => JFile, FileFilter} import java.net.URL import scala.reflect.internal.FatalError import scala.reflect.io.AbstractFile @@ -65,4 +65,8 @@ object FileUtils { // because then some tests in partest don't pass private def mayBeValidPackage(dirName: String): Boolean = (dirName != "META-INF") && (dirName != "") && (dirName.charAt(0) != '.') + + def mkFileFilter(f: JFile => Boolean) = new FileFilter { + def accept(pathname: JFile): Boolean = f(pathname) + } } diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala index cb201617d2..e95ffe02e3 100644 --- a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala @@ -28,11 +28,8 @@ trait FlatClassPath extends ClassFileLookup[AbstractFile] { override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = { val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) - val foundClassFromClassFiles = classes(pkg) - .find(_.name == simpleClassName) - - def findClassInSources = sources(pkg) - .find(_.name == simpleClassName) + val foundClassFromClassFiles = classes(pkg).find(_.name == simpleClassName) + def findClassInSources = sources(pkg).find(_.name == simpleClassName) foundClassFromClassFiles orElse findClassInSources } @@ -50,7 +47,7 @@ case class FlatClassPathEntries(packages: Seq[PackageEntry], classesAndSources: object FlatClassPathEntries { import scala.language.implicitConversions // to have working unzip method - implicit def entry2Tuple(entry: FlatClassPathEntries) = (entry.packages, entry.classesAndSources) + implicit def entry2Tuple(entry: FlatClassPathEntries): (Seq[PackageEntry], Seq[ClassRepClassPathEntry]) = (entry.packages, entry.classesAndSources) } sealed trait ClassRepClassPathEntry extends ClassRepresentation[AbstractFile] diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala index 7f67381d4d..463301696e 100644 --- a/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala +++ b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala @@ -3,9 +3,9 @@ */ package scala.tools.nsc.classpath +import scala.reflect.io.VirtualDirectory import scala.tools.nsc.Settings import scala.tools.nsc.io.AbstractFile -import scala.tools.nsc.util.ClassPath import FileUtils.AbstractFileOps /** @@ -13,16 +13,9 @@ import FileUtils.AbstractFileOps * it uses proper type of classpath depending on a types of particular files containing sources or classes. */ class FlatClassPathFactory(settings: Settings) extends ClassPathFactory[FlatClassPath] { + def newClassPath(file: AbstractFile): FlatClassPath = FlatClassPathFactory.newClassPath(file, settings) - override def newClassPath(file: AbstractFile): FlatClassPath = - if (file.isJarOrZip) - ZipAndJarFlatClassPathFactory.create(file, settings) - else if (file.isDirectory) - new DirectoryFlatClassPath(file.file) - else - sys.error(s"Unsupported classpath element: $file") - - override def sourcesInPath(path: String): List[FlatClassPath] = + def sourcesInPath(path: String): List[FlatClassPath] = for { file <- expandPath(path, expandStar = false) dir <- Option(AbstractFile getDirectory file) @@ -36,3 +29,16 @@ class FlatClassPathFactory(settings: Settings) extends ClassPathFactory[FlatClas else sys.error(s"Unsupported sourcepath element: $file") } + +object FlatClassPathFactory { + def newClassPath(file: AbstractFile, settings: Settings): FlatClassPath = file match { + case vd: VirtualDirectory => VirtualDirectoryFlatClassPath(vd) + case _ => + if (file.isJarOrZip) + ZipAndJarFlatClassPathFactory.create(file, settings) + else if (file.isDirectory) + new DirectoryFlatClassPath(file.file) + else + sys.error(s"Unsupported classpath element: $file") + } +} diff --git a/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryFlatClassPath.scala new file mode 100644 index 0000000000..06cdab583c --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryFlatClassPath.scala @@ -0,0 +1,39 @@ +package scala.tools.nsc.classpath + +import scala.tools.nsc.util.ClassRepresentation +import scala.reflect.io.{Path, PlainFile, VirtualDirectory, AbstractFile} +import FileUtils._ +import java.net.URL + +case class VirtualDirectoryFlatClassPath(dir: VirtualDirectory) extends FlatClassPath with DirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { + type F = AbstractFile + + protected def emptyFiles: Array[AbstractFile] = Array.empty + protected def getSubDir(packageDirName: String): Option[AbstractFile] = + Option(dir.lookupName(packageDirName, directory = true)) + protected def listChildren(dir: AbstractFile, filter: Option[AbstractFile => Boolean] = None): Array[F] = filter match { + case Some(f) => dir.iterator.filter(f).toArray + case _ => dir.toArray + } + def getName(f: AbstractFile): String = f.name + def toAbstractFile(f: AbstractFile): AbstractFile = f + def isPackage(f: AbstractFile): Boolean = f.isPackage + + // mimic the behavior of the old nsc.util.DirectoryClassPath + def asURLs: Seq[URL] = Seq(new URL(dir.name)) + def asClassPathStrings: Seq[String] = Seq(dir.path) + + override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = findClassFile(className) map ClassFileEntryImpl + + def findClassFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + val classFile = new PlainFile(Path(s"$dir/$relativePath.class")) + if (classFile.exists) Some(classFile) + else None + } + + private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + + protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) + protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass +} diff --git a/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala b/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala index 85c7c3c843..6ec3805d8b 100644 --- a/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala +++ b/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala @@ -19,7 +19,6 @@ import FileUtils._ * when there are a lot of projects having a lot of common dependencies. */ sealed trait ZipAndJarFileLookupFactory { - private val cache = collection.mutable.Map.empty[AbstractFile, FlatClassPath] def create(zipFile: AbstractFile, settings: Settings): FlatClassPath = { @@ -44,7 +43,6 @@ sealed trait ZipAndJarFileLookupFactory { * It should be the only way of creating them as it provides caching. */ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory { - private case class ZipArchiveFlatClassPath(zipFile: File) extends ZipArchiveFileLookup[ClassFileEntryImpl] with NoSourcePaths { @@ -67,10 +65,7 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory { * with a particularly prepared scala-library.jar. It should have all classes listed in the manifest like e.g. this entry: * Name: scala/Function2$mcFJD$sp.class */ - private case class ManifestResourcesFlatClassPath(file: ManifestResources) - extends FlatClassPath - with NoSourcePaths { - + private case class ManifestResourcesFlatClassPath(file: ManifestResources) extends FlatClassPath with NoSourcePaths { override def findClassFile(className: String): Option[AbstractFile] = { val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) classes(pkg).find(_.name == simpleClassName).map(_.file) @@ -163,7 +158,6 @@ object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory { * It should be the only way of creating them as it provides caching. */ object ZipAndJarFlatSourcePathFactory extends ZipAndJarFileLookupFactory { - private case class ZipArchiveFlatSourcePath(zipFile: File) extends ZipArchiveFileLookup[SourceFileEntryImpl] with NoClassPaths { diff --git a/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala b/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala index 1d0de57779..a24d989306 100644 --- a/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala +++ b/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala @@ -57,7 +57,7 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepClassPathEntry] extends Flat } getOrElse FlatClassPathEntries(Seq.empty, Seq.empty) } - private def findDirEntry(pkg: String) = { + private def findDirEntry(pkg: String): Option[archive.DirEntry] = { val dirName = s"${FileUtils.dirPath(pkg)}/" archive.allDirs.get(dirName) } 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..cc851b6330 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -264,7 +264,8 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { } } - def typ(): Tree = + def typ(): Tree = { + annotations() optArrayBrackets { if (in.token == FINAL) in.nextToken() if (in.token == IDENTIFIER) { @@ -287,6 +288,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { basicType() } } + } def typeArgs(t: Tree): Tree = { val wildcards = new ListBuffer[TypeDef] @@ -404,6 +406,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { def typeParam(): TypeDef = atPos(in.currentPos) { + annotations() val name = identForType() val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else EmptyTree TypeDef(Modifiers(Flags.JAVA | Flags.DEFERRED | Flags.PARAM), name, Nil, TypeBoundsTree(EmptyTree, hi)) @@ -509,7 +512,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { EmptyTree } } - // for abstract methods (of classes), the `DEFERRED` flag is alredy set. + // for abstract methods (of classes), the `DEFERRED` flag is already set. // here we also set it for interface methods that are not static and not default. if (!isConcreteInterfaceMethod) mods1 |= Flags.DEFERRED List { @@ -800,16 +803,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 5bf611a7b0..9b9d94bb0f 100644 --- a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala @@ -7,12 +7,11 @@ 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._ -/** - * This class implements a Reporter that displays messages on a text console. +/** This class implements a Reporter that displays messages on a text console. */ class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: PrintWriter) extends AbstractReporter { def this(settings: Settings) = this(settings, Console.in, new PrintWriter(Console.err, true)) @@ -85,5 +84,7 @@ class ConsoleReporter(val settings: Settings, reader: BufferedReader, writer: Pr } } - override def flush() { writer.flush() } + override def flush() = writer.flush() + + override def finish() = printSummary() } diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala index 6b339b2a6d..8386722b63 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala @@ -31,6 +31,7 @@ trait AbsScalaSettings { def BooleanSetting(name: String, descr: String): BooleanSetting def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting + def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]): IntSetting def MultiStringSetting(name: String, helpArg: String, descr: String): MultiStringSetting def MultiChoiceSetting[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]]): MultiChoiceSetting[E] diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala index fffbb4333f..d6013e0b00 100644 --- a/src/compiler/scala/tools/nsc/settings/FscSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala @@ -37,9 +37,7 @@ class FscSettings(error: String => Unit) extends Settings(error) { /** If a setting (other than a PathSetting) represents a path or paths. * For use in absolutization. */ - private def holdsPath = Set[Settings#Setting]( - d, dependencyfile, pluginsDir, Ygenjavap - ) + private def holdsPath = Set[Settings#Setting](d, dependencyfile, pluginsDir) override def processArguments(arguments: List[String], processAll: Boolean): (Boolean, List[String]) = { val (r, args) = super.processArguments(arguments, processAll) diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index b4987e1240..9cc8faf8c2 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -221,6 +221,13 @@ class MutableSettings(val errorFn: String => Unit) def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr)) def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) = add(new ChoiceSetting(name, helpArg, descr, choices, default)) + def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String) = + ChoiceSetting(name, helpArg, descr, choices, default).withPostSetHook(sett => + if (sett.value != default) { + sett.withDeprecationMessage(s"${name}:${sett.value} is deprecated, forcing use of $default") + sett.value = default + } + ) def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]) = add(new IntSetting(name, descr, default, range, parser)) def MultiStringSetting(name: String, arg: String, descr: String) = add(new MultiStringSetting(name, arg, descr)) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 1817cfa25a..7b98011759 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -9,10 +9,11 @@ package tools package nsc package settings +import scala.language.existentials + import scala.annotation.elidable import scala.tools.util.PathResolver.Defaults import scala.collection.mutable -import scala.language.{implicitConversions, existentials} trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings @@ -22,13 +23,9 @@ trait ScalaSettings extends AbsScalaSettings /** Set of settings */ protected[scala] lazy val allSettings = mutable.HashSet[Setting]() - /** Against my better judgment, giving in to martin here and allowing - * CLASSPATH to be used automatically. So for the user-specified part - * of the classpath: - * - * - If -classpath or -cp is given, it is that - * - Otherwise, if CLASSPATH is set, it is that - * - If neither of those, then "." is used. + /** The user class path, specified by `-classpath` or `-cp`, + * defaults to the value of CLASSPATH env var if it is set, as in Java, + * or else to `"."` for the current user directory. */ protected def defaultClasspath = sys.env.getOrElse("CLASSPATH", ".") @@ -38,9 +35,6 @@ trait ScalaSettings extends AbsScalaSettings /** Enabled under -Xfuture. */ protected def futureSettings = List[BooleanSetting]() - /** Enabled under -optimise. */ - def optimiseSettings = List[BooleanSetting](inline, inlineHandlers, Xcloselim, Xdce, YconstOptimization) - /** If any of these settings is enabled, the compiler should print a message and exit. */ def infoSettings = List[Setting](version, help, Xhelp, Yhelp, showPlugins, showPhases, genPhaseGraph) @@ -116,14 +110,13 @@ trait ScalaSettings extends AbsScalaSettings val Xmigration = ScalaVersionSetting ("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", initial = NoScalaVersion, default = Some(AnyScalaVersion)) val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.") - val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)") + val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode.") val plugin = MultiStringSetting ("-Xplugin", "paths", "Load a plugin from each classpath.") val disable = MultiStringSetting ("-Xplugin-disable", "plugin", "Disable plugins by name.") val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.") 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).") @@ -134,8 +127,9 @@ trait ScalaSettings extends AbsScalaSettings val Xshowobj = StringSetting ("-Xshow-object", "object", "Show internal representation of object.", "") val showPhases = BooleanSetting ("-Xshow-phases", "Print a synopsis of compiler phases.") val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files.", "") + val reporter = StringSetting ("-Xreporter", "classname", "Specify a custom reporter for compiler messages.", "scala.tools.nsc.reporters.ConsoleReporter") val strictInference = BooleanSetting ("-Xstrict-inference", "Don't infer known-unsound types") - val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.11")) + val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.12")) val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.") val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.") @@ -143,7 +137,7 @@ trait ScalaSettings extends AbsScalaSettings // XML parsing options object XxmlSettings extends MultiChoiceEnumeration { val coalescing = Choice("coalescing", "Convert PCData to Text and coalesce sibling nodes") - def isCoalescing = (Xxml contains coalescing) || (!isScala212 && !Xxml.isSetByUser) + def isCoalescing = Xxml contains coalescing } val Xxml = MultiChoiceSetting( name = "-Xxml", @@ -171,19 +165,10 @@ trait ScalaSettings extends AbsScalaSettings val browse = PhasesSetting ("-Ybrowse", "Browse the abstract syntax tree after") val check = PhasesSetting ("-Ycheck", "Check the tree at the end of") val Yshow = PhasesSetting ("-Yshow", "(Requires -Xshow-class or -Xshow-object) Show after") - val Xcloselim = BooleanSetting ("-Yclosure-elim", "Perform closure elimination.") - val YconstOptimization = BooleanSetting ("-Yconst-opt", "Perform optimization with constant values.") val Ycompacttrees = BooleanSetting ("-Ycompact-trees", "Use compact tree printer when displaying trees.") val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL.") - val completion = ChoiceSetting ("-Ycompletion", "provider", "Select tab-completion in the REPL.", List("pc","adhoc","none"), "pc") - val Xdce = BooleanSetting ("-Ydead-code", "Perform dead code elimination.") val debug = BooleanSetting ("-Ydebug", "Increase the quantity of debugging output.") - //val doc = BooleanSetting ("-Ydoc", "Generate documentation") val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") - val inline = BooleanSetting ("-Yinline", "Perform inlining when possible.") - val inlineHandlers = BooleanSetting ("-Yinline-handlers", "Perform exception handler inlining when possible.") - val YinlinerWarnings= BooleanSetting ("-Yinline-warnings", "Emit inlining warnings. (Normally suppressed due to high volume)") - val Xlinearizer = ChoiceSetting ("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo") val log = PhasesSetting ("-Ylog", "Log operations during") val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.") val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") @@ -200,7 +185,6 @@ trait ScalaSettings extends AbsScalaSettings val Yshowsymkinds = BooleanSetting ("-Yshow-symkinds", "Print abbreviated symbol kinds next to symbol names.") val Yshowsymowners = BooleanSetting ("-Yshow-symowners", "Print owner identifiers next to symbol names.") val skip = PhasesSetting ("-Yskip", "Skip") - val Ygenjavap = StringSetting ("-Ygen-javap", "dir", "Generate a parallel output directory of .javap files.", "") val Ygenasmp = StringSetting ("-Ygen-asmp", "dir", "Generate a parallel output directory of .asmp files (ie ASM Textifier output).", "") val Ydumpclasses = StringSetting ("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") val stopAfter = PhasesSetting ("-Ystop-after", "Stop after") withAbbreviation ("-stop") // backward compat @@ -216,78 +200,85 @@ trait ScalaSettings extends AbsScalaSettings val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212) val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) - val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Recursive) + val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Flat) val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") - val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") - val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.") - val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.") - - val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() - // the current standard is "inline" but we are moving towards "method" - val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline") - - val YskipInlineInfoAttribute = BooleanSetting("-Yskip-inline-info-attribute", "Do not add the ScalaInlineInfo attribute to classfiles generated by -Ybackend:GenASM") + 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") object YoptChoices extends MultiChoiceEnumeration { - val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.") + 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.") - val emptyLineNumbers = Choice("empty-line-numbers", "Eliminate unnecessary line number information.") - val emptyLabels = Choice("empty-labels", "Eliminate and collapse redundant labels in the bytecode.") val compactLocals = Choice("compact-locals", "Eliminate empty slots in the sequence of local variables.") + val copyPropagation = Choice("copy-propagation", "Eliminate redundant local variables and unused values (including closures). Enables unreachable-code.") + val redundantCasts = Choice("redundant-casts", "Eliminate redundant casts using a type propagation analysis.") + val boxUnbox = Choice("box-unbox", "Eliminate box-unbox pairs within the same method (also tuples, xRefs, value class instances). Enables unreachable-code.") val nullnessTracking = Choice("nullness-tracking", "Track nullness / non-nullness of local variables and apply optimizations.") - val closureElimination = Choice("closure-elimination" , "Rewrite closure invocations to the implementation method and eliminate closures.") - val inlineProject = Choice("inline-project", "Inline only methods defined in the files being compiled.") - val inlineGlobal = Choice("inline-global", "Inline methods from any source, including classfiles on the compile classpath.") + val closureInvocations = Choice("closure-invocations" , "Rewrite closure invocations to the implementation method.") + val inlineProject = Choice("inline-project", "Inline only methods defined in the files being compiled. Enables unreachable-code.") + val inlineGlobal = Choice("inline-global", "Inline methods from any source, including classfiles on the compile classpath. Enables unreachable-code.") - val lNone = Choice("l:none", "Don't enable any optimizations.") + // note: unlike the other optimizer levels, "l:none" appears up in the `Yopt.value` set because it's not an expanding option (expandsTo is empty) + val lNone = Choice("l:none", "Disable optimizations. Takes precedence: `-Yopt:l:none,+box-unbox` / `-Yopt:l:none -Yopt:box-unbox` don't enable box-unbox.") private val defaultChoices = List(unreachableCode) - val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices) + val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString("", ",", "."), expandsTo = defaultChoices) - private val methodChoices = List(unreachableCode, simplifyJumps, emptyLineNumbers, emptyLabels, compactLocals, nullnessTracking, closureElimination) - val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString(","), expandsTo = methodChoices) + private val methodChoices = List(unreachableCode, simplifyJumps, compactLocals, copyPropagation, redundantCasts, boxUnbox, nullnessTracking, closureInvocations) + val lMethod = Choice("l:method", "Enable intra-method optimizations: "+ methodChoices.mkString("", ",", "."), expandsTo = methodChoices) private val projectChoices = List(lMethod, inlineProject) - val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString(","), expandsTo = projectChoices) + val lProject = Choice("l:project", "Enable cross-method optimizations within the current project: "+ projectChoices.mkString("", ",", "."), expandsTo = projectChoices) private val classpathChoices = List(lProject, inlineGlobal) - val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString(","), expandsTo = classpathChoices) + val lClasspath = Choice("l:classpath", "Enable cross-method optimizations across the entire classpath: "+ classpathChoices.mkString("", ",", "."), expandsTo = classpathChoices) } + // We don't use the `default` parameter of `MultiChoiceSetting`: it specifies the default values + // when `-Yopt` is passed without explicit choices. When `-Yopt` is not explicitly specified, the + // set `Yopt.value` is empty. val Yopt = MultiChoiceSetting( name = "-Yopt", helpArg = "optimization", descr = "Enable optimizations", domain = YoptChoices) - def YoptNone = Yopt.isSetByUser && Yopt.value.isEmpty - def YoptUnreachableCode = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode) - def YoptSimplifyJumps = Yopt.contains(YoptChoices.simplifyJumps) - def YoptEmptyLineNumbers = Yopt.contains(YoptChoices.emptyLineNumbers) - def YoptEmptyLabels = Yopt.contains(YoptChoices.emptyLabels) - def YoptCompactLocals = Yopt.contains(YoptChoices.compactLocals) - def YoptNullnessTracking = Yopt.contains(YoptChoices.nullnessTracking) - def YoptClosureElimination = Yopt.contains(YoptChoices.closureElimination) - - def YoptInlineProject = Yopt.contains(YoptChoices.inlineProject) - def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal) + private def optEnabled(choice: YoptChoices.Choice) = { + !Yopt.contains(YoptChoices.lNone) && { + Yopt.contains(choice) || + !Yopt.isSetByUser && YoptChoices.lDefault.expandsTo.contains(choice) + } + } + + def YoptNone = Yopt.contains(YoptChoices.lNone) + def YoptUnreachableCode = optEnabled(YoptChoices.unreachableCode) + def YoptSimplifyJumps = optEnabled(YoptChoices.simplifyJumps) + def YoptCompactLocals = optEnabled(YoptChoices.compactLocals) + def YoptCopyPropagation = optEnabled(YoptChoices.copyPropagation) + def YoptRedundantCasts = optEnabled(YoptChoices.redundantCasts) + def YoptBoxUnbox = optEnabled(YoptChoices.boxUnbox) + def YoptNullnessTracking = optEnabled(YoptChoices.nullnessTracking) + def YoptClosureInvocations = optEnabled(YoptChoices.closureInvocations) + + def YoptInlineProject = optEnabled(YoptChoices.inlineProject) + def YoptInlineGlobal = optEnabled(YoptChoices.inlineGlobal) def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal - def YoptBuildCallGraph = YoptInlinerEnabled || YoptClosureElimination - def YoptAddToBytecodeRepository = YoptInlinerEnabled || YoptClosureElimination + def YoptBuildCallGraph = YoptInlinerEnabled || YoptClosureInvocations + def YoptAddToBytecodeRepository = YoptBuildCallGraph || YoptInlinerEnabled || YoptClosureInvocations val YoptInlineHeuristics = ChoiceSetting( name = "-Yopt-inline-heuristics", helpArg = "strategy", descr = "Set the heuristics for inlining decisions.", - choices = List("at-inline-annotated", "everything"), - default = "at-inline-annotated") + choices = List("at-inline-annotated", "everything", "default"), + default = "default") object YoptWarningsChoices extends MultiChoiceEnumeration { val none = Choice("none" , "No optimizer warnings.") val atInlineFailedSummary = Choice("at-inline-failed-summary" , "One-line summary if there were @inline method calls that could not be inlined.") val atInlineFailed = Choice("at-inline-failed" , "A detailed warning for each @inline method call that could not be inlined.") + val anyInlineFailed = Choice("any-inline-failed" , "A detailed warning for every callsite that was chosen for inlining by the heuristics, but could not be inlined.") val noInlineMixed = Choice("no-inline-mixed" , "In mixed compilation, warn at callsites methods defined in java sources (the inlining decision cannot be made without bytecode).") val noInlineMissingBytecode = Choice("no-inline-missing-bytecode" , "Warn if an inlining decision cannot be made because a the bytecode of a class or member cannot be found on the compilation classpath.") val noInlineMissingScalaInlineInfoAttr = Choice("no-inline-missing-attribute", "Warn if an inlining decision cannot be made because a Scala classfile does not have a ScalaInlineInfo attribute.") @@ -298,20 +289,22 @@ trait ScalaSettings extends AbsScalaSettings helpArg = "warning", descr = "Enable optimizer warnings", domain = YoptWarningsChoices, - default = Some(List(YoptWarningsChoices.atInlineFailed.name))) withPostSetHook (self => { - if (self.value subsetOf Set(YoptWarningsChoices.none, YoptWarningsChoices.atInlineFailedSummary)) YinlinerWarnings.value = false - else YinlinerWarnings.value = true - }) + default = Some(List(YoptWarningsChoices.atInlineFailed.name))) + + def YoptWarningsSummaryOnly = YoptWarnings.value subsetOf Set(YoptWarningsChoices.none, YoptWarningsChoices.atInlineFailedSummary) def YoptWarningEmitAtInlineFailed = !YoptWarnings.isSetByUser || YoptWarnings.contains(YoptWarningsChoices.atInlineFailedSummary) || - YoptWarnings.contains(YoptWarningsChoices.atInlineFailed) + YoptWarnings.contains(YoptWarningsChoices.atInlineFailed) || + YoptWarnings.contains(YoptWarningsChoices.anyInlineFailed) def YoptWarningNoInlineMixed = YoptWarnings.contains(YoptWarningsChoices.noInlineMixed) def YoptWarningNoInlineMissingBytecode = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingBytecode) def YoptWarningNoInlineMissingScalaInlineInfoAttr = YoptWarnings.contains(YoptWarningsChoices.noInlineMissingScalaInlineInfoAttr) + val YoptTrace = StringSetting("-Yopt-trace", "package/Class.method", "Trace the optimizer progress for a specific method.", "") + private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value } @@ -343,22 +336,15 @@ trait ScalaSettings extends AbsScalaSettings str => Some(if(str.equalsIgnoreCase("off")) Int.MaxValue else str.toInt)) val Yquasiquotedebug = BooleanSetting("-Yquasiquote-debug", "Trace quasiquote-related activities.") - // TODO 2.12 Remove - val Yinferdebug = BooleanSetting("-Yinfer-debug", "Trace type inference and implicit search.") withDeprecationMessage("Use -Ytyper-debug") enabling(List(Ytyperdebug)) - /** Groups of Settings. */ val future = BooleanSetting("-Xfuture", "Turn on future language features.") enablingIfNotSetByUser futureSettings - val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" enablingIfNotSetByUser optimiseSettings - val nooptimise = BooleanSetting("-Ynooptimise", "Clears all the flags set by -optimise. Useful for testing optimizations in isolation.") withAbbreviation "-Ynooptimize" disabling optimise::optimiseSettings + val optimise = BooleanSetting("-optimise", "Compiler flag for the optimizer in Scala 2.11") + .withAbbreviation("-optimize") + .withDeprecationMessage("In 2.12, -optimise enables -Yopt:l:classpath. Check -Yopt:help for using the Scala 2.12 optimizer.") + .withPostSetHook(_ => Yopt.tryToSet(List(YoptChoices.lClasspath.name))) val Xexperimental = BooleanSetting("-Xexperimental", "Enable experimental extensions.") enablingIfNotSetByUser experimentalSettings - /** - * Settings motivated by GenBCode - */ - val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.", - List("GenASM", "GenBCode"), - "GenASM") // Feature extensions val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.") @@ -382,13 +368,26 @@ trait ScalaSettings extends AbsScalaSettings /** Test whether this is scaladoc we're looking at */ def isScaladoc = false - def isBCodeActive = Ybackend.value == "GenBCode" - object MacroExpand { val None = "none" val Normal = "normal" val Discard = "discard" } + + def conflictWarning: Option[String] = { + // See cd878232b5 for an example how to warn about conflicting settings + + /* + def checkSomeConflict: Option[String] = ... + + List(/* checkSomeConflict, ... */).flatten match { + case Nil => None + case warnings => Some("Conflicting compiler settings were detected. Some settings will be ignored.\n" + warnings.mkString("\n")) + } + */ + + None + } } object ClassPathRepresentationType { diff --git a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala index 43bdad5882..0b051ef89d 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala @@ -68,45 +68,37 @@ case object AnyScalaVersion extends ScalaVersion { * Factory methods for producing ScalaVersions */ object ScalaVersion { - private val dot = "\\." - private val dash = "\\-" - private def not(s:String) = s"[^${s}]" - private val R = s"((${not(dot)}*)(${dot}(${not(dot)}*)(${dot}(${not(dash)}*)(${dash}(.*))?)?)?)".r - - def apply(versionString : String, errorHandler: String => Unit): ScalaVersion = { - def errorAndValue() = { - errorHandler( - s"There was a problem parsing ${versionString}. " + - "Versions should be in the form major[.minor[.revision]] " + - "where each part is a positive number, as in 2.10.1. " + - "The minor and revision parts are optional." - ) - AnyScalaVersion - } + private val dot = """\.""" + private val dash = "-" + private val vchar = """\d""" //"[^-+.]" + private val vpat = s"(?s)($vchar+)(?:$dot($vchar+)(?:$dot($vchar+)(?:$dash(.*))?)?)?".r + private val rcpat = """(?i)rc(\d*)""".r + private val mspat = """(?i)m(\d*)""".r + + def apply(versionString: String, errorHandler: String => Unit): ScalaVersion = { + def error() = errorHandler( + s"Bad version (${versionString}) not major[.minor[.revision[-suffix]]]" + ) def toInt(s: String) = s match { case null | "" => 0 - case _ => s.toInt + case _ => s.toInt } - def isInt(s: String) = util.Try(toInt(s)).isSuccess - def toBuild(s: String) = s match { case null | "FINAL" => Final - case s if (s.toUpperCase.startsWith("RC") && isInt(s.substring(2))) => RC(toInt(s.substring(2))) - case s if (s.toUpperCase.startsWith("M") && isInt(s.substring(1))) => Milestone(toInt(s.substring(1))) - case _ => Development(s) + case rcpat(i) => RC(toInt(i)) + case mspat(i) => Milestone(toInt(i)) + case _ /* | "" */ => Development(s) } - try versionString match { + versionString match { case "none" => NoScalaVersion - case "any" => AnyScalaVersion - case R(_, majorS, _, minorS, _, revS, _, buildS) => + case "" => NoScalaVersion + case "any" => AnyScalaVersion + case vpat(majorS, minorS, revS, buildS) => SpecificScalaVersion(toInt(majorS), toInt(minorS), toInt(revS), toBuild(buildS)) - case _ => - errorAndValue() - } catch { - case e: NumberFormatException => errorAndValue() + case _ => error() ; AnyScalaVersion } } diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index d42c0dd730..f197a4930d 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -38,8 +38,8 @@ trait StandardScalaSettings { val nowarn = BooleanSetting ("-nowarn", "Generate no warnings.") val optimise: BooleanSetting // depends on post hook which mutates other settings val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.") - val target = ChoiceSetting ("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.", - List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.6") + val target = ChoiceSettingForcedDefault ("-target", "target", "Target platform for object files. All JVM 1.5 - 1.7 targets are deprecated.", + List("jvm-1.5", "jvm-1.6", "jvm-1.7", "jvm-1.8"), "jvm-1.8") val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions.") val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.") diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index f570037760..79795bcc17 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -7,8 +7,6 @@ package scala.tools package nsc package settings -import language.existentials - /** Settings influencing the printing of warnings. */ trait Warnings { diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 99e61d2482..fffd48d145 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 @@ -816,6 +816,23 @@ abstract class ClassfileParser { val c1 = convertTo(c, symtype) if (c1 ne null) sym.setInfo(ConstantType(c1)) else devWarning(s"failure to convert $c to $symtype") + case tpnme.MethodParametersATTR => + def readParamNames(): Unit = { + import tools.asm.Opcodes.ACC_SYNTHETIC + val paramCount = u1 + var i = 0 + while (i < paramCount) { + val name = pool.getName(u2) + val access = u2 + if ((access & ACC_SYNTHETIC) != ACC_SYNTHETIC) { // name not synthetic + val params = sym.paramss.head // Java only has exactly one parameter list + params(i).name = name.encode + params(i).resetFlag(SYNTHETIC) + } + i += 1 + } + } + readParamNames() case tpnme.ScalaSignatureATTR => if (!isScalaAnnot) { devWarning(s"symbol ${sym.fullName} has pickled signature in attribute") 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..9a8eca152f 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -8,208 +8,17 @@ package transform import symtab._ import Flags._ -import scala.tools.nsc.util.ClassPath abstract class AddInterfaces extends InfoTransform { self: Erasure => import global._ // the global environment import definitions._ // standard classes and methods - /** The phase sets lateINTERFACE for non-interface traits that now - * become interfaces. It sets lateDEFERRED for formerly concrete - * methods in such traits. + /** lateDEFERRED for formerly concrete methods in such traits. */ - override def phaseNewFlags: Long = lateDEFERRED | lateINTERFACE - - /** A lazily constructed map that associates every non-interface trait with - * its implementation class. - */ - private val implClassMap = perRunCaches.newMap[Symbol, Symbol]() - - /** A lazily constructed map that associates every concrete method in a non-interface - * trait that's currently compiled with its corresponding method in the trait's - * implementation class. - */ - private val implMethodMap = perRunCaches.newMap[Symbol, Symbol]() - - override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { - implClassMap.clear() - implMethodMap.clear() - super.newPhase(prev) - } - - /** Is given trait member symbol a member of the trait's interface - * after this transform is performed? - */ - private def isInterfaceMember(sym: Symbol) = ( - sym.isType || { - sym.info // initialize to set lateMETHOD flag if necessary - - ( sym.isMethod - && !sym.isLabel - && !sym.isPrivate - && (!(sym hasFlag BRIDGE) || sym.hasBridgeAnnotation) // count @bridge annotated classes as interface members - && !sym.isConstructor - && !sym.isImplOnly - ) - } - ) - - /** Does symbol need an implementation method? */ - def needsImplMethod(sym: Symbol) = ( - sym.isMethod - && isInterfaceMember(sym) - && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED)) - ) - - def implClassPhase = currentRun.erasurePhase.next - - private def newImplClass(iface: Symbol): Symbol = { - val inClass = iface.owner.isClass - val implName = tpnme.implClassName(iface.name) - val implFlags = (iface.flags & ~(INTERFACE | lateINTERFACE)) | IMPLCLASS - - val impl0 = { - if (!inClass) NoSymbol - else { - val typeInfo = iface.owner.info - typeInfo.decl(implName) match { - case NoSymbol => NoSymbol - case implSym => - // Unlink a pre-existing symbol only if the implementation class is - // visible on the compilation classpath. In general this is true under - // -optimise and not otherwise, but the classpath can use arbitrary - // logic so the classpath must be queried. - // TODO this is not taken into account by flat classpath yet - classPath match { - case cp: ClassPath[_] if !cp.context.isValidName(implName + ".class") => - log(s"not unlinking $iface's existing implClass ${implSym.name} because it is not on the classpath.") - implSym - case _ => - typeInfo.decls unlink implSym - NoSymbol - } - } - } - } - - val impl = impl0 orElse { - val impl = iface.owner.newImplClass(implName, iface.pos, implFlags) - if (iface.thisSym != iface) { - impl.typeOfThis = iface.typeOfThis - impl.thisSym setName iface.thisSym.name - } - impl.associatedFile = iface.sourceFile - if (inClass) - iface.owner.info.decls enter impl - - impl - } - if (currentRun compiles iface) - currentRun.symSource(impl) = iface.sourceFile - - implClassMap(iface) = impl - impl setInfo new LazyImplClassType(iface) - } - - /** Return the implementation class of a trait; create a new one if one does not yet exist */ - def implClass(iface: Symbol): Symbol = { - iface.info - - implClassMap.getOrElse(iface, enteringPhase(implClassPhase) { - if (iface.implClass eq NoSymbol) - debuglog(s"${iface.fullLocationString} has no implClass yet, creating it now.") - else - log(s"${iface.fullLocationString} impl class is ${iface.implClass.nameString}") - - newImplClass(iface) - }) - } - - /** A lazy type to set the info of an implementation class - * The parents of an implementation class for trait iface are: - * - * - superclass: Object - * - mixin classes: mixin classes of iface where every non-interface - * trait is mapped to its implementation class, followed by iface itself. - * - * The declarations of a mixin class are: - * - for every interface member of iface: its implementation method, if one is needed - * - every former member of iface that is implementation only - */ - private class LazyImplClassType(iface: Symbol) extends LazyType with FlagAgnosticCompleter { - /** Compute the decls of implementation class implClass, - * given the decls ifaceDecls of its interface. - */ - private def implDecls(implClass: Symbol, ifaceDecls: Scope): Scope = { - debuglog("LazyImplClassType calculating decls for " + implClass) - - val decls = newScope - if ((ifaceDecls lookup nme.MIXIN_CONSTRUCTOR) == NoSymbol) { - log("Adding mixin constructor to " + implClass) - - decls enter ( - implClass.newMethod(nme.MIXIN_CONSTRUCTOR, implClass.pos) - setInfo MethodType(Nil, UnitTpe) - ) - } - - for (sym <- ifaceDecls) { - if (isInterfaceMember(sym)) { - if (needsImplMethod(sym)) { - val clone = sym.cloneSymbol(implClass).resetFlag(lateDEFERRED) - if (currentRun.compiles(implClass)) implMethodMap(sym) = clone - decls enter clone - sym setFlag lateDEFERRED - if (!sym.isSpecialized) - log(s"Cloned ${sym.name} from ${sym.owner} into implClass ${implClass.fullName}") - } - } - else { - log(s"Destructively modifying owner of $sym from ${sym.owner} to $implClass") - sym.owner = implClass - // note: OK to destructively modify the owner here, - // because symbol will not be accessible from outside the sourcefile. - // mixin constructors are corrected separately; see TermSymbol.owner - decls enter sym - } - } - - decls - } - - override def complete(implSym: Symbol) { - debuglog("LazyImplClassType completing " + implSym) - - /* If `tp` refers to a non-interface trait, return a - * reference to its implementation class. Otherwise return `tp`. - */ - def mixinToImplClass(tp: Type): Type = AddInterfaces.this.erasure(implSym) { - tp match { //@MATN: no normalize needed (comes after erasure) - case TypeRef(pre, sym, _) if sym.needsImplClass => - typeRef(pre, implClass(sym), Nil) - case _ => - tp - } - } - def implType(tp: Type): Type = tp match { - case ClassInfoType(parents, decls, _) => - assert(phase == implClassPhase, tp) - // Impl class parents: Object first, matching interface last. - val implParents = ObjectTpe +: (parents.tail map mixinToImplClass filter (_.typeSymbol != ObjectClass)) :+ iface.tpe - ClassInfoType(implParents, implDecls(implSym, decls), implSym) - case PolyType(_, restpe) => - implType(restpe) - } - implSym setInfo implType(enteringErasure(iface.info)) - } - - override def load(clazz: Symbol) { complete(clazz) } - } + override def phaseNewFlags: Long = lateDEFERRED def transformMixinInfo(tp: Type): Type = tp match { case ClassInfoType(parents, decls, clazz) if clazz.isPackageClass || !clazz.isJavaDefined => - if (clazz.needsImplClass) - implClass(clazz setFlag lateINTERFACE) // generate an impl class val parents1 = parents match { case Nil => Nil @@ -218,21 +27,20 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => if (clazz.isTrait) ObjectTpe :: tl else parents } - val decls1 = scopeTransform(clazz)( - decls filter (sym => - if (clazz.isInterface) isInterfaceMember(sym) - else sym.isClass || sym.isTerm - ) - ) - ClassInfoType(parents1, decls1, clazz) + if (clazz.isTrait) { + decls foreach { sym => + if (!sym.isType) sym.info // initialize to set lateMETHOD flag if necessary + } + } + if (parents1 eq parents) tp + else ClassInfoType(parents1, decls, clazz) case _ => tp } // Tree transformation -------------------------------------------------------------- - private class ChangeOwnerAndReturnTraverser(oldowner: Symbol, newowner: Symbol) - extends ChangeOwnerTraverser(oldowner, newowner) { + extends ChangeOwnerTraverser(oldowner, newowner) { override def traverse(tree: Tree) { tree match { case _: Return => change(tree.symbol) @@ -242,63 +50,10 @@ 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 ifaceTemplate(templ: Template): Template = - treeCopy.Template(templ, templ.parents, noSelfType, templ.body map ifaceMemberDef) - - /** Transforms the member tree containing the implementation - * into a member of the impl class. - */ - private def implMethodDef(tree: Tree): Tree = { - val impl = implMethodMap.getOrElse(tree.symbol, abort("implMethod missing for " + tree.symbol)) - - val newTree = if (impl.isErroneous) tree else { // e.g. res/t687 - // SI-5167: Ensure that the tree that we are grafting refers the parameter symbols from the - // new method symbol `impl`, rather than the symbols of the original method signature in - // the trait. `tree setSymbol impl` does *not* suffice! - val DefDef(_, _, _, vparamss, _, _) = tree - val oldSyms = vparamss.flatten.map(_.symbol) - val newSyms = impl.info.paramss.flatten - assert(oldSyms.length == newSyms.length, (oldSyms, impl, impl.info)) - tree.substituteSymbols(oldSyms, newSyms) - } - new ChangeOwnerAndReturnTraverser(newTree.symbol, impl)(newTree setSymbol impl) - } - - /** Add mixin constructor definition - * def $init$(): Unit = () - * to `stats` unless there is already one. - */ - private def addMixinConstructorDef(clazz: Symbol, stats: List[Tree]): List[Tree] = - if (treeInfo.firstConstructor(stats) != EmptyTree) stats - else DefDef(clazz.primaryConstructor, Block(List(), Literal(Constant(())))) :: stats - - private def implTemplate(clazz: Symbol, templ: Template): Template = atPos(templ.pos) { - val templ1 = ( - Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map implMemberDef)) - setSymbol clazz.newLocalDummy(templ.pos) - ) - templ1.changeOwner(templ.symbol.owner -> clazz, templ.symbol -> templ1.symbol) - templ1 - } - - def implClassDefs(trees: List[Tree]): List[Tree] = { - trees collect { - case cd: ClassDef if cd.symbol.needsImplClass => - val clazz = implClass(cd.symbol).initialize - ClassDef(clazz, implTemplate(clazz, cd.impl)) - } + private def mkAssign(clazz: Symbol, assignSym: Symbol, rhs: Tree): Tree = { + val qual = Select(This(clazz), assignSym) + if (assignSym.isSetter) Apply(qual, List(rhs)) + else Assign(qual, rhs) } /** Add calls to supermixin constructors @@ -306,15 +61,16 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => * to tree, which is assumed to be the body of a constructor of class clazz. */ private def addMixinConstructorCalls(tree: Tree, clazz: Symbol): Tree = { - def mixinConstructorCall(impl: Symbol): Tree = atPos(tree.pos) { - Apply(Select(This(clazz), impl.primaryConstructor), List()) + def mixinConstructorCall(mc: Symbol): Tree = atPos(tree.pos) { + Apply(SuperSelect(clazz, mc.primaryConstructor), Nil) } val mixinConstructorCalls: List[Tree] = { for (mc <- clazz.mixinClasses.reverse - if mc.hasFlag(lateINTERFACE)) - yield mixinConstructorCall(implClass(mc)) + if mc.isTrait && mc.primaryConstructor != NoSymbol) + yield mixinConstructorCall(mc) } tree match { + case Block(Nil, expr) => // AnyVal constructor - have to provide a real body so the // jvm doesn't throw a VerifyError. But we can't add the @@ -331,42 +87,14 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => } protected val mixinTransformer = new Transformer { - override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = - (super.transformStats(stats, exprOwner) ::: - super.transformStats(implClassDefs(stats), exprOwner)) override def transform(tree: Tree): Tree = { val sym = tree.symbol val tree1 = tree match { - case ClassDef(mods, _, _, impl) if sym.needsImplClass => - implClass(sym).initialize // to force lateDEFERRED flags - copyClassDef(tree)(mods = mods | INTERFACE, impl = ifaceTemplate(impl)) case DefDef(_,_,_,_,_,_) if sym.isClassConstructor && sym.isPrimaryConstructor && sym.owner != ArrayClass => deriveDefDef(tree)(addMixinConstructorCalls(_, sym.owner)) // (3) case Template(parents, self, body) => val parents1 = sym.owner.info.parents map (t => TypeTree(t) setPos tree.pos) treeCopy.Template(tree, parents1, noSelfType, body) - case This(_) if sym.needsImplClass => - val impl = implClass(sym) - var owner = currentOwner - while (owner != sym && owner != impl) owner = owner.owner; - if (owner == impl) This(impl) setPos tree.pos - else tree - //TODO what about this commented out code? -/* !!! - case Super(qual, mix) => - val mix1 = mix - if (mix == tpnme.EMPTY) mix - else { - val ps = enteringErasure { - sym.info.parents dropWhile (p => p.symbol.name != mix) - } - assert(!ps.isEmpty, tree); - if (ps.head.symbol.needsImplClass) implClass(ps.head.symbol).name - else mix - } - if (sym.needsImplClass) Super(implClass(sym), mix1) setPos tree.pos - else treeCopy.Super(tree, qual, mix1) -*/ case _ => tree } diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index c29826551b..0fb6213d36 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -21,16 +21,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { val phaseName: String = "cleanup" /* used in GenBCode: collects ClassDef symbols owning a main(Array[String]) method */ - private var entryPoints: List[Symbol] = null - def getEntryPoints: List[Symbol] = { - assert(settings.isBCodeActive, "Candidate Java entry points are collected here only when GenBCode in use.") - entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages. - } - - override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { - entryPoints = if (settings.isBCodeActive) Nil else null; - super.newPhase(prev) - } + private var entryPoints: List[Symbol] = Nil + def getEntryPoints: List[Symbol] = entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages. protected def newTransformer(unit: CompilationUnit): Transformer = new CleanUpTransformer(unit) @@ -49,7 +41,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) @@ -85,24 +79,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { /* ### CREATING THE METHOD CACHE ### */ - def addStaticVariableToClass(forName: TermName, forType: Type, forInit: Tree, isFinal: Boolean): Symbol = { - val flags = PRIVATE | STATIC | SYNTHETIC | ( - if (isFinal) FINAL else 0 - ) - - val varSym = currentClass.newVariable(mkTerm("" + forName), ad.pos, flags.toLong) setInfoAndEnter forType - if (!isFinal) - varSym.addAnnotation(VolatileAttr) - - val varDef = typedPos(ValDef(varSym, forInit)) - newStaticMembers append transform(varDef) - - val varInit = typedPos( REF(varSym) === forInit ) - newStaticInits append transform(varInit) - - varSym - } - def addStaticMethodToClass(forBody: (Symbol, Symbol) => Tree): Symbol = { val methSym = currentClass.newMethod(mkTerm(nme.reflMethodName.toString), ad.pos, STATIC | SYNTHETIC) val params = methSym.newSyntheticValueParams(List(ClassClass.tpe)) @@ -113,9 +89,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { methSym } - def fromTypesToClassArrayLiteral(paramTypes: List[Type]): Tree = - ArrayValue(TypeTree(ClassClass.tpe), paramTypes map LIT) - def reflectiveMethodCache(method: String, paramTypes: List[Type]): Symbol = { /* Implementation of the cache is as follows for method "def xyz(a: A, b: B)" (SoftReference so that it does not interfere with classloader garbage collection, @@ -126,7 +99,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { var reflPoly$Cache: SoftReference[scala.runtime.MethodCache] = new SoftReference(new EmptyMethodCache()) def reflMethod$Method(forReceiver: JClass[_]): JMethod = { - var methodCache: MethodCache = reflPoly$Cache.find(forReceiver) + var methodCache: StructuralCallSite = indy[StructuralCallSite.bootstrap, "(LA;LB;)Ljava/lang/Object;] if (methodCache eq null) { methodCache = new EmptyMethodCache reflPoly$Cache = new SoftReference(methodCache) @@ -135,41 +108,32 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { if (method ne null) return method else { - method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", reflParams$Cache)) - reflPoly$Cache = new SoftReference(methodCache.add(forReceiver, method)) + method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", methodCache.parameterTypes())) + methodCache.add(forReceiver, method) return method } } - */ - - val reflParamsCacheSym: Symbol = - addStaticVariableToClass(nme.reflParamsCacheName, arrayType(ClassClass.tpe), fromTypesToClassArrayLiteral(paramTypes), true) - - def mkNewPolyCache = gen.mkSoftRef(NEW(TypeTree(EmptyMethodCacheClass.tpe))) - val reflPolyCacheSym: Symbol = addStaticVariableToClass(nme.reflPolyCacheName, SoftReferenceClass.tpe, mkNewPolyCache, false) - def getPolyCache = gen.mkCast(fn(REF(reflPolyCacheSym), nme.get), MethodCacheClass.tpe) + invokedynamic is used rather than a static field for the cache to support emitting bodies of methods + in Java 8 interfaces, which don't support private static fields. + */ addStaticMethodToClass((reflMethodSym, forReceiverSym) => { - val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo MethodCacheClass.tpe + val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo StructuralCallSite.tpe val methodSym = reflMethodSym.newVariable(mkTerm("method"), ad.pos) setInfo MethodClass.tpe + val dummyMethodType = MethodType(NoSymbol.newSyntheticValueParams(paramTypes), AnyTpe) BLOCK( - ValDef(methodCache, getPolyCache), - IF (REF(methodCache) OBJ_EQ NULL) THEN BLOCK( - REF(methodCache) === NEW(TypeTree(EmptyMethodCacheClass.tpe)), - REF(reflPolyCacheSym) === gen.mkSoftRef(REF(methodCache)) - ) ENDIF, - - ValDef(methodSym, (REF(methodCache) DOT methodCache_find)(REF(forReceiverSym))), + ValDef(methodCache, ApplyDynamic(gen.mkAttributedIdent(StructuralCallSite_dummy), LIT(StructuralCallSite_bootstrap) :: LIT(dummyMethodType) :: Nil).setType(StructuralCallSite.tpe)), + ValDef(methodSym, (REF(methodCache) DOT StructuralCallSite_find)(REF(forReceiverSym))), IF (REF(methodSym) OBJ_NE NULL) . THEN (Return(REF(methodSym))) ELSE { - def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), REF(reflParamsCacheSym))) - def cacheRHS = ((REF(methodCache) DOT methodCache_add)(REF(forReceiverSym), REF(methodSym))) + def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), (REF(methodCache) DOT StructuralCallSite_getParameterTypes)())) + def cacheAdd = ((REF(methodCache) DOT StructuralCallSite_add)(REF(forReceiverSym), REF(methodSym))) BLOCK( REF(methodSym) === (REF(currentRun.runDefinitions.ensureAccessibleMethod) APPLY (methodSymRHS)), - REF(reflPolyCacheSym) === gen.mkSoftRef(cacheRHS), + cacheAdd, Return(REF(methodSym)) ) } @@ -369,6 +333,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { reporter.error(ad.pos, "Cannot resolve overload.") (Nil, NoType) } + case NoType => + abort(ad.symbol.toString) } typedPos { val sym = currentOwner.newValue(mkTerm("qual"), ad.pos) setInfo qual0.tpe @@ -404,11 +370,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { } override def transform(tree: Tree): Tree = tree match { - - case _: ClassDef - if (entryPoints != null) && - genBCode.isJavaEntryPoint(tree.symbol, currentUnit) - => + case _: ClassDef if genBCode.isJavaEntryPoint(tree.symbol, currentUnit) => // collecting symbols for entry points here (as opposed to GenBCode where they are used) // has the advantage of saving an additional pass over all ClassDefs. entryPoints ::= tree.symbol @@ -446,7 +408,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { * refinement, where the refinement defines a parameter based on a * type variable. */ - case tree: ApplyDynamic => + case tree: ApplyDynamic if tree.symbol.owner.isRefinementClass => transformApplyDynamic(tree) /* Some cleanup transformations add members to templates (classes, traits, etc). @@ -476,46 +438,15 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { /* * This transformation should identify Scala symbol invocations in the tree and replace them - * with references to a static member. Also, whenever a class has at least a single symbol invocation - * somewhere in its methods, a new static member should be created and initialized for that symbol. - * For instance, say we have a Scala class: - * - * class Cls { - * def someSymbol1 = 'Symbolic1 - * def someSymbol2 = 'Symbolic2 - * def sameSymbol1 = 'Symbolic1 - * val someSymbol3 = 'Symbolic3 - * } - * - * After transformation, this class looks like this: - * - * class Cls { - * private <static> var symbol$1: scala.Symbol - * private <static> var symbol$2: scala.Symbol - * private <static> var symbol$3: scala.Symbol - * private val someSymbol3: scala.Symbol - * - * private <static> def <clinit> = { - * symbol$1 = Symbol.apply("Symbolic1") - * symbol$2 = Symbol.apply("Symbolic2") - * } - * - * private def <init> = { - * someSymbol3 = symbol$3 - * } - * - * def someSymbol1 = symbol$1 - * def someSymbol2 = symbol$2 - * def sameSymbol1 = symbol$1 - * val someSymbol3 = someSymbol3 - * } + * with references to a statically cached instance. * * The reasoning behind this transformation is the following. Symbols get interned - they are stored * in a global map which is protected with a lock. The reason for this is making equality checks * quicker. But calling Symbol.apply, although it does return a unique symbol, accesses a locked object, * making symbol access slow. To solve this, the unique symbol from the global symbol map in Symbol - * is accessed only once during class loading, and after that, the unique symbol is in the static - * member. Hence, it is cheap to both reach the unique symbol and do equality checks on it. + * is accessed only once during class loading, and after that, the unique symbol is in the statically + * initialized call site returned by invokedynamic. Hence, it is cheap to both reach the unique symbol + * and do equality checks on it. * * And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler * have little in common. @@ -523,15 +454,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { case Apply(fn @ Select(qual, _), (arg @ Literal(Constant(symname: String))) :: Nil) if treeInfo.isQualifierSafeToElide(qual) && fn.symbol == Symbol_apply && !currentClass.isTrait => - def transformApply = { - // add the symbol name to a map if it's not there already - val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil) - val staticFieldSym = getSymbolStaticField(tree.pos, symname, rhs, tree) - // create a reference to a static field - val ntree = typedWithPos(tree.pos)(REF(staticFieldSym)) - super.transform(ntree) - } - transformApply + super.transform(treeCopy.ApplyDynamic(tree, atPos(fn.pos)(Ident(SymbolLiteral_dummy).setType(SymbolLiteral_dummy.info)), LIT(SymbolLiteral_bootstrap) :: arg :: Nil)) // Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), <tag>)` // with just `ArrayValue(...).$asInstanceOf[...]` @@ -548,32 +471,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { super.transform(tree) } - /* Returns the symbol and the tree for the symbol field interning a reference to a symbol 'synmname'. - * If it doesn't exist, i.e. the symbol is encountered the first time, - * it creates a new static field definition and initialization and returns it. - */ - private def getSymbolStaticField(pos: Position, symname: String, rhs: Tree, tree: Tree): Symbol = { - symbolsStoredAsStatic.getOrElseUpdate(symname, { - val theTyper = typer.atOwner(tree, currentClass) - - // create a symbol for the static field - val stfieldSym = ( - currentClass.newVariable(mkTerm("symbol$"), pos, PRIVATE | STATIC | SYNTHETIC | FINAL) - setInfoAndEnter SymbolClass.tpe - ) - - // create field definition and initialization - val stfieldDef = theTyper.typedPos(pos)(ValDef(stfieldSym, rhs)) - val stfieldInit = theTyper.typedPos(pos)(REF(stfieldSym) === rhs) - - // add field definition to new defs - newStaticMembers append stfieldDef - newStaticInits append stfieldInit - - stfieldSym - }) - } - } // CleanUpTransformer } diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index b2aac587eb..636fb08b89 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. @@ -75,7 +73,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { override def transform(tree: Tree): Tree = { tree match { - case cd @ ClassDef(mods0, name0, tparams0, impl0) if !cd.symbol.isInterface && !isPrimitiveValueClass(cd.symbol) => + case cd @ ClassDef(mods0, name0, tparams0, impl0) if !isPrimitiveValueClass(cd.symbol) && cd.symbol.primaryConstructor != NoSymbol => if(cd.symbol eq AnyValClass) { cd } @@ -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,62 +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 - } - - def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal) - def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && sym.owner.isEffectivelyFinal && !sym.isOverridingSymbol) - - val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (clazz.info.decls.toSet filter isParamCandidateForElision) - val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (clazz.info.decls.toSet filter isOuterCandidateForElision) - - omittables ++= paramCandidatesForElision - omittables ++= outerCandidatesForElision - - val bodyOfOuterAccessor: Map[Symbol, DefDef] = - defBuf.collect { case dd: DefDef if outerCandidatesForElision(dd.symbol) => dd.symbol -> dd }.toMap + 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. + 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: @@ -248,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) @@ -310,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 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 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 @@ -347,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] = @@ -375,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), _) => @@ -388,9 +372,9 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } if (stat1 eq stat) { - assert(ctorParams(genericClazz).length == constrInfo.constrParams.length) + assert(ctorParams(genericClazz).length == primaryConstrParams.length) // this is just to make private fields public - (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), constrInfo.constrParams, null, true))(stat1) + (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), primaryConstrParams, null, true))(stat1) val stat2 = rewriteArrayUpdate(stat1) // statements coming from the original class need retyping in the current context @@ -405,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 @@ -427,16 +410,16 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { // postfix = postfix.tail // } - if (shouldGuard && usesSpecializedField && stats.nonEmpty) { + if (guardSpecializedFieldInit && usesSpecializedField && stats.nonEmpty) { // save them for duplication in the specialized subclass guardedCtorStats(clazz) = stats - ctorParams(clazz) = constrInfo.constrParams + ctorParams(clazz) = primaryConstrParams val tree = If( Apply( CODE.NOT ( - Apply(gen.mkAttributedRef(specializedFlag), List())), + Apply(gen.mkAttributedRef(hasSpecializedFieldsSym), List())), List()), Block(stats, Literal(Constant(()))), EmptyTree) @@ -464,34 +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 specializedFlag: Symbol = clazz.info.decl(nme.SPECIALIZED_INSTANCE) - val shouldGuard = (specializedFlag != NoSymbol) && !clazz.hasFlag(SPECIALIZED) - - val isDelayedInitSubclass = (clazz isSubClass DelayedInitClass) - - case class ConstrInfo( - constr: DefDef, // The primary constructor - constrParams: List[Symbol], // ... and its parameters - constrBody: Block // ... and its body - ) - // decompose primary constructor into the three entities above. - val constrInfo: ConstrInfo = { - val ddef = (stats find (_.symbol.isPrimaryConstructor)) - ddef match { - case Some(ddef @ DefDef(_, _, _, List(vparams), _, rhs @ Block(_, _))) => - ConstrInfo(ddef, vparams map (_.symbol), rhs) - case x => - abort("no constructor in template: impl = " + impl) - } + 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 + private val (primaryConstr, _primaryConstrParams, primaryConstrBody) = stats collectFirst { + case dd@DefDef(_, _, _, vps :: Nil, _, rhs: Block) if dd.symbol.isPrimaryConstructor || dd.symbol.isMixinConstructor => (dd, vps map (_.symbol), rhs) + } getOrElse { + abort("no constructor in template: impl = " + impl) } - import constrInfo._ - // 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) @@ -501,27 +473,26 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { def parameterNamed(name: Name): Symbol = { def matchesName(param: Symbol) = param.name == name || param.name.startsWith(name + nme.NAME_JOIN_STRING) - (constrParams filter matchesName) match { - case Nil => abort(name + " not in " + constrParams) + primaryConstrParams filter matchesName match { + case Nil => abort(name + " not in " + primaryConstrParams) case p :: _ => p } } - /* - * `usesSpecializedField` makes a difference in deciding whether constructor-statements - * should be guarded in a `shouldGuard` class, ie in a class that's the generic super-class of - * one or more specialized sub-classes. - * - * Given that `usesSpecializedField` isn't read for any other purpose than the one described above, - * we skip setting `usesSpecializedField` in case the current class isn't `shouldGuard` to start with. - * That way, trips to a map in `specializeTypes` are saved. - */ - var usesSpecializedField: Boolean = false - // A transformer for expressions that go into the constructor - private class IntoCtorTransformer extends Transformer { - - private def isParamRef(sym: Symbol) = (sym.isParamAccessor && sym.owner == clazz) + object intoConstructor extends Transformer { + /* + * `usesSpecializedField` makes a difference in deciding whether constructor-statements + * should be guarded in a `guardSpecializedFieldInit` class, ie in a class that's the generic super-class of + * one or more specialized sub-classes. + * + * Given that `usesSpecializedField` isn't read for any other purpose than the one described above, + * we skip setting `usesSpecializedField` in case the current class isn't `guardSpecializedFieldInit` to start with. + * That way, trips to a map in `specializeTypes` are saved. + */ + var usesSpecializedField: Boolean = false + + private def isParamRef(sym: Symbol) = sym.isParamAccessor && sym.owner == clazz // Terminology: a stationary location is never written after being read. private def isStationaryParamRef(sym: Symbol) = ( @@ -530,8 +501,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { !sym.isSetter ) - private def possiblySpecialized(s: Symbol) = specializeTypes.specializedTypeVars(s).nonEmpty - /* * whether `sym` denotes a param-accessor (ie a field) that fulfills all of: * (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and @@ -540,16 +509,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * (b.2) the constructor in the specialized (sub-)class. * (c) isn't part of a DelayedInit subclass. */ - private def canBeSupplanted(sym: Symbol) = (!isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym)) + private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !specializeTypes.possiblySpecialized(sym) override def transform(tree: Tree): Tree = tree match { - case Apply(Select(This(_), _), List()) => // references to parameter accessor methods of own class become references to parameters // outer accessors become references to $outer parameter - if (canBeSupplanted(tree.symbol)) + if (clazz.isTrait) + super.transform(tree) + else if (canBeSupplanted(tree.symbol)) gen.mkAttributedIdent(parameter(tree.symbol.accessed)) setPos tree.pos - else if (tree.symbol.outerSource == clazz && !clazz.isImplClass) + else if (tree.symbol.outerSource == clazz) gen.mkAttributedIdent(parameterNamed(nme.OUTER)) setPos tree.pos else super.transform(tree) @@ -558,8 +528,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { // references to parameter accessor field of own class become references to parameters gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos - case Select(_, _) if shouldGuard => // reasoning behind this guard in the docu of `usesSpecializedField` - if (possiblySpecialized(tree.symbol)) { + case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField` + if (specializeTypes.possiblySpecialized(tree.symbol)) { usesSpecializedField = true } super.transform(tree) @@ -568,23 +538,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { super.transform(tree) } - } - - private val intoConstructorTransformer = new IntoCtorTransformer - - // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol - def intoConstructor(oldowner: Symbol, tree: Tree) = - intoConstructorTransformer transform tree.changeOwner(oldowner -> constr.symbol) - - // Should tree be moved in front of super constructor call? - def canBeMoved(tree: Tree) = tree match { - case ValDef(mods, _, _, _) => (mods hasFlag PRESUPER | PARAMACCESSOR) - case _ => false + // Move tree into constructor, take care of changing owner from `oldOwner` to `newOwner` (the primary constructor symbol) + def apply(oldOwner: Symbol, newOwner: Symbol)(tree: Tree) = + if (tree eq EmptyTree) tree + 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 @@ -594,139 +558,180 @@ 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] - - // The early initialized field definitions of the class (these are the class members) - val presupers = treeInfo.preSuperFields(stats) - - // The list of statements that go into the class initializer - val classInitStatBuf = new ListBuffer[Tree] - - // generate code to copy pre-initialized fields - for (stat <- constrBody.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 _ => - } - } + // Constant typed vals are not memoized. + def memoizeValue(sym: Symbol) = !sym.info.resultType.isInstanceOf[ConstantType] + + /** 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 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, s"expected exactly one field by name $name in $presupers of $clazz's early initializers") + val to = fields.head.symbol - // Triage all template definitions to go into defBuf/auxConstructorBuf, constrStatBuf, or constrPrefixBuf. - for (stat <- stats) stat match { - case DefDef(_,_,_,_,_,rhs) => - // methods with constant result type get literals as their body - // all methods except the primary constructor go into template - stat.symbol.tpe match { - case MethodType(List(), tp @ ConstantType(c)) => - defBuf += deriveDefDef(stat)(Literal(c) setPos _.pos setType tp) + if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol)) case _ => - if (stat.symbol.isPrimaryConstructor) () - else if (stat.symbol.isConstructor) auxConstructorBuf += stat - else defBuf += stat } - case ValDef(mods, _, _, rhs) if !mods.hasStaticFlag => - // val defs with constant right-hand sides are eliminated. - // for all other val defs, an empty valdef goes into the template and - // the initializer goes as an assignment into the constructor - // if the val def is an early initialized or a parameter accessor, it goes - // before the superclass constructor call, otherwise it goes after. - // Lazy vals don't get the assignment in the constructor. - if (!stat.symbol.tpe.isInstanceOf[ConstantType]) { - if (rhs != EmptyTree && !stat.symbol.isLazy) { - val rhs1 = intoConstructor(stat.symbol, rhs) - (if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign( - stat.symbol, rhs1) + } + + 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 ((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) { + val initPhase = + if (mods hasFlag STATIC) classInitStatBuf + else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf + else constrStatBuf + + initPhase += mkAssign(assignSym, initializingRhs) } - defBuf += deriveValDef(stat)(_ => EmptyTree) } - case ValDef(_, _, _, rhs) => - // Add static initializer statements to classInitStatBuf and remove the rhs from the val def. - classInitStatBuf += mkAssign(stat.symbol, rhs) - defBuf += deriveValDef(stat)(_ => EmptyTree) - - case ClassDef(_, _, _, _) => - // classes are treated recursively, and left in the template - defBuf += new ConstructorTransformer(unit).transform(stat) - case _ => - // all other statements go into the constructor - constrStatBuf += intoConstructor(impl.symbol, stat) - } - 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)) + stat match { + // recurse on class definition, store in defBuf + case _: ClassDef if !stat.symbol.isInterface => 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 || statSym.isMixinConstructor => () + 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) + } + } - 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(constr)(_ => - treeCopy.Block( - constrBody, - paramInits ::: constrPrefixBuf.toList ::: uptoSuperStats ::: - guardSpecializedInitializer(remainingConstrStats), - constrBody.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))) - // Add the static initializers - val transformed: Template = addStaticInits(templateWithoutOmittables, classInitStatBuf, localTyper) + // 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) + } + 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 8e323de623..d350ca8e17 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -4,35 +4,21 @@ package transform import symtab._ import Flags._ import scala.collection._ -import scala.language.postfixOps -import scala.reflect.internal.Symbols -import scala.collection.mutable.LinkedHashMap /** - * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes - * or to a tree that will be convereted to invokedynamic by the JVM 1.8+ backend. - * - * The main assumption it makes is that a lambda {args => body} has been turned into - * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda. - * Currently Uncurry is responsible for that transformation. - * - * From a lambda, Delambdafy will create: - * - * Under -target:jvm-1.7 and below: - * - * 1) a new top level class that - a) has fields and a constructor taking the captured environment (including possibly the "this" - * reference) - * b) an apply method that calls the target method - * c) if needed a bridge method for the apply method - * 2) an instantiation of the newly created class which replaces the lambda - * - * Under -target:jvm-1.8 with GenBCode: - * - * 1) An application of the captured arguments to a fictional symbol representing the lambda factory. - * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. - * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. - */ + * This transformer is responsible for preparing Function nodes for runtime, + * by translating to a tree that will be converted to an invokedynamic by the backend. + * + * The main assumption it makes is that a Function {args => body} has been turned into + * {args => liftedBody()} where lifted body is a top level method that implements the body of the function. + * Currently Uncurry is responsible for that transformation. + * + * From this shape of Function, Delambdafy will create: + * + * An application of the captured arguments to a fictional symbol representing the lambda factory. + * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. + * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. + */ abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer { import global._ import definitions._ @@ -42,6 +28,19 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre /** the following two members override abstract members in Transform */ val phaseName: String = "delambdafy" + final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol) + + /** + * Get the symbol of the target lifted lambda body method from a function. I.e. if + * the function is {args => anonfun(args)} then this method returns anonfun's symbol + */ + private def targetMethod(fun: Function): Symbol = fun match { + case Function(_, Apply(target, _)) => target.symbol + case _ => + // any other shape of Function is unexpected at this point + abort(s"could not understand function with tree $fun") + } + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { if (settings.Ydelambdafy.value == "method") new Phase(prev) else new SkipPhase(prev) @@ -54,433 +53,217 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre protected def newTransformer(unit: CompilationUnit): Transformer = new DelambdafyTransformer(unit) - class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter { - private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil + class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it + // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes? + private[this] lazy val methodReferencesThis: Set[Symbol] = + (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body) + + private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = { + val pos = fun.pos + val allCapturedArgRefs = { + // find which variables are free in the lambda because those are captures that need to be + // passed into the constructor of the anonymous function class + val captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture => + gen.mkAttributedRef(capture) setPos pos + ).toList + + if (target hasFlag STATIC) captureArgs // no `this` reference needed + else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs + } + // Create a symbol representing a fictional lambda factory method that accepts the captured + // arguments and returns the SAM type. + val msym = { + val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, pos, ARTIFACT) + val capturedParams = meth.newSyntheticValueParams(allCapturedArgRefs.map(_.tpe)) + meth.setInfo(MethodType(capturedParams, fun.tpe)) + } - val typer = localTyper + // We then apply this symbol to the captures. + val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs)) - // we need to know which methods refer to the 'this' reference so that we can determine - // which lambdas need access to it - val thisReferringMethods: Set[Symbol] = { - val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser() - thisReferringMethodsTraverser traverse unit.body - val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences - val referrers = thisReferringMethodsTraverser.thisReferringMethods - // recursively find methods that refer to 'this' directly or indirectly via references to other methods - // for each method found add it to the referrers set - def refersToThis(symbol: Symbol): Boolean = { - if (referrers contains symbol) true - else if (methodReferringMap(symbol) exists refersToThis) { - // add it early to memoize - debuglog(s"$symbol indirectly refers to 'this'") - referrers += symbol - true - } else false + // TODO: this is a bit gross + val sam = samUserDefined orElse { + if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get + else functionalInterface.info.member(nme.apply) } - methodReferringMap.keys foreach refersToThis - referrers + + // no need for adaptation when the implemented sam is of a specialized built-in function type + val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface, sam) + + // The backend needs to know the target of the lambda and the functional interface in order + // to emit the invokedynamic instruction. We pass this information as tree attachment. + // + // see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + // instantiatedMethodType is derived from lambdaTarget's signature + // samMethodType is derived from samOf(functionalInterface)'s signature + apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface, sam)) + + apply } - // the result of the transformFunction method. - sealed abstract class TransformedFunction - // A class definition for the lambda, an expression instantiating the lambda class - case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction - case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]() - // here's the main entry point of the transform - override def transform(tree: Tree): Tree = tree match { - // the main thing we care about is lambdas - case fun @ Function(_, _) => - transformFunction(fun) match { - case DelambdafyAnonClass(lambdaClassDef, newExpr) => - // a lambda becomes a new class, an instantiation expression - val pkg = lambdaClassDef.symbol.owner - - // we'll add the lambda class to the package later - lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg) - - super.transform(newExpr) - case InvokeDynamicLambda(apply) => - // ... or an invokedynamic call - super.transform(apply) - } - case Template(_, _, _) => - try { - // during this call boxingBridgeMethods will be populated from the Function case - val Template(parents, self, body) = super.transform(tree) - Template(parents, self, body ++ boxingBridgeMethods) - } finally boxingBridgeMethods.clear() - case _ => super.transform(tree) + private def reboxValueClass(tp: Type) = tp match { + case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) + case _ => tp } - // this entry point is aimed at the statements in the compilation unit. - // after working on the entire compilation until we'll have a set of - // new class definitions to add to the top level - override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { - // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same - // package when defining a package object. We only add the lambda class to one. See SI-9097. - super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil) + // exclude primitives and value classes, which need special boxing + private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && { + val sym = tp.typeSymbol + !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass) } - private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None + // determine which lambda target to use with java's LMF -- create a new one if scala-specific boxing is required + def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol, sam: Symbol): Symbol = { + val oldClass = fun.symbol.enclClass + val pos = fun.pos + + // At erasure, there won't be any captured arguments (they are added in constructors) + val functionParamTypes = exitingErasure(target.info.paramTypes) + val functionResultType = exitingErasure(target.info.resultType) + + val samParamTypes = exitingErasure(sam.info.paramTypes) + val samResultType = exitingErasure(sam.info.resultType) + + /** How to satisfy the linking invariants of https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + * + * Given samMethodType: (U1..Un)Ru and function type T1,..., Tn => Rt (the target method created by uncurry) + * + * Do we need a bridge, or can we use the original lambda target for implMethod: (<captured args> A1..An)Ra + * (We can ignore capture here.) + * + * If, for i=1..N: + * Ai =:= Ui || (Ai <:< Ui <:< AnyRef) + * Ru =:= void || (Ra =:= Ru || (Ra <:< AnyRef, Ru <:< AnyRef)) + * + * We can use the target method as-is -- if not, we create a bridging one that uses the types closest + * to the target method that still meet the above requirements. + */ + val resTpOk = ( + samResultType =:= UnitTpe + || functionResultType =:= samResultType + || (isReferenceType(samResultType) && isReferenceType(functionResultType))) // yes, this is what the spec says -- no further correspondance required + if (resTpOk && (samParamTypes corresponds functionParamTypes){ (samParamTp, funParamTp) => + funParamTp =:= samParamTp || (isReferenceType(funParamTp) && isReferenceType(samParamTp) && funParamTp <:< samParamTp) }) target + else { + // We have to construct a new lambda target that bridges to the one created by uncurry. + // The bridge must satisfy the above invariants, while also minimizing adaptation on our end. + // LMF will insert runtime casts according to the spec at the above link. + + // we use the more precise type between samParamTp and funParamTp to minimize boxing in the bridge method + // we are constructing a method whose signature matches the sam's signature (because the original target did not) + // whenever a type in the sam's signature is (erases to) a primitive type, we must pick the sam's version, + // as we don't implement the logic regarding widening that's performed by LMF -- we require =:= for primitives + // + // We use the sam's type for the check whether we're dealin with a reference type, as it could be a generic type, + // which means the function's parameter -- even if it expects a value class -- will need to be + // boxed on the generic call to the sam method. - // turns a lambda into a new class def, a New expression instantiating that class - private def transformFunction(originalFunction: Function): TransformedFunction = { - val functionTpe = originalFunction.tpe - val targs = functionTpe.typeArgs - val formals :+ restpe = targs - val oldClass = originalFunction.symbol.enclClass + val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) => + if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp + else samParamTp + } - // find which variables are free in the lambda because those are captures that need to be - // passed into the constructor of the anonymous function class - val captures = FreeVarTraverser.freeVarsOf(originalFunction) + val bridgeResultType = + if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType + else samResultType - val target = targetMethod(originalFunction) - target.makeNotPrivate(target.owner) - if (!thisReferringMethods.contains(target)) - target setFlag STATIC + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) } + import typeAdapter.{adaptToType, unboxValueClass} - val isStatic = target.hasFlag(STATIC) + val targetParams = target.paramss.head + val numCaptures = targetParams.length - functionParamTypes.length + val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - def createBoxingBridgeMethod(functionParamTypes: List[Type], functionResultType: Type): Tree = { - // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required. - // If we need to improve performance, we could check the types first before creating the - // method and parameter symbols. val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) - var neededAdaptation = false - def boxedType(tpe: Type): Type = { - if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe} - else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe} - else tpe - } - val targetParams: List[Symbol] = target.paramss.head - val numCaptures = targetParams.length - functionParamTypes.length - val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - val bridgeParams: List[Symbol] = - targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) ::: - map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName)) - - val bridgeResultType: Type = { - if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) { - neededAdaptation = true - ObjectTpe - } else - boxedType(functionResultType) - } - val methodType = MethodType(bridgeParams, bridgeResultType) - methSym setInfo methodType - if (!neededAdaptation) - EmptyTree - else { - val bridgeParamTrees = bridgeParams.map(ValDef(_)) - - oldClass.info.decls enter methSym - - val body = localTyper.typedPos(originalFunction.pos) { - val newTarget = Select(gen.mkAttributedThis(oldClass), target) - val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) => - if (i < numCaptures) { - gen.mkAttributedRef(param) - } else { - val functionParam = functionParamTypes(i - numCaptures) - val targetParam = targetParams(i) - if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) { - val casted = cast(gen.mkAttributedRef(param), functionParam) - val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType) - unboxed - } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe) - } + val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) + val bridgeFunctionParams = + map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName)) + + val bridgeParams = bridgeCapturedParams ::: bridgeFunctionParams + + methSym setInfo MethodType(bridgeParams, bridgeResultType) + oldClass.info.decls enter methSym + + val forwarderCall = localTyper.typedPos(pos) { + val capturedArgRefs = bridgeCapturedParams map gen.mkAttributedRef + val functionArgRefs = + map3(bridgeFunctionParams, functionParamTypes, targetParams.drop(numCaptures)) { (bridgeParam, functionParamTp, targetParam) => + val bridgeParamRef = gen.mkAttributedRef(bridgeParam) + val targetParamTp = targetParam.tpe + + // TODO: can we simplify this to something like `adaptToType(adaptToType(bridgeParamRef, functionParamTp), targetParamTp)`? + val unboxed = + functionParamTp match { + case ErasedValueType(clazz, underlying) => + // when the original function expected an argument of value class type, + // the original target will expect the unboxed underlying value, + // whereas the bridge will receive the boxed value (since the sam's argument type did not match and we had to adapt) + localTyper.typed(unboxValueClass(bridgeParamRef, clazz, underlying), targetParamTp) + case _ => bridgeParamRef + } + + adaptToType(unboxed, targetParamTp) } - gen.mkMethodCall(newTarget, args) - } - val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) - adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType) - else adaptToType(body, bridgeResultType) - val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) - postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] - } - } - /** - * Creates the apply method for the anonymous subclass of FunctionN - */ - def createApplyMethod(newClass: Symbol, fun: Function, thisProxy: Symbol): DefDef = { - val methSym = newClass.newMethod(nme.apply, fun.pos, FINAL | SYNTHETIC) - val params = fun.vparams map (_.duplicate) - - val paramSyms = map2(formals, params) { - (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) - } - params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym } - params foreach (_.symbol.owner = methSym) - - val methodType = MethodType(paramSyms, restpe) - methSym setInfo methodType - - newClass.info.decls enter methSym - - val Apply(_, oldParams) = fun.body - val qual = if (thisProxy.exists) - Select(gen.mkAttributedThis(newClass), thisProxy) - else - gen.mkAttributedThis(oldClass) // sort of a lie, EmptyTree.<static method> would be more honest, but the backend chokes on that. - val body = localTyper typed Apply(Select(qual, target), oldParams) - body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol)) - body changeOwner (fun.symbol -> methSym) - - val methDef = DefDef(methSym, List(params), body) + gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs) + } - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - // TODO probably don't need packedType - methDef.tpt setType localTyper.packedType(body, methSym) - methDef - } + val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))), + adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef] - /** - * Creates the constructor on the newly created class. It will handle - * initialization of members that represent the captured environment - */ - def createConstructor(newClass: Symbol, members: List[ValDef]): DefDef = { - val constrSym = newClass.newConstructor(originalFunction.pos, SYNTHETIC) - - val (paramSymbols, params, assigns) = (members map {member => - val paramSymbol = newClass.newVariable(member.symbol.name.toTermName, newClass.pos, 0) - paramSymbol.setInfo(member.symbol.info) - val paramVal = ValDef(paramSymbol) - val paramIdent = Ident(paramSymbol) - val assign = Assign(Select(gen.mkAttributedThis(newClass), member.symbol), paramIdent) - - (paramSymbol, paramVal, assign) - }).unzip3 - - val constrType = MethodType(paramSymbols, newClass.thisType) - constrSym setInfoAndEnter constrType - - val body = - Block( - List( - Apply(Select(Super(gen.mkAttributedThis(newClass), tpnme.EMPTY) setPos newClass.pos, nme.CONSTRUCTOR) setPos newClass.pos, Nil) setPos newClass.pos - ) ++ assigns, - Literal(Constant(())): Tree - ) setPos newClass.pos - - (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef] + boxingBridgeMethods += bridge + bridge.symbol } + } - val pkg = oldClass.owner - - // Parent for anonymous class def - val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe - - // anonymous subclass of FunctionN with an apply method - def makeAnonymousClass: ClassDef = { - val parents = addSerializable(abstractFunctionErasedType) - val funOwner = originalFunction.symbol.owner - - // TODO harmonize the naming of delambdafy anon-fun classes with those spun up by Uncurry - // - make `anonClass.isAnonymousClass` true. - // - use `newAnonymousClassSymbol` or push the required variations into a similar factory method - // - reinstate the assertion in `Erasure.resolveAnonymousBridgeClash` - val suffix = nme.DELAMBDAFY_LAMBDA_CLASS_NAME + "$" + ( - if (funOwner.isPrimaryConstructor) "" - else "$" + funOwner.name + "$" - ) - val oldClassPart = oldClass.name.decode - // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true - 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) - assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name) - assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name) - - val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol] - captures foreach {capture => - val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC) - sym setInfo capture.info - captureProxies2 += ((capture, sym)) - } + private def transformFunction(originalFunction: Function): Tree = { + val target = targetMethod(originalFunction) + target.makeNotPrivate(target.owner) - // the Optional proxy that will hold a reference to the 'this' - // object used by the lambda, if any. NoSymbol if there is no this proxy - val thisProxy = { - if (isStatic) - NoSymbol - else { - val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) - sym.setInfo(oldClass.tpe) - } - } + // must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall + if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC - val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy) + val funSym = originalFunction.tpe.typeSymbolDirect + // The functional interface that can be used to adapt the lambda target method `target` to the given function type. + val (functionalInterface, isSpecialized) = + if (!isFunctionSymbol(funSym)) (funSym, false) + else { + val specializedName = + specializeTypes.specializedFunctionName(funSym, + exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName - val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] + val isSpecialized = specializedName != funSym.name + val functionalInterface = // TODO: this is no longer needed, right? we can just use the regular function classes + if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J")) + else FunctionClass(originalFunction.vparams.length) - val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => - lambdaClass.info.decls enter member - ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos + (functionalInterface, isSpecialized) } - // constructor - val constr = createConstructor(lambdaClass, members) - - // apply method with same arguments and return type as original lambda. - val applyMethodDef = createApplyMethod(lambdaClass, decapturedFunction, thisProxy) - - val bridgeMethod = createBridgeMethod(lambdaClass, originalFunction, applyMethodDef) - - def fulldef(sym: Symbol) = - if (sym == NoSymbol) sym.toString - else s"$sym: ${sym.tpe} in ${sym.owner}" - - bridgeMethod foreach (bm => - // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases - // rather than the method+bridge pair. - if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe) - erasure.resolveAnonymousBridgeClash(applyMethodDef.symbol, bm.symbol) - ) - - val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod - - // TODO if member fields are private this complains that they're not accessible - localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef] - } - - val allCaptureArgs: List[Tree] = { - val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil - val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList - thisArg ::: captureArgs - } - - val arity = originalFunction.vparams.length - - // Reconstruct the type of the function entering erasure. - // We do this by taking the type after erasure, and re-boxing `ErasedValueType`. - // - // Unfortunately, the more obvious `enteringErasure(target.info)` doesn't work - // as we would like, value classes in parameter position show up as the unboxed types. - val (functionParamTypes, functionResultType) = exitingErasure { - def boxed(tp: Type) = tp match { - case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) - case _ => tp - } - // We don't need to deeply map `boxedValueClassType` over the infos as `ErasedValueType` - // will only appear directly as a parameter type in a method signature, as shown - // https://gist.github.com/retronym/ba81dbd462282c504ff8 - val info = target.info - val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed) - (boxedParamTypes, boxed(info.resultType)) - } - val functionType = definitions.functionType(functionParamTypes, functionResultType) - - val (functionalInterface, isSpecialized) = java8CompatFunctionalInterface(target, functionType) - if (functionalInterface.exists) { - // Create a symbol representing a fictional lambda factory method that accepts the captured - // arguments and returns a Function. - val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) - val argTypes: List[Type] = allCaptureArgs.map(_.tpe) - val params = msym.newSyntheticValueParams(argTypes) - msym.setInfo(MethodType(params, functionType)) - val arity = originalFunction.vparams.length - - val lambdaTarget = - if (isSpecialized) - target - else { - createBoxingBridgeMethod(functionParamTypes, functionResultType) match { - case EmptyTree => - target - case bridge => - boxingBridgeMethods += bridge - bridge.symbol - } - } - - // We then apply this symbol to the captures. - val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] - - // The backend needs to know the target of the lambda and the functional interface in order - // to emit the invokedynamic instruction. We pass this information as tree attachment. - apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface)) - InvokeDynamicLambda(apply) - } else { - val anonymousClassDef = makeAnonymousClass - pkg.info.decls enter anonymousClassDef.symbol - val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType)) - val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) - DelambdafyAnonClass(anonymousClassDef, typedNewStat) - } + val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol) + mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized) } - /** - * Creates a bridge method if needed. The bridge method forwards from apply(x1: Object, x2: Object...xn: Object): Object to - * apply(x1: T1, x2: T2...xn: Tn): T0 using type adaptation on each input and output. The only time a bridge isn't needed - * is when the original lambda is already erased to type Object, Object, Object... => Object - */ - def createBridgeMethod(newClass:Symbol, originalFunction: Function, applyMethod: DefDef): Option[DefDef] = { - val bridgeMethSym = newClass.newMethod(nme.apply, applyMethod.pos, FINAL | SYNTHETIC | BRIDGE) - val originalParams = applyMethod.vparamss(0) - val bridgeParams = originalParams map { originalParam => - val bridgeSym = bridgeMethSym.newSyntheticValueParam(ObjectTpe, originalParam.name) - ValDef(bridgeSym) - } - - val bridgeSyms = bridgeParams map (_.symbol) - - val methodType = MethodType(bridgeSyms, ObjectTpe) - bridgeMethSym setInfo methodType - - def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = { - if (tree.tpe =:= expectedTpe) (false, tree) - else (true, adaptToType(tree, expectedTpe)) - } - - def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = { - val (needsAdapt, adaptedTree) = adapt(tree, pt) - val trans = postErasure.newTransformer(unit) - val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes - (needsAdapt, postErasedTree) - } - - enteringPhase(currentRun.posterasurePhase) { - // e.g, in: - // class C(val a: Int) extends AnyVal; (x: Int) => new C(x) - // - // This type is: - // (x: Int)ErasedValueType(class C, Int) - val liftedBodyDefTpe: MethodType = { - val liftedBodySymbol = { - val Apply(method, _) = originalFunction.body - method.symbol - } - liftedBodySymbol.info.asInstanceOf[MethodType] - } - val (paramNeedsAdaptation, adaptedParams) = (bridgeSyms zip liftedBodyDefTpe.params map {case (bridgeSym, param) => adapt(Ident(bridgeSym) setType bridgeSym.tpe, param.tpe)}).unzip - // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`. - // But that symbol doesn't have a type history that goes back before `delambdafy`, - // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`. - // This triggered primitive boxing, rather than value class boxing. - val resTp = liftedBodyDefTpe.finalResultType - val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp - val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe) - - val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation - if (needsBridge) { - val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody) - newClass.info.decls enter bridgeMethSym - Some((localTyper typed methDef).asInstanceOf[DefDef]) - } else None - } + // here's the main entry point of the transform + override def transform(tree: Tree): Tree = tree match { + // the main thing we care about is lambdas + case fun: Function => super.transform(transformFunction(fun)) + case Template(_, _, _) => + try { + // during this call boxingBridgeMethods will be populated from the Function case + val Template(parents, self, body) = super.transform(tree) + Template(parents, self, body ++ boxingBridgeMethods) + } finally boxingBridgeMethods.clear() + case _ => super.transform(tree) } } // DelambdafyTransformer + // A traverser that finds symbols used but not defined in the given Tree // TODO freeVarTraverser in LambdaLift does a very similar task. With some // analysis this could probably be unified with it @@ -513,40 +296,36 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } } - // A transformer that converts specified captured symbols into other symbols - // TODO this transform could look more like ThisSubstituter and TreeSymSubstituter. It's not clear that it needs that level of sophistication since the types - // at this point are always very simple flattened/erased types, but it would probably be more robust if it tried to take more complicated types into account - class DeCapturifyTransformer(captureProxies: Map[Symbol, TermSymbol], unit: CompilationUnit, oldClass: Symbol, newClass:Symbol, pos: Position, thisProxy: Symbol) extends TypingTransformer(unit) { - override def transform(tree: Tree) = tree match { - case tree@This(encl) if tree.symbol == oldClass && thisProxy.exists => - gen mkAttributedSelect (gen mkAttributedThis newClass, thisProxy) - case Ident(name) if (captureProxies contains tree.symbol) => - gen mkAttributedSelect (gen mkAttributedThis newClass, captureProxies(tree.symbol)) - case _ => super.transform(tree) + // finds all methods that reference 'this' + class ThisReferringMethodsTraverser extends Traverser { + // the set of methods that refer to this + private val thisReferringMethods = mutable.Set[Symbol]() + + // the set of lifted lambda body methods that each method refers to + private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + + def methodReferencesThisIn(tree: Tree) = { + traverse(tree) + liftedMethodReferences.keys foreach refersToThis + + thisReferringMethods } - } - /** - * Get the symbol of the target lifted lambda body method from a function. I.e. if - * the function is {args => anonfun(args)} then this method returns anonfun's symbol - */ - private def targetMethod(fun: Function): Symbol = fun match { - case Function(_, Apply(target, _)) => - target.symbol - case _ => - // any other shape of Function is unexpected at this point - abort(s"could not understand function with tree $fun") - } + // recursively find methods that refer to 'this' directly or indirectly via references to other methods + // for each method found add it to the referrers set + private def refersToThis(symbol: Symbol): Boolean = + (thisReferringMethods contains symbol) || + (liftedMethodReferences(symbol) exists refersToThis) && { + // add it early to memoize + debuglog(s"$symbol indirectly refers to 'this'") + thisReferringMethods += symbol + true + } - // finds all methods that reference 'this' - class ThisReferringMethodsTraverser() extends Traverser { private var currentMethod: Symbol = NoSymbol - // the set of methods that refer to this - val thisReferringMethods = mutable.Set[Symbol]() - // the set of lifted lambda body methods that each method refers to - val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + override def traverse(tree: Tree) = tree match { - case DefDef(_, _, _, _, _, _) => + case DefDef(_, _, _, _, _, _) if tree.symbol.isDelambdafyTarget => // we don't expect defs within defs. At this phase trees should be very flat if (currentMethod.exists) devWarning("Found a def within a def at a phase where defs are expected to be flattened out.") currentMethod = tree.symbol @@ -562,32 +341,10 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre debuglog(s"$currentMethod directly refers to 'this'") thisReferringMethods add currentMethod } + case _: ClassDef if !tree.symbol.isTopLevel => + case _: DefDef => case _ => super.traverse(tree) } } - - final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) - - // The functional interface that can be used to adapt the lambda target method `target` to the - // given function type. Returns `NoSymbol` if the compiler settings are unsuitable. - private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = { - val canUseLambdaMetafactory: Boolean = { - val isTarget18 = settings.target.value.contains("jvm-1.8") - settings.isBCodeActive && isTarget18 - } - - val sym = functionType.typeSymbol - val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage - val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs) - val paramTps :+ restpe = functionType.typeArgs - val arity = paramTps.length - val isSpecialized = name1.toTypeName != sym.name - val functionalInterface = if (!isSpecialized) { - currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity) - } else { - pack.info.decl(name1.toTypeName.prepend("J")) - } - (if (canUseLambdaMetafactory) functionalInterface else NoSymbol, isSpecialized) - } } diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index a04625c9c5..0301e06c87 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -71,7 +71,9 @@ abstract class Erasure extends AddInterfaces } override protected def verifyJavaErasure = settings.Xverify || settings.debug - def needsJavaSig(tp: Type) = !settings.Ynogenericsig && NeedsSigCollector.collect(tp) + def needsJavaSig(tp: Type, throwsArgs: List[Type]) = !settings.Ynogenericsig && { + NeedsSigCollector.collect(tp) || throwsArgs.exists(NeedsSigCollector.collect) + } // only refer to type params that will actually make it into the sig, this excludes: // * higher-order type parameters @@ -251,7 +253,7 @@ abstract class Erasure extends AddInterfaces // Anything which could conceivably be a module (i.e. isn't known to be // a type parameter or similar) must go through here or the signature is // likely to end up with Foo<T>.Empty where it needs Foo<T>.Empty$. - def fullNameInSig(sym: Symbol) = "L" + enteringIcode(sym.javaBinaryName) + def fullNameInSig(sym: Symbol) = "L" + enteringJVM(sym.javaBinaryName) def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, toplevel: Boolean = false, primitiveOK: Boolean = true): String = { val tp = tp0.dealias @@ -277,7 +279,7 @@ abstract class Erasure extends AddInterfaces val preRebound = pre.baseType(sym.owner) // #2585 dotCleanup( ( - if (needsJavaSig(preRebound)) { + if (needsJavaSig(preRebound, Nil)) { val s = jsig(preRebound, existentiallyBound) if (s.charAt(0) == 'L') s.substring(0, s.length - 1) + "." + sym.javaSimpleName else fullNameInSig(sym) @@ -341,8 +343,8 @@ abstract class Erasure extends AddInterfaces buf append (if (restpe.typeSymbol == UnitClass || sym0.isConstructor) VOID_TAG.toString else jsig(restpe)) buf.toString - case RefinedType(parent :: _, decls) => - boxedSig(parent) + case RefinedType(parents, decls) => + boxedSig(intersectionDominator(parents)) case ClassInfoType(parents, _, _) => superSig(parents) case AnnotatedType(_, atp) => @@ -356,8 +358,9 @@ abstract class Erasure extends AddInterfaces else jsig(etp) } } - if (needsJavaSig(info)) { - try Some(jsig(info, toplevel = true)) + val throwsArgs = sym0.annotations flatMap ThrownException.unapply + if (needsJavaSig(info, throwsArgs)) { + try Some(jsig(info, toplevel = true) + throwsArgs.map("^" + jsig(_, toplevel = true)).mkString("")) catch { case ex: UnknownSig => None } } else None @@ -575,8 +578,9 @@ abstract class Erasure extends AddInterfaces } /** The modifier typer which retypes with erased types. */ - class Eraser(_context: Context) extends Typer(_context) with TypeAdapter { - val typer = this.asInstanceOf[analyzer.Typer] + class Eraser(_context: Context) extends Typer(_context) { + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = Eraser.this.typedPos(pos)(tree) } + import typeAdapter._ override protected def stabilize(tree: Tree, pre: Type, mode: Mode, pt: Type): Tree = tree @@ -644,7 +648,7 @@ abstract class Erasure extends AddInterfaces var qual1 = typedQualifier(qual) if ((isPrimitiveValueType(qual1.tpe) && !isPrimitiveValueMember(tree.symbol)) || isErasedValueType(qual1.tpe)) - qual1 = box(qual1, "owner "+tree.symbol.owner) + qual1 = box(qual1) else if (!isPrimitiveValueType(qual1.tpe) && isPrimitiveValueMember(tree.symbol)) qual1 = unbox(qual1, tree.symbol.owner.tpe) @@ -653,13 +657,12 @@ abstract class Erasure extends AddInterfaces if (isPrimitiveValueMember(tree.symbol) && !isPrimitiveValueType(qual1.tpe)) { tree.symbol = NoSymbol selectFrom(qual1) - } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { + } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { // see also adaptToType in TypeAdapter assert(qual1.symbol.isStable, qual1.symbol) - val applied = Apply(qual1, List()) setPos qual1.pos setType qual1.tpe.resultType - adaptMember(selectFrom(applied)) + adaptMember(selectFrom(applyMethodWithEmptyParams(qual1))) } else if (!(qual1.isInstanceOf[Super] || (qual1.tpe.typeSymbol isSubClass tree.symbol.owner))) { assert(tree.symbol.owner != ArrayClass) - selectFrom(cast(qual1, tree.symbol.owner.tpe)) + selectFrom(cast(qual1, tree.symbol.owner.tpe.resultType)) } else { selectFrom(qual1) } @@ -718,6 +721,12 @@ abstract class Erasure extends AddInterfaces if (branch == EmptyTree) branch else adaptToType(branch, tree1.tpe) tree1 match { + case fun: Function => + fun.attachments.get[SAMFunction] match { + case Some(SAMFunction(samTp, _)) => fun setType specialScalaErasure(samTp) + case _ => fun + } + case If(cond, thenp, elsep) => treeCopy.If(tree1, cond, adaptBranch(thenp), adaptBranch(elsep)) case Match(selector, cases) => @@ -1004,24 +1013,20 @@ abstract class Erasure extends AddInterfaces // erasure the ScalaRunTime.hash overload goes from Unit => Int to BoxedUnit => Int. // This must be because some earlier transformation is being skipped on ##, but so // far I don't know what. For null we now define null.## == 0. + def staticsCall(methodName: TermName): Tree = { + val newTree = gen.mkMethodCall(RuntimeStaticsModule, methodName, qual :: Nil) + global.typer.typed(newTree) + } + qual.tpe.typeSymbol match { case UnitClass | NullClass => LIT(0) case IntClass => qual case s @ (ShortClass | ByteClass | CharClass) => numericConversion(qual, s) case BooleanClass => If(qual, LIT(true.##), LIT(false.##)) - case _ => - // Since we are past typer, we need to avoid creating trees carrying - // overloaded types. This logic is custom (and technically incomplete, - // although serviceable) for def hash. What is really needed is for - // the overloading logic presently hidden away in a few different - // places to be properly exposed so we can just call "resolveOverload" - // after typer. Until then: - val alts = ScalaRunTimeModule.info.member(nme.hash_).alternatives - def alt1 = alts find (_.info.paramTypes.head =:= qual.tpe) - def alt2 = ScalaRunTimeModule.info.member(nme.hash_) suchThat (_.info.paramTypes.head.typeSymbol == AnyClass) - val newTree = gen.mkRuntimeCall(nme.hash_, qual :: Nil) setSymbol (alt1 getOrElse alt2) - - global.typer.typed(newTree) + case LongClass => staticsCall(nme.longHash) + case FloatClass => staticsCall(nme.floatHash) + case DoubleClass => staticsCall(nme.doubleHash) + case _ => staticsCall(nme.anyHash) } } else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) { // Rewrite 5.getClass to ScalaRunTime.anyValClass(5) @@ -1100,7 +1105,6 @@ abstract class Erasure extends AddInterfaces } } else tree case Template(parents, self, body) => - assert(!currentOwner.isImplClass) //Console.println("checking no dble defs " + tree)//DEBUG checkNoDoubleDefs(tree.symbol.owner) treeCopy.Template(tree, parents, noSelfType, addBridges(body, currentOwner)) @@ -1110,7 +1114,7 @@ abstract class Erasure extends AddInterfaces case Literal(ct) if ct.tag == ClazzTag && ct.typeValue.typeSymbol != definitions.UnitClass => - val erased = ct.typeValue match { + val erased = ct.typeValue.dealiasWiden match { case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr) case tpe => specialScalaErasure(tpe) } @@ -1147,6 +1151,8 @@ abstract class Erasure extends AddInterfaces case DefDef(_, _, _, _, tpt, _) => try super.transform(tree1).clearType() finally tpt setType specialErasure(tree1.symbol)(tree1.symbol.tpe).resultType + case ApplyDynamic(qual, Literal(Constant(boostrapMethodRef: Symbol)) :: _) => + tree case _ => super.transform(tree1).clearType() } @@ -1177,5 +1183,41 @@ abstract class Erasure extends AddInterfaces bridge.resetFlag(BRIDGE) } + /** Does this symbol compile to the underlying platform's notion of an interface, + * without requiring compiler magic before it can be instantiated? + * + * More specifically, we're interested in whether LambdaMetaFactory can instantiate this type, + * assuming it has a single abstract method. In other words, if we were to mix this + * trait into a class, it should not result in any compiler-generated members having to be + * implemented in ("mixed in to") this class (except for the SAM). + * + * Thus, the type must erase to a java interface, either by virtue of being defined as one, + * or by being a trait that: + * - is static (explicitouter or lambdalift may add disqualifying members) + * - extends only other traits that compile to pure interfaces (except for Any) + * - has no val/var members + * + * TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction? + */ + final def compilesToPureInterface(tpSym: Symbol): Boolean = { + def ok(sym: Symbol) = + sym.isJavaInterface || + sym.isTrait && + // Unless sym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift. + // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry + // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures). + // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether + // to expand sam at compile time or use LMF, and this implementation restriction could be lifted. + sym.isStatic && + // HACK: this is to rule out traits with an effectful initializer. + // The constructor only exists if the trait's template has statements. + // Sadly, we can't be more precise without access to the tree that defines the SAM's owner. + !sym.primaryConstructor.exists && + (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true}) + + // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account + ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym)) + } + private class TypeRefAttachment(val tpe: TypeRef) } diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 540de2cfe1..3d6fad4238 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) { @@ -207,7 +210,7 @@ abstract class ExplicitOuter extends InfoTransform // class needs to have a common naming scheme, independently of whether // the field was accessed from an inner class or not. See #2946 if (sym.owner.isTrait && sym.isLocalToThis && - (sym.getterIn(sym.owner.toInterface) == NoSymbol)) + (sym.getterIn(sym.owner) == NoSymbol)) sym.makeNotPrivate(sym.owner) tp } @@ -238,12 +241,17 @@ abstract class ExplicitOuter extends InfoTransform * Will return `EmptyTree` if there is no outer accessor because of a premature self reference. */ private def outerSelect(base: Tree): Tree = { - val baseSym = base.tpe.typeSymbol.toInterface + val baseSym = base.tpe.typeSymbol val outerAcc = outerAccessor(baseSym) - if (outerAcc == NoSymbol && baseSym.ownersIterator.exists(isUnderConstruction)) { - // e.g neg/t6666.scala - // The caller will report the error with more information. - EmptyTree + if (outerAcc == NoSymbol) { + if (baseSym.ownersIterator.exists(isUnderConstruction)) { + // e.g neg/t6666.scala + // The caller will report the error with more information. + EmptyTree + } else { + globalError(currentOwner.pos, s"Internal error: unable to find the outer accessor symbol of $baseSym") + EmptyTree + } } else { val currentClass = this.currentClass //todo: !!! if this line is removed, we get a build failure that protected$currentClass need an override modifier // outerFld is the $outer field of the current class, if the reference can @@ -251,6 +259,7 @@ abstract class ExplicitOuter extends InfoTransform // otherwise it is NoSymbol val outerFld = if (outerAcc.owner == currentClass && + !outerAcc.owner.isTrait && base.tpe =:= currentClass.thisType && outerAcc.owner.isEffectivelyFinal) outerField(currentClass) suchThat (_.owner == currentClass) @@ -271,8 +280,7 @@ abstract class ExplicitOuter extends InfoTransform */ protected def outerPath(base: Tree, from: Symbol, to: Symbol): Tree = { //Console.println("outerPath from "+from+" to "+to+" at "+base+":"+base.tpe) - //assert(base.tpe.widen.baseType(from.toInterface) != NoType, ""+base.tpe.widen+" "+from.toInterface)//DEBUG - if (from == to || from.isImplClass && from.toInterface == to) base + if (from == to) base else outerPath(outerSelect(base), from.outerClass, to) } @@ -397,7 +405,7 @@ abstract class ExplicitOuter extends InfoTransform case Template(parents, self, decls) => val newDefs = new ListBuffer[Tree] atOwner(tree, currentOwner) { - if (!currentClass.isInterface || (currentClass hasFlag lateINTERFACE)) { + if (!currentClass.isInterface) { if (isInner(currentClass)) { if (hasOuterField(currentClass)) newDefs += outerFieldDef // (1a) @@ -446,8 +454,10 @@ abstract class ExplicitOuter extends InfoTransform // // See SI-6552 for an example of why `sym.owner.enclMethod hasAnnotation ScalaInlineClass` // is not suitable; if we make a method-local class non-private, it mangles outer pointer names. - if (currentClass != sym.owner || - (closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass)) + def enclMethodIsInline = closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass + // SI-8710 The extension method condition reflects our knowledge that a call to `new Meter(12).privateMethod` + // with later be rewritten (in erasure) to `Meter.privateMethod$extension(12)`. + if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension) sym.makeNotPrivate(sym.owner) val qsym = qual.tpe.widen.typeSymbol @@ -474,14 +484,15 @@ abstract class ExplicitOuter extends InfoTransform // base.<outer>.eq(o) --> base.$outer().eq(o) if there's an accessor, else the whole tree becomes TRUE // TODO remove the synthetic `<outer>` method from outerFor?? case Apply(eqsel@Select(eqapp@Apply(sel@Select(base, nme.OUTER_SYNTH), Nil), eq), args) => - val outerFor = sel.symbol.owner.toInterface // TODO: toInterface necessary? + val outerFor = sel.symbol.owner val acc = outerAccessor(outerFor) if (acc == NoSymbol || // since we can't fix SI-4440 properly (we must drop the outer accessors of final classes when there's no immediate reference to them in sight) // at least don't crash... this duplicates maybeOmittable from constructors (acc.owner.isEffectivelyFinal && !acc.isOverridingSymbol)) { - currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.") + if (!base.tpe.hasAnnotation(UncheckedClass)) + currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.") transform(TRUE) // urgh... drop condition if there's no accessor (or if it may disappear after constructors) } else { // println("(base, acc)= "+(base, acc)) diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index 116047a2ad..9d02228ab5 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 @@ -208,7 +208,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { def makeExtensionMethodSymbol = { val extensionName = extensionNames(origMeth).head.toTermName val extensionMeth = ( - companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~LOCAL | FINAL) + companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~PRIVATE & ~LOCAL | FINAL) setAnnotations origMeth.annotations ) origMeth.removeAnnotation(TailrecClass) // it's on the extension method, now. @@ -244,7 +244,10 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) // which leaves the actual argument application for extensionCall. - val sel = Select(gen.mkAttributedRef(companion), extensionMeth) + // SI-9542 We form the selection here from the thisType of the companion's owner. This is motivated + // by the test case, and is a valid way to construct the reference because we know that this + // method is also enclosed by that owner. + val sel = Select(gen.mkAttributedRef(companion.owner.thisType, companion), extensionMeth) val targs = origTpeParams map (_.tpeHK) val callPrefix = gen.mkMethodCall(sel, targs, This(origThis) :: Nil) diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index fbb0307773..0db9f19597 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -41,8 +41,6 @@ abstract class Flatten extends InfoTransform { } private def liftSymbol(sym: Symbol) { liftClass(sym) - if (sym.needsImplClass) - liftClass(erasure implClass sym) } // This is a short-term measure partially working around objects being // lifted out of parameterized classes, leaving them referencing 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 d1be1558b9..074acc1332 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 { @@ -31,11 +31,6 @@ abstract class LambdaLift extends InfoTransform { } } - /** scala.runtime.*Ref classes */ - private lazy val allRefClasses: Set[Symbol] = { - refClass.values.toSet ++ volatileRefClass.values.toSet ++ Set(VolatileObjectRefClass, ObjectRefClass) - } - /** Each scala.runtime.*Ref class has a static method `create(value)` that simply instantiates the Ref to carry that value. */ private lazy val refCreateMethod: Map[Symbol, Symbol] = { mapFrom(allRefClasses.toList)(x => getMemberMethod(x.companionModule, nme.create)) @@ -103,11 +98,6 @@ abstract class LambdaLift extends InfoTransform { */ private val proxyNames = mutable.HashMap[Symbol, Name]() - // (trait, name) -> owner - private val localTraits = mutable.HashMap[(Symbol, Name), Symbol]() - // (owner, name) -> implClass - private val localImplClasses = mutable.HashMap[(Symbol, Name), Symbol]() - /** A flag to indicate whether new free variables have been found */ private var changedFreeVars: Boolean = _ @@ -148,7 +138,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 @@ -158,7 +148,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 @@ -167,7 +157,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 } @@ -175,30 +165,13 @@ 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(_, _, _, _) => liftedDefs(tree.symbol) = Nil if (sym.isLocalToBlock) { - // Don't rename implementation classes independently of their interfaces. If - // the interface is to be renamed, then we will rename the implementation - // class at that time. You'd think we could call ".implClass" on the trait - // rather than collecting them in another map, but that seems to fail for - // exactly the traits being renamed here (i.e. defined in methods.) - // - // !!! - it makes no sense to have methods like "implClass" and - // "companionClass" which fail for an arbitrary subset of nesting - // arrangements, and then have separate methods which attempt to compensate - // for that failure. There should be exactly one method for any given - // entity which always gives the right answer. - if (sym.isImplClass) - localImplClasses((sym.owner, tpnme.interfaceName(sym.name))) = sym - else { - renamable += sym - if (sym.isTrait) - localTraits((sym, sym.name)) = sym.owner - } + renamable += sym } case DefDef(_, _, _, _, _, _) => if (sym.isLocalToBlock) { @@ -222,11 +195,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 +// } } } @@ -240,7 +213,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) @@ -250,11 +223,6 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) } - // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by - // introducing `$anon` in its name. to be cautious, we don't make this change in the default - // backend under 2.11.x, so only in GenBCode. - def nonAnon(s: String) = if (settings.Ybackend.value == "GenBCode") nme.ensureNonAnon(s) else s - def newName(sym: Symbol): Name = { val originalName = sym.name def freshen(prefix: String): Name = @@ -263,56 +231,37 @@ abstract class LambdaLift extends InfoTransform { val join = nme.NAME_JOIN_STRING if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join) + freshen(sym.name + join + nme.ensureNonAnon(sym.owner.name.toString) + join) } else { val name = freshen(sym.name + join) // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) // Generating a unique name, mangled with the enclosing full class name (including // package - subclass might have the same name), avoids a VerifyError in the case // that a sub-class happens to lifts out a method with the *same* name. - if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) - newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) + if (originalName.isTermName && calledFromInner(sym)) + newTermNameCached(nme.ensureNonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) else name } } - /* Rename a trait's interface and implementation class in coordinated fashion. */ - def renameTrait(traitSym: Symbol, implSym: Symbol) { - val originalImplName = implSym.name - renameSym(traitSym) - implSym setName tpnme.implClassName(traitSym.name) - - debuglog("renaming impl class in step with %s: %s => %s".format(traitSym, originalImplName, implSym.name)) - } - val allFree: Set[Symbol] = free.values.flatMap(_.iterator).toSet for (sym <- renamable) { - // If we renamed a trait from Foo to Foo$1, we must rename the implementation - // class from Foo$class to Foo$1$class. (Without special consideration it would - // become Foo$class$1 instead.) Since the symbols are being renamed out from - // under us, and there's no reliable link between trait symbol and impl symbol, - // we have maps from ((trait, name)) -> owner and ((owner, name)) -> impl. - localTraits remove ((sym, sym.name)) match { - case None => - if (allFree(sym)) proxyNames(sym) = newName(sym) - else renameSym(sym) - case Some(owner) => - localImplClasses remove ((owner, sym.name)) match { - case Some(implSym) => renameTrait(sym, implSym) - case _ => renameSym(sym) // pure interface, no impl class - } - } + if (allFree(sym)) proxyNames(sym) = newName(sym) + else renameSym(sym) } 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 @@ -342,31 +291,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) = { @@ -374,41 +323,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: @@ -451,11 +404,10 @@ 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 - if (sym.isClass) sym.owner = sym.owner.toInterface if (sym.isMethod) sym setFlag LIFTED liftedDefs(sym.owner) ::= tree // TODO: this modifies the ClassInfotype of the enclosing class, which is associated with another phase (explicitouter). @@ -468,12 +420,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..bc9f70679c 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 => @@ -192,21 +215,23 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): Tree = { - // Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then - // changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod? - val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) + val owner = lzyVal.owner + val defSym = owner.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) defSym setInfo MethodType(List(), lzyVal.tpe.resultType) - defSym.owner = lzyVal.owner + if (owner.isClass) owner.info.decls.enter(defSym) debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") - if (bitmaps.contains(lzyVal)) - bitmaps(lzyVal).map(_.owner = defSym) + // this is a hack i don't understand for lazy vals nested in a lazy val, introduced in 3769f4d, + // tested in pos/t3670 (add9be64). class A { val n = { lazy val b = { lazy val dd = 3; dd }; b } } + // bitmaps has an entry bMethodSym -> List(bitmap$0), where bitmap$0.owner == bMethodSym. + // now we set bitmap$0.owner = b$lzycomputeMethodSym. + for (bitmap <- bitmaps(lzyVal)) bitmap.owner = defSym val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym) DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) } - 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 +286,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 a079a76ce7..03935c3d67 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._ @@ -32,8 +32,9 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // --------- helper functions ----------------------------------------------- /** A member of a trait is implemented statically if its implementation after the - * mixin transform is in the static implementation module. To be statically - * implemented, a member must be a method that belonged to the trait's implementation class + * mixin transform is RHS of the method body (destined to be in a interface default method) + * + * To be statically implemented, a member must be a method that belonged to the trait's implementation class * before (i.e. it is not abstract). Not statically implemented are * - non-private modules: these are implemented directly in the mixin composition class * (private modules, on the other hand, are implemented statically, but their @@ -43,33 +44,17 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * methods in the impl class (because they can have arbitrary initializers) */ private def isImplementedStatically(sym: Symbol) = ( - sym.owner.isImplClass - && sym.isMethod + sym.isMethod + && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED)) + && sym.owner.isTrait && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) + && !sym.isPrivate + && !sym.hasAllFlags(LIFTED | MODULE | METHOD) + && !sym.isConstructor + && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE)) ) - /** A member of a trait is static only if it belongs only to the - * implementation class, not the interface, and it is implemented - * statically. - */ - private def isStaticOnly(sym: Symbol) = - isImplementedStatically(sym) && sym.isImplOnly - - /** A member of a trait is forwarded if it is implemented statically and it - * is also visible in the trait's interface. In that case, a forwarder to - * the member's static implementation will be added to the class that - * inherits the trait. - */ - private def isForwarded(sym: Symbol) = - isImplementedStatically(sym) && !sym.isImplOnly - - /** Maps the type of an implementation class to its interface; - * maps all other types to themselves. - */ - private def toInterface(tp: Type): Type = - enteringMixin(tp.typeSymbol.toInterface).tpe - private def isFieldWithBitmap(field: Symbol) = { field.info // ensure that nested objects are transformed // For checkinit consider normal value getters @@ -97,22 +82,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { && !(sym.accessed hasAnnotation TransientAttr) ) - /** Maps all parts of this type that refer to implementation classes to - * their corresponding interfaces. - */ - private val toInterfaceMap = new TypeMap { - def apply(tp: Type): Type = mapOver( tp match { - case TypeRef(pre, sym, args) if sym.isImplClass => - typeRef(pre, enteringMixin(sym.toInterface), args) - case _ => tp - }) - } - - /** The implementation class corresponding to a currently compiled interface. - * todo: try to use Symbol.implClass instead? - */ - private def implClass(iface: Symbol) = iface.implClass orElse (erasure implClass iface) - /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * * @param base The class in which everything is mixed together @@ -159,8 +128,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { /** Add given member to given class, and mark member as mixed-in. */ def addMember(clazz: Symbol, member: Symbol): Symbol = { - debuglog("new member of " + clazz + ":" + member.defString) - clazz.info.decls enter member setFlag MIXEDIN + debuglog(s"mixing into $clazz: ${member.defString}") + clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD } def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz)) @@ -227,12 +196,11 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } clazz.info // make sure info is up to date, so that implClass is set. - val impl = implClass(clazz) orElse abort("No impl class for " + clazz) - for (member <- impl.info.decls) { + for (member <- clazz.info.decls) { if (!member.isMethod && !member.isModule && !member.isModuleVar) { assert(member.isTerm && !member.isDeferred, member) - if (member.getterIn(impl).isPrivate) { + if (member.getterIn(clazz).isPrivate) { member.makeNotPrivate(clazz) // this will also make getter&setter not private } val getter = member.getterIn(clazz) @@ -241,6 +209,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val setter = member.setterIn(clazz) if (setter == NoSymbol) addMember(clazz, newSetter(member)) } + clazz.info.decls.unlink(member) } } debuglog("new defs of " + clazz + " = " + clazz.info.decls) @@ -266,16 +235,45 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { ) /* Mix in members of implementation class mixinClass into class clazz */ - def mixinImplClassMembers(mixinClass: Symbol, mixinInterface: Symbol) { - if (!mixinClass.isImplClass) devWarning ("Impl class flag is not set " + - ((mixinClass.debugLocationString, mixinInterface.debugLocationString))) - - for (member <- mixinClass.info.decls ; if isForwarded(member)) { - val imember = member overriddenSymbol mixinInterface - imember overridingSymbol clazz match { + def mixinTraitForwarders(mixinClass: Symbol) { + for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) { + member overridingSymbol clazz match { case NoSymbol => - if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember) - cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member + val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) + if (isMemberOfClazz) { + val competingMethods = clazz.baseClasses.iterator + .filter(_ ne mixinClass) + .map(member.overriddenSymbol) + .filter(_.exists) + .toList + + // `member` is a concrete `method` defined in `mixinClass`, which is a base class of + // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: + // + // - A non-trait base class defines matching method. Example: + // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T + // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would + // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. + // + // - There exists another concrete, matching method in any of the base classes, and + // the `mixinClass` does not itself extend that base class. In this case the + // forwarder is needed to disambiguate. Example: + // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 + // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves + // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". + // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 + // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM + // level. + + lazy val mixinSuperInterfaces = mixinClass.ancestors.filter(_.isTraitOrInterface) + val needsForwarder = competingMethods.exists(m => { + !m.owner.isTraitOrInterface || + (!m.isDeferred && !mixinSuperInterfaces.contains(m.owner)) + }) + if (needsForwarder) + cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + } + case _ => } } @@ -296,7 +294,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) if (mixinMember.isLazy) { initializer(mixedInAccessor) = ( - implClass(mixinClass).info.decl(mixinMember.name) + mixinClass.info.decl(mixinMember.name) orElse abort("Could not find initializer for " + mixinMember.name) ) } @@ -358,67 +356,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // first complete the superclass with mixed in members addMixedinMembers(clazz.superClass, unit) - for (mc <- clazz.mixinClasses ; if mc hasFlag lateINTERFACE) { + for (mc <- clazz.mixinClasses ; if mc.isTrait) { // @SEAN: adding trait tracking so we don't have to recompile transitive closures unit.depends += mc addLateInterfaceMembers(mc) mixinTraitMembers(mc) - mixinImplClassMembers(implClass(mc), mc) + mixinTraitForwarders(mc) } } - /** The info transform for this phase does the following: - * - The parents of every class are mapped from implementation class to interface - * - Implementation classes become modules that inherit nothing - * and that define all. - */ - override def transformInfo(sym: Symbol, tp: Type): Type = tp match { - case ClassInfoType(parents, decls, clazz) => - var parents1 = parents - var decls1 = decls - if (!clazz.isPackageClass) { - exitingMixin(clazz.owner.info) - if (clazz.isImplClass) { - clazz setFlag lateMODULE - var sourceModule = clazz.owner.info.decls.lookup(sym.name.toTermName) - if (sourceModule == NoSymbol) { - sourceModule = ( - clazz.owner.newModuleSymbol(sym.name.toTermName, sym.pos, MODULE) - setModuleClass sym.asInstanceOf[ClassSymbol] - ) - clazz.owner.info.decls enter sourceModule - } - else { - sourceModule setPos sym.pos - if (sourceModule.flags != MODULE) { - log(s"!!! Directly setting sourceModule flags for $sourceModule from ${sourceModule.flagString} to MODULE") - sourceModule.flags = MODULE - } - } - sourceModule setInfo sym.tpe - // Companion module isn't visible for anonymous class at this point anyway - assert(clazz.sourceModule != NoSymbol || clazz.isAnonymousClass, s"$clazz has no sourceModule: $sym ${sym.tpe}") - parents1 = List() - decls1 = newScopeWith(decls.toList filter isImplementedStatically: _*) - } else if (!parents.isEmpty) { - parents1 = parents.head :: (parents.tail map toInterface) - } - } - //decls1 = enteringPhase(phase.next)(newScopeWith(decls1.toList: _*))//debug - if ((parents1 eq parents) && (decls1 eq decls)) tp - else ClassInfoType(parents1, decls1, clazz) - - case MethodType(params, restp) => - toInterfaceMap( - if (isImplementedStatically(sym)) { - val ownerParam = sym.newSyntheticValueParam(toInterface(sym.owner.typeOfThis)) - MethodType(ownerParam :: params, restp) - } else - tp) - - case _ => - tp - } + override def transformInfo(sym: Symbol, tp: Type): Type = tp /** Return a map of single-use fields to the lazy value that uses them during initialization. * Each field has to be private and defined in the enclosing class, and there must @@ -466,10 +413,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { new MixinTransformer(unit) class MixinTransformer(unit : CompilationUnit) extends Transformer { - /** Within a static implementation method: the parameter referring to the - * current object. Undefined everywhere else. - */ - private var self: Symbol = _ /** The rootContext used for typing */ private val rootContext = @@ -505,15 +448,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * (that is, every node is processed before its children). * What transform does: * - For every non-trait class, add all mixed in members to the class info. - * - For every trait, add all late interface members to the class info - * - For every static implementation method: - * - remove override flag - * - create a new method definition that also has a `self` parameter - * (which comes first) Iuli: this position is assumed by tail call elimination - * on a different receiver. Storing a new 'this' assumes it is located at - * index 0 in the local variable table. See 'STORE_THIS' and GenASM. - * - Map implementation class types in type-apply's to their interfaces - * - Remove all fields in implementation classes */ private def preTransform(tree: Tree): Tree = { val sym = tree.symbol @@ -524,87 +458,19 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (!currentOwner.isTrait && !isPrimitiveValueClass(currentOwner)) addMixedinMembers(currentOwner, unit) - else if (currentOwner hasFlag lateINTERFACE) + else if (currentOwner.isTrait) addLateInterfaceMembers(currentOwner) tree - case DefDef(_, _, _, vparams :: Nil, _, _) => - if (currentOwner.isImplClass) { - if (isImplementedStatically(sym)) { - sym setFlag notOVERRIDE - self = sym.newValueParameter(nme.SELF, sym.pos) setInfo toInterface(currentOwner.typeOfThis) - val selfdef = ValDef(self) setType NoType - copyDefDef(tree)(vparamss = List(selfdef :: vparams)) - } - else EmptyTree - } - else { - if (currentOwner.isTrait && sym.isSetter && !enteringPickler(sym.isDeferred)) { - sym.addAnnotation(TraitSetterAnnotationClass) - } - tree - } - // !!! What is this doing, and why is it only looking for exactly - // one type parameter? It would seem to be - // "Map implementation class types in type-apply's to their interfaces" - // from the comment on preTransform, but is there some way we should know - // that impl class types in type applies can only appear in single - // type parameter type constructors? - case Apply(tapp @ TypeApply(fn, List(arg)), List()) => - if (arg.tpe.typeSymbol.isImplClass) { - val ifacetpe = toInterface(arg.tpe) - arg setType ifacetpe - tapp setType MethodType(Nil, ifacetpe) - tree setType ifacetpe - } - tree - case ValDef(_, _, _, _) if currentOwner.isImplClass => - EmptyTree + case _ => + if (currentOwner.isTrait && sym.isSetter && !enteringPickler(sym.isDeferred)) { + sym.addAnnotation(TraitSetterAnnotationClass) + } tree } } - /** Create an identifier which references self parameter. - */ - private def selfRef(pos: Position) = - gen.mkAttributedIdent(self) setPos pos - - /** Replace a super reference by this or the self parameter, depending - * on whether we are in an implementation class or not. - * Leave all other trees unchanged. - */ - private def transformSuper(tree: Tree) = tree match { - case Super(qual, _) => - transformThis(qual) - case _ => - tree - } - - /** Replace a this reference to the current implementation class by the self - * parameter. Leave all other trees unchanged. - */ - private def transformThis(tree: Tree) = tree match { - case This(_) if tree.symbol.isImplClass => - assert(tree.symbol == currentOwner.enclClass) - selfRef(tree.pos) - case _ => - tree - } - - /** Create a static reference to given symbol `sym` of the - * form `M.sym` where M is the symbol's implementation module. - */ - private def staticRef(sym: Symbol): Tree = { - sym.owner.info //todo: needed? - sym.owner.owner.info //todo: needed? - - if (sym.owner.sourceModule eq NoSymbol) - abort(s"Cannot create static reference to $sym because ${sym.safeOwner} has no source module") - else - REF(sym.owner.sourceModule) DOT sym - } - def needsInitAndHasOffset(sym: Symbol) = needsInitFlag(sym) && (fieldOffset contains sym) @@ -643,6 +509,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * - A super accessor for every super accessor in a mixin class * - Forwarders for all methods that are implemented statically * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) + * * @param clazz The class to which definitions are added */ private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { @@ -700,7 +567,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { */ def completeSuperAccessor(stat: Tree) = stat match { case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor => - val body = atPos(stat.pos)(Apply(Select(Super(clazz, tpnme.EMPTY), stat.symbol.alias), vparams map (v => Ident(v.symbol)))) + val body = atPos(stat.pos)(Apply(SuperSelect(clazz, stat.symbol.alias), vparams map (v => Ident(v.symbol)))) val pt = stat.symbol.tpe.resultType copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt)))) @@ -778,18 +645,18 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, PRIVATE) val params = defSym newSyntheticValueParams args.map(_.symbol.tpe) defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType) - val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym) + val rhs: Tree = gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats).changeOwner(currentOwner -> defSym) val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params) addDef(position(defSym), DefDef(defSym, strictSubst(BLOCK(rhs, retVal)))) defSym } - 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 +728,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) @@ -897,7 +754,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def isUnit = sym.tpe.resultType.typeSymbol == UnitClass def isEmpty = stat.rhs == EmptyTree - if (sym.isLazy && !isEmpty && !clazz.isImplClass) { + if (!clazz.isTrait && sym.isLazy && !isEmpty) { assert(fieldOffset contains sym, sym) deriveDefDef(stat) { case t if isUnit => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) @@ -908,7 +765,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { case t => t // pass specialized lazy vals through } } - else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(IMPLCLASS | TRAIT)) { + else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(TRAIT)) { assert(fieldOffset contains sym, sym) deriveDefDef(stat)(rhs => (mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))( @@ -926,18 +783,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 { @@ -1014,7 +859,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // if it is a mixed-in lazy value, complete the accessor if (getter.isLazy) { val isUnit = isUnitGetter(getter) - val initCall = Apply(staticRef(initializer(getter)), gen.mkAttributedThis(clazz) :: Nil) + val initCall = Apply(SuperSelect(clazz, initializer(getter)), Nil) val selection = fieldAccess(getter) val init = if (isUnit) initCall else atPos(getter.pos)(Assign(selection, initCall)) val returns = if (isUnit) UNIT else selection @@ -1062,12 +907,11 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // for all symbols `sym` in the class definition, which are mixed in: for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { - // if current class is a trait interface, add an abstract method for accessor `sym` - if (clazz hasFlag lateINTERFACE) { + // if current class is a trait, add an abstract method for accessor `sym` + if (clazz.isTrait) { addDefDef(sym) - } - // if class is not a trait add accessor definitions - else if (!clazz.isTrait) { + } else { + // if class is not a trait add accessor definitions if (isConcreteAccessor(sym)) { // add accessor definitions addDefDef(sym, { @@ -1082,18 +926,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 @@ -1105,13 +938,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } else { // add forwarders - assert(sym.alias != NoSymbol, sym) + assert(sym.alias != NoSymbol, (sym, sym.debugFlagString, clazz)) // debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString) - if (!sym.isMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident))) + if (!sym.isMacro) addDefDef(sym, Apply(SuperSelect(clazz, sym.alias), sym.paramss.head.map(Ident(_)))) } } } stats1 = add(stats1, newDefs.toList) + if (clazz.isTrait) stats1 = + stats1.filter { + case vd: ValDef => + // TODO do we get here? + false + case _ => true + } if (!clazz.isTrait) stats1 = stats1 map completeSuperAccessor stats1 } @@ -1146,14 +986,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * - refer to fields in some implementation class via an abstract method in the interface. */ private def postTransform(tree: Tree): Tree = { - def siteWithinImplClass = currentOwner.enclClass.isImplClass val sym = tree.symbol - // change every node type that refers to an implementation class to its - // corresponding interface, unless the node's symbol is an implementation class. - if (tree.tpe.typeSymbol.isImplClass && ((sym eq null) || !sym.isImplClass)) - tree modifyType toInterface - tree match { case templ @ Template(parents, self, body) => // change parents of templates to conform to parents in the symbol info @@ -1163,87 +997,21 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // add all new definitions to current class or interface treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, body)) - // remove widening casts - case Apply(TypeApply(Select(qual, _), targ :: _), _) if isCastSymbol(sym) && (qual.tpe <:< targ.tpe) => - qual - - case Apply(Select(qual, _), args) => - /* Changes `qual.m(args)` where m refers to an implementation - * class method to Q.m(S, args) where Q is the implementation module of - * `m` and S is the self parameter for the call, which - * is determined as follows: - * - if qual != super, qual itself - * - if qual == super, and we are in an implementation class, - * the current self parameter. - * - if qual == super, and we are not in an implementation class, `this` - */ - def staticCall(target: Symbol) = { - def implSym = implClass(sym.owner).info.member(sym.name) - assert(target ne NoSymbol, - List(sym + ":", sym.tpe, sym.owner, implClass(sym.owner), implSym, - enteringPrevPhase(implSym.tpe), phase) mkString " " - ) - typedPos(tree.pos)(Apply(staticRef(target), transformSuper(qual) :: args)) - } + case Select(qual, name) if sym.owner.isTrait && !sym.isMethod => + // refer to fields in some trait an abstract getter in the interface. + val ifaceGetter = sym getterIn sym.owner - if (isStaticOnly(sym)) { - // change calls to methods which are defined only in implementation - // classes to static calls of methods in implementation modules - staticCall(sym) - } - else qual match { - case Super(_, mix) => - // change super calls to methods in implementation classes to static calls. - // Transform references super.m(args) as follows: - // - if `m` refers to a trait, insert a static call to the corresponding static - // implementation - // - otherwise return tree unchanged - assert( - !(mix == tpnme.EMPTY && siteWithinImplClass), - "illegal super in trait: " + currentOwner.enclClass + " " + tree - ) - - if (sym.owner hasFlag lateINTERFACE) { - if (sym.hasAccessorFlag) { - assert(args.isEmpty, args) - val sym1 = sym.overridingSymbol(currentOwner.enclClass) - typedPos(tree.pos)((transformSuper(qual) DOT sym1)()) - } - else { - staticCall(enteringPrevPhase(sym.overridingSymbol(implClass(sym.owner)))) - } - } - else { - assert(!siteWithinImplClass, currentOwner.enclClass) - tree - } - case _ => - tree - } - - case This(_) => - transformThis(tree) - - case Select(Super(_, _), name) => - tree - - case Select(qual, name) if sym.owner.isImplClass && !isStaticOnly(sym) => - assert(!sym.isMethod, "no method allowed here: %s%s %s".format(sym, sym.isImplOnly, sym.flagString)) - // refer to fields in some implementation class via an abstract - // getter in the interface. - val iface = toInterface(sym.owner.tpe).typeSymbol - val ifaceGetter = sym getterIn iface - - if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + iface) + if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + sym.owner) else typedPos(tree.pos)((qual DOT ifaceGetter)()) case Assign(Apply(lhs @ Select(qual, _), List()), rhs) => - // assign to fields in some implementation class via an abstract - // setter in the interface. - def setter = lhs.symbol.setterIn(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos + // assign to fields in some trait via an abstract setter in the interface. + // Note that the case above has added the empty application. + val setter = lhs.symbol.setterIn(lhs.symbol.owner.tpe.typeSymbol) setPos lhs.pos typedPos(tree.pos)((qual DOT setter)(rhs)) + case _ => tree } diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 53a1347a48..4b1f1efee4 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -9,8 +9,6 @@ package transform import scala.tools.nsc.symtab.Flags import scala.collection.{ mutable, immutable } -import scala.language.postfixOps -import scala.language.existentials import scala.annotation.tailrec /** Specialize code on types. @@ -168,7 +166,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { /** Reduce the given environment to contain mappings only for type variables in tps. */ def restrict(env: TypeEnv, tps: immutable.Set[Symbol]): TypeEnv = - env filterKeys tps toMap + env.filterKeys(tps).toMap /** Is the given environment a valid specialization for sym? * It is valid if each binding is from a @specialized type parameter in sym (or its owner) @@ -285,6 +283,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe)) yield tpe + /** Is `member` potentially affected by specialization? This is a gross overapproximation, + * but it should be okay for use outside of specialization. + */ + def possiblySpecialized(sym: Symbol) = specializedTypeVars(sym).nonEmpty + + /** Refines possiblySpecialized taking into account the instantiation of the specialized type variables at `site` */ + def isSpecializedIn(sym: Symbol, site: Type) = + specializedTypeVars(sym) exists { tvar => + val concretes = concreteTypes(tvar) + (concretes contains AnyRefClass) || (concretes contains site.memberType(tvar)) + } + + val specializedType = new TypeMap { override def apply(tp: Type): Type = tp match { case TypeRef(pre, sym, args) if args.nonEmpty => @@ -354,7 +365,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } ) - lazy val specializableTypes = ScalaValueClasses map (_.tpe) sorted + lazy val specializableTypes = ScalaValueClasses.map(_.tpe).sorted /** If the symbol is the companion of a value class, the value class. * Otherwise, AnyRef. @@ -373,7 +384,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { val types = if (!sym.isSpecialized) Nil // no @specialized Annotation else - specializedOn(sym) map (s => specializesClass(s).tpe) sorted + specializedOn(sym).map(s => specializesClass(s).tpe).sorted if (isBoundedGeneric(sym.tpe) && (types contains AnyRefClass)) reporter.warning(sym.pos, sym + " is always a subtype of " + AnyRefTpe + ".") @@ -461,7 +472,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case ExistentialType(_, res) => specializedTypeVars(res) case AnnotatedType(_, tp) => specializedTypeVars(tp) case TypeBounds(lo, hi) => specializedTypeVars(lo :: hi :: Nil) - case RefinedType(parents, _) => parents flatMap specializedTypeVars toSet + case RefinedType(parents, _) => parents.flatMap(specializedTypeVars).toSet case _ => immutable.Set.empty } @@ -697,7 +708,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { else debuglog("conflicting env for " + m + " env: " + env) } - else if (m.isDeferred) { // abstract methods + else if (m.isDeferred && m.isSpecialized) { // abstract methods val specMember = enterMember(cloneInSpecializedClass(m, _ | DEFERRED)) // debuglog("deferred " + specMember.fullName + " remains abstract") @@ -705,11 +716,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // was: new Forward(specMember) { // override def target = m.owner.info.member(specializedName(m, env)) // } - } else if (m.isMethod && !m.hasAccessorFlag) { // other concrete methods + } else if (!sClass.isTrait && m.isMethod && !m.hasAccessorFlag) { // other concrete methods // log("other concrete " + m) forwardToOverload(m) - } else if (m.isMethod && m.hasFlag(LAZY)) { + } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) { forwardToOverload(m) } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition @@ -848,7 +859,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { if (unusedStvars.length == 1) "is" else "are") ) unusedStvars foreach (_ removeAnnotation SpecializedClass) - specializingOn = specializingOn filterNot (unusedStvars contains) + specializingOn = specializingOn filterNot (unusedStvars contains _) } for (env0 <- specializations(specializingOn) if needsSpecialization(env0, sym)) yield { // !!! Can't this logic be structured so that the new symbol's name is @@ -1008,7 +1019,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case (NoSymbol, _) => if (overriding.isSuperAccessor) { val alias = overriding.alias - debuglog("checking special overload for super accessor: %s, alias for %s".format(overriding.fullName, alias.fullName)) + debuglog(s"checking special overload for super accessor: ${overriding.fullName}, alias for ${alias.fullName}") needsSpecialOverride(alias) match { case nope @ (NoSymbol, _) => None case (overridden, env) => @@ -1030,7 +1041,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { param.name = overriding.paramss(i)(j).name // SI-6555 Retain the parameter names from the subclass. } } - debuglog("specialized overload %s for %s in %s: %s".format(om, overriding.name.decode, pp(env), om.info)) + debuglog(s"specialized overload $om for ${overriding.name.decode} in ${pp(env)}: ${om.info}") if (overriding.isAbstractOverride) om.setFlag(ABSOVERRIDE) typeEnv(om) = env addConcreteSpecMethod(overriding) @@ -1079,7 +1090,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { */ private def unify(tp1: Type, tp2: Type, env: TypeEnv, strict: Boolean, tparams: Boolean = false): TypeEnv = (tp1, tp2) match { case (TypeRef(_, sym1, _), _) if sym1.isSpecialized => - debuglog("Unify " + tp1 + ", " + tp2) + debuglog(s"Unify $tp1, $tp2") if (isPrimitiveValueClass(tp2.typeSymbol) || isSpecializedAnyRefSubtype(tp2, sym1)) env + ((sym1, tp2)) else if (isSpecializedAnyRefSubtype(tp2, sym1)) @@ -1090,20 +1101,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { env case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) => if (args1.nonEmpty || args2.nonEmpty) - debuglog("Unify types " + tp1 + " and " + tp2) + debuglog(s"Unify types $tp1 and $tp2") if (strict && args1.length != args2.length) unifyError(tp1, tp2) val e = unify(args1, args2, env, strict) - if (e.nonEmpty) debuglog("unified to: " + e) + if (e.nonEmpty) debuglog(s"unified to: $e") e case (TypeRef(_, sym1, _), _) if sym1.isTypeParameterOrSkolem => env case (MethodType(params1, res1), MethodType(params2, res2)) => if (strict && params1.length != params2.length) unifyError(tp1, tp2) - debuglog("Unify methods " + tp1 + " and " + tp2) + debuglog(s"Unify methods $tp1 and $tp2") unify(res1 :: (params1 map (_.tpe)), res2 :: (params2 map (_.tpe)), env, strict) case (PolyType(tparams1, res1), PolyType(tparams2, res2)) => - debuglog("Unify polytypes " + tp1 + " and " + tp2) + debuglog(s"Unify polytypes $tp1 and $tp2") if (strict && tparams1.length != tparams2.length) unifyError(tp1, tp2) else if (tparams && tparams1.length == tparams2.length) @@ -1121,7 +1132,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { case (ExistentialType(_, res1), _) => unify(tp2, res1, env, strict) case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => unify(List(lo1, hi1), List(lo2, hi2), env, strict) case _ => - debuglog("don't know how to unify %s [%s] with %s [%s]".format(tp1, tp1.getClass, tp2, tp2.getClass)) + debuglog(s"don't know how to unify $tp1 [${tp1.getClass}] with $tp2 [${tp2.getClass}]") env } @@ -1131,9 +1142,9 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { if (!strict) unify(args._1, args._2, env, strict) else { val nenv = unify(args._1, args._2, emptyEnv, strict) - if (env.keySet intersect nenv.keySet isEmpty) env ++ nenv + if (env.keySet.intersect(nenv.keySet).isEmpty) env ++ nenv else { - debuglog("could not unify: u(" + args._1 + ", " + args._2 + ") yields " + nenv + ", env: " + env) + debuglog(s"could not unify: u(${args._1}, ${args._2}) yields $nenv, env: $env") unifyError(tp1, tp2) } } @@ -1229,7 +1240,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { env forall { case (tvar, tpe) => matches(tvar.info.bounds.lo, tpe) && matches(tpe, tvar.info.bounds.hi) || { if (warnings) - reporter.warning(tvar.pos, "Bounds prevent specialization of " + tvar) + reporter.warning(tvar.pos, s"Bounds prevent specialization of $tvar") debuglog("specvars: " + tvar.info.bounds.lo + ": " + @@ -1360,7 +1371,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { sym, currentClass, sym.owner.enclClass, isAccessible(sym), nme.isLocalName(sym.name)) ) if (shouldMakePublic(sym) && !isAccessible(sym)) { - debuglog("changing private flag of " + sym) + debuglog(s"changing private flag of $sym") sym.makeNotPrivate(sym.owner) } super.transform(tree) @@ -1415,10 +1426,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { (treeType =:= memberType) || { // anyref specialization memberType match { case PolyType(_, resTpe) => - debuglog("Conformance for anyref - polytype with result type: " + resTpe + " and " + treeType + "\nOrig. sym.: " + origSymbol) + debuglog(s"Conformance for anyref - polytype with result type: $resTpe and $treeType\nOrig. sym.: $origSymbol") try { val e = unify(origSymbol.tpe, memberType, emptyEnv, true) - debuglog("obtained env: " + e) + debuglog(s"obtained env: $e") e.keySet == env.keySet } catch { case _: Throwable => @@ -1518,7 +1529,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { ) val tree1 = gen.mkTypeApply(specTree, residualTargs) - debuglog("rewrote " + tree + " to " + tree1) + debuglog(s"rewrote $tree to $tree1") localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method } @@ -1526,7 +1537,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { tree match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => def transformNew = { - debuglog("Attempting to specialize new %s(%s)".format(tpt, args.mkString(", "))) + debuglog(s"Attempting to specialize new $tpt(${args.mkString(", ")})") val found = specializedType(tpt.tpe) if (found.typeSymbol ne tpt.tpe.typeSymbol) { // the ctor can be specialized val inst = New(found, transformTrees(args): _*) 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/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index 16ea3ea90f..fa7c503213 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -69,7 +69,7 @@ abstract class TailCalls extends Transform { * are optimized. Since 'this' is not a local variable, a dummy local val * is added and used as a label parameter. The backend knows to load * the corresponding argument in the 'this' (local at index 0). This dummy local - * is never used and should be cleand up by dead code elimination (when enabled). + * is never used and should be cleaned up by dead code elimination (when enabled). * </p> * <p> * This phase has been moved before pattern matching to catch more diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala index 3b23306386..afafdedce7 100644 --- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -1,90 +1,64 @@ package scala.tools.nsc package transform +import scala.annotation.tailrec 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 */ -trait TypeAdaptingTransformer { - self: TreeDSL => - - val analyzer: typechecker.Analyzer { val global: self.global.type } - - trait TypeAdapter { - val typer: analyzer.Typer +trait TypeAdaptingTransformer { self: TreeDSL => + abstract class TypeAdapter { import global._ import definitions._ - import CODE._ - def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { - case MethodType(Nil, _) => true - case _ => false - } + def typedPos(pos: Position)(tree: Tree): Tree private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { currentRun.runDefinitions.isUnbox(fn.symbol) && { val cls = arg.tpe.typeSymbol - (cls == definitions.NullClass) || isBoxedValueClass(cls) + (cls == NullClass) || isBoxedValueClass(cls) } } - private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) - - private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType] + private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) + final def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) + final def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty + final def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType - private def isDifferentErasedValueType(tpe: Type, other: Type) = - isErasedValueType(tpe) && (tpe ne other) - - def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) - - @inline def box(tree: Tree, target: => String): Tree = { - val result = box1(tree) - if (tree.tpe =:= UnitTpe) () - else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") - result - } + import CODE._ /** Box `tree` of unboxed type */ - private def box1(tree: Tree): Tree = tree match { + final def box(tree: Tree): Tree = tree match { case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(box1) + val ldef = deriveLabelDef(tree)(box) ldef setType ldef.rhs.tpe case _ => val tree1 = tree.tpe match { - case ErasedValueType(clazz, _) => - New(clazz, cast(tree, underlyingOfValueClass(clazz))) - case _ => - tree.tpe.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) - else BLOCK(tree, REF(BoxedUnit_UNIT)) - case NothingClass => tree // a non-terminating expression doesn't need boxing - case x => - assert(x != ArrayClass) - tree match { - /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x - * may lead to throwing an exception. - * - * This is important for specialization: calls to the super constructor should not box/unbox specialized - * fields (see TupleX). (ID) - */ - case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => - log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") - arg - case _ => - (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe - } - } + case ErasedValueType(clazz, _) => New(clazz, cast(tree, underlyingOfValueClass(clazz))) + case _ => tree.tpe.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) + else BLOCK(tree, REF(BoxedUnit_UNIT)) + case NothingClass => tree // a non-terminating expression doesn't need boxing + case x => + assert(x != ArrayClass) + tree match { + /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x + * may lead to throwing an exception. + * + * This is important for specialization: calls to the super constructor should not box/unbox specialized + * fields (see TupleX). (ID) + */ + case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => + log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") + arg + case _ => + (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe + } + } } - typer.typedPos(tree.pos)(tree1) - } - - def unbox(tree: Tree, pt: Type): Tree = { - val result = unbox1(tree, pt) - log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") - result + typedPos(tree.pos)(tree1) } /** Unbox `tree` of boxed type to expected type `pt`. @@ -93,27 +67,13 @@ trait TypeAdaptingTransformer { * @param pt the expected type. * @return the unboxed tree */ - private def unbox1(tree: Tree, pt: Type): Tree = tree match { -/* - case Boxed(unboxed) => - println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. - adaptToType(unboxed, pt) - */ + final def unbox(tree: Tree, pt: Type): Tree = tree match { case LabelDef(_, _, _) => val ldef = deriveLabelDef(tree)(unbox(_, pt)) ldef setType ldef.rhs.tpe case _ => val tree1 = pt match { - case ErasedValueType(clazz, underlying) => - val tree0 = - if (tree.tpe.typeSymbol == NullClass && - isPrimitiveValueClass(underlying.typeSymbol)) { - // convert `null` directly to underlying type, as going - // via the unboxed type would yield a NPE (see SI-5866) - unbox1(tree, underlying) - } else - Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) - cast(tree0, pt) + case ErasedValueType(clazz, underlying) => cast(unboxValueClass(tree, clazz, underlying), pt) case _ => pt.typeSymbol match { case UnitClass => @@ -125,21 +85,28 @@ trait TypeAdaptingTransformer { Apply(currentRun.runDefinitions.unboxMethod(pt.typeSymbol), tree) } } - typer.typedPos(tree.pos)(tree1) + typedPos(tree.pos)(tree1) } + final def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree = + if (tree.tpe.typeSymbol == NullClass && isPrimitiveValueClass(underlying.typeSymbol)) { + // convert `null` directly to underlying type, as going via the unboxed type would yield a NPE (see SI-5866) + unbox(tree, underlying) + } else + Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) + /** Generate a synthetic cast operation from tree.tpe to pt. - * @pre pt eq pt.normalize + * + * @pre pt eq pt.normalize */ - def cast(tree: Tree, pt: Type): Tree = { - if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { - def word = ( + final def cast(tree: Tree, pt: Type): Tree = { + if (settings.debug && (tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { + def word = if (tree.tpe <:< pt) "upcast" else if (pt <:< tree.tpe) "downcast" else if (pt weak_<:< tree.tpe) "coerce" else if (tree.tpe weak_<:< pt) "widen" else "cast" - ) log(s"erasure ${word}s from ${tree.tpe} to $pt") } if (pt =:= UnitTpe) { @@ -160,27 +127,23 @@ trait TypeAdaptingTransformer { * @param pt the expected type * @return the adapted tree */ - def adaptToType(tree: Tree, pt: Type): Tree = { - if (settings.debug && pt != WildcardType) - log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug - if (tree.tpe <:< pt) - tree - else if (isDifferentErasedValueType(tree.tpe, pt)) - adaptToType(box(tree, pt.toString), pt) - else if (isDifferentErasedValueType(pt, tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { - adaptToType(box(tree, pt.toString), pt) - } else if (isMethodTypeWithEmptyParams(tree.tpe)) { - // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val - //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) - adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) -// } else if (pt <:< tree.tpe) -// cast(tree, pt) - } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else - cast(tree, pt) + @tailrec final def adaptToType(tree: Tree, pt: Type): Tree = { + val tpe = tree.tpe + + if ((tpe eq pt) || tpe <:< pt) tree + else if (tpe.isInstanceOf[ErasedValueType]) adaptToType(box(tree), pt) // what if pt is an erased value type? + else if (pt.isInstanceOf[ErasedValueType]) adaptToType(unbox(tree, pt), pt) + // See corresponding case in `Eraser`'s `adaptMember` + // [H] this does not hold here, however: `assert(tree.symbol.isStable)` (when typechecking !(SomeClass.this.bitmap) for single lazy val) + else if (isMethodTypeWithEmptyParams(tpe)) adaptToType(applyMethodWithEmptyParams(tree), pt) + else { + val gotPrimitiveVC = isPrimitiveValueType(tpe) + val expectedPrimitiveVC = isPrimitiveValueType(pt) + + if (gotPrimitiveVC && !expectedPrimitiveVC) adaptToType(box(tree), pt) + else if (!gotPrimitiveVC && expectedPrimitiveVC) adaptToType(unbox(tree, pt), pt) + else cast(tree, pt) + } } } } 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 870c35338c..e0b1543f24 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 scala.annotation.tailrec + import symtab.Flags._ -import scala.collection.{ mutable, immutable } -import scala.language.postfixOps +import scala.collection.mutable import scala.reflect.internal.util.ListOfNil /*<export> */ @@ -64,19 +65,30 @@ abstract class UnCurry extends InfoTransform // uncurry and uncurryType expand type aliases class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline" + private val forceExpandFunction = settings.Ydelambdafy.value == "inline" private var needTryLift = false private var inConstructorFlag = 0L private val byNameArgs = mutable.HashSet[Tree]() private val noApply = mutable.HashSet[Tree]() private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]() - private lazy val forceSpecializationInfoTransformOfFunctionN: Unit = { - if (currentRun.specializePhase != NoPhase) { // be robust in case of -Ystop-after:uncurry - exitingSpecialize { - FunctionClass.seq.foreach(cls => cls.info) - } - } + // Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363) + // We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner + private def mustExpandFunction(fun: Function) = { + // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding) + val canUseLambdaMetaFactory = (fun.attachments.get[SAMFunction] match { + case Some(SAMFunction(userDefinedSamTp, sam)) => + // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply + erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) && + // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167) + // specialization and LMF are at odds, since LMF implements the single abstract method, + // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing + !specializeTypes.isSpecializedIn(sam, userDefinedSamTp) + + case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction + }) + + !canUseLambdaMetaFactory } /** Add a new synthetic member for `currentOwner` */ @@ -87,25 +99,17 @@ abstract class UnCurry extends InfoTransform @inline private def useNewMembers[T](owner: Symbol)(f: List[Tree] => T): T = f(newMembers.remove(owner).getOrElse(Nil).toList) - private def newFunction0(body: Tree): Tree = { - val result = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] - log("Change owner from %s to %s in %s".format(currentOwner, result.symbol, result.body)) - result.body changeOwner (currentOwner -> result.symbol) - transformFunction(result) - } - // I don't have a clue why I'm catching TypeErrors here, but it's better // than spewing stack traces at end users for internal errors. Examples // which hit at this point should not be hard to come by, but the immediate // motivation can be seen in continuations-neg/t3718. - override def transform(tree: Tree): Tree = ( + override def transform(tree: Tree): Tree = try postTransform(mainTransform(tree)) catch { case ex: TypeError => reporter.error(ex.pos, ex.msg) debugStack(ex) EmptyTree } - ) /* Is tree a reference `x` to a call by name parameter that needs to be converted to * x.apply()? Note that this is not the case if `x` is used as an argument to another @@ -114,7 +118,7 @@ abstract class UnCurry extends InfoTransform def isByNameRef(tree: Tree) = ( tree.isTerm && (tree.symbol ne null) - && (isByName(tree.symbol)) + && isByName(tree.symbol) && !byNameArgs(tree) ) @@ -191,16 +195,6 @@ abstract class UnCurry extends InfoTransform // ------ Transforming anonymous functions and by-name-arguments ---------------- - /** Undo eta expansion for parameterless and nullary methods */ - def deEta(fun: Function): Tree = fun match { - case Function(List(), expr) if isByNameRef(expr) => - noApply += expr - expr - case _ => - fun - } - - /** Transform a function node (x_1,...,x_n) => body of type FunctionN[T_1, .., T_N, R] to * * class $anon() extends AbstractFunctionN[T_1, .., T_N, R] with Serializable { @@ -209,66 +203,30 @@ abstract class UnCurry extends InfoTransform * new $anon() * */ - def transformFunction(fun: Function): Tree = { - fun.tpe match { - // can happen when analyzer plugins assign refined types to functions, e.g. - // (() => Int) { def apply(): Int @typeConstraint } - case RefinedType(List(funTp), decls) => - debuglog(s"eliminate refinement from function type ${fun.tpe}") - fun.setType(funTp) - case _ => - () - } - - deEta(fun) match { - // nullary or parameterless - case fun1 if fun1 ne fun => fun1 - case _ => - def typedFunPos(t: Tree) = localTyper.typedPos(fun.pos)(t) - val funParams = fun.vparams map (_.symbol) - def mkMethod(owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags): DefDef = - gen.mkMethodFromFunction(localTyper)(fun, owner, name, additionalFlags) - - def isSpecialized = { - forceSpecializationInfoTransformOfFunctionN - val specialized = specializeTypes.specializedType(fun.tpe) - !(specialized =:= fun.tpe) - } - - def canUseDelamdafyMethod = ( - (inConstructorFlag == 0) // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation - && (!isSpecialized || (settings.isBCodeActive && settings.target.value == "jvm-1.8")) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime - ) - if (inlineFunctionExpansion || !canUseDelamdafyMethod) { - val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) - val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation - // The original owner is used in the backend for the EnclosingMethod attribute. If fun is - // nested in a value-class method, its owner was already changed to the extension method. - // Saving the original owner allows getting the source structure from the class symbol. - defineOriginalOwner(anonClass, fun.symbol.originalOwner) - anonClass setInfo ClassInfoType(parents, newScope, anonClass) - - val applyMethodDef = mkMethod(anonClass, nme.apply) - anonClass.info.decls enter applyMethodDef.symbol - - typedFunPos { - Block( - ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos), - Typed(New(anonClass.tpe), TypeTree(fun.tpe))) - } - } else { - // method definition with the same arguments, return type, and body as the original lambda - val liftedMethod = mkMethod(fun.symbol.owner, nme.ANON_FUN_NAME, additionalFlags = ARTIFACT) - - // new function whose body is just a call to the lifted method - val newFun = deriveFunction(fun)(_ => typedFunPos( - gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), funParams :: Nil) - )) - typedFunPos(Block(liftedMethod, super.transform(newFun))) - } - } - } + def transformFunction(fun: Function): Tree = + // Undo eta expansion for parameterless and nullary methods, EXCEPT if `fun` targets a SAM. + // Normally, we can unwrap `() => cbn` to `cbn` where `cbn` refers to a CBN argument (typically `cbn` is an Ident), + // because we know `cbn` will already be a `Function0` thunk. When we're targeting a SAM, + // the types don't align and we must preserve the function wrapper. + if (fun.vparams.isEmpty && isByNameRef(fun.body) && fun.attachments.get[SAMFunction].isEmpty) { noApply += fun.body ; fun.body } + else if (forceExpandFunction || inConstructorFlag != 0) { + // Expand the function body into an anonymous class + gen.expandFunction(localTyper)(fun, inConstructorFlag) + } else { + // method definition with the same arguments, return type, and body as the original lambda + val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun) + + // new function whose body is just a call to the lifted method + val newFun = deriveFunction(fun)(_ => localTyper.typedPos(fun.pos)( + gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil) + )) + val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun))) + if (mustExpandFunction(fun)) { + val Block(stats, expr : Function) = typedNewFun + treeCopy.Block(typedNewFun, stats, gen.expandFunction(localTyper)(expr, inConstructorFlag)) + } else typedNewFun + } def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = { val isJava = fun.isJavaDefined @@ -346,25 +304,22 @@ abstract class UnCurry extends InfoTransform val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head) else args map2(formals, args1) { (formal, arg) => - if (!isByNameParamType(formal)) - arg - else if (isByNameRef(arg)) { + if (!isByNameParamType(formal)) arg + else if (isByNameRef(arg)) { // thunk does not need to be forced because it's a reference to a by-name arg passed to a by-name param byNameArgs += arg arg setType functionType(Nil, arg.tpe) - } - else { + } else { log(s"Argument '$arg' at line ${arg.pos.line} is $formal from ${fun.fullName}") - def canUseDirectly(recv: Tree) = ( - recv.tpe.typeSymbol.isSubClass(FunctionClass(0)) - && treeInfo.isExprSafeToInline(recv) - ) + def canUseDirectly(qual: Tree) = qual.tpe.typeSymbol.isSubClass(FunctionClass(0)) && treeInfo.isExprSafeToInline(qual) arg match { // don't add a thunk for by-name argument if argument already is an application of // a Function0. We can then remove the application and use the existing Function0. - case Apply(Select(recv, nme.apply), Nil) if canUseDirectly(recv) => - recv - case _ => - newFunction0(arg) + case Apply(Select(qual, nme.apply), Nil) if canUseDirectly(qual) => qual + case body => + val thunkFun = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] + log(s"Change owner from $currentOwner to ${thunkFun.symbol} in ${thunkFun.body}") + thunkFun.body.changeOwner((currentOwner, thunkFun.symbol)) + transformFunction(thunkFun) } } } @@ -376,8 +331,9 @@ abstract class UnCurry extends InfoTransform */ private def replaceElidableTree(tree: Tree): Tree = { tree match { - case DefDef(_,_,_,_,_,_) => - deriveDefDef(tree)(rhs => Block(Nil, gen.mkZero(rhs.tpe)) setType rhs.tpe) setSymbol tree.symbol setType tree.tpe + case DefDef(_,_,_,_,_,rhs) => + val rhs1 = if (rhs == EmptyTree) rhs else Block(Nil, gen.mkZero(rhs.tpe)) setType rhs.tpe + deriveDefDef(tree)(_ => rhs1) setSymbol tree.symbol setType tree.tpe case _ => gen.mkZero(tree.tpe) setType tree.tpe } @@ -434,9 +390,10 @@ abstract class UnCurry extends InfoTransform val sym = tree.symbol // true if the target is a lambda body that's been lifted into a method - def isLiftedLambdaBody(target: Tree) = target.symbol.isLocalToBlock && target.symbol.isArtifact && target.symbol.name.containsName(nme.ANON_FUN_NAME) + def isLiftedLambdaMethod(funSym: Symbol) = + funSym.isArtifact && funSym.name.containsName(nme.ANON_FUN_NAME) && funSym.isLocalToBlock - val result = ( + val result = if ((sym ne null) && sym.elisionLevel.exists(_ < settings.elidebelow.value)) replaceElidableTree(tree) else translateSynchronized(tree) match { @@ -489,7 +446,7 @@ abstract class UnCurry extends InfoTransform case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) => withNeedLift(needLift = true) { super.transform(tree) } - case ret @ Return(_) if (isNonLocalReturn(ret)) => + case ret @ Return(_) if isNonLocalReturn(ret) => withNeedLift(needLift = true) { super.transform(ret) } case Try(_, Nil, _) => @@ -508,7 +465,7 @@ abstract class UnCurry extends InfoTransform treeCopy.CaseDef(tree, pat1, transform(guard), transform(body)) // if a lambda is already the right shape we don't need to transform it again - case fun @ Function(_, Apply(target, _)) if (!inlineFunctionExpansion) && isLiftedLambdaBody(target) => + case fun @ Function(_, Apply(target, _)) if !forceExpandFunction && isLiftedLambdaMethod(target.symbol) => super.transform(fun) case fun @ Function(_, _) => @@ -528,9 +485,8 @@ abstract class UnCurry extends InfoTransform } tree1 } - ) - assert(result.tpe != null, result.shortClass + " tpe is null:\n" + result) - result modifyType uncurry + + result.setType(uncurry(result.tpe)) } def postTransform(tree: Tree): Tree = exitingUncurry { @@ -549,7 +505,7 @@ abstract class UnCurry extends InfoTransform tree } - def isThrowable(pat: Tree): Boolean = pat match { + @tailrec def isThrowable(pat: Tree): Boolean = pat match { case Typed(Ident(nme.WILDCARD), tpt) => tpt.tpe =:= ThrowableTpe case Bind(_, pat) => @@ -575,6 +531,7 @@ abstract class UnCurry extends InfoTransform } case dd @ DefDef(_, _, _, vparamss0, _, rhs0) => + val ddSym = dd.symbol val (newParamss, newRhs): (List[List[ValDef]], Tree) = if (dependentParamTypeErasure isDependent dd) dependentParamTypeErasure erase dd @@ -586,11 +543,22 @@ abstract class UnCurry extends InfoTransform (vparamss1, rhs0) } + // A no-arg method with ConstantType result type can safely be reduced to the corresponding Literal + // (only pure methods are typed as ConstantType). We could also do this for methods with arguments, + // after ensuring the arguments are not referenced. + val literalRhsIfConst = + if (newParamss.head.isEmpty) { // We know newParamss.length == 1 from above + ddSym.info.resultType match { + case tp@ConstantType(value) => Literal(value) setType tp setPos newRhs.pos // inlining of gen.mkAttributedQualifier(tp) + case _ => newRhs + } + } else newRhs + val flatdd = copyDefDef(dd)( vparamss = newParamss, - rhs = nonLocalReturnKeys get dd.symbol match { - case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(newRhs, k, dd.symbol)) - case None => newRhs + rhs = nonLocalReturnKeys get ddSym match { + case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(literalRhsIfConst, k, ddSym)) + case None => literalRhsIfConst } ) addJavaVarargsForwarders(dd, flatdd) @@ -609,7 +577,7 @@ abstract class UnCurry extends InfoTransform case Select(_, _) | TypeApply(_, _) => applyUnary() case ret @ Return(expr) if isNonLocalReturn(ret) => - log("non-local return from %s to %s".format(currentOwner.enclMethod, ret.symbol)) + log(s"non-local return from ${currentOwner.enclMethod} to ${ret.symbol}") atPos(ret.pos)(nonLocalReturnThrow(expr, ret.symbol)) case TypeTree() => tree @@ -684,7 +652,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 = { @@ -713,7 +681,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 @@ -734,7 +702,7 @@ abstract class UnCurry extends InfoTransform case Packed(param, tempVal) => (param, tempVal) }.unzip - val rhs1 = if (tempVals.isEmpty) rhs else { + val rhs1 = if (rhs == EmptyTree || tempVals.isEmpty) rhs else { localTyper.typedPos(rhs.pos) { // Patch the method body to refer to the temp vals val rhsSubstituted = rhs.substituteSymbols(packedParams map (_.symbol), tempVals map (_.symbol)) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index 91777b34d5..4ae97ce281 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._ @@ -691,7 +691,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { // if X is mutable. freshExistentialSubtype(t.tpe) } - else trees find (a => a.correspondsStructure(t)(sameValue)) match { + else trees find (a => equivalentTree(a, t)) match { case Some(orig) => debug.patmat("unique tp for tree: " + ((orig, orig.tpe))) orig.tpe diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index 1331eb6993..ec493b9507 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -6,9 +6,6 @@ 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 @@ -85,11 +82,15 @@ trait TreeAndTypeAnalysis extends Debugging { tp <:< tpImpliedNormalizedToAny } - // TODO: improve, e.g., for constants - def sameValue(a: Tree, b: Tree): Boolean = (a eq b) || ((a, b) match { - case (_ : Ident, _ : Ident) => a.symbol eq b.symbol - case _ => false - }) + def equivalentTree(a: Tree, b: Tree): Boolean = (a, b) match { + case (Select(qual1, _), Select(qual2, _)) => equivalentTree(qual1, qual2) && a.symbol == b.symbol + case (Ident(_), Ident(_)) => a.symbol == b.symbol + case (Literal(c1), Literal(c2)) => c1 == c2 + case (This(_), This(_)) => a.symbol == b.symbol + case (Apply(fun1, args1), Apply(fun2, args2)) => equivalentTree(fun1, fun2) && args1.corresponds(args2)(equivalentTree) + // Those are the only cases we need to handle in the pattern matcher + case _ => false + } trait CheckableTreeAndTypeAnalysis { val typer: Typer @@ -138,7 +139,7 @@ trait TreeAndTypeAnalysis extends Debugging { if(grouped) { def enumerateChildren(sym: Symbol) = { - sym.children.toList + sym.sealedChildren.toList .sortBy(_.sealedSortName) .filterNot(x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x)) } @@ -173,6 +174,8 @@ trait TreeAndTypeAnalysis extends Debugging { filterChildren(subclasses) }) } + case sym if sym.isCase => + List(List(tp)) case sym => debug.patmat("enum unsealed "+ ((tp, sym, sym.isSealed, isPrimitiveValueClass(sym)))) @@ -277,7 +280,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT // hashconsing trees (modulo value-equality) def unique(t: Tree, tpOverride: Type = NoType): Tree = - trees find (a => a.correspondsStructure(t)(sameValue)) match { + trees find (a => equivalentTree(a, t)) match { case Some(orig) => // debug.patmat("unique: "+ (t eq orig, orig)) orig @@ -346,7 +349,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy { type Result = Prop def and(a: Result, b: Result) = And(a, b) - def outerTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType) + def withOuterTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType) def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T) val p = binderToUniqueTree(b); And(uniqueNonNullProp(p), uniqueTypeProp(p, uniqueTp(pt))) } @@ -707,9 +710,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 a2afb76b0e..e12b8548a8 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..8c59ced28f 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. @@ -101,7 +101,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker { val pos = NoPosition - val localSubstitution = Substitution(prevBinder, CODE.REF(nextBinder)) + val localSubstitution = Substitution(prevBinder, gen.mkAttributedStableRef(nextBinder)) def chainBefore(next: Tree)(casegen: Casegen): Tree = substitution(next) override def toString = "S"+ localSubstitution } @@ -118,7 +118,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { val res: Tree lazy val nextBinder = freshSym(pos, nextBinderTp) - lazy val localSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder))) + lazy val localSubstitution = Substitution(List(prevBinder), List(gen.mkAttributedStableRef(nextBinder))) def chainBefore(next: Tree)(casegen: Casegen): Tree = atPos(pos)(casegen.flatMapCond(cond, res, nextBinder, substitution(next))) @@ -316,7 +316,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { trait TypeTestCondStrategy { type Result - def outerTest(testedBinder: Symbol, expectedTp: Type): Result + def withOuterTest(orig: Result)(testedBinder: Symbol, expectedTp: Type): Result = orig // TODO: can probably always widen def typeTest(testedBinder: Symbol, expectedTp: Type): Result def nonNullTest(testedBinder: Symbol): Result @@ -336,18 +336,34 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder) def eqTest(pat: Tree, testedBinder: Symbol) = REF(testedBinder) OBJ_EQ pat - def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = { - val expectedOuter = expectedTp.prefix match { - case ThisType(clazz) => This(clazz) - case NoType => mkTRUE // fallback for SI-6183 - case pre => REF(pre.prefix, pre.termSymbol) + override def withOuterTest(orig: Tree)(testedBinder: Symbol, expectedTp: Type): Tree = { + val expectedPrefix = expectedTp.prefix + val testedPrefix = testedBinder.info.prefix + + // Check if a type is defined in a static location. Unlike `tp.isStatic` before `flatten`, + // this also includes methods and (possibly nested) objects inside of methods. + def definedInStaticLocation(tp: Type): Boolean = { + def isStatic(tp: Type): Boolean = + if (tp == NoType || tp.typeSymbol.isPackageClass || tp == NoPrefix) true + else if (tp.typeSymbol.isModuleClass) isStatic(tp.prefix) + else false + tp.typeSymbol.owner == tp.prefix.typeSymbol && isStatic(tp.prefix) } - // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix` - // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor? - val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix - - (Select(codegen._asInstanceOf(testedBinder, expectedTp), outer)) OBJ_EQ expectedOuter + if ((expectedPrefix eq NoPrefix) + || definedInStaticLocation(expectedTp) + || testedPrefix =:= expectedPrefix) orig + else gen.mkAttributedQualifierIfPossible(expectedPrefix) match { + case None => orig + case Some(expectedOuterRef) => + // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` + // by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix` + // if there's an outer accessor, otherwise the condition becomes `true` + // TODO: centralize logic whether there's an outer accessor and use here? + val synthOuterGetter = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedPrefix + val outerTest = (Select(codegen._asInstanceOf(testedBinder, expectedTp), synthOuterGetter)) OBJ_EQ expectedOuterRef + and(orig, outerTest) + } } } @@ -356,7 +372,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true - def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false def nonNullTest(testedBinder: Symbol): Result = false def equalsTest(pat: Tree, testedBinder: Symbol): Result = false def eqTest(pat: Tree, testedBinder: Symbol): Result = false @@ -368,7 +383,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { type Result = Boolean def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder - def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null @@ -405,12 +419,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { import TypeTestTreeMaker._ debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp))) - lazy val outerTestNeeded = ( - (expectedTp.prefix ne NoPrefix) - && !expectedTp.prefix.typeSymbol.isPackageClass - && needsOuterTest(expectedTp, testedBinder.info, matchOwner) - ) - // the logic to generate the run-time test that follows from the fact that // a `prevBinder` is expected to have type `expectedTp` // the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees @@ -429,12 +437,11 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp) def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe) def mkNullTest = nonNullTest(testedBinder) - def mkOuterTest = outerTest(testedBinder, expectedTp) def mkTypeTest = typeTest(testedBinder, expectedWide) def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder) def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder) - def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res + def addOuterTest(res: cs.Result): cs.Result = withOuterTest(res)(testedBinder, expectedTp) // If we conform to expected primitive type: // it cannot be null and cannot have an outer pointer. No further checking. @@ -485,7 +492,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { // NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null) // equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required) val cond = codegen._equals(patTree, prevBinder) - val res = CODE.REF(prevBinder) + val res = gen.mkAttributedStableRef(prevBinder) override def toString = "ET"+((prevBinder.name, patTree)) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala index 8beb1837ad..3f27d18e64 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala @@ -6,10 +6,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 +79,4 @@ trait MatchWarnings { } } } -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index b2f2516b5b..05f2d60be1 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -222,7 +222,7 @@ trait Interface extends ast.TreeDSL { object substIdentsForTrees extends Transformer { private def typedIfOrigTyped(to: Tree, origTp: Type): Tree = if (origTp == null || origTp == NoType) to - // important: only type when actually substing and when original tree was typed + // important: only type when actually substituting and when original tree was typed // (don't need to use origTp as the expected type, though, and can't always do this anyway due to unknown type params stemming from polymorphic extractors) else typer.typed(to) diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 309b80f9ba..2b6a4c763a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -212,8 +212,8 @@ trait Checkable { ) /** Are all children of these symbols pairwise irreconcilable? */ def allChildrenAreIrreconcilable(sym1: Symbol, sym2: Symbol) = ( - sym1.children.toList forall (c1 => - sym2.children.toList forall (c2 => + sym1.sealedChildren.toList forall (c1 => + sym2.sealedChildren.toList forall (c2 => areIrreconcilableAsParents(c1, c2) ) ) diff --git a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala index 56ed0ee16c..2f4771e9d4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala @@ -75,7 +75,7 @@ abstract class ConstantFolder { case nme.AND => Constant(x.booleanValue & y.booleanValue) case nme.EQ => Constant(x.booleanValue == y.booleanValue) case nme.NE => Constant(x.booleanValue != y.booleanValue) - case _ => null + case _ => null } private def foldSubrangeOp(op: Name, x: Constant, y: Constant): Constant = op match { case nme.OR => Constant(x.intValue | y.intValue) @@ -95,14 +95,20 @@ abstract class ConstantFolder { case nme.MUL => Constant(x.intValue * y.intValue) case nme.DIV => Constant(x.intValue / y.intValue) case nme.MOD => Constant(x.intValue % y.intValue) - case _ => null + case _ => null } private def foldLongOp(op: Name, x: Constant, y: Constant): Constant = op match { case nme.OR => Constant(x.longValue | y.longValue) case nme.XOR => Constant(x.longValue ^ y.longValue) case nme.AND => Constant(x.longValue & y.longValue) - case nme.LSL => Constant(x.longValue << y.longValue) + case nme.LSL if x.tag <= IntTag + => Constant(x.intValue << y.longValue) + case nme.LSL => Constant(x.longValue << y.longValue) + case nme.LSR if x.tag <= IntTag + => Constant(x.intValue >>> y.longValue) case nme.LSR => Constant(x.longValue >>> y.longValue) + case nme.ASR if x.tag <= IntTag + => Constant(x.intValue >> y.longValue) case nme.ASR => Constant(x.longValue >> y.longValue) case nme.EQ => Constant(x.longValue == y.longValue) case nme.NE => Constant(x.longValue != y.longValue) @@ -115,7 +121,7 @@ abstract class ConstantFolder { case nme.MUL => Constant(x.longValue * y.longValue) case nme.DIV => Constant(x.longValue / y.longValue) case nme.MOD => Constant(x.longValue % y.longValue) - case _ => null + case _ => null } private def foldFloatOp(op: Name, x: Constant, y: Constant): Constant = op match { case nme.EQ => Constant(x.floatValue == y.floatValue) @@ -129,7 +135,7 @@ abstract class ConstantFolder { case nme.MUL => Constant(x.floatValue * y.floatValue) case nme.DIV => Constant(x.floatValue / y.floatValue) case nme.MOD => Constant(x.floatValue % y.floatValue) - case _ => null + case _ => null } private def foldDoubleOp(op: Name, x: Constant, y: Constant): Constant = op match { case nme.EQ => Constant(x.doubleValue == y.doubleValue) @@ -143,7 +149,7 @@ abstract class ConstantFolder { case nme.MUL => Constant(x.doubleValue * y.doubleValue) case nme.DIV => Constant(x.doubleValue / y.doubleValue) case nme.MOD => Constant(x.doubleValue % y.doubleValue) - case _ => null + case _ => null } private def foldBinop(op: Name, x: Constant, y: Constant): Constant = { @@ -162,7 +168,7 @@ abstract class ConstantFolder { case _ => null } catch { - case ex: ArithmeticException => null + case _: ArithmeticException => null } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 727f09290a..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 @@ -1105,7 +1104,7 @@ trait ContextErrors { def GetterDefinedTwiceError(getter: Symbol) = issueSymbolTypeError(getter, getter+" is defined twice") - def ValOrValWithSetterSuffixError(tree: Tree) = + def ValOrVarWithSetterSuffixError(tree: Tree) = issueNormalTypeError(tree, "Names of vals or vars may not end in `_='") def PrivateThisCaseClassParameterError(tree: Tree) = @@ -1212,7 +1211,8 @@ trait ContextErrors { import definitions._ - def AmbiguousImplicitError(info1: ImplicitInfo, info2: ImplicitInfo, + def AmbiguousImplicitError(info1: ImplicitInfo, tree1: Tree, + info2: ImplicitInfo, tree2: Tree, pre1: String, pre2: String, trailer: String) (isView: Boolean, pt: Type, tree: Tree)(implicit context0: Context) = { if (!info1.tpe.isErroneous && !info2.tpe.isErroneous) { @@ -1248,10 +1248,21 @@ trait ContextErrors { if (explanation == "") "" else "\n" + explanation ) } + + def treeTypeArgs(annotatedTree: Tree): List[String] = annotatedTree match { + case TypeApply(_, args) => args.map(_.toString) + case Block(_, Function(_, treeInfo.Applied(_, targs, _))) => targs.map(_.toString) // eta expansion, see neg/t9527b.scala + case _ => Nil + } + context.issueAmbiguousError(AmbiguousImplicitTypeError(tree, - if (isView) viewMsg - else s"ambiguous implicit values:\n${coreMsg}match expected type $pt") - ) + (info1.sym, info2.sym) match { + case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1)) + case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2)) + case (_, _) if isView => viewMsg + case (_, _) => s"ambiguous implicit values:\n${coreMsg}match expected type $pt" + } + )) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 5ec16e84bb..bcc1ed3e64 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -24,7 +24,8 @@ trait Contexts { self: Analyzer => object NoContext extends Context(EmptyTree, NoSymbol, EmptyScope, NoCompilationUnit, - null) { // We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer` + // We can't pass the uninitialized `this`. Instead, we treat null specially in `Context#outer` + null) { enclClass = this enclMethod = this @@ -48,12 +49,11 @@ trait Contexts { self: Analyzer => def ambiguousDefnAndImport(owner: Symbol, imp: ImportInfo) = LookupAmbiguous(s"it is both defined in $owner and imported subsequently by \n$imp") - private lazy val startContext = { - NoContext.make( + private lazy val startContext = NoContext.make( Template(List(), noSelfType, List()) setSymbol global.NoSymbol setType global.NoType, rootMirror.RootClass, - rootMirror.RootClass.info.decls) - } + rootMirror.RootClass.info.decls + ) private lazy val allUsedSelectors = mutable.Map[ImportInfo, Set[ImportSelector]]() withDefaultValue Set() @@ -387,8 +387,10 @@ trait Contexts { self: Analyzer => @inline final def withImplicitsEnabled[T](op: => T): T = withMode(enabled = ImplicitsEnabled)(op) @inline final def withImplicitsDisabled[T](op: => T): T = withMode(disabled = ImplicitsEnabled | EnrichmentEnabled)(op) @inline final def withImplicitsDisabledAllowEnrichment[T](op: => T): T = withMode(enabled = EnrichmentEnabled, disabled = ImplicitsEnabled)(op) + @inline final def withImplicits[T](enabled: Boolean)(op: => T): T = if (enabled) withImplicitsEnabled(op) else withImplicitsDisabled(op) @inline final def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) @inline final def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) + @inline final def withMacros[T](enabled: Boolean)(op: => T): T = if (enabled) withMacrosEnabled(op) else withMacrosDisabled(op) @inline final def withinStarPatterns[T](op: => T): T = withMode(enabled = StarPatterns)(op) @inline final def withinSuperInit[T](op: => T): T = withMode(enabled = SuperInit)(op) @inline final def withinSecondTry[T](op: => T): T = withMode(enabled = SecondTry)(op) @@ -723,7 +725,6 @@ trait Contexts { self: Analyzer => ( (ab.isTerm || ab == rootMirror.RootClass) || (accessWithin(ab) || accessWithinLinked(ab)) && ( !sym.isLocalToThis - || sym.owner.isImplClass // allow private local accesses to impl classes || sym.isProtected && isSubThisType(pre, sym.owner) || pre =:= sym.owner.thisType ) @@ -806,11 +807,13 @@ trait Contexts { self: Analyzer => (e ne null) && (e.owner == scope) && (!settings.isScala212 || e.sym.exists) }) - private def withQualifyingImplicitAlternatives(imp: ImportInfo, name: Name, pre: Type)(f: Symbol => Unit) = - for { - sym <- importedAccessibleSymbol(imp, name, requireExplicit = false, record = false).alternatives - if isQualifyingImplicit(name, sym, pre, imported = true) - } f(sym) + /** Do something with the symbols with name `name` imported via the import in `imp`, + * if any such symbol is accessible from this context and is a qualifying implicit. + */ + private def withQualifyingImplicitAlternatives(imp: ImportInfo, name: Name, pre: Type)(f: Symbol => Unit) = for { + sym <- importedAccessibleSymbol(imp, name, requireExplicit = false, record = false).alternatives + if isQualifyingImplicit(name, sym, pre, imported = true) + } f(sym) private def collectImplicits(syms: Scope, pre: Type, imported: Boolean = false): List[ImplicitInfo] = for (sym <- syms.toList if isQualifyingImplicit(sym.name, sym, pre, imported)) yield @@ -819,12 +822,8 @@ trait Contexts { self: Analyzer => private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = { val qual = imp.qual - val pre = - if (qual.tpe.typeSymbol.isPackageClass) - // SI-6225 important if the imported symbol is inherited by the package object. - singleType(qual.tpe, qual.tpe member nme.PACKAGE) - else - qual.tpe + val qualSym = qual.tpe.typeSymbol + val pre = qual.tpe def collect(sels: List[ImportSelector]): List[ImplicitInfo] = sels match { case List() => List() @@ -900,7 +899,8 @@ trait Contexts { self: Analyzer => Some(collectImplicitImports(imports.head)) } else if (owner.isPackageClass) { // the corresponding package object may contain implicit members. - Some(collectImplicits(owner.tpe.implicitMembers, owner.tpe)) + val pre = owner.packageObject.typeOfThis + Some(collectImplicits(pre.implicitMembers, pre)) } else Some(Nil) } @@ -967,51 +967,19 @@ trait Contexts { self: Analyzer => private def importedAccessibleSymbol(imp: ImportInfo, name: Name, requireExplicit: Boolean, record: Boolean): Symbol = imp.importedSymbol(name, requireExplicit, record) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false)) - /** Is `sym` defined in package object of package `pkg`? - * Since sym may be defined in some parent of the package object, - * we cannot inspect its owner only; we have to go through the - * info of the package object. However to avoid cycles we'll check - * what other ways we can before pushing that way. + private def requiresQualifier(s: Symbol): Boolean = ( + s.owner.isClass + && !s.owner.isPackageClass + && !s.isTypeParameterOrSkolem + && !s.isExistentiallyBound + ) + + /** Must `sym` defined in package object of package `pkg`, if + * it selected from a prefix with `pkg` as its type symbol? */ def isInPackageObject(sym: Symbol, pkg: Symbol): Boolean = { - def uninitialized(what: String) = { - log(s"Cannot look for $sym in package object of $pkg; $what is not initialized.") - false - } - def pkgClass = if (pkg.isTerm) pkg.moduleClass else pkg - def matchesInfo = ( - // need to be careful here to not get a cyclic reference during bootstrap - if (pkg.isInitialized) { - val module = pkg.info member nme.PACKAGEkw - if (module.isInitialized) - module.info.member(sym.name).alternatives contains sym - else - uninitialized("" + module) - } - else uninitialized("" + pkg) - ) - def inPackageObject(sym: Symbol) = ( - // To be in the package object, one of these must be true: - // 1) sym.owner is a package object class, and sym.owner.owner is the package class for `pkg` - // 2) sym.owner is inherited by the correct package object class - // We try to establish 1) by inspecting the owners directly, and then we try - // to rule out 2), and only if both those fail do we resort to looking in the info. - !sym.hasPackageFlag && sym.owner.exists && ( - if (sym.owner.isPackageObjectClass) - sym.owner.owner == pkgClass - else - !sym.owner.isPackageClass && matchesInfo - ) - ) - - // An overloaded symbol might not have the expected owner! - // The alternatives must be inspected directly. - pkgClass.isPackageClass && ( - if (sym.isOverloaded) - sym.alternatives forall (isInPackageObject(_, pkg)) - else - inPackageObject(sym) - ) + if (sym.isOverloaded) sym.alternatives.exists(alt => isInPackageObject(alt, pkg)) + else pkg.hasPackageFlag && sym.owner != pkg && requiresQualifier(sym) } def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess @@ -1048,11 +1016,6 @@ trait Contexts { self: Analyzer => || unit.exists && s.sourceFile != unit.source.file ) ) - def requiresQualifier(s: Symbol) = ( - s.owner.isClass - && !s.owner.isPackageClass - && !s.isTypeParameterOrSkolem - ) def lookupInPrefix(name: Name) = pre member name filter qualifies def accessibleInPrefix(s: Symbol) = isAccessible(s, pre, superAccess = false) @@ -1407,7 +1370,6 @@ trait Contexts { self: Analyzer => protected def handleError(pos: Position, msg: String): Unit = onTreeCheckerError(pos, msg) } - class ImportInfo(val tree: Import, val depth: Int) { def pos = tree.pos def posOf(sel: ImportSelector) = tree.pos withPoint sel.namePos @@ -1423,14 +1385,15 @@ trait Contexts { self: Analyzer => def isExplicitImport(name: Name): Boolean = tree.selectors exists (_.rename == name.toTermName) - /** The symbol with name `name` imported from import clause `tree`. - */ + /** The symbol with name `name` imported from import clause `tree`. */ def importedSymbol(name: Name): Symbol = importedSymbol(name, requireExplicit = false, record = true) - private def recordUsage(sel: ImportSelector, result: Symbol) { - def posstr = pos.source.file.name + ":" + posOf(sel).line - def resstr = if (tree.symbol.hasCompleteInfo) s"(qual=$qual, $result)" else s"(expr=${tree.expr}, ${result.fullLocationString})" - debuglog(s"In $this at $posstr, selector '${selectorString(sel)}' resolved to $resstr") + private def recordUsage(sel: ImportSelector, result: Symbol): Unit = { + debuglog(s"In $this at ${ pos.source.file.name }:${ posOf(sel).line }, selector '${ selectorString(sel) + }' resolved to ${ + if (tree.symbol.hasCompleteInfo) s"(qual=$qual, $result)" + else s"(expr=${tree.expr}, ${result.fullLocationString})" + }") allUsedSelectors(this) += sel } 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/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala index 7092f00bff..97de2b6c85 100644 --- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala +++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala @@ -15,39 +15,29 @@ import symtab.Flags._ * @version 1.0 */ trait EtaExpansion { self: Analyzer => - import global._ - object etaExpansion { - private def isMatch(vparam: ValDef, arg: Tree) = arg match { - case Ident(name) => vparam.name == name - case _ => false - } - - def unapply(tree: Tree): Option[(List[ValDef], Tree, List[Tree])] = tree match { - case Function(vparams, Apply(fn, args)) if (vparams corresponds args)(isMatch) => - Some((vparams, fn, args)) - case _ => - None - } - } - - /** <p> - * Expand partial function applications of type `type`. - * </p><pre> - * p.f(es_1)...(es_n) - * ==> { - * <b>private synthetic val</b> eta$f = p.f // if p is not stable - * ... - * <b>private synthetic val</b> eta$e_i = e_i // if e_i is not stable - * ... - * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m)) - * }</pre> - * <p> - * tree is already attributed - * </p> - */ - def etaExpand(unit : CompilationUnit, tree: Tree, typer: Typer): Tree = { + /** Expand partial method application `p.f(es_1)...(es_n)`. + * + * We expand this to the following block, which evaluates + * the target of the application and its supplied arguments if needed (they are not stable), + * and then wraps a Function that abstracts over the missing arguments. + * + * ``` + * { + * private synthetic val eta$f = p.f // if p is not stable + * ... + * private synthetic val eta$e_i = e_i // if e_i is not stable + * ... + * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m)) + * } + * ``` + * + * This is called from instantiateToMethodType after type checking `tree`, + * and we realize we have a method type, where a function type (builtin or SAM) is expected. + * + **/ + def etaExpand(unit: CompilationUnit, tree: Tree, typer: Typer): Tree = { val tpe = tree.tpe var cnt = 0 // for NoPosition def freshName() = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 509ce59104..bee2ae8e99 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._ @@ -34,14 +34,33 @@ trait Implicits { import typingStack.{ printTyping } import typeDebug._ + // standard usage + def inferImplicitFor(pt: Type, tree: Tree, context: Context, reportAmbiguous: Boolean = true): SearchResult = + inferImplicit(tree, pt, reportAmbiguous, isView = false, context, saveAmbiguousDivergent = true, tree.pos) + + // used by typer to find an implicit coercion + def inferImplicitView(from: Type, to: Type, tree: Tree, context: Context, reportAmbiguous: Boolean, saveAmbiguousDivergent: Boolean) = + inferImplicit(tree, Function1(from, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent, tree.pos) + + // used for manifests, typetags, checking language features, scaladoc + def inferImplicitByType(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = + inferImplicit(EmptyTree, pt, reportAmbiguous = true, isView = false, context, saveAmbiguousDivergent = true, pos) + + def inferImplicitByTypeSilent(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = + inferImplicit(EmptyTree, pt, reportAmbiguous = false, isView = false, context, saveAmbiguousDivergent = false, pos) + + @deprecated("Unused in scalac", "2.12.0-M4") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent = true, tree.pos) + @deprecated("Unused in scalac", "2.12.0-M4") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, tree.pos) - /** Search for an implicit value. See the comment on `result` at the end of class `ImplicitSearch` - * for more info how the search is conducted. + /** Search for an implicit value. Consider using one of the convenience methods above. This one has many boolean levers. + * + * See the comment on `result` at the end of class `ImplicitSearch` for more info how the search is conducted. + * * @param tree The tree for which the implicit needs to be inserted. * (the inference might instantiate some of the undetermined * type parameters of that tree. @@ -92,9 +111,10 @@ trait Implicits { /** A friendly wrapper over inferImplicit to be used in macro contexts and toolboxes. */ def inferImplicit(tree: Tree, pt: Type, isView: Boolean, context: Context, silent: Boolean, withMacrosDisabled: Boolean, pos: Position, onError: (Position, String) => Unit): Tree = { - val wrapper1 = if (!withMacrosDisabled) (context.withMacrosEnabled[SearchResult] _) else (context.withMacrosDisabled[SearchResult] _) - def wrapper(inference: => SearchResult) = wrapper1(inference) - val result = wrapper(inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) + val result = context.withMacros(enabled = !withMacrosDisabled) { + inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) + } + if (result.isFailure && !silent) { val err = context.reporter.firstError val errPos = err.map(_.errPos).getOrElse(pos) @@ -304,6 +324,10 @@ trait Implicits { */ object Function1 { val Sym = FunctionClass(1) + val Pre = Sym.typeConstructor.prefix + + def apply(from: Type, to: Type) = TypeRef(Pre, Sym, List(from, to)) + // It is tempting to think that this should be inspecting "tp baseType Sym" // rather than tp. See test case run/t8280 and the commit message which // accompanies it for explanation why that isn't done. @@ -887,7 +911,7 @@ trait Implicits { * - find the most likely one * - if it matches, forget about all others it improves upon */ - @tailrec private def rankImplicits(pending: Infos, acc: Infos): Infos = pending match { + @tailrec private def rankImplicits(pending: Infos, acc: List[(SearchResult, ImplicitInfo)]): List[(SearchResult, ImplicitInfo)] = pending match { case Nil => acc case firstPending :: otherPending => def firstPendingImproves(alt: ImplicitInfo) = @@ -914,7 +938,7 @@ trait Implicits { val pendingImprovingBest = undoLog undo { otherPending filterNot firstPendingImproves } - rankImplicits(pendingImprovingBest, firstPending :: acc) + rankImplicits(pendingImprovingBest, (newBest, firstPending) :: acc) } } @@ -930,14 +954,14 @@ trait Implicits { // So if there is any element not improved upon by the first it is an error. rankImplicits(eligible, Nil) match { case Nil => () - case chosen :: rest => - rest find (alt => !improves(chosen, alt)) match { - case Some(competing) => - AmbiguousImplicitError(chosen, competing, "both", "and", "")(isView, pt, tree)(context) + case (chosenResult, chosenInfo) :: rest => + rest find { case (_, alt) => !improves(chosenInfo, alt) } match { + case Some((competingResult, competingInfo)) => + AmbiguousImplicitError(chosenInfo, chosenResult.tree, competingInfo, competingResult.tree, "both", "and", "")(isView, pt, tree)(context) return AmbiguousSearchFailure // Stop the search once ambiguity is encountered, see t4457_2.scala case _ => - if (isView) chosen.useCountView += 1 - else chosen.useCountArg += 1 + if (isView) chosenInfo.useCountView += 1 + else chosenInfo.useCountArg += 1 } } @@ -1014,15 +1038,12 @@ trait Implicits { } case None => if (pre.isStable && !pre.typeSymbol.isExistentiallyBound) { - val companion = companionSymbolOf(sym, context) - companion.moduleClass match { - case mc: ModuleClassSymbol => - val infos = - for (im <- mc.implicitMembers.toList) yield new ImplicitInfo(im.name, singleType(pre, companion), im) - if (infos.nonEmpty) - infoMap += (sym -> infos) - case _ => - } + val pre1 = + if (sym.isPackageClass) sym.packageObject.typeOfThis + else singleType(pre, companionSymbolOf(sym, context)) + val infos = pre1.implicitMembers.iterator.map(mem => new ImplicitInfo(mem.name, pre1, mem)).toList + if (infos.nonEmpty) + infoMap += (sym -> infos) } val bts = tp.baseTypeSeq var i = 1 @@ -1210,7 +1231,7 @@ trait Implicits { /* Re-wraps a type in a manifest before calling inferImplicit on the result */ def findManifest(tp: Type, manifestClass: Symbol = if (full) FullManifestClass else PartialManifestClass) = - inferImplicit(tree, appliedType(manifestClass, tp), reportAmbiguous = true, isView = false, context).tree + inferImplicitFor(appliedType(manifestClass, tp), tree, context).tree def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) def mot(tp0: Type, from: List[Symbol], to: List[Type]): SearchResult = { @@ -1413,7 +1434,7 @@ trait Implicits { } if (result.isFailure && settings.debug) // debuglog is not inlined for some reason - log("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+implicitsOfExpectedType) + log(s"no implicits found for ${pt} ${pt.typeSymbol.info.baseClasses} ${implicitsOfExpectedType}") result } @@ -1450,9 +1471,9 @@ trait Implicits { } } - object ImplicitNotFoundMsg { - def unapply(sym: Symbol): Option[(Message)] = sym.implicitNotFoundMsg match { - case Some(m) => Some(new Message(sym, m)) + class ImplicitAnnotationMsg(f: Symbol => Option[String], clazz: Symbol, annotationName: String) { + def unapply(sym: Symbol): Option[(Message)] = f(sym) match { + case Some(m) => Some(new Message(sym, m, annotationName)) case None if sym.isAliasType => // perform exactly one step of dealiasing // this is necessary because ClassManifests are now aliased to ClassTags @@ -1464,41 +1485,45 @@ trait Implicits { // check the message's syntax: should be a string literal that may contain occurrences of the string "${X}", // where `X` refers to a type parameter of `sym` def check(sym: Symbol): Option[String] = - sym.getAnnotation(ImplicitNotFoundClass).flatMap(_.stringArg(0) match { - case Some(m) => new Message(sym, m).validate - case None => Some("Missing argument `msg` on implicitNotFound annotation.") + sym.getAnnotation(clazz).flatMap(_.stringArg(0) match { + case Some(m) => new Message(sym, m, annotationName).validate + case None => Some(s"Missing argument `msg` on $annotationName annotation.") }) + } + object ImplicitNotFoundMsg extends ImplicitAnnotationMsg(_.implicitNotFoundMsg, ImplicitNotFoundClass, "implicitNotFound") + + object ImplicitAmbiguousMsg extends ImplicitAnnotationMsg(_.implicitAmbiguousMsg, ImplicitAmbiguousClass, "implicitAmbiguous") + + class Message(sym: Symbol, msg: String, annotationName: String) { // http://dcsobral.blogspot.com/2010/01/string-interpolation-in-scala-with.html private val Intersobralator = """\$\{\s*([^}\s]+)\s*\}""".r - class Message(sym: Symbol, msg: String) { - private def interpolate(text: String, vars: Map[String, String]) = - Intersobralator.replaceAllIn(text, (_: Regex.Match) match { - case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "") + private def interpolate(text: String, vars: Map[String, String]) = + Intersobralator.replaceAllIn(text, (_: Regex.Match) match { + case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "") // #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw) - }) + }) - private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName) - private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs + private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName) + private def typeArgsAtSym(paramTp: Type) = paramTp.baseType(sym).typeArgs - def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString)) + def format(paramName: Name, paramTp: Type): String = format(typeArgsAtSym(paramTp) map (_.toString)) - def format(typeArgs: List[String]): String = - interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc? + def format(typeArgs: List[String]): String = + interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc? - def validate: Option[String] = { - val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet - val decls = typeParamNames.toSet + def validate: Option[String] = { + val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet + val decls = typeParamNames.toSet - (refs &~ decls) match { - case s if s.isEmpty => None - case unboundNames => - val singular = unboundNames.size == 1 - val ess = if (singular) "" else "s" - val bee = if (singular) "is" else "are" - Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @implicitNotFound annotation $bee not defined by $sym.") - } + (refs &~ decls) match { + case s if s.isEmpty => None + case unboundNames => + val singular = unboundNames.size == 1 + val ess = if (singular) "" else "s" + val bee = if (singular) "is" else "are" + Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not defined by $sym.") } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 9f7bdf7aff..dc91d23011 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -164,7 +164,9 @@ trait Infer extends Checkable { | was: $restpe | now""")(normalize(restpe)) case mt @ MethodType(_, restpe) if mt.isImplicit => normalize(restpe) - case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => functionType(mt.paramTypes, normalize(restpe)) + case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => + if (phase.erasedTypes) FunctionClass(mt.params.length).tpe + else functionType(mt.paramTypes, normalize(restpe)) case NullaryMethodType(restpe) => normalize(restpe) case ExistentialType(tparams, qtpe) => newExistentialType(tparams, normalize(qtpe)) case _ => tp // @MAT aliases already handled by subtyping @@ -295,7 +297,7 @@ trait Infer extends Checkable { && !isByNameParamType(tp) && isCompatible(tp, dropByName(pt)) ) - def isCompatibleSam(tp: Type, pt: Type): Boolean = { + def isCompatibleSam(tp: Type, pt: Type): Boolean = (definitions.isFunctionType(tp) || tp.isInstanceOf[MethodType] || tp.isInstanceOf[PolyType]) && { val samFun = typer.samToFunctionType(pt) (samFun ne NoType) && isCompatible(tp, samFun) } @@ -1207,6 +1209,7 @@ trait Infer extends Checkable { } } tvars foreach instantiateTypeVar + invalidateTreeTpeCaches(tree0, tvars.map(_.origin.typeSymbol)) } /* If the scrutinee has free type parameters but the pattern does not, * we have to flip the arguments so the expected type is treated as more @@ -1217,7 +1220,7 @@ trait Infer extends Checkable { } def inferModulePattern(pat: Tree, pt: Type) = - if (!(pat.tpe <:< pt)) { + if ((pat.symbol ne null) && pat.symbol.isModule && !(pat.tpe <:< pt)) { val ptparams = freeTypeParamsOfTerms(pt) debuglog("free type params (2) = " + ptparams) val ptvars = ptparams map freshVar @@ -1446,7 +1449,7 @@ trait Infer extends Checkable { log(s"Attaching AntiPolyType-carrying overloaded type to $sym") // Multiple alternatives which are within bounds; spin up an // overloaded type which carries an "AntiPolyType" as a prefix. - val tparams = newAsSeenFromMap(pre, hd.owner) mapOver hd.typeParams + val tparams = new AsSeenFromMap(pre, hd.owner) mapOver hd.typeParams val bounds = tparams map (_.tpeHK) // see e.g., #1236 val tpe = PolyType(tparams, OverloadedType(AntiPolyType(pre, bounds), alts)) finish(sym setInfo tpe, tpe) diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 3ed128cbc5..d7c53ed3c4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -5,17 +5,13 @@ 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 import Fingerprint._ /** @@ -242,7 +238,7 @@ trait Macros extends MacroRuntimes with Traces with Helpers { if (!payload.contains(field)) failField("is supposed to be there") val raw: Any = payload(field) if (raw == null) failField(s"is not supposed to be null") - val expected = ScalaRunTime.box(clazz) + val expected = box(clazz) val actual = raw.getClass if (!expected.isAssignableFrom(actual)) failField(s"has wrong type: expected $expected, actual $actual") raw.asInstanceOf[T] @@ -259,6 +255,19 @@ trait Macros extends MacroRuntimes with Traces with Helpers { val signature = unpickle("signature", classOf[List[List[Fingerprint]]]) MacroImplBinding(isBundle, isBlackbox, className, methodName, signature, targs) } + + private def box[T](clazz: Class[T]): Class[_] = clazz match { + case java.lang.Byte.TYPE => classOf[java.lang.Byte] + case java.lang.Short.TYPE => classOf[java.lang.Short] + case java.lang.Character.TYPE => classOf[java.lang.Character] + case java.lang.Integer.TYPE => classOf[java.lang.Integer] + case java.lang.Long.TYPE => classOf[java.lang.Long] + case java.lang.Float.TYPE => classOf[java.lang.Float] + case java.lang.Double.TYPE => classOf[java.lang.Double] + case java.lang.Void.TYPE => classOf[scala.runtime.BoxedUnit] + case java.lang.Boolean.TYPE => classOf[java.lang.Boolean] + case _ => clazz + } } def bindMacroImpl(macroDef: Symbol, macroImplRef: Tree): Unit = { diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index f3856db552..c03094bc6a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -6,11 +6,8 @@ package scala.tools.nsc package typechecker 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 +19,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 @@ -134,39 +120,41 @@ trait MethodSynthesis { ImplicitClassWrapper(tree).createAndEnterSymbol() } - def enterGetterSetter(tree: ValDef) { - val ValDef(mods, name, _, _) = tree - if (nme.isSetterName(name)) - ValOrValWithSetterSuffixError(tree) - - tree.symbol = ( - if (mods.isLazy) { + // TODO: see if we can link symbol creation & tree derivation by sharing the Field/Getter/Setter factories + def enterGetterSetter(tree: ValDef): Unit = { + tree.symbol = + if (tree.mods.isLazy) { val lazyValGetter = LazyValGetter(tree).createAndEnterSymbol() enterLazyVal(tree, lazyValGetter) } else { - if (mods.isPrivateLocal) - PrivateThisCaseClassParameterError(tree) - val getter = Getter(tree).createAndEnterSymbol() + val getter = Getter(tree) + val getterSym = getter.createAndEnterSymbol() + // Create the setter if necessary. - if (mods.isMutable) + if (getter.needsSetter) Setter(tree).createAndEnterSymbol() - // If abstract, the tree gets the getter's symbol. Otherwise, create a field. - if (mods.isDeferred) getter setPos tree.pos + // If the getter's abstract the tree gets the getter's symbol, + // otherwise, create a field (assume the getter requires storage). + // NOTE: we cannot look at symbol info, since we're in the process of deriving them + // (luckily, they only matter for lazy vals, which we've ruled out in this else branch, + // and `doNotDeriveField` will skip them if `!mods.isLazy`) + if (Field.noFieldFor(tree)) getterSym setPos tree.pos else enterStrictVal(tree) } - ) enterBeans(tree) } + 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)) } @@ -177,11 +165,11 @@ trait MethodSynthesis { } def addDerivedTrees(typer: Typer, stat: Tree): List[Tree] = stat match { - case vd @ ValDef(mods, name, tpt, rhs) if !noFinishGetterSetter(vd) => + case vd @ ValDef(mods, name, tpt, rhs) if deriveAccessors(vd) && !vd.symbol.isModuleVar => // If we don't save the annotations, they seem to wander off. val annotations = stat.symbol.initialize.annotations val trees = ( - allValDefDerived(vd) + (field(vd) ::: standardAccessors(vd) ::: beanAccessors(vd)) map (acc => atPos(vd.pos.focus)(acc derive annotations)) filterNot (_ eq EmptyTree) ) @@ -208,8 +196,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 @@ -221,11 +209,14 @@ trait MethodSynthesis { stat :: Nil } - def standardAccessors(vd: ValDef): List[DerivedFromValDef] = ( - if (vd.mods.isMutable && !vd.mods.isLazy) List(Getter(vd), Setter(vd)) - else if (vd.mods.isLazy) List(LazyValGetter(vd)) - else List(Getter(vd)) - ) + def standardAccessors(vd: ValDef): List[DerivedFromValDef] = + if (vd.mods.isLazy) List(LazyValGetter(vd)) + else { + val getter = Getter(vd) + if (getter.needsSetter) List(getter, Setter(vd)) + else List(getter) + } + def beanAccessors(vd: ValDef): List[DerivedFromValDef] = { val setter = if (vd.mods.isMutable) List(BeanSetter(vd)) else Nil if (vd.symbol hasAnnotation BeanPropertyAttr) @@ -234,15 +225,8 @@ trait MethodSynthesis { BooleanBeanGetter(vd) :: setter else Nil } - def allValDefDerived(vd: ValDef) = { - val field = if (vd.mods.isDeferred || (vd.mods.isLazy && hasUnitType(vd.symbol))) Nil - else List(Field(vd)) - field ::: standardAccessors(vd) ::: beanAccessors(vd) - } - // Take into account annotations so that we keep annotated unit lazy val - // to get better error message already from the cps plugin itself - def hasUnitType(sym: Symbol) = (sym.tpe.typeSymbol == UnitClass) && sym.tpe.annotations.isEmpty + def field(vd: ValDef): List[Field] = if (Field.noFieldFor(vd)) Nil else List(Field(vd)) /** This trait assembles what's needed for synthesizing derived methods. * Important: Typically, instances of this trait are created TWICE for each derived @@ -260,7 +244,6 @@ trait MethodSynthesis { def name: TermName /** The flags that are retained from the original symbol */ - def flagsMask: Long /** The flags that the derived symbol has in addition to those retained from @@ -284,8 +267,9 @@ trait MethodSynthesis { def enclClass: Symbol // Final methods to make the rest easier to reason about. - final def mods = tree.mods - final def basisSym = tree.symbol + final def mods = tree.mods + final def basisSym = tree.symbol + final def derivedMods = mods & flagsMask | flagsExtra } sealed trait DerivedFromClassDef extends DerivedFromMemberDef { @@ -297,25 +281,19 @@ 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) final def fieldSelection = Select(This(enclClass), basisSym) - final def derivedMods: Modifiers = mods & flagsMask | flagsExtra mapAnnotations (_ => Nil) def derivedSym: Symbol = tree.symbol def derivedTree: Tree = EmptyTree 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(): Symbol = { - val sym = owner.newMethod(name, tree.pos.focus, (tree.mods.flags & flagsMask) | flagsExtra) + def createAndEnterSymbol(): MethodSymbol = { + val sym = owner.newMethod(name, tree.pos.focus, derivedMods.flags) setPrivateWithin(tree, sym) enterInScope(sym) sym setInfo completer(sym) @@ -328,12 +306,35 @@ 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) } } sealed trait DerivedGetter extends DerivedFromValDef { - // TODO + // A getter must be accompanied by a setter if the ValDef is mutable. + def needsSetter = mods.isMutable } sealed trait DerivedSetter extends DerivedFromValDef { override def isSetter = true @@ -341,10 +342,13 @@ trait MethodSynthesis { case (p :: Nil) :: _ => p case _ => NoSymbol } - private def setterRhs = ( - if (mods.isDeferred || derivedSym.isOverloaded) EmptyTree + + private def setterRhs = { + assert(!derivedSym.isOverloaded, s"Unexpected overloaded setter $derivedSym for $basisSym in $enclClass") + if (Field.noFieldFor(tree) || derivedSym.isOverloaded) EmptyTree else Assign(fieldSelection, Ident(setterParam)) - ) + } + private def setterDef = DefDef(derivedSym, setterRhs) override def derivedTree: Tree = if (setterParam == NoSymbol) EmptyTree else setterDef } @@ -363,8 +367,7 @@ trait MethodSynthesis { context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $name in $enclClass / ${enclClass.info.decls}") result } - def derivedTree: DefDef = - factoryMeth(mods & flagsMask | flagsExtra, name, tree) + def derivedTree: DefDef = factoryMeth(derivedMods, name, tree) def flagsExtra: Long = METHOD | IMPLICIT | SYNTHETIC def flagsMask: Long = AccessFlags def name: TermName = tree.name.toTermName @@ -372,7 +375,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 ) @@ -385,8 +387,9 @@ trait MethodSynthesis { } } case class Getter(tree: ValDef) extends BaseGetter(tree) { - override def derivedSym = if (mods.isDeferred) basisSym else basisSym.getterIn(enclClass) - private def derivedRhs = if (mods.isDeferred) EmptyTree else fieldSelection + override def derivedSym = if (Field.noFieldFor(tree)) basisSym else basisSym.getterIn(enclClass) + private def derivedRhs = if (Field.noFieldFor(tree)) tree.rhs else fieldSelection + private def derivedTpt = { // For existentials, don't specify a type for the getter, even one derived // from the symbol! This leads to incompatible existentials for the field and @@ -396,23 +399,26 @@ 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 mods.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 } override def derivedTree: DefDef = newDefDef(derivedSym, derivedRhs)(tpt = derivedTpt) } + /** Implements lazy value accessors: - * - for lazy values of type Unit and all lazy fields inside traits, - * the rhs is the initializer itself - * - for all other lazy values z the accessor is a block of this form: - * { z = <rhs>; z } where z can be an identifier or a field. - */ + * - for lazy values of type Unit and all lazy fields inside traits, + * the rhs is the initializer itself, because we'll just "compute" the result on every access + * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap) + * - for all other lazy values z the accessor is a block of this form: + * { z = <rhs>; z } where z can be an identifier or a field. + */ case class LazyValGetter(tree: ValDef) extends BaseGetter(tree) { class ChangeOwnerAndModuleClassTraverser(oldowner: Symbol, newowner: Symbol) extends ChangeOwnerTraverser(oldowner, newowner) { @@ -432,10 +438,10 @@ trait MethodSynthesis { override def derivedTree: DefDef = { val ValDef(_, _, tpt0, rhs0) = tree val rhs1 = context.unit.transformed.getOrElse(rhs0, rhs0) - val body = ( - if (tree.symbol.owner.isTrait || hasUnitType(basisSym)) rhs1 + val body = + if (tree.symbol.owner.isTrait || Field.noFieldFor(tree)) rhs1 // TODO move tree.symbol.owner.isTrait into noFieldFor else gen.mkAssignAndReturn(basisSym, rhs1) - ) + derivedSym setPos tree.pos // cannot set it at createAndEnterSymbol because basisSym can possibly still have NoPosition val ddefRes = DefDef(derivedSym, new ChangeOwnerAndModuleClassTraverser(basisSym, derivedSym)(body)) // ValDef will have its position focused whereas DefDef will have original correct rangepos @@ -448,32 +454,45 @@ trait MethodSynthesis { } case class Setter(tree: ValDef) extends DerivedSetter { def name = tree.setterName - def category = SetterTargetClass def flagsMask = SetterFlags def flagsExtra = ACCESSOR override def derivedSym = basisSym.setterIn(enclClass) } + + object Field { + // No field for these vals (either never emitted or eliminated later on): + // - abstract vals have no value we could store (until they become concrete, potentially) + // - lazy vals of type Unit + // - [Emitted, later removed during AddInterfaces/Mixins] concrete vals in traits can't have a field + // - [Emitted, later removed during Constructors] a concrete val with a statically known value (Unit / ConstantType) + // performs its side effect according to lazy/strict semantics, but doesn't need to store its value + // each access will "evaluate" the RHS (a literal) again + // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer. + // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does. + // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`), + // as the symbol info is in the process of being created then. + // TODO: harmonize tree & symbol creation + // TODO: the `def field` call-site 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 - override def derivedTree = ( - if (mods.isDeferred) EmptyTree - else if (mods.isLazy) copyValDef(tree)(mods = mods | flagsExtra, name = this.name, rhs = EmptyTree).setPos(tree.pos.focus) + + // handle lazy val first for now (we emit a Field even though we probably shouldn't...) + override def derivedTree = + if (mods.isLazy) copyValDef(tree)(mods = mods | flagsExtra, name = this.name, rhs = EmptyTree).setPos(tree.pos.focus) + else if (Field.noFieldFor(tree)) EmptyTree else copyValDef(tree)(mods = mods | flagsExtra, name = this.name) - ) + } case class Param(tree: ValDef) extends DerivedFromValDef { def name = tree.name - def category = ParamTargetClass def flagsMask = -1L def flagsExtra = 0L - override def keepClean = true override def derivedTree = EmptyTree } def validateParam(tree: ValDef) { @@ -487,7 +506,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 @@ -501,18 +519,16 @@ trait MethodSynthesis { // Derives a tree without attempting to use the original tree's symbol. override def derivedTree = { atPos(tree.pos.focus) { - DefDef(derivedMods, name, Nil, ListOfNil, tree.tpt.duplicate, + DefDef(derivedMods mapAnnotations (_ => Nil), name, Nil, ListOfNil, tree.tpt.duplicate, if (isDeferred) EmptyTree else Select(This(owner), tree.name) ) } } - override def createAndEnterSymbol(): Symbol = enterSyntheticSym(derivedTree) + override def createAndEnterSymbol(): MethodSymbol = enterSyntheticSym(derivedTree).asInstanceOf[MethodSymbol] } case class BooleanBeanGetter(tree: ValDef) extends BeanAccessor("is") with AnyBeanGetter { } case class BeanGetter(tree: ValDef) extends BeanAccessor("get") with AnyBeanGetter { } - 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 4ad81b60ae..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 @@ -115,21 +114,14 @@ trait Namers extends MethodSynthesis { TypeSigError(tree, ex) alt } - // PRIVATE | LOCAL are fields generated for primary constructor arguments - // @PP: ...or fields declared as private[this]. PARAMACCESSOR marks constructor arguments. - // Neither gets accessors so the code is as far as I know still correct. - def noEnterGetterSetter(vd: ValDef) = !vd.mods.isLazy && ( - !owner.isClass - || (vd.mods.isPrivateLocal && !vd.mods.isCaseAccessor) - || (vd.name startsWith nme.OUTER) - || (context.unit.isJava) - || isEnumConstant(vd) - ) - def noFinishGetterSetter(vd: ValDef) = ( - (vd.mods.isPrivateLocal && !vd.mods.isLazy) // all lazy vals need accessors, even private[this] - || vd.symbol.isModuleVar - || isEnumConstant(vd)) + // All lazy vals need accessors, including those owned by terms (e.g., in method) or private[this] in a class + def deriveAccessors(vd: ValDef) = vd.mods.isLazy || (owner.isClass && deriveAccessorsInClass(vd)) + + private def deriveAccessorsInClass(vd: ValDef) = + !vd.mods.isPrivateLocal && // note, private[this] lazy vals do get accessors -- see outer disjunction of deriveAccessors + !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter + !isEnumConstant(vd) // enums can only occur in classes, so only check here /** Determines whether this field holds an enum constant. * To qualify, the following conditions must be met: @@ -170,13 +162,9 @@ trait Namers extends MethodSynthesis { def updatePosFlags(sym: Symbol, pos: Position, flags: Long): Symbol = { debuglog("[overwrite] " + sym) val newFlags = (sym.flags & LOCKED) | flags - sym.rawInfo match { - case tr: TypeRef => - // !!! needed for: pos/t5954d; the uniques type cache will happily serve up the same TypeRef - // over this mutated symbol, and we witness a stale cache for `parents`. - tr.invalidateCaches() - case _ => - } + // !!! needed for: pos/t5954d; the uniques type cache will happily serve up the same TypeRef + // over this mutated symbol, and we witness a stale cache for `parents`. + invalidateCaches(sym.rawInfo, sym :: sym.moduleClass :: Nil) sym reset NoType setFlag newFlags setPos pos sym.moduleClass andAlso (updatePosFlags(_, pos, moduleClassFlags(flags))) @@ -655,14 +643,20 @@ trait Namers extends MethodSynthesis { } } - def enterValDef(tree: ValDef) { - if (noEnterGetterSetter(tree)) - assignAndEnterFinishedSymbol(tree) - else - enterGetterSetter(tree) + def enterValDef(tree: ValDef): Unit = { + val isScala = !context.unit.isJava + if (isScala) { + if (nme.isSetterName(tree.name)) ValOrVarWithSetterSuffixError(tree) + if (tree.mods.isPrivateLocal && tree.mods.isCaseAccessor) PrivateThisCaseClassParameterError(tree) + } - if (isEnumConstant(tree)) + if (isScala && deriveAccessors(tree)) enterGetterSetter(tree) + else assignAndEnterFinishedSymbol(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 = { @@ -1600,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) } @@ -1643,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 @@ -1658,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/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 39cd610b1c..5062289ed1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -9,6 +9,7 @@ package typechecker import symtab.Flags._ import scala.collection.mutable import scala.reflect.ClassTag +import PartialFunction.{ cond => when } /** * @author Lukas Rytz @@ -468,8 +469,7 @@ trait NamesDefaults { self: Analyzer => else { // isClass also works for methods in objects, owner is the ModuleClassSymbol if (param.owner.owner.isClass) { - // .toInterface: otherwise we get the method symbol of the impl class - param.owner.owner.toInterface.info.member(defGetterName) + param.owner.owner.info.member(defGetterName) } else { // the owner of the method is another method. find the default // getter in the context. @@ -551,64 +551,73 @@ trait NamesDefaults { self: Analyzer => } } - /** - * Removes name assignments from args. Additionally, returns an array mapping - * argument indices from call-site-order to definition-site-order. + /** Removes name assignments from args. Additionally, returns an array mapping + * argument indices from call-site-order to definition-site-order. * - * Verifies that names are not specified twice, positional args don't appear - * after named ones. + * Verifies that names are not specified twice, and positional args don't appear after named ones. */ def removeNames(typer: Typer)(args: List[Tree], params: List[Symbol]): (List[Tree], Array[Int]) = { implicit val context0 = typer.context - // maps indices from (order written by user) to (order of definition) - val argPos = Array.fill(args.length)(-1) - var positionalAllowed = true - val namelessArgs = mapWithIndex(args) { (arg, argIndex) => - arg match { - case arg @ AssignOrNamedArg(Ident(name), rhs) => - def matchesName(param: Symbol) = !param.isSynthetic && ( - (param.name == name) || (param.deprecatedParamName match { - case Some(`name`) => - context0.deprecationWarning(arg.pos, param, - s"the parameter name $name has been deprecated. Use ${param.name} instead.") - true - case _ => false - }) - ) - val paramPos = params indexWhere matchesName - if (paramPos == -1) { - if (positionalAllowed) { - argPos(argIndex) = argIndex - // prevent isNamed from being true when calling doTypedApply recursively, - // treat the arg as an assignment of type Unit - Assign(arg.lhs, rhs) setPos arg.pos - } - else UnknownParameterNameNamesDefaultError(arg, name) - } - else if (argPos contains paramPos) { + def matchesName(param: Symbol, name: Name, argIndex: Int) = { + def warn(w: String) = context0.deprecationWarning(args(argIndex).pos, param, w) + def checkDeprecation(anonOK: Boolean) = + when (param.deprecatedParamName) { + case Some(`name`) => true + case Some(nme.NO_NAME) => anonOK + } + def checkName = { + val res = param.name == name + if (res && checkDeprecation(true)) warn(s"naming parameter $name has been deprecated.") + res + } + def checkAltName = { + val res = checkDeprecation(false) + if (res) warn(s"the parameter name $name has been deprecated. Use ${param.name} instead.") + res + } + !param.isSynthetic && (checkName || checkAltName) + } + // argPos maps indices from (order written by user) to (order of definition) + val argPos = Array.fill(args.length)(-1) + val namelessArgs = { + var positionalAllowed = true + def stripNamedArg(arg: AssignOrNamedArg, argIndex: Int): Tree = { + val AssignOrNamedArg(Ident(name), rhs) = arg + params indexWhere (p => matchesName(p, name, argIndex)) match { + case -1 if positionalAllowed => + // prevent isNamed from being true when calling doTypedApply recursively, + // treat the arg as an assignment of type Unit + Assign(arg.lhs, rhs) setPos arg.pos + case -1 => + UnknownParameterNameNamesDefaultError(arg, name) + case paramPos if argPos contains paramPos => val existingArgIndex = argPos.indexWhere(_ == paramPos) - val otherName = args(paramPos) match { - case AssignOrNamedArg(Ident(oName), rhs) if oName != name => Some(oName) - case _ => None + val otherName = Some(args(paramPos)) collect { + case AssignOrNamedArg(Ident(oName), _) if oName != name => oName } DoubleParamNamesDefaultError(arg, name, existingArgIndex+1, otherName) - } else if (isAmbiguousAssignment(typer, params(paramPos), arg)) + case paramPos if isAmbiguousAssignment(typer, params(paramPos), arg) => AmbiguousReferenceInNamesDefaultError(arg, name) - else { - // if the named argument is on the original parameter - // position, positional after named is allowed. - if (argIndex != paramPos) - positionalAllowed = false - argPos(argIndex) = paramPos + case paramPos if paramPos != argIndex => + positionalAllowed = false // named arg is not in original parameter order: require names after this + argPos(argIndex) = paramPos // fix up the arg position rhs - } - case _ => - argPos(argIndex) = argIndex - if (positionalAllowed) arg - else PositionalAfterNamedNamesDefaultError(arg) + case _ => rhs + } + } + mapWithIndex(args) { + case (arg: AssignOrNamedArg, argIndex) => + val t = stripNamedArg(arg, argIndex) + if (!t.isErroneous && argPos(argIndex) < 0) argPos(argIndex) = argIndex + t + case (arg, argIndex) => + if (positionalAllowed) { + argPos(argIndex) = argIndex + arg + } else + PositionalAfterNamedNamesDefaultError(arg) } } - (namelessArgs, argPos) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 3b2e07bdbd..da269168ec 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" @@ -86,17 +88,19 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans if (sym.hasAccessBoundary) "" + sym.privateWithin.name else "" ) - def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type): Boolean = (tp1.dealiasWiden, tp2.dealiasWiden) match { + def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type, isModuleOverride: Boolean): Boolean = (tp1.dealiasWiden, tp2.dealiasWiden) match { case (MethodType(List(), rtp1), NullaryMethodType(rtp2)) => rtp1 <:< rtp2 case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) => rtp1 <:< rtp2 case (TypeRef(_, sym, _), _) if sym.isModuleClass => - overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix) + overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix, isModuleOverride) case _ => def classBoundAsSeen(tp: Type) = tp.typeSymbol.classBound.asSeenFrom(prefix, tp.typeSymbol.owner) - - (tp1 <:< tp2) || ( // object override check + (tp1 <:< tp2) || isModuleOverride && ( + // Object override check. This requires that both the overridden and the overriding member are object + // definitions. The overriding module type is allowed to replace the original one with the same name + // as long as it conform to the original non-singleton type. tp1.typeSymbol.isModuleClass && tp2.typeSymbol.isModuleClass && { val cb1 = classBoundAsSeen(tp1) val cb2 = classBoundAsSeen(tp2) @@ -518,7 +522,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } def checkOverrideTerm() { other.cookJavaRawInfo() // #2454 - if (!overridesTypeInPrefix(lowType, highType, rootType)) { // 8 + if (!overridesTypeInPrefix(lowType, highType, rootType, low.isModuleOrModuleClass && high.isModuleOrModuleClass)) { // 8 overrideTypeError() explainTypes(lowType, highType) } @@ -1150,11 +1154,13 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans def toConstructor(pos: Position, tpe: Type): Tree = { val rtpe = tpe.finalResultType assert(rtpe.typeSymbol hasFlag CASE, tpe) - localTyper.typedOperator { + val tree = localTyper.typedOperator { atPos(pos) { Select(New(TypeTree(rtpe)), rtpe.typeSymbol.primaryConstructor) } } + checkUndesiredProperties(rtpe.typeSymbol, tree.pos) + tree } override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { @@ -1182,59 +1188,54 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans private def eliminateModuleDefs(moduleDef: Tree): List[Tree] = exitingRefchecks { val ModuleDef(_, _, impl) = moduleDef val module = moduleDef.symbol - val moduleClass = module.moduleClass val site = module.owner val moduleName = module.name.toTermName // The typer doesn't take kindly to seeing this ClassDef; we have to // set NoType so it will be ignored. - val cdef = ClassDef(moduleClass, impl) setType NoType - - // This code is related to the fix of SI-9375, which stops adding `readResolve` methods to - // non-static (nested) modules. Before the fix, the method would cause the module accessor - // to become notPrivate. To prevent binary changes in the 2.11.x branch, we mimic that behavior. - // There is a bit of code duplication between here and SyntheticMethods. We cannot call - // makeNotPrivate already in SyntheticMethod: that is during type checking, and not all references - // are resolved yet, so we cannot rename a definition. This code doesn't exist in the 2.12.x branch. - def hasConcreteImpl(name: Name) = moduleClass.info.member(name).alternatives exists (m => !m.isDeferred) - val hadReadResolveBeforeSI9375 = moduleClass.isSerializable && !hasConcreteImpl(nme.readResolve) - if (hadReadResolveBeforeSI9375) - moduleClass.sourceModule.makeNotPrivate(moduleClass.sourceModule.owner) - - // Create the module var unless the immediate owner is a class and - // the module var already exists there. See SI-5012, SI-6712. - def findOrCreateModuleVar() = { - val vsym = ( - if (site.isTerm) NoSymbol - else site.info decl nme.moduleVarName(moduleName) - ) - vsym orElse (site newModuleVarSymbol module) - } - def newInnerObject() = { - // Create the module var unless it is already in the module owner's scope. - // The lookup is on module.enclClass and not module.owner lest there be a - // nullary method between us and the class; see SI-5012. - val moduleVar = findOrCreateModuleVar() - val 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 - } + val cdef = ClassDef(module.moduleClass, impl) setType NoType + def matchingInnerObject() = { val newFlags = (module.flags | STABLE) & ~MODULE - val newInfo = NullaryMethodType(moduleClass.tpe) + val newInfo = NullaryMethodType(module.moduleClass.tpe) val accessor = site.newMethod(moduleName, module.pos, newFlags) setInfoAndEnter newInfo DefDef(accessor, Select(This(site), module)) :: Nil } val newTrees = cdef :: ( if (module.isStatic) + // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O. if (module.isOverridingSymbol) matchingInnerObject() else Nil else - newInnerObject() + 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) => @@ -1480,10 +1481,13 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans case m: MemberDef => val sym = m.symbol applyChecks(sym.annotations) - // validate implicitNotFoundMessage - analyzer.ImplicitNotFoundMsg.check(sym) foreach { warn => - reporter.warning(tree.pos, f"Invalid implicitNotFound message for ${sym}%s${sym.locationString}%s:%n$warn") - } + + def messageWarning(name: String)(warn: String) = + reporter.warning(tree.pos, f"Invalid $name message for ${sym}%s${sym.locationString}%s:%n$warn") + + // validate implicitNotFoundMessage and implicitAmbiguousMessage + analyzer.ImplicitNotFoundMsg.check(sym) foreach messageWarning("implicitNotFound") + analyzer.ImplicitAmbiguousMsg.check(sym) foreach messageWarning("implicitAmbiguous") case tpt@TypeTree() => if(tpt.original != null) { @@ -1526,11 +1530,21 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans } private def transformCaseApply(tree: Tree) = { + def loop(t: Tree): Unit = t match { + case Ident(_) => + checkUndesiredProperties(t.symbol, t.pos) + case Select(qual, _) => + checkUndesiredProperties(t.symbol, t.pos) + loop(qual) + case _ => + } + tree foreach { case i@Ident(_) => enterReference(i.pos, i.symbol) // SI-5390 need to `enterReference` for `a` in `a.B()` case _ => } + loop(tree) toConstructor(tree.pos, tree.tpe) } @@ -1669,11 +1683,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 1b3f066fc1..6d883aee3d 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: @@ -87,33 +89,18 @@ trait SyntheticMethods extends ast.TreeDSL { def accessors = clazz.caseFieldAccessors val arity = accessors.size - // If this is ProductN[T1, T2, ...], accessorLub is the lub of T1, T2, ..., . - // !!! Hidden behind -Xexperimental due to bummer type inference bugs. - // Refining from Iterator[Any] leads to types like - // - // Option[Int] { def productIterator: Iterator[String] } - // - // appearing legitimately, but this breaks invariant places - // like Tags and Arrays which are not robust and infer things - // which they shouldn't. - val accessorLub = ( - if (settings.Xexperimental) { - global.lub(accessors map (_.tpe.finalResultType)) match { - case RefinedType(parents, decls) if !decls.isEmpty => intersectionType(parents) - case tp => tp - } - } - else AnyTpe - ) def forwardToRuntime(method: Symbol): Tree = forwardMethod(method, getMember(ScalaRunTimeModule, (method.name prepend "_")))(mkThis :: _) - def callStaticsMethod(name: String)(args: Tree*): Tree = { - val method = termMember(RuntimeStaticsModule, name) + def callStaticsMethodName(name: TermName)(args: Tree*): Tree = { + val method = RuntimeStaticsModule.info.member(name) Apply(gen.mkAttributedRef(method), args.toList) } + def callStaticsMethod(name: String)(args: Tree*): Tree = + callStaticsMethodName(newTermName(name))(args: _*) + // Any concrete member, including private def hasConcreteImpl(name: Name) = clazz.info.member(name).alternatives exists (m => !m.isDeferred) @@ -125,8 +112,8 @@ trait SyntheticMethods extends ast.TreeDSL { } } def productIteratorMethod = { - createMethod(nme.productIterator, iteratorOfType(accessorLub))(_ => - gen.mkMethodCall(ScalaRunTimeModule, nme.typedProductIterator, List(accessorLub), List(mkThis)) + createMethod(nme.productIterator, iteratorOfType(AnyTpe))(_ => + gen.mkMethodCall(ScalaRunTimeModule, nme.typedProductIterator, List(AnyTpe), List(mkThis)) ) } @@ -246,7 +233,7 @@ trait SyntheticMethods extends ast.TreeDSL { List( Product_productPrefix -> (() => constantNullary(nme.productPrefix, clazz.name.decode)), Product_productArity -> (() => constantNullary(nme.productArity, arity)), - Product_productElement -> (() => perElementMethod(nme.productElement, accessorLub)(mkThisSelect)), + Product_productElement -> (() => perElementMethod(nme.productElement, AnyTpe)(mkThisSelect)), Product_iterator -> (() => productIteratorMethod), Product_canEqual -> (() => canEqualMethod) // This is disabled pending a reimplementation which doesn't add any @@ -261,10 +248,10 @@ trait SyntheticMethods extends ast.TreeDSL { case BooleanClass => If(Ident(sym), Literal(Constant(1231)), Literal(Constant(1237))) case IntClass => Ident(sym) case ShortClass | ByteClass | CharClass => Select(Ident(sym), nme.toInt) - case LongClass => callStaticsMethod("longHash")(Ident(sym)) - case DoubleClass => callStaticsMethod("doubleHash")(Ident(sym)) - case FloatClass => callStaticsMethod("floatHash")(Ident(sym)) - case _ => callStaticsMethod("anyHash")(Ident(sym)) + case LongClass => callStaticsMethodName(nme.longHash)(Ident(sym)) + case DoubleClass => callStaticsMethodName(nme.doubleHash)(Ident(sym)) + case FloatClass => callStaticsMethodName(nme.floatHash)(Ident(sym)) + case _ => callStaticsMethodName(nme.anyHash)(Ident(sym)) } } @@ -354,16 +341,18 @@ trait SyntheticMethods extends ast.TreeDSL { } for ((m, impl) <- methods ; if shouldGenerate(m)) yield impl() } - def extras = ( + def extras = { if (needsReadResolve) { // Aha, I finally decoded the original comment. // This method should be generated as private, but apparently if it is, then // it is name mangled afterward. (Wonder why that is.) So it's only protected. // For sure special methods like "readResolve" should not be mangled. - List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => { m setFlag PRIVATE ; REF(clazz.sourceModule) })) + List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => { + m setFlag PRIVATE; REF(clazz.sourceModule) + })) } else Nil - ) + } try impls ++ extras catch { case _: TypeError if reporter.hasErrors => Nil } @@ -381,7 +370,14 @@ trait SyntheticMethods extends ast.TreeDSL { for (ddef @ DefDef(_, _, _, _, _, _) <- templ.body ; if isRewrite(ddef.symbol)) { val original = ddef.symbol - val newAcc = deriveMethod(ddef.symbol, name => context.unit.freshTermName(name + "$")) { newAcc => + val i = original.owner.caseFieldAccessors.indexOf(original) + def freshAccessorName = { + devWarning(s"Unable to find $original among case accessors of ${original.owner}: ${original.owner.caseFieldAccessors}") + context.unit.freshTermName(original.name + "$") + } + def nameSuffixedByParamIndex = original.name.append(nme.CASE_ACCESSOR + "$" + i).toTermName + val newName = if (i < 0) freshAccessorName else nameSuffixedByParamIndex + val newAcc = deriveMethod(ddef.symbol, name => newName) { newAcc => newAcc.makePublic newAcc resetFlag (ACCESSOR | PARAMACCESSOR | OVERRIDE) ddef.rhs.duplicate diff --git a/src/compiler/scala/tools/nsc/typechecker/Tags.scala b/src/compiler/scala/tools/nsc/typechecker/Tags.scala index 56127f4026..e29451f379 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Tags.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Tags.scala @@ -13,16 +13,7 @@ trait Tags { private val runDefinitions = currentRun.runDefinitions private def resolveTag(pos: Position, taggedTp: Type, allowMaterialization: Boolean) = enteringTyper { - def wrapper (tree: => Tree): Tree = if (allowMaterialization) (context.withMacrosEnabled[Tree](tree)) else (context.withMacrosDisabled[Tree](tree)) - wrapper(inferImplicit( - EmptyTree, - taggedTp, - reportAmbiguous = true, - isView = false, - context, - saveAmbiguousDivergent = true, - pos - ).tree) + context.withMacros(enabled = allowMaterialization) { inferImplicitByType(taggedTp, context, pos).tree } } /** Finds in scope or materializes a ClassTag. diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index e8db8309f1..990edcd86d 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) { @@ -113,8 +111,6 @@ abstract class TreeCheckers extends Analyzer { newSyms += sym else if (prevTrees exists (t => (t eq tree) || (t.symbol == sym))) () - else if (prevTrees exists (_.symbol.owner == sym.owner.implClass)) - errorFn("Noticed " + ownerstr(sym) + " moving to implementation class.") else { val s1 = (prevTrees map wholetreestr).sorted.distinct val s2 = wholetreestr(tree) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 6b73a538df..8f5c4b9f6d 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._ @@ -105,12 +105,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // that are turned private by typedBlock private final val SYNTHETIC_PRIVATE = TRANS_FLAG - private final val InterpolatorCodeRegex = """\$\{.*?\}""".r + private final val InterpolatorCodeRegex = """\$\{\s*(.*?)\s*\}""".r private final val InterpolatorIdentRegex = """\$[$\w]+""".r // note that \w doesn't include $ 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._ @@ -128,6 +128,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def canTranslateEmptyListToNil = true def missingSelectErrorTree(tree: Tree, qual: Tree, name: Name): Tree = tree + // when type checking during erasure, generate erased types in spots that aren't transformed by erasure + // (it erases in TypeTrees, but not in, e.g., the type a Function node) + def phasedAppliedType(sym: Symbol, args: List[Type]) = { + val tp = appliedType(sym, args) + if (phase.erasedTypes) erasure.specialScalaErasure(tp) else tp + } + def typedDocDef(docDef: DocDef, mode: Mode, pt: Type): Tree = typed(docDef.definition, mode, pt) @@ -151,7 +158,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper for(ar <- argResultsBuff) paramTp = paramTp.subst(ar.subst.from, ar.subst.to) - val res = if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context) + val res = + if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure + else inferImplicitFor(paramTp, fun, context, reportAmbiguous = context.reportErrors) argResultsBuff += res if (res.isSuccess) { @@ -187,14 +196,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper !from.isError && !to.isError && context.implicitsEnabled - && (inferView(context.tree, from, to, reportAmbiguous = false, saveErrors = true) != EmptyTree) + && (inferView(context.tree, from, to, reportAmbiguous = false) != EmptyTree) // SI-8230 / SI-8463 We'd like to change this to `saveErrors = false`, but can't. // For now, we can at least pass in `context.tree` rather then `EmptyTree` so as // to avoid unpositioned type errors. ) - def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean): Tree = - inferView(tree, from, to, reportAmbiguous, saveErrors = true) /** Infer an implicit conversion (`view`) between two types. * @param tree The tree which needs to be converted. @@ -207,25 +214,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * during the inference of a view be put into the original buffer. * False iff we don't care about them. */ - def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { - debuglog("infer view from "+from+" to "+to)//debug - if (isPastTyper) EmptyTree - else from match { - case MethodType(_, _) => EmptyTree - case OverloadedType(_, _) => EmptyTree - case PolyType(_, _) => EmptyTree - case _ => - def wrapImplicit(from: Type): Tree = { - val result = inferImplicit(tree, functionType(from.withoutAnnotations :: Nil, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent = saveErrors) - if (result.subst != EmptyTreeTypeSubstituter) { - result.subst traverse tree - notifyUndetparamsInferred(result.subst.from, result.subst.to) - } - result.tree - } - wrapImplicit(from) orElse wrapImplicit(byNameType(from)) + def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = + if (isPastTyper || from.isInstanceOf[MethodType] || from.isInstanceOf[OverloadedType] || from.isInstanceOf[PolyType]) EmptyTree + else { + debuglog(s"Inferring view from $from to $to for $tree (reportAmbiguous= $reportAmbiguous, saveErrors=$saveErrors)") + + val fromNoAnnot = from.withoutAnnotations + val result = inferImplicitView(fromNoAnnot, to, tree, context, reportAmbiguous, saveErrors) match { + case fail if fail.isFailure => inferImplicitView(byNameType(fromNoAnnot), to, tree, context, reportAmbiguous, saveErrors) + case ok => ok + } + + if (result.subst != EmptyTreeTypeSubstituter) { + result.subst traverse tree + notifyUndetparamsInferred(result.subst.from, result.subst.to) + } + result.tree } - } import infer._ @@ -239,6 +244,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper var context = context0 def context1 = context + // for use with silent type checking to when we can't have results with undetermined type params + // note that this captures the context var + val isMonoContext = (_: Any) => context.undetparams.isEmpty + def dropExistential(tp: Type): Type = tp match { case ExistentialType(tparams, tpe) => new SubstWildcardMap(tparams).apply(tp) @@ -542,7 +551,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } val qual = typedQualifier { atPos(tree.pos.makeTransparent) { tree match { - case Ident(_) => Ident(rootMirror.getPackageObjectWithMember(pre, sym)) + case Ident(_) => + val packageObject = + if (!sym.isOverloaded && sym.owner.isModuleClass) sym.owner.sourceModule // historical optimization, perhaps no longer needed + else pre.typeSymbol.packageObject + Ident(packageObject) case Select(qual, _) => Select(qual, nme.PACKAGEkw) case SelectFromTypeTree(qual, _) => Select(qual, nme.PACKAGEkw) } @@ -721,7 +734,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper featureTrait.owner.ownerChain.takeWhile(_ != languageFeatureModule.moduleClass).reverse val featureName = (nestedOwners map (_.name + ".")).mkString + featureTrait.name def action(): Boolean = { - def hasImport = inferImplicit(EmptyTree: Tree, featureTrait.tpe, reportAmbiguous = true, isView = false, context).isSuccess + def hasImport = inferImplicitByType(featureTrait.tpe, context).isSuccess def hasOption = settings.language contains featureName val OK = hasImport || hasOption if (!OK) { @@ -798,7 +811,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * (11) Widen numeric literals to their expected type, if necessary * (12) When in mode EXPRmode, convert E to { E; () } if expected type is scala.Unit. * (13) When in mode EXPRmode, apply AnnotationChecker conversion if expected type is annotated. - * (14) When in mode EXPRmode, apply a view + * (14) When in mode EXPRmode, do SAM conversion + * (15) When in mode EXPRmode, apply a view * If all this fails, error */ protected def adapt(tree: Tree, mode: Mode, pt: Type, original: Tree = EmptyTree): Tree = { @@ -858,7 +872,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) @@ -928,24 +949,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def insertApply(): Tree = { assert(!context.inTypeConstructorAllowed, mode) //@M val adapted = adaptToName(tree, nme.apply) - def stabilize0(pre: Type): Tree = stabilize(adapted, pre, MonoQualifierModes, WildcardType) - - // TODO reconcile the overlap between Typers#stablize and TreeGen.stabilize - val qual = adapted match { - case This(_) => - gen.stabilize(adapted) - case Ident(_) => - val owner = adapted.symbol.owner - val pre = - if (owner.isPackageClass) owner.thisType - else if (owner.isClass) context.enclosingSubClassContext(owner).prefix - else NoPrefix - stabilize0(pre) - case Select(qualqual, _) => - stabilize0(qualqual.tpe) - case other => - other - } + val qual = gen.stabilize(adapted) typedPos(tree.pos, mode, pt) { Select(qual setPos tree.pos.makeTransparent, nme.apply) } @@ -1020,72 +1024,70 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - def fallbackAfterVanillaAdapt(): Tree = { - def isPopulatedPattern = { - if ((tree.symbol ne null) && tree.symbol.isModule) - inferModulePattern(tree, pt) - - isPopulated(tree.tpe, approximateAbstracts(pt)) + def adaptExprNotFunMode(): Tree = { + def lastTry(err: AbsTypeError = null): Tree = { + debuglog("error tree = " + tree) + if (settings.debug && settings.explaintypes) explainTypes(tree.tpe, pt) + if (err ne null) context.issue(err) + if (tree.tpe.isErroneous || pt.isErroneous) setError(tree) + else adaptMismatchedSkolems() } - if (mode.inPatternMode && isPopulatedPattern) - return tree - val tree1 = constfold(tree, pt) // (10) (11) - if (tree1.tpe <:< pt) - return adapt(tree1, mode, pt, original) + // TODO: should we even get to fallbackAfterVanillaAdapt for an ill-typed tree? + if (mode.typingExprNotFun && !tree.tpe.isErroneous) { + @inline def tpdPos(transformed: Tree) = typedPos(tree.pos, mode, pt)(transformed) + @inline def tpd(transformed: Tree) = typed(transformed, mode, pt) - if (mode.typingExprNotFun) { - // The <: Any requirement inhibits attempts to adapt continuation types - // to non-continuation types. - if (tree.tpe <:< AnyTpe) pt.dealias match { - case TypeRef(_, UnitClass, _) => // (12) - if (!isPastTyper && settings.warnValueDiscard) - context.warning(tree.pos, "discarded non-Unit value") - return typedPos(tree.pos, mode, pt)(Block(List(tree), Literal(Constant(())))) - case TypeRef(_, sym, _) if isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt) => - if (!isPastTyper && settings.warnNumericWiden) - context.warning(tree.pos, "implicit numeric widening") - return typedPos(tree.pos, mode, pt)(Select(tree, "to" + sym.name)) - case _ => - } - if (pt.dealias.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt)) // (13) - return typed(adaptAnnotations(tree, this, mode, pt), mode, pt) + @inline def warnValueDiscard(): Unit = + if (!isPastTyper && settings.warnValueDiscard) context.warning(tree.pos, "discarded non-Unit value") + @inline def warnNumericWiden(): Unit = + if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening") - if (hasUndets) - return instantiate(tree, mode, pt) - - if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { - // (14); the condition prevents chains of views - debuglog("inferring view from " + tree.tpe + " to " + pt) - inferView(tree, tree.tpe, pt, reportAmbiguous = true) match { - case EmptyTree => - case coercion => - def msg = "inferred view from " + tree.tpe + " to " + pt + " = " + coercion + ":" + coercion.tpe - if (settings.logImplicitConv) - context.echo(tree.pos, msg) - - debuglog(msg) - val silentContext = context.makeImplicit(context.ambiguousErrors) - val res = newTyper(silentContext).typed( - new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) - silentContext.reporter.firstError match { - case Some(err) => context.issue(err) - case None => return res + // The <: Any requirement inhibits attempts to adapt continuation types to non-continuation types. + val anyTyped = tree.tpe <:< AnyTpe + + pt.dealias match { + case TypeRef(_, UnitClass, _) if anyTyped => // (12) + warnValueDiscard() ; tpdPos(gen.mkUnitBlock(tree)) + case TypeRef(_, numValueCls, _) if anyTyped && isNumericValueClass(numValueCls) && isNumericSubType(tree.tpe, pt) => // (10) (11) + warnNumericWiden() ; tpdPos(Select(tree, s"to${numValueCls.name}")) + case dealiased if dealiased.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt) => // (13) + tpd(adaptAnnotations(tree, this, mode, pt)) + case _ => + if (hasUndets) instantiate(tree, mode, pt) + else { + // (14) sam conversion + // TODO: figure out how to avoid partially duplicating typedFunction (samMatchingFunction) + // Could we infer the SAM type, assign it to the tree and add the attachment, + // all in one fell swoop at the end of typedFunction? + val samAttach = inferSamType(tree, pt, mode) + + if (samAttach.samTp ne NoType) tree.setType(samAttach.samTp).updateAttachment(samAttach) + else { // (15) implicit view application + val coercion = + if (context.implicitsEnabled) inferView(tree, tree.tpe, pt) + else EmptyTree + if (coercion ne EmptyTree) { + def msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" + if (settings.logImplicitConv) context.echo(tree.pos, msg) + else debuglog(msg) + + val viewApplied = new ApplyImplicitView(coercion, List(tree)) setPos tree.pos + val silentContext = context.makeImplicit(context.ambiguousErrors) + val typedView = newTyper(silentContext).typed(viewApplied, mode, pt) + + silentContext.reporter.firstError match { + case None => typedView + case Some(err) => lastTry(err) + } + } else lastTry() } - } + } } - } - - debuglog("error tree = " + tree) - if (settings.debug && settings.explaintypes) - explainTypes(tree.tpe, pt) - - if (tree.tpe.isErroneous || pt.isErroneous) - setError(tree) - else - adaptMismatchedSkolems() + } else lastTry() } + def vanillaAdapt(tree: Tree) = { def applyPossible = { def applyMeth = member(adaptToName(tree, nme.apply), nme.apply) @@ -1119,8 +1121,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } else if (tree.tpe <:< pt) tree - else - fallbackAfterVanillaAdapt() + else if (mode.inPatternMode && { inferModulePattern(tree, pt); isPopulated(tree.tpe, approximateAbstracts(pt)) }) + tree + else { + val constFolded = constfold(tree, pt) + if (constFolded.tpe <:< pt) adapt(constFolded, mode, pt, original) // set stage for (0) + else adaptExprNotFunMode() // (10) -- (15) + } } // begin adapt @@ -1185,7 +1192,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val savedUndetparams = context.undetparams silent(_.instantiate(tree, mode, UnitTpe)) orElse { _ => context.undetparams = savedUndetparams - val valueDiscard = atPos(tree.pos)(Block(List(instantiate(tree, mode, WildcardType)), Literal(Constant(())))) + val valueDiscard = atPos(tree.pos)(gen.mkUnitBlock(instantiate(tree, mode, WildcardType))) typed(valueDiscard, mode, UnitTpe) } } @@ -1246,7 +1253,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * If no conversion is found, return `qual` unchanged. * */ - def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { + def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = { def doAdapt(restpe: Type) = //util.trace("adaptToArgs "+qual+", name = "+name+", argtpes = "+(args map (_.tpe))+", pt = "+pt+" = ") adaptToMember(qual, HasMethodMatching(name, args map (_.tpe), restpe), reportAmbiguous, saveErrors) @@ -1262,7 +1269,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * a method `name`. If that's ambiguous try taking arguments into * account using `adaptToArguments`. */ - def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { + def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = { def onError(reportError: => Tree): Tree = context.tree match { case Apply(tree1, args) if (tree1 eq tree) && args.nonEmpty => ( silent (_.typedArgs(args.map(_.duplicate), mode)) @@ -1707,6 +1714,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper psym addChild context.owner else pending += ParentSealedInheritanceError(parent, psym) + if (psym.isLocalToBlock && !phase.erasedTypes) + psym addChild context.owner val parentTypeOfThis = parent.tpe.dealias.typeOfThis if (!(selfType <:< parentTypeOfThis) && @@ -1742,17 +1751,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper classinfo.parents map (_.instantiateTypeParams(List(tparam), List(AnyRefTpe))), classinfo.decls, clazz) - clazz.setInfo { - clazz.info match { - case PolyType(tparams, _) => PolyType(tparams, newinfo) - case _ => newinfo - } - } + updatePolyClassInfo(clazz, newinfo) FinitaryError(tparam) } } } + private def updatePolyClassInfo(clazz: Symbol, newinfo: ClassInfoType): clazz.type = { + clazz.setInfo { + clazz.info match { + case PolyType(tparams, _) => PolyType(tparams, newinfo) + case _ => newinfo + } + } + } + def typedClassDef(cdef: ClassDef): Tree = { val clazz = cdef.symbol val typedMods = typedModifiers(cdef.mods) @@ -1861,10 +1874,30 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // please FIXME: uncommenting this line breaks everything // val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents) val clazz = context.owner + + val parentTypes = parents1.map(_.tpe) + + // The parents may have been normalized by typedParentTypes. + // We must update the info as well, or we won't find the super constructor for our now-first parent class + // Consider `class C ; trait T extends C ; trait U extends T` + // `U`'s info will start with parent `T`, but `typedParentTypes` will return `List(C, T)` (`== parents1`) + // now, the super call in the primary ctor will fail to find `C`'s ctor, since it bases its search on + // `U`'s info, not the trees. + // + // For correctness and performance, we restrict this rewrite to anonymous classes, + // as others have their parents in order already (it seems!), and we certainly + // don't want to accidentally rewire superclasses for e.g. the primitive value classes. + // + // TODO: Find an example of a named class needing this rewrite, I tried but couldn't find one. + if (clazz.isAnonymousClass && clazz.info.parents != parentTypes) { +// println(s"updating parents of $clazz from ${clazz.info.parents} to $parentTypes") + updatePolyClassInfo(clazz, ClassInfoType(parentTypes, clazz.info.decls, clazz)) + } + clazz.annotations.map(_.completeInfo()) if (templ.symbol == NoSymbol) templ setSymbol clazz.newLocalDummy(templ.pos) - val self1 = templ.self match { + val self1 = (templ.self: @unchecked) match { case vd @ ValDef(_, _, tpt, EmptyTree) => val tpt1 = checkNoEscaping.privates( clazz.thisSym, @@ -2228,7 +2261,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val allParams = meth.paramss.flatten for (p <- allParams) { for (n <- p.deprecatedParamName) { - if (allParams.exists(p1 => p1.name == n || (p != p1 && p1.deprecatedParamName.exists(_ == n)))) + if (allParams.exists(p1 => p != p1 && (p1.name == n || p1.deprecatedParamName.exists(_ == n)))) DeprecatedParamNameError(p, n) } } @@ -2581,8 +2614,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // the default uses applyOrElse's first parameter since the scrut's type has been widened val match_ = { - val defaultCase = methodBodyTyper.typedCase( - mkDefaultCase(methodBodyTyper.typed1(REF(default) APPLY (REF(x)), mode, B1.tpe).setType(B1.tpe)), argTp, B1.tpe) + val cdef = mkDefaultCase(methodBodyTyper.typed1(REF(default) APPLY (REF(x)), mode, B1.tpe).setType(B1.tpe)) + val List(defaultCase) = methodBodyTyper.typedCases(List(cdef), argTp, B1.tpe) treeCopy.Match(match0, match0.selector, match0.cases :+ defaultCase) } match_ setType B1.tpe @@ -2710,187 +2743,99 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - /** Synthesize and type check the implementation of a type with a Single Abstract Method - * - * `{ (p1: T1, ..., pN: TN) => body } : S` - * - * expands to (where `S` is the expected type that defines a single abstract method named `apply`) - * - * `{ - * def apply$body(p1: T1, ..., pN: TN): T = body - * new S { - * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN) - * } - * }` - * - * If 'T' is not fully defined, it is inferred by type checking - * `apply$body` without a result type before type checking the block. - * The method's inferred result type is used instead of `T`. [See test/files/pos/sammy_poly.scala] - * - * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`, - * and `resPt` is derived from `samClassTp` -- it may be fully defined, or not... - * If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters. - * - * The types T1' ... TN' and T' are derived from the method signature of the sam method, - * as seen from the fully defined `samClassTpFullyDefined`. - * - * The function's body is put in a method outside of the class definition to enforce scoping. - * S's members should not be in scope in `body`. - * - * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), - * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. - * - * NOTE: it would be nicer to not have to type check `apply$body` separately when `T` is not fully defined. - * However T must be fully defined before we type the instantiation, as it'll end up as a parent type, - * which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code, - * and have the instantiation of the first occurrence propagate to the rest of the block. - * - * TODO: by-name params - * scala> trait LazySink { def accept(a: => Any): Unit } - * defined trait LazySink - * - * scala> val f: LazySink = (a) => (a, a) - * f: LazySink = $anonfun$1@1fb26910 - * - * scala> f(println("!")) - * <console>:10: error: LazySink does not take parameters - * f(println("!")) - * ^ - * - * scala> f.accept(println("!")) - * ! - * ! - */ - def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = { - // assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info - val sampos = fun.pos - - // if the expected sam type is fully defined, use it for the method's result type - // otherwise, NoType, so that type inference will determine the method's result type - // resPt is syntactically contained in samClassTp, so if the latter is fully defined, so is the former - // ultimately, we want to fully define samClassTp as it is used as the superclass of our anonymous class - val samDefTp = if (isFullyDefined(resPt)) resPt else NoType - val bodyName = newTermName(sam.name + "$body") - - // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body` - val samBodyDef = - DefDef(NoMods, - bodyName, - Nil, - List(fun.vparams.map(_.duplicate)), // must duplicate as we're also using them for `samDef` - TypeTree(samDefTp) setPos sampos.focus, - fun.body) - - // If we need to enter the sym for the body def before type checking the block, - // we'll create a nested context, as explained below. - var nestedTyper = this - - // Type check body def before classdef to fully determine samClassTp (if necessary). - // As `samClassTp` determines a parent type for the class, - // we can't type check `block` in one go unless `samClassTp` is fully defined. - val samClassTpFullyDefined = - if (isFullyDefined(samClassTp)) samClassTp + /** Synthesize and type check the implementation of a type with a Single Abstract Method. + * + * Based on a type checked Function node `{ (p1: T1, ..., pN: TN) => body } : S` + * where `S` is the expected type that defines a single abstract method (call it `apply` for the example), + * that has signature `(p1: T1', ..., pN: TN'): T'`, synthesize the instantiation of the following anonymous class + * + * ``` + * new S { + * def apply$body(p1: T1, ..., pN: TN): T = body + * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN) + * } + * ``` + * + * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `pt`, + * If `pt` is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters. + * + * The types T1' ... TN' and T' are derived from the method signature of the sam method, + * as seen from the fully defined `samClassTpFullyDefined`. + * + * The function's body is put in a (static) method in the class definition to enforce scoping. + * S's members should not be in scope in `body`. (Putting it in the block outside the class runs into implementation problems described below) + * + * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), + * is to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. + * + * Impl notes: + * - `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that + * - `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms) + * - after experimentation, it works best to type check function literals fully first and then adapt to a sam type, + * as opposed to a sam-specific code paths earlier on in type checking (in typedFunction). + * For one, we want to emit the same bytecode regardless of whether the expected + * function type is a built-in FunctionN or some SAM type + * + */ + def inferSamType(fun: Tree, pt: Type, mode: Mode): SAMFunction = { + val sam = + if (fun.isInstanceOf[Function] && !isFunctionType(pt)) { + // TODO: can we ensure there's always a SAMFunction attachment, instead of looking up the sam again??? + // seems like overloading complicates things? + val sam = samOf(pt) + if (samMatchesFunctionBasedOnArity(sam, fun.asInstanceOf[Function].vparams)) sam + else NoSymbol + } else NoSymbol + + def fullyDefinedMeetsExpectedFunTp(pt: Type): Boolean = isFullyDefined(pt) && { + val samMethType = pt memberInfo sam + fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType) + } + + SAMFunction( + if (!sam.exists) NoType + else if (fullyDefinedMeetsExpectedFunTp(pt)) pt else try { - // This creates a symbol for samBodyDef with a type completer that'll be triggered immediately below. - // The symbol is entered in the same scope used for the block below, and won't thus be reentered later. - // It has to be a new scope, though, or we'll "get ambiguous reference to overloaded definition" [pos/sammy_twice.scala] - // makeSilent: [pos/nonlocal-unchecked.scala -- when translation all functions to sams] - val nestedCtx = enterSym(context.makeNewScope(context.tree, context.owner).makeSilent(), samBodyDef) - nestedTyper = newTyper(nestedCtx) - - // NOTE: this `samBodyDef.symbol.info` runs the type completer set up by the enterSym above - val actualSamType = samBodyDef.symbol.info + val samClassSym = pt.typeSymbol // we're trying to fully define the type arguments for this type constructor - val samTyCon = samClassTp.typeSymbol.typeConstructor + val samTyCon = samClassSym.typeConstructor // the unknowns - val tparams = samClassTp.typeSymbol.typeParams + val tparams = samClassSym.typeParams // ... as typevars - val tvars = tparams map freshVar - - // 1. Recover partial information: - // - derive a type from samClassTp that has the corresponding tparams for type arguments that aren't fully defined - // - constrain typevars to be equal to type args that are fully defined - val samClassTpMoreDefined = appliedType(samTyCon, - (samClassTp.typeArgs, tparams, tvars).zipped map { - case (a, _, tv) if isFullyDefined(a) => tv =:= a; a - case (_, p, _) => p.typeConstructor - }) + val tvars = tparams map freshVar - // the method type we're expecting the synthesized sam to have, based on the expected sam type, - // where fully defined type args to samClassTp have been preserved, - // with the unknown args replaced by their corresponding type param - val expectedSamType = samClassTpMoreDefined.memberInfo(sam) + val ptVars = appliedType(samTyCon, tvars) - // 2. make sure the body def's actual type (formals and result) conforms to - // sam's expected type (in terms of the typevars that represent the sam's class's type params) - actualSamType <:< expectedSamType.substituteTypes(tparams, tvars) + // carry over info from pt + ptVars <:< pt - // solve constraints tracked by tvars - val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil)) + val samInfoWithTVars = ptVars.memberInfo(sam) - debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $targs for $tparams") + // use function type subtyping, not method type subtyping (the latter is invariant in argument types) + fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) - // a fully defined samClassTp - appliedType(samTyCon, targs) - } catch { - case _: NoInstance | _: TypeError => - devWarning(sampos, s"Could not define type $samClassTp using ${samBodyDef.symbol.rawInfo} <:< ${samClassTp memberInfo sam} (for $sam)") - samClassTp - } - - // what's the signature of the method that we should actually be overriding? - val samMethTp = samClassTpFullyDefined memberInfo sam - // Before the mutation, `tp <:< vpar.tpt.tpe` should hold. - // TODO: error message when this is not the case, as the expansion won't type check - // - Ti' <:< Ti and T <: T' must hold for the samDef body to type check - val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp) - - // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)` - val samDef = - DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC), - sam.name.toTermName, - Nil, - List(fun.vparams), - TypeTree(samMethTp.finalResultType) setPos sampos.focus, - Apply(Ident(bodyName), fun.vparams map gen.paramToArg) - ) + val variances = tparams map varianceInType(sam.info) - val serializableParentAddendum = - if (typeIsSubTypeOfSerializable(samClassTp)) Nil - else List(TypeTree(SerializableTpe)) - - val classDef = - ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil, - gen.mkTemplate( - parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum, - self = noSelfType, - constrMods = NoMods, - vparamss = ListOfNil, - body = List(samDef), - superPos = sampos.focus - ) - ) - - // type checking the whole block, so that everything is packaged together nicely - // and we don't have to create any symbols by hand - val block = - nestedTyper.typedPos(sampos, mode, samClassTpFullyDefined) { - Block( - samBodyDef, - classDef, - Apply(Select(New(Ident(tpnme.ANON_FUN_NAME)), nme.CONSTRUCTOR), Nil) - ) - } + // solve constraints tracked by tvars + val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) - // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`) - // the errors in the function don't get out... - if (block exists (_.isErroneous)) - context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.") + debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") - classDef.symbol addAnnotation SerialVersionUIDAnnotation - block + val ptFullyDefined = appliedType(samTyCon, targs) + if (ptFullyDefined <:< pt && fullyDefinedMeetsExpectedFunTp(ptFullyDefined)) { + debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") + ptFullyDefined + } else { + debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)") + NoType + } + } catch { + case e@(_: NoInstance | _: TypeError) => + debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") + NoType + }, sam) } /** Type check a function literal. @@ -2900,16 +2845,19 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * - a type with a Single Abstract Method (under -Xexperimental for now). */ private def typedFunction(fun: Function, mode: Mode, pt: Type): Tree = { - val numVparams = fun.vparams.length + val vparams = fun.vparams + val numVparams = vparams.length val FunctionSymbol = if (numVparams > definitions.MaxFunctionArity) NoSymbol else FunctionClass(numVparams) + val ptSym = pt.typeSymbol + /* The Single Abstract Member of pt, unless pt is the built-in function type of the expected arity, * as `(a => a): Int => Int` should not (yet) get the sam treatment. */ val sam = - if (pt.typeSymbol == FunctionSymbol) NoSymbol + if (ptSym == NoSymbol || ptSym == FunctionSymbol || ptSym == PartialFunctionClass) NoSymbol else samOf(pt) /* The SAM case comes first so that this works: @@ -2917,79 +2865,101 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * (a => a): MyFun * * Note that the arity of the sam must correspond to the arity of the function. + * TODO: handle vararg sams? */ - val samViable = sam.exists && sameLength(sam.info.params, fun.vparams) - val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt + val ptNorm = + if (samMatchesFunctionBasedOnArity(sam, vparams)) samToFunctionType(pt, sam) + else pt val (argpts, respt) = ptNorm baseType FunctionSymbol match { case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) - case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType) + case _ => (vparams map (if (pt == ErrorType) (_ => ErrorType) else (_ => NoType)), WildcardType) } - if (!FunctionSymbol.exists) - MaxFunctionArityError(fun) - else if (argpts.lengthCompare(numVparams) != 0) - WrongNumberOfParametersError(fun, argpts) + if (!FunctionSymbol.exists) MaxFunctionArityError(fun) + else if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts) else { - var issuedMissingParameterTypeError = false - foreach2(fun.vparams, argpts) { (vparam, argpt) => + val paramsMissingType = mutable.ArrayBuffer.empty[ValDef] //.sizeHint(numVparams) probably useless, since initial size is 16 and max fun arity is 22 + // first, try to define param types from expected function's arg types if needed + foreach2(vparams, argpts) { (vparam, argpt) => if (vparam.tpt.isEmpty) { - val vparamType = - if (isFullyDefined(argpt)) argpt - else { - fun match { - case etaExpansion(vparams, fn, args) => - silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 => - // if context.undetparams is not empty, the function was polymorphic, - // so we need the missing arguments to infer its type. See #871 - //println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams) - val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams) - if (isFunctionType(ftpe) && isFullyDefined(ftpe)) - return typedFunction(fun, mode, ftpe) - } - case _ => - } - MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError) - issuedMissingParameterTypeError = true - ErrorType - } - vparam.tpt.setType(vparamType) + if (isFullyDefined(argpt)) vparam.tpt setType argpt + else paramsMissingType += vparam + if (!vparam.tpt.pos.isDefined) vparam.tpt setPos vparam.pos.focus } } - fun.body match { - // translate `x => x match { <cases> }` : PartialFunction to - // `new PartialFunction { def applyOrElse(x, default) = x match { <cases> } def isDefinedAt(x) = ... }` - case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => - // go to outer context -- must discard the context that was created for the Function since we're discarding the function - // thus, its symbol, which serves as the current context.owner, is not the right owner - // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) - val outerTyper = newTyper(context.outer) - val p = fun.vparams.head - if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe + // If we're typing `(a1: T1, ..., aN: TN) => m(a1,..., aN)`, where some Ti are not fully defined, + // type `m` directly (undoing eta-expansion of method m) to determine the argument types. + // This tree is the result from one of: + // - manual eta-expansion with named arguments (x => f(x)); + // - wildcard-style eta expansion (`m(_, _,)`); + // - instantiateToMethodType adapting a tree of method type to a function type using etaExpand. + // + // Note that method values are a separate thing (`m _`): they have the idiosyncratic shape + // of `Typed(expr, Function(Nil, EmptyTree))` + val ptUnrollingEtaExpansion = + if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match { + // we can compare arguments and parameters by name because there cannot be a binder between + // the function's valdefs and the Apply's arguments + case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } => + // We're looking for a method (as indicated by FUNmode in the silent typed below), + // so let's make sure our expected type is a MethodType + val methArgs = NoSymbol.newSyntheticValueParams(argpts map { case NoType => WildcardType case tp => tp }) + silent(_.typed(meth, mode.forFunMode, MethodType(methArgs, respt))) filter (isMonoContext) map { methTyped => + // if context.undetparams is not empty, the method was polymorphic, + // so we need the missing arguments to infer its type. See #871 + val funPt = normalize(methTyped.tpe) baseType FunctionClass(numVparams) + // println(s"typeUnEtaExpanded $meth : ${methTyped.tpe} --> normalized: $funPt") + + // If we are sure this function type provides all the necesarry info, so that we won't have + // any undetermined argument types, go ahead an recurse below (`typedFunction(fun, mode, ptUnrollingEtaExpansion)`) + // and rest assured we won't end up right back here (and keep recursing) + if (isFunctionType(funPt) && funPt.typeArgs.iterator.take(numVparams).forall(isFullyDefined)) funPt + else null + } orElse { _ => null } + case _ => null + } else null + + + if (ptUnrollingEtaExpansion ne null) typedFunction(fun, mode, ptUnrollingEtaExpansion) + else { + // we ran out of things to try, missing parameter types are an irrevocable error + var issuedMissingParameterTypeError = false + paramsMissingType.foreach { vparam => + vparam.tpt setType ErrorType + MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError) + issuedMissingParameterTypeError = true + } - outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt) + fun.body match { + // translate `x => x match { <cases> }` : PartialFunction to + // `new PartialFunction { def applyOrElse(x, default) = x match { <cases> } def isDefinedAt(x) = ... }` + case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => + // go to outer context -- must discard the context that was created for the Function since we're discarding the function + // thus, its symbol, which serves as the current context.owner, is not the right owner + // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) + val outerTyper = newTyper(context.outer) + val p = vparams.head + if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe - // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` - // to an instance of the corresponding anonymous subclass of `pt`. - case _ if samViable => - newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode) + outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt) - // regular Function - case _ => - val vparamSyms = fun.vparams map { vparam => - enterSym(context, vparam) - if (context.retyping) context.scope enter vparam.symbol - vparam.symbol - } - val vparams = fun.vparams mapConserve typedValDef - val formals = vparamSyms map (_.tpe) - val body1 = typed(fun.body, respt) - val restpe = packedType(body1, fun.symbol).deconst.resultType - val funtpe = appliedType(FunctionSymbol, formals :+ restpe: _*) + case _ => + val vparamSyms = vparams map { vparam => + enterSym(context, vparam) + if (context.retyping) context.scope enter vparam.symbol + vparam.symbol + } + val vparamsTyped = vparams mapConserve typedValDef + val formals = vparamSyms map (_.tpe) + val body1 = typed(fun.body, respt) + val restpe = packedType(body1, fun.symbol).deconst.resultType + val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe) - treeCopy.Function(fun, vparams, body1) setType funtpe + treeCopy.Function(fun, vparamsTyped, body1) setType funtpe + } } } } @@ -3152,7 +3122,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (phase.erasedTypes) stats1 else { // As packages are open, it doesn't make sense to check double definitions here. Furthermore, - // it is expensive if the package is large. Instead, such double defininitions are checked in `Namers.enterInScope` + // it is expensive if the package is large. Instead, such double definitions are checked in `Namers.enterInScope` if (!context.owner.isPackageClass) checkNoDoubleDefs addSynthetics(stats1) @@ -3219,6 +3189,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // less expensive than including them in inferMethodAlternative (see below). def shapeType(arg: Tree): Type = arg match { case Function(vparams, body) => + // No need for phasedAppliedType, as we don't get here during erasure -- + // overloading resolution happens during type checking. + // During erasure, the condition above (fun.symbol.isOverloaded) is false. functionType(vparams map (_ => AnyTpe), shapeType(body)) case AssignOrNamedArg(Ident(name), rhs) => NamedType(name, shapeType(rhs)) @@ -3664,7 +3637,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val annType = annTpt.tpe finish( - if (typedFun.isErroneous) + if (typedFun.isErroneous || annType == null) ErroneousAnnotation else if (annType.typeSymbol isNonBottomSubClass ClassfileAnnotationClass) { // annotation to be saved as java classfile annotation @@ -4141,6 +4114,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ann setType arg1.tpe.withAnnotation(annotInfo) } val atype = ann.tpe + // For `f(): @inline/noinline` callsites, add the InlineAnnotatedAttachment. TypeApplys + // are eliminated by erasure, so add it to the underlying function in this case. + def setInlineAttachment(t: Tree, att: InlineAnnotatedAttachment): Unit = t match { + case TypeApply(fun, _) => setInlineAttachment(fun, att) + case _ => t.updateAttachment(att) + } + if (atype.hasAnnotation(definitions.ScalaNoInlineClass)) setInlineAttachment(arg1, NoInlineCallsiteAttachment) + else if (atype.hasAnnotation(definitions.ScalaInlineClass)) setInlineAttachment(arg1, InlineCallsiteAttachment) Typed(arg1, resultingTypeTree(atype)) setPos tree.pos setType atype } } @@ -4292,7 +4273,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (pt.typeSymbol == PartialFunctionClass) synthesizePartialFunction(newTermName(context.unit.fresh.newName("x")), tree.pos, paramSynthetic = true, tree, mode, pt) else { - val arity = if (isFunctionType(pt)) pt.dealiasWiden.typeArgs.length - 1 else 1 + val arity = functionArityFromType(pt) match { case -1 => 1 case arity => arity } // SI-8429: consider sam and function type equally in determining function arity + val params = for (i <- List.range(0, arity)) yield atPos(tree.pos.focusStart) { ValDef(Modifiers(PARAM | SYNTHETIC), @@ -4392,31 +4374,43 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper treeCopy.New(tree, tpt1).setType(tp) } - def functionTypeWildcard(tree: Tree, arity: Int): Type = { - val tp = functionType(List.fill(arity)(WildcardType), WildcardType) - if (tp == NoType) MaxFunctionArityError(tree) - tp - } - - def typedEta(expr1: Tree): Tree = expr1.tpe match { - case TypeRef(_, ByNameParamClass, _) => - val expr2 = Function(List(), expr1) setPos expr1.pos - new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) - typed1(expr2, mode, pt) - case NullaryMethodType(restpe) => - val expr2 = Function(List(), expr1) setPos expr1.pos - new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) - typed1(expr2, mode, pt) - case PolyType(_, MethodType(formals, _)) => - if (isFunctionType(pt)) expr1 - else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) - case MethodType(formals, _) => - if (isFunctionType(pt)) expr1 - else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) + def functionTypeWildcard(arity: Int): Type = + functionType(List.fill(arity)(WildcardType), WildcardType) + + def checkArity(tree: Tree)(tp: Type): tp.type = tp match { + case NoType => MaxFunctionArityError(tree); tp + case _ => tp + } + + + /** Eta expand an expression like `m _`, where `m` denotes a method or a by-name argument + * + * The spec says: + * The expression `$e$ _` is well-formed if $e$ is of method type or if $e$ is a call-by-name parameter. + * (1) If $e$ is a method with parameters, `$e$ _` represents $e$ converted to a function type + * by [eta expansion](#eta-expansion). + * (2) If $e$ is a parameterless method or call-by-name parameter of type `=>$T$`, `$e$ _` represents + * the function of type `() => $T$`, which evaluates $e$ when it is applied to the empty parameterlist `()`. + */ + def typedEta(methodValue: Tree): Tree = methodValue.tpe match { + case tp@(MethodType(_, _) | PolyType(_, MethodType(_, _))) => // (1) + val formals = tp.params + if (isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)) methodValue + else adapt(methodValue, mode, checkArity(methodValue)(functionTypeWildcard(formals.length))) + + case TypeRef(_, ByNameParamClass, _) | NullaryMethodType(_) => // (2) + val pos = methodValue.pos + // must create it here to change owner (normally done by typed's typedFunction) + val funSym = context.owner.newAnonymousFunctionValue(pos) + new ChangeOwnerTraverser(context.owner, funSym) traverse methodValue + + typed(Function(List(), methodValue) setSymbol funSym setPos pos, mode, pt) + case ErrorType => - expr1 + methodValue + case _ => - UnderscoreEtaError(expr1) + UnderscoreEtaError(methodValue) } def tryTypedArgs(args: List[Tree], mode: Mode): Option[List[Tree]] = { @@ -4460,7 +4454,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case Annotated(_, r) => treesInResult(r) case If(_, t, e) => treesInResult(t) ++ treesInResult(e) case Try(b, catches, _) => treesInResult(b) ++ catches - case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) + case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) // a method value case Select(qual, name) => treesInResult(qual) case Apply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult) case TypeApply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult) @@ -4479,7 +4473,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper tryTypedArgs(args, forArgMode(fun, mode)) match { case Some(args1) if !args1.exists(arg => arg.exists(_.isErroneous)) => val qual1 = - if (!pt.isError) adaptToArguments(qual, name, args1, pt, reportAmbiguous = true, saveErrors = true) + if (!pt.isError) adaptToArguments(qual, name, args1, pt) else qual if (qual1 ne qual) { val tree1 = Apply(Select(qual1, name) setPos fun.pos, args1) setPos tree.pos @@ -4640,10 +4634,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def findMixinSuper(site: Type): Type = { var ps = site.parents filter (_.typeSymbol.name == mix) if (ps.isEmpty) - ps = site.parents filter (_.typeSymbol.toInterface.name == mix) + ps = site.parents filter (_.typeSymbol.name == mix) if (ps.isEmpty) { debuglog("Fatal: couldn't find site " + site + " in " + site.parents.map(_.typeSymbol.name)) - if (phase.erasedTypes && context.enclClass.owner.isImplClass) { + if (phase.erasedTypes && context.enclClass.owner.isTrait) { // the reference to super class got lost during erasure restrictionError(tree.pos, unit, "traits may not select fields or methods from super[C] where C is a class") ErrorType @@ -4706,7 +4700,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // member. Added `| PATTERNmode` to allow enrichment in patterns (so we can add e.g., an // xml member to StringContext, which in turn has an unapply[Seq] method) if (name != nme.CONSTRUCTOR && mode.inAny(EXPRmode | PATTERNmode)) { - val qual1 = adaptToMemberWithArgs(tree, qual, name, mode, reportAmbiguous = true, saveErrors = true) + val qual1 = adaptToMemberWithArgs(tree, qual, name, mode) if ((qual1 ne qual) && !qual1.isErrorTyped) return typed(treeCopy.Select(tree, qual1, name), mode, pt) } @@ -5100,11 +5094,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // because `expr` might contain nested macro calls (see SI-6673) // // Note: apparently `Function(Nil, EmptyTree)` is the secret parser marker - // which means trailing underscore. + // which means trailing underscore -- denoting a method value. See makeMethodValue in TreeBuilder. case Typed(expr, Function(Nil, EmptyTree)) => typed1(suppressMacroExpansion(expr), mode, pt) match { case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef) - case exprTyped => typedEta(checkDead(exprTyped)) + case methodValue => typedEta(checkDead(methodValue)) } case Typed(expr, tpt) => val tpt1 = typedType(tpt, mode) // type the ascribed type first @@ -5195,17 +5189,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case MethodType(p :: _, _) => p.isImplicit // implicit method requires no args case _ => true // catches all others including NullaryMethodType } - def isPlausible(m: Symbol) = m.alternatives exists (m => requiresNoArgs(m.info)) + def isPlausible(m: Symbol) = !m.isPackage && m.alternatives.exists(x => requiresNoArgs(x.info)) def maybeWarn(s: String): Unit = { def warn(message: String) = context.warning(lit.pos, s"possible missing interpolator: $message") def suspiciousSym(name: TermName) = context.lookupSymbol(name, _ => true).symbol - def suspiciousExpr = InterpolatorCodeRegex findFirstIn s + val suspiciousExprs = InterpolatorCodeRegex findAllMatchIn s def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(TermName(s drop 1))) - - if (suspiciousExpr.nonEmpty) - warn("detected an interpolated expression") // "${...}" - else + def isCheapIdent(expr: String) = (Character.isJavaIdentifierStart(expr.charAt(0)) && + expr.tail.forall(Character.isJavaIdentifierPart)) + def warnableExpr(expr: String) = !expr.isEmpty && (!isCheapIdent(expr) || isPlausible(suspiciousSym(TermName(expr)))) + + if (suspiciousExprs.nonEmpty) { + val exprs = (suspiciousExprs map (_ group 1)).toList + // short-circuit on leading ${} + if (!exprs.head.isEmpty && exprs.exists(warnableExpr)) + warn("detected an interpolated expression") // "${...}" + } else suspiciousIdents find isPlausible foreach (sym => warn(s"detected interpolated identifier `$$${sym.name}`")) // "$id" } lit match { @@ -5229,7 +5229,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (refTyped.isErrorTyped) { setError(tree) } else { - tree setType refTyped.tpe.resultType + tree setType refTyped.tpe.resultType.deconst if (refTyped.isErrorTyped || treeInfo.admitsTypeSelection(refTyped)) tree else UnstableTreeError(tree) } @@ -5417,6 +5417,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (!isPastTyper) signalDone(context.asInstanceOf[analyzer.Context], tree, result) + if (mode.inPatternMode && !mode.inPolyMode && result.isType) + PatternMustBeValue(result, pt) + result } @@ -5506,10 +5509,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // as a compromise, context.enrichmentEnabled tells adaptToMember to go ahead and enrich, // but arbitrary conversions (in adapt) are disabled // TODO: can we achieve the pattern matching bit of the string interpolation SIP without this? - typingInPattern(context.withImplicitsDisabledAllowEnrichment(typed(tree, PATTERNmode, pt))) match { - case tpt if tpt.isType => PatternMustBeValue(tpt, pt); tpt - case pat => pat - } + typingInPattern(context.withImplicitsDisabledAllowEnrichment(typed(tree, PATTERNmode, pt))) } /** Types a (fully parameterized) type tree */ 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/nsc/util/ClassFileLookup.scala b/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala index 4451651229..5d8831a607 100644 --- a/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala +++ b/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala @@ -3,6 +3,8 @@ */ package scala.tools.nsc.util +import scala.tools.nsc.Settings +import scala.tools.nsc.classpath.{AggregateFlatClassPath, FlatClassPath, FlatClassPathFactory} import scala.tools.nsc.io.AbstractFile import java.net.URL @@ -39,6 +41,25 @@ trait ClassFileLookup[T] { def asSourcePathString: String } +object ClassFileLookup { + def createForFile(f: AbstractFile, current: ClassFileLookup[AbstractFile], settings: Settings): ClassFileLookup[AbstractFile] = current match { + case cp: ClassPath[_] => cp.context.newClassPath(f) + case _: FlatClassPath => FlatClassPathFactory.newClassPath(f, settings) + } + + def createAggregate(elems: Iterable[ClassFileLookup[AbstractFile]], current: ClassFileLookup[AbstractFile]): ClassFileLookup[AbstractFile] = { + assert(elems.nonEmpty) + if (elems.size == 1) elems.head + else current match { + case cp: ClassPath[_] => + new MergedClassPath(elems.asInstanceOf[Iterable[ClassPath[AbstractFile]]], cp.context) + + case _: FlatClassPath => + AggregateFlatClassPath.createAggregate(elems.asInstanceOf[Iterable[FlatClassPath]].toSeq : _*) + } + } +} + /** * Represents classes which can be loaded with a ClassfileLoader and/or SourcefileLoader. */ diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 2811520b67..6cdc3856cd 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -278,7 +278,7 @@ class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[Ab f => // Optimization: We assume the file was not changed since `dir` called // `Path.apply` and categorized existent files as `Directory` - // or `File`. + // or `File` (avoids IO operation JFile.isDirectory()). val isDirectory = f match { case pf: io.PlainFile => pf.givenPath match { case _: io.Directory => true @@ -300,13 +300,6 @@ class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[Ab override def toString() = "directory classpath: "+ origin.getOrElse("?") } -class DeltaClassPath[T](original: MergedClassPath[T], subst: Map[ClassPath[T], ClassPath[T]]) -extends MergedClassPath[T](original.entries map (e => subst getOrElse (e, e)), original.context) { - // not sure we should require that here. Commented out for now. - // require(subst.keySet subsetOf original.entries.toSet) - // We might add specialized operations for computing classes packages here. Not sure it's worth it. -} - /** * A classpath unifying multiple class- and sourcepath entries. */ 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..9c4d521336 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 @@ -118,13 +117,15 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => def transformDuringTyper(expr: Tree, mode: scala.reflect.internal.Mode, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean)(transform: (analyzer.Typer, Tree) => Tree): Tree = { def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) wrappingIntoTerm(tree)(op) else op(tree) - withWrapping(verify(expr))(expr1 => { + withWrapping(verify(expr)) { expr => // need to extract free terms, because otherwise you won't be able to typecheck macros against something that contains them - val exprAndFreeTerms = extractFreeTerms(expr1, wrapFreeTermRefs = false) - var expr2 = exprAndFreeTerms._1 - val freeTerms = exprAndFreeTerms._2 - val dummies = freeTerms.map{ case (freeTerm, name) => ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) }.toList - expr2 = Block(dummies, expr2) + val (extracted, freeTerms) = extractFreeTerms(expr, wrapFreeTermRefs = false) + val exprBound = { + val binders = freeTerms.toList.map { case (freeTerm, name) => + ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) + } + Block(binders, extracted) + } // !!! Why is this is in the empty package? If it's only to make // it inaccessible then please put it somewhere designed for that @@ -132,26 +133,29 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => // [Eugene] how can we implement that? val ownerClass = rootMirror.EmptyPackageClass.newClassSymbol(newTypeName("<expression-owner>")) build.setInfo(ownerClass, ClassInfoType(List(ObjectTpe), newScope, ownerClass)) - val owner = ownerClass.newLocalDummy(expr2.pos) - val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(expr2, owner)) - val withImplicitFlag = if (!withImplicitViewsDisabled) (currentTyper.context.withImplicitsEnabled[Tree] _) else (currentTyper.context.withImplicitsDisabled[Tree] _) - val withMacroFlag = if (!withMacrosDisabled) (currentTyper.context.withMacrosEnabled[Tree] _) else (currentTyper.context.withMacrosDisabled[Tree] _) - def withContext (tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) + val owner = ownerClass.newLocalDummy(exprBound.pos) + val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(exprBound, owner)) + import currentTyper.{context => currCtx} val run = new Run run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled globalPhase = run.typerPhase // amazing... looks like phase and globalPhase are different things, so we need to set them separately - currentTyper.context.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions + currCtx.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions reporter.reset() - val expr3 = withContext(transform(currentTyper, expr2)) - var (dummies1, result) = expr3 match { - case Block(dummies, result) => ((dummies, result)) - case result => ((Nil, result)) - } + val (binders, transformed) = + currCtx.withImplicits(enabled = !withImplicitViewsDisabled) { + currCtx.withMacros(enabled = !withMacrosDisabled) { + transform(currentTyper, exprBound) + } + } match { + case Block(binders, transformed) => (binders, transformed) + case transformed => (Nil, transformed) + } + val invertedIndex = freeTerms map (_.swap) - result = new Transformer { + val indexed = new Transformer { override def transform(tree: Tree): Tree = tree match { case Ident(name: TermName) if invertedIndex contains name => @@ -159,10 +163,10 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => case _ => super.transform(tree) } - }.transform(result) - new TreeTypeSubstituter(dummies1 map (_.symbol), dummies1 map (dummy => SingleType(NoPrefix, invertedIndex(dummy.symbol.name.toTermName)))).traverse(result) - result - }) + }.transform(transformed) + new TreeTypeSubstituter(binders map (_.symbol), binders map (b => SingleType(NoPrefix, invertedIndex(b.symbol.name.toTermName)))).traverse(indexed) + indexed + } } def typecheck(expr: Tree, pt: Type, mode: scala.reflect.internal.Mode, silent: Boolean, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean): Tree = diff --git a/src/compiler/scala/tools/reflect/WrappedProperties.scala b/src/compiler/scala/tools/reflect/WrappedProperties.scala index 523287fc66..348d000d15 100644 --- a/src/compiler/scala/tools/reflect/WrappedProperties.scala +++ b/src/compiler/scala/tools/reflect/WrappedProperties.scala @@ -30,9 +30,10 @@ trait WrappedProperties extends PropertiesTrait { def systemProperties: List[(String, String)] = { import scala.collection.JavaConverters._ wrap { + // SI-7269,7775 Avoid `ConcurrentModificationException` and nulls if another thread modifies properties val props = System.getProperties - // SI-7269 Be careful to avoid `ConcurrentModificationException` if another thread modifies the properties map - props.stringPropertyNames().asScala.toList.map(k => (k, props.get(k).asInstanceOf[String])) + val it = props.stringPropertyNames().asScala.iterator map (k => (k, props getProperty k)) filter (_._2 ne null) + it.toList } getOrElse Nil } } diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala index 8e5b1e0a5c..9decc99c8d 100644 --- a/src/compiler/scala/tools/util/PathResolver.scala +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -12,10 +12,8 @@ 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 @@ -52,7 +50,7 @@ object PathResolver { */ object Environment { private def searchForBootClasspath = - systemProperties find (_._1 endsWith ".boot.class.path") map (_._2) getOrElse "" + systemProperties collectFirst { case (k, v) if k endsWith ".boot.class.path" => v } getOrElse "" /** Environment variables which java pays attention to so it * seems we do as well. @@ -166,12 +164,6 @@ object PathResolver { |}""".asLines } - // used in PathResolver constructor - private object NoImplClassJavaContext extends JavaContext { - override def isValidName(name: String): Boolean = - !ReflectionUtils.scalacShouldntLoadClassfile(name) - } - @deprecated("This method is no longer used be scalap and will be deleted", "2.11.5") def fromPathString(path: String, context: JavaContext = DefaultJavaContext): JavaClassPath = { val s = new Settings() @@ -254,17 +246,7 @@ abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFil * TODO: we should refactor this as a separate -bootstrap option to have a clean implementation, no? */ def sourcePath = if (!settings.isScaladoc) cmdLineOrElse("sourcepath", Defaults.scalaSourcePath) else "" - /** Against my better judgment, giving in to martin here and allowing - * CLASSPATH to be used automatically. So for the user-specified part - * of the classpath: - * - * - If -classpath or -cp is given, it is that - * - Otherwise, if CLASSPATH is set, it is that - * - If neither of those, then "." is used. - */ - def userClassPath = - if (!settings.classpath.isDefault) settings.classpath.value - else sys.env.getOrElse("CLASSPATH", ".") + def userClassPath = settings.classpath.value // default is specified by settings and can be overridden there import classPathFactory._ @@ -322,10 +304,7 @@ abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFil class PathResolver(settings: Settings, context: JavaContext) extends PathResolverBase[ClassPath[AbstractFile], JavaClassPath](settings, context) { - def this(settings: Settings) = - this(settings, - if (settings.YnoLoadImplClass) PathResolver.NoImplClassJavaContext - else DefaultJavaContext) + def this(settings: Settings) = this(settings, DefaultJavaContext) override protected def computeResult(): JavaClassPath = new JavaClassPath(containers.toIndexedSeq, context) |